Skip to content

Commit

Permalink
Merge pull request #935 from jbellister-slac/fix_spinbox
Browse files Browse the repository at this point in the history
FIX: Allow the user to set the range of PyDMSpinbox, instead of always taking it from the channel
  • Loading branch information
YektaY committed Oct 25, 2022
2 parents 8bc7878 + f0e80e1 commit 86d39f8
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 11 deletions.
62 changes: 58 additions & 4 deletions pydm/tests/widgets/test_spinbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,13 @@ def test_send_value(qtbot, signals, init_value, user_typed_value, precision):
assert pydm_spinbox.value == user_typed_value


@pytest.mark.parametrize("which_limit, new_limit", [
("UPPER", 123.456),
("LOWER", 12.345)
@pytest.mark.parametrize("which_limit, new_limit, user_defined_limits", [
("UPPER", 123.456, False),
("LOWER", 12.345, False),
("UPPER", 987.654, True),
("LOWER", 9.321, True)
])
def test_ctrl_limit_changed(qtbot, signals, which_limit, new_limit):
def test_ctrl_limit_changed(qtbot, signals, which_limit, new_limit, user_defined_limits):
"""
Test the upper and lower limit settings.
Expand All @@ -310,20 +312,72 @@ def test_ctrl_limit_changed(qtbot, signals, which_limit, new_limit):
"UPPER" if the new value is intended for the upper limit, "LOWER" for the lower limit
new_limit : float
The new limit value
user_defined_limits : bool
True if the spinbox should set its range based on user defined values, False if it should take its
range from the PV itself
"""
pydm_spinbox = PyDMSpinbox()
qtbot.addWidget(pydm_spinbox)
pydm_spinbox.userDefinedLimits = user_defined_limits
pydm_spinbox.userMinimum = -10.5
pydm_spinbox.userMaximum = 10.5
pydm_spinbox.precisionFromPV = False
pydm_spinbox.precision = 3

if which_limit == "UPPER":
signals.upper_ctrl_limit_signal[type(new_limit)].connect(pydm_spinbox.upperCtrlLimitChanged)
signals.upper_ctrl_limit_signal[type(new_limit)].emit(new_limit)

assert pydm_spinbox.get_ctrl_limits()[1] == new_limit
if not user_defined_limits:
# Not user_defined_limits means the range of the spinbox should stay in sync with the PV
assert pydm_spinbox.maximum() == new_limit
else:
# Otherwise while we still store the limits on the base widget, the spinbox remains at the user-set values
assert pydm_spinbox.maximum() == 10.5
elif which_limit == "LOWER":
signals.lower_ctrl_limit_signal[type(new_limit)].connect(pydm_spinbox.lowerCtrlLimitChanged)
signals.lower_ctrl_limit_signal[type(new_limit)].emit(new_limit)

assert pydm_spinbox.get_ctrl_limits()[0] == new_limit
if not user_defined_limits:
assert pydm_spinbox.minimum() == new_limit
else:
assert pydm_spinbox.minimum() == -10.5


def test_reset_limits(qtbot):
"""
Test that when reset_limits() is called, the range of the spinbox is set as expected
"""
# Set up a spinbox with some initial user set limits
pydm_spinbox = PyDMSpinbox()
qtbot.addWidget(pydm_spinbox)
pydm_spinbox.userDefinedLimits = True
pydm_spinbox.userMinimum = -10.5
pydm_spinbox.userMaximum = 10.5
pydm_spinbox.precisionFromPV = False
pydm_spinbox.precision = 1

# Also set a couple limits based on info we could have gotten back from the channel
pydm_spinbox._lower_ctrl_limit = -5
pydm_spinbox._upper_ctrl_limit = 5

# This first call to reset_limits should essentially do nothing as nothing has happened
# that would cause the range of the spinbox to change
pydm_spinbox.reset_limits()
assert pydm_spinbox.minimum() == -10.5
assert pydm_spinbox.maximum() == 10.5

# Turning off user defined limits should now cause the range to be set by the values received from the channel
pydm_spinbox.userDefinedLimits = False
assert pydm_spinbox.minimum() == -5
assert pydm_spinbox.maximum() == 5

# Finally flip it back to ensure it switches back to the user-defined range as expected
pydm_spinbox.userDefinedLimits = True
assert pydm_spinbox.minimum() == -10.5
assert pydm_spinbox.maximum() == 10.5


@pytest.mark.parametrize("key_pressed, initial_spinbox_value, expected_result, write_on_press", [
Expand Down
104 changes: 97 additions & 7 deletions pydm/widgets/spinbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ def __init__(self, parent=None, init_channel=None):
self._alarm_sensitive_border = False
self._show_step_exponent = True
self._write_on_press = False
self._user_defined_limits = False
self._user_minimum = 0
self._user_maximum = 0
self.step_exponent = 0
self.setDecimals(0)
self.app = QApplication.instance()
Expand All @@ -32,7 +35,6 @@ def __init__(self, parent=None, init_channel=None):
child = self.findChild(QLineEdit)
child.installEventFilter(self)


def stepBy(self, step):
"""
Method triggered whenever user triggers a step. If the writeOnPress property
Expand All @@ -46,7 +48,6 @@ def stepBy(self, step):
if self._write_on_press:
self.send_value()


def keyPressEvent(self, ev):
"""
Method invoked when a key press event happens on the QDoubleSpinBox.
Expand Down Expand Up @@ -74,7 +75,6 @@ def keyPressEvent(self, ev):

else:
super(PyDMSpinbox, self).keyPressEvent(ev)


def widget_ctx_menu(self):
"""
Expand Down Expand Up @@ -156,6 +156,95 @@ def send_value(self):
if not self.valueBeingSet:
self.send_value_signal[float].emit(value)

@Property(bool)
def userDefinedLimits(self) -> bool:
"""
True if the range of the spinbox should be set based on user-defined limits, False if
it should be set based on the limits received from the channel
Returns
-------
bool
"""
return self._user_defined_limits

@userDefinedLimits.setter
def userDefinedLimits(self, user_defined_limits: bool) -> None:
"""
Whether or not to set the range of the spinbox based on user-defined limits. Will also reset
the range of the spinbox in case this is called while the application is running to ensure it matches
what the user requested.
Parameters
----------
user_defined_limits : bool
"""
self._user_defined_limits = user_defined_limits
self.reset_limits()

@Property(float)
def userMinimum(self) -> float:
"""
Lower user-defined limit value
Returns
-------
float
"""
return self._user_minimum

@userMinimum.setter
def userMinimum(self, new_min: float) -> None:
"""
Set the Lower user-defined limit value, updates the range of the spinbox if needed
Parameters
----------
new_min : float
"""
self._user_minimum = new_min
self.reset_limits()

@Property(float)
def userMaximum(self) -> float:
"""
Upper user-defined limit value
Returns
-------
float
"""
return self._user_maximum

@userMaximum.setter
def userMaximum(self, new_max: float) -> None:
"""
Set the upper user-defined limit value, updates the range of the spinbox if needed
Parameters
----------
new_max : float
"""
self._user_maximum = new_max
self.reset_limits()

def reset_limits(self) -> None:
"""
Will reset the lower and upper limits to either the ones set by the user, or the ones from the
channel depending on the current state of userDefinedLimits(). If a None value would be set,
do not attempt the update.
"""
if self.userDefinedLimits:
if self.userMinimum is None or self.userMaximum is None:
return
self.setMinimum(self.userMinimum)
self.setMaximum(self.userMaximum)
else:
if self._lower_ctrl_limit is None or self._upper_ctrl_limit is None:
return
self.setMinimum(self._lower_ctrl_limit)
self.setMaximum(self._upper_ctrl_limit)

def ctrl_limit_changed(self, which, new_limit):
"""
Callback invoked when the Channel receives new control limit
Expand All @@ -169,10 +258,11 @@ def ctrl_limit_changed(self, which, new_limit):
New value for the control limit
"""
super(PyDMSpinbox, self).ctrl_limit_changed(which, new_limit)
if which == "UPPER":
self.setMaximum(new_limit)
else:
self.setMinimum(new_limit)
if not self.userDefinedLimits:
if which == "UPPER":
self.setMaximum(new_limit)
else:
self.setMinimum(new_limit)

def precision_changed(self, new_precision):
"""
Expand Down

0 comments on commit 86d39f8

Please sign in to comment.