Commit b4dca197 authored by Samuel GAIST's avatar Samuel GAIST
Browse files

[widgets] Implement spin boxes for 64bit integer types

parent 0e275a04
# vim: set fileencoding=utf-8 :
###############################################################################
# #
# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/ #
# Contact: beat.support@idiap.ch #
# #
# This file is part of the beat.editor module of the BEAT platform. #
# #
# Commercial License Usage #
# Licensees holding valid commercial BEAT licenses may use this file in #
# accordance with the terms contained in a written agreement between you #
# and Idiap. For further information contact tto@idiap.ch #
# #
# Alternatively, this file may be used under the terms of the GNU Affero #
# Public License version 3 as published by the Free Software and appearing #
# in the file LICENSE.AGPL included in the packaging of this file. #
# The BEAT platform is distributed in the hope that it will be useful, but #
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
# or FITNESS FOR A PARTICULAR PURPOSE. #
# #
# You should have received a copy of the GNU Affero Public License along #
# with the BEAT platform. If not, see http://www.gnu.org/licenses/. #
# #
###############################################################################
from ..widgets.spinboxes import Int64SpinBox
from ..widgets.spinboxes import Uint64SpinBox
class SpinBoxBaseTest:
klass = None
def test_range(self, qtbot):
spinbox = self.klass()
qtbot.addWidget(spinbox)
minimum = 0
maximum = 10
spinbox.setRange(minimum, maximum)
assert spinbox.minimum() == minimum
assert spinbox.maximum() == maximum
spinbox.setValue(-10)
assert spinbox.value() == spinbox.minimum()
spinbox.setValue(20)
assert spinbox.value() == spinbox.maximum()
def test_valid_input(self, qtbot):
spinbox = self.klass()
qtbot.addWidget(spinbox)
value = "42"
with qtbot.waitSignal(spinbox.valueChanged):
qtbot.keyClicks(spinbox, value)
assert spinbox.value() == spinbox.numpy_type(value)
def test_invalid_input(self, qtbot):
spinbox = self.klass()
qtbot.addWidget(spinbox)
value = "44444444444444444444444444444444444444444444444444444444444"
expected_value = 4444444444444444444
with qtbot.waitSignal(spinbox.valueChanged):
qtbot.keyClicks(spinbox, value)
assert spinbox.value() == spinbox.numpy_type(expected_value)
class TestIn64SpinBox(SpinBoxBaseTest):
klass = Int64SpinBox
def test_values(self, qtbot):
spinbox = Int64SpinBox()
qtbot.addWidget(spinbox)
spinbox.setRange(0, 10)
spinbox.setValue(5)
assert spinbox.value() == 5
spinbox.setValue(-5)
assert spinbox.value() == spinbox.minimum()
class TestUin64SpinBox(SpinBoxBaseTest):
klass = Uint64SpinBox
def test_invalid_range(self, qtbot):
spinbox = Uint64SpinBox()
qtbot.addWidget(spinbox)
spinbox.setRange(-10, 10)
assert spinbox.minimum() == 0
def test_values(self, qtbot):
spinbox = Uint64SpinBox()
qtbot.addWidget(spinbox)
spinbox.setRange(0, 10)
spinbox.setValue(-10)
assert spinbox.value() == spinbox.minimum()
spinbox.setValue(20)
assert spinbox.value() == spinbox.maximum()
# vim: set fileencoding=utf-8 :
###############################################################################
# #
# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/ #
# Contact: beat.support@idiap.ch #
# #
# This file is part of the beat.editor module of the BEAT platform. #
# #
# Commercial License Usage #
# Licensees holding valid commercial BEAT licenses may use this file in #
# accordance with the terms contained in a written agreement between you #
# and Idiap. For further information contact tto@idiap.ch #
# #
# Alternatively, this file may be used under the terms of the GNU Affero #
# Public License version 3 as published by the Free Software and appearing #
# in the file LICENSE.AGPL included in the packaging of this file. #
# The BEAT platform is distributed in the hope that it will be useful, but #
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
# or FITNESS FOR A PARTICULAR PURPOSE. #
# #
# You should have received a copy of the GNU Affero Public License along #
# with the BEAT platform. If not, see http://www.gnu.org/licenses/. #
# #
###############################################################################
import numpy as np
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QValidator
from PyQt5.QtWidgets import QAbstractSpinBox
from ..utils import frozen
@frozen
class AbstractNumpySpinBox(QAbstractSpinBox):
"""Spinbox base class using numpy types
Subclasses must provide the nump_type with the numpy
type that will be used.
They also must declare the following signals:
- valueChanged
- minimumChanged
- maximumChanged
Based on https://stackoverflow.com/a/32628421/5843716
"""
numpy_type = None
def __init__(self, parent=None):
"""Constructor
:param parent QWidget: parent widget
"""
super(AbstractNumpySpinBox, self).__init__(parent)
self._info = np.iinfo(self.numpy_type)
self._minimum = self._info.min
self._maximum = self._info.max
self._value = 0
self.setInputMethodHints(Qt.ImhFormattedNumbersOnly)
self.lineEdit().textEdited.connect(self.__on_text_edited)
def __on_text_edited(self):
"""Act when the spinbox line edit content has been changed"""
input_ = self.lineEdit().text()
pos = 0
is_valid, input_, pos = self.validate(input_, pos)
if is_valid == QValidator.Acceptable:
self.setValue(self.valueFromText(input_, pos))
else:
self.lineEdit().setText(self.textFromValue(self.value()))
def textFromValue(self, value):
"""Returns the text version of value
:param value numpy_type: value to change to text
"""
return "{}".format(value)
def valueFromText(self, text, pos):
"""Returns the value contained in the text
:param text str: text to change in numpy_type
:param pos int: position to start considerating the text from
"""
return self.numpy_type(text)
def minimum(self):
"""Returns the spin box minimum value"""
return self._minimum
def setMinimum(self, minimum):
"""Set the minium value of the spin box.
In case the value given is outside of the spin box range,
it will be adapated.
:param minimum numpy_type: minimum value
"""
if self._minimum == minimum:
return
if minimum > self.maximum():
minimum = self.maximum()
elif minimum < self._info.min:
minimum = self._info.min
self._minimum = self.numpy_type(minimum)
self.minimumChanged.emit(self._minimum)
def maximum(self):
"""Returns the spin box maximum value"""
return self._maximum
def setMaximum(self, maximum):
"""Set the maximum value of the spin box.
In case the value given is outside of the spin box range,
it will be adapated.
:param maximum numpy_type: maximum value
"""
if self._maximum == maximum:
return
if maximum < self.minimum():
maximum = self.minimum()
elif maximum > self._info.max:
maximum = self._info.max
self._maximum = self.numpy_type(maximum)
self.maximumChanged.emit(self._maximum)
def setRange(self, minimum, maximum):
"""Set the range of the spin box.
In case the values given are outside of the type range,
they will be adapated.
:param minimum numpy_type: minimum value
:param maximum numpy_type: maximum value
"""
self.setMinimum(minimum)
self.setMaximum(maximum)
def value(self):
"""Returns the spin box current value"""
return self._value
def setValue(self, value):
"""Set the value of the spin box.
If the value is outside the spin box range, it will
be adapted.
:param value numpy_type: value to set
"""
if self._value == value:
return
if value > self.maximum():
value = self.maximum()
elif value < self.minimum():
value = self.minimum()
self.lineEdit().setText(self.textFromValue(value))
self._value = self.numpy_type(value)
self.valueChanged.emit(self._value)
def stepBy(self, steps):
"""Update the value of the spin box by steps.
This method is called for example when clicking on the arrow
or using the keyboard.
:param steps int: step to increment/decrement the spin box
"""
new_value = self.value()
if steps < 0 and new_value + steps < self.minimum():
new_value = self.minimum()
elif steps > 0 and new_value + steps > self.maximum():
new_value = self.maximum()
else:
new_value += steps
self.setValue(new_value)
def validate(self, input_, pos):
"""Validate the input value
:param input_ text: text to to validate
:param pos int: start of text to consider
"""
is_valid = QValidator.Acceptable
try:
value = self.valueFromText(input_, pos)
except (ValueError, OverflowError):
is_valid = QValidator.Invalid
else:
if value < self.minimum() or value > self.maximum():
is_valid = QValidator.Invalid
return is_valid, input_, pos
def stepEnabled(self):
"""Returns which kind of steps are available to modify the value
of the spin box
"""
return QAbstractSpinBox.StepUpEnabled | QAbstractSpinBox.StepDownEnabled
@frozen
class Int64SpinBox(AbstractNumpySpinBox):
"""Spin box handling the int64 type"""
numpy_type = np.int64
valueChanged = pyqtSignal(numpy_type)
minimumChanged = pyqtSignal(numpy_type)
maximumChanged = pyqtSignal(numpy_type)
@frozen
class Uint64SpinBox(AbstractNumpySpinBox):
"""Spin box handling the uint64 type"""
numpy_type = np.uint64
valueChanged = pyqtSignal(numpy_type)
minimumChanged = pyqtSignal(numpy_type)
maximumChanged = pyqtSignal(numpy_type)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment