from qtpy.QtWidgets import QDoubleSpinBox, QApplication, QLineEdit
from qtpy.QtCore import Property, Qt
from .base import PyDMWritableWidget, TextFormatter
[docs]class PyDMSpinbox(QDoubleSpinBox, TextFormatter, PyDMWritableWidget):
"""
A QDoubleSpinBox with support for Channels and more from PyDM.
Parameters
----------
parent : QWidget
The parent widget for the Label
init_channel : str, optional
The channel to be used by the widget.
"""
def __init__(self, parent=None, init_channel=None):
QDoubleSpinBox.__init__(self, parent)
PyDMWritableWidget.__init__(self, init_channel=init_channel)
self.valueBeingSet = False
self.setEnabled(False)
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()
self.setAccelerated(True)
# install an event filter on the QDoubleSpinBox's children
# in order to catch the click events
child = self.findChild(QLineEdit)
child.installEventFilter(self)
[docs] def stepBy(self, step):
"""
Method triggered whenever user triggers a step. If the writeOnPress property
is enabled, the updated value will be sent.
Parameters
----------
step: int
"""
super(PyDMSpinbox, self).stepBy(step)
if self._write_on_press:
self.send_value()
[docs] def keyPressEvent(self, ev):
"""
Method invoked when a key press event happens on the QDoubleSpinBox.
For PyDMSpinBox we are interested on the Keypress events for:
- CTRL + Left/Right : Increase or Decrease the step exponent;
- Up / Down : Add or Remove `singleStep` units to the value;
- PageUp / PageDown : Add or Remove 10 times `singleStep` units
to the value;
- Return or Enter : Send the value to the channel using the
`send_value_signal`.
Parameters
----------
ev : QEvent
"""
ctrl_hold = self.app.queryKeyboardModifiers() == Qt.ControlModifier
if ctrl_hold and (ev.key() in (Qt.Key_Left, Qt.Key_Right)):
self.step_exponent += 1 if ev.key() == Qt.Key_Left else -1
self.step_exponent = max(-self.decimals(), self.step_exponent)
self.update_step_size()
elif ev.key() in (Qt.Key_Return, Qt.Key_Enter):
self.send_value()
else:
super(PyDMSpinbox, self).keyPressEvent(ev)
[docs] def update_step_size(self):
"""
Update the Single Step size on the QDoubleSpinBox.
"""
self.setSingleStep(10**self.step_exponent)
self.update_format_string()
[docs] def value_changed(self, new_val):
"""
Callback invoked when the Channel value is changed.
Parameters
----------
new_val : int or float
The new value from the channel.
"""
if new_val is None:
# SpinBox is unable to work with None and
# but sometimes it can arrive as an initial value
return
super(PyDMSpinbox, self).value_changed(new_val)
self.valueBeingSet = True
self.setValue(new_val)
self.valueBeingSet = False
[docs] def send_value(self):
"""
Method invoked to send the current value on the QDoubleSpinBox to
the channel using the `send_value_signal`.
"""
value = QDoubleSpinBox.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()
[docs] 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)
[docs] def ctrl_limit_changed(self, which, new_limit):
"""
Callback invoked when the Channel receives new control limit
values.
Parameters
----------
which : str
Which control limit was changed. "UPPER" or "LOWER"
new_limit : float
New value for the control limit
"""
super(PyDMSpinbox, self).ctrl_limit_changed(which, new_limit)
if not self.userDefinedLimits:
if which == "UPPER":
self.setMaximum(new_limit)
else:
self.setMinimum(new_limit)
[docs] def precision_changed(self, new_precision):
"""
Callback invoked when the Channel has new precision value.
This callback also triggers an update_format_string call so the
new precision value is considered.
Parameters
----------
new_precison : int or float
The new precision value
"""
super(PyDMSpinbox, self).precision_changed(new_precision)
self.setDecimals(self.precision)
@Property(int)
def precision(self):
if self.precisionFromPV:
return self._prec
else:
return self._user_prec
@precision.setter
def precision(self, new_prec):
if self.precisionFromPV:
return
if new_prec and self._user_prec != int(new_prec) and new_prec >= 0:
self._user_prec = int(new_prec)
self.value_changed(self.value)
self.setDecimals(new_prec)
@Property(bool)
def showStepExponent(self):
"""
Whether to show or not the step exponent
Returns
-------
bool
"""
return self._show_step_exponent
@showStepExponent.setter
def showStepExponent(self, val):
"""
Whether to show or not the step exponent
Parameters
----------
val : bool
"""
self._show_step_exponent = val
self.update_format_string()
@Property(bool)
def writeOnPress(self):
"""
Whether to write value on key press
Returns
-------
bool
"""
return self._write_on_press
@writeOnPress.setter
def writeOnPress(self, val):
"""
Whether value to write on key press.
Parameters
----------
val : bool
"""
self._write_on_press = val