forked from M-Labs/thermostat
ctrl_panel: Pin down units for editable fields
User input always has the same order of magnitude, so allowing multiple siPrefixes would be unwanted complexity. Don't allow them to be changed. The Parameter option "noUnitEditing" is added to do so by the following measures: 1. Don't validate for changed siPrefix and suffix, which avoids their removal. 2. Avoid getting the cursor embedded within the unit.
This commit is contained in:
parent
c4ced31d18
commit
7b899eca2a
@ -4,7 +4,7 @@ from pyqtgraph.parametertree import (
|
|||||||
Parameter,
|
Parameter,
|
||||||
registerParameterType,
|
registerParameterType,
|
||||||
)
|
)
|
||||||
import pytec.gui.view.pin_si_prefix
|
import pytec.gui.view.unitful
|
||||||
|
|
||||||
|
|
||||||
class MutexParameter(pTypes.ListParameter):
|
class MutexParameter(pTypes.ListParameter):
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
"name": "temperature",
|
"name": "temperature",
|
||||||
"title": "Temperature",
|
"title": "Temperature",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"format": "{value:.4f} °C",
|
"format": "{value:.4f} {suffix}",
|
||||||
|
"suffix": "°C",
|
||||||
"readonly": true,
|
"readonly": true,
|
||||||
"tip": "The measured temperature at the thermistor"
|
"tip": "The measured temperature at the thermistor"
|
||||||
},
|
},
|
||||||
@ -58,6 +59,7 @@
|
|||||||
"pinSiPrefix": "m",
|
"pinSiPrefix": "m",
|
||||||
"suffix": "A",
|
"suffix": "A",
|
||||||
"siPrefix": true,
|
"siPrefix": true,
|
||||||
|
"noUnitEditing": true,
|
||||||
"param": [
|
"param": [
|
||||||
"pwm",
|
"pwm",
|
||||||
"ch",
|
"ch",
|
||||||
@ -76,7 +78,9 @@
|
|||||||
-273,
|
-273,
|
||||||
300
|
300
|
||||||
],
|
],
|
||||||
"format": "{value:.4f} °C",
|
"format": "{value:.4f} {suffix}",
|
||||||
|
"suffix": "°C",
|
||||||
|
"noUnitEditing": true,
|
||||||
"param": [
|
"param": [
|
||||||
"pid",
|
"pid",
|
||||||
"ch",
|
"ch",
|
||||||
@ -108,6 +112,7 @@
|
|||||||
"siPrefix": true,
|
"siPrefix": true,
|
||||||
"pinSiPrefix": "m",
|
"pinSiPrefix": "m",
|
||||||
"suffix": "A",
|
"suffix": "A",
|
||||||
|
"noUnitEditing": true,
|
||||||
"param": [
|
"param": [
|
||||||
"pwm",
|
"pwm",
|
||||||
"ch",
|
"ch",
|
||||||
@ -126,6 +131,7 @@
|
|||||||
"siPrefix": true,
|
"siPrefix": true,
|
||||||
"pinSiPrefix": "m",
|
"pinSiPrefix": "m",
|
||||||
"suffix": "A",
|
"suffix": "A",
|
||||||
|
"noUnitEditing": true,
|
||||||
"limits": [
|
"limits": [
|
||||||
0,
|
0,
|
||||||
2
|
2
|
||||||
@ -150,6 +156,7 @@
|
|||||||
],
|
],
|
||||||
"siPrefix": true,
|
"siPrefix": true,
|
||||||
"suffix": "V",
|
"suffix": "V",
|
||||||
|
"noUnitEditing": true,
|
||||||
"param": [
|
"param": [
|
||||||
"pwm",
|
"pwm",
|
||||||
"ch",
|
"ch",
|
||||||
@ -179,7 +186,9 @@
|
|||||||
-100,
|
-100,
|
||||||
100
|
100
|
||||||
],
|
],
|
||||||
"format": "{value:.4f} °C",
|
"format": "{value:.4f} {suffix}",
|
||||||
|
"suffix": "°C",
|
||||||
|
"noUnitEditing": true,
|
||||||
"param": [
|
"param": [
|
||||||
"s-h",
|
"s-h",
|
||||||
"ch",
|
"ch",
|
||||||
@ -196,6 +205,7 @@
|
|||||||
"step": 1,
|
"step": 1,
|
||||||
"siPrefix": true,
|
"siPrefix": true,
|
||||||
"suffix": "Ω",
|
"suffix": "Ω",
|
||||||
|
"noUnitEditing": true,
|
||||||
"param": [
|
"param": [
|
||||||
"s-h",
|
"s-h",
|
||||||
"ch",
|
"ch",
|
||||||
@ -211,6 +221,7 @@
|
|||||||
"value": 3950,
|
"value": 3950,
|
||||||
"step": 1,
|
"step": 1,
|
||||||
"suffix": "K",
|
"suffix": "K",
|
||||||
|
"noUnitEditing": true,
|
||||||
"decimals": 4,
|
"decimals": 4,
|
||||||
"param": [
|
"param": [
|
||||||
"s-h",
|
"s-h",
|
||||||
@ -269,6 +280,7 @@
|
|||||||
"type": "float",
|
"type": "float",
|
||||||
"step": 0.1,
|
"step": 0.1,
|
||||||
"suffix": "Hz",
|
"suffix": "Hz",
|
||||||
|
"noUnitEditing": true,
|
||||||
"param": [
|
"param": [
|
||||||
"pid",
|
"pid",
|
||||||
"ch",
|
"ch",
|
||||||
@ -283,6 +295,7 @@
|
|||||||
"type": "float",
|
"type": "float",
|
||||||
"step": 0.1,
|
"step": 0.1,
|
||||||
"suffix": "s",
|
"suffix": "s",
|
||||||
|
"noUnitEditing": true,
|
||||||
"param": [
|
"param": [
|
||||||
"pid",
|
"pid",
|
||||||
"ch",
|
"ch",
|
||||||
@ -311,6 +324,7 @@
|
|||||||
"siPrefix": true,
|
"siPrefix": true,
|
||||||
"pinSiPrefix": "m",
|
"pinSiPrefix": "m",
|
||||||
"suffix": "A",
|
"suffix": "A",
|
||||||
|
"noUnitEditing": true,
|
||||||
"param": [
|
"param": [
|
||||||
"pid",
|
"pid",
|
||||||
"ch",
|
"ch",
|
||||||
@ -332,6 +346,7 @@
|
|||||||
"siPrefix": true,
|
"siPrefix": true,
|
||||||
"pinSiPrefix": "m",
|
"pinSiPrefix": "m",
|
||||||
"suffix": "A",
|
"suffix": "A",
|
||||||
|
"noUnitEditing": true,
|
||||||
"param": [
|
"param": [
|
||||||
"pid",
|
"pid",
|
||||||
"ch",
|
"ch",
|
||||||
@ -355,7 +370,9 @@
|
|||||||
"type": "float",
|
"type": "float",
|
||||||
"value": 20,
|
"value": 20,
|
||||||
"step": 0.1,
|
"step": 0.1,
|
||||||
"format": "{value:.4f} °C",
|
"format": "{value:.4f} {suffix}",
|
||||||
|
"suffix": "°C",
|
||||||
|
"noUnitEditing": true,
|
||||||
"pid_autotune": [
|
"pid_autotune": [
|
||||||
"target_temp",
|
"target_temp",
|
||||||
"ch"
|
"ch"
|
||||||
@ -376,6 +393,7 @@
|
|||||||
"siPrefix": true,
|
"siPrefix": true,
|
||||||
"pinSiPrefix": "m",
|
"pinSiPrefix": "m",
|
||||||
"suffix": "A",
|
"suffix": "A",
|
||||||
|
"noUnitEditing": true,
|
||||||
"pid_autotune": [
|
"pid_autotune": [
|
||||||
"test_current",
|
"test_current",
|
||||||
"ch"
|
"ch"
|
||||||
@ -389,7 +407,9 @@
|
|||||||
"value": 1.5,
|
"value": 1.5,
|
||||||
"step": 0.1,
|
"step": 0.1,
|
||||||
"prefix": "±",
|
"prefix": "±",
|
||||||
"format": "{value:.4f} °C",
|
"format": "{value:.4f} {suffix}",
|
||||||
|
"suffix": "°C",
|
||||||
|
"noUnitEditing": true,
|
||||||
"pid_autotune": [
|
"pid_autotune": [
|
||||||
"temp_swing",
|
"temp_swing",
|
||||||
"ch"
|
"ch"
|
||||||
@ -402,7 +422,9 @@
|
|||||||
"type": "float",
|
"type": "float",
|
||||||
"value": 3.0,
|
"value": 3.0,
|
||||||
"step": 0.1,
|
"step": 0.1,
|
||||||
"format": "{value:.4f} s",
|
"format": "{value:.4f} {suffix}",
|
||||||
|
"noUnitEditing": true,
|
||||||
|
"suffix": "s",
|
||||||
"pid_autotune": [
|
"pid_autotune": [
|
||||||
"lookback",
|
"lookback",
|
||||||
"ch"
|
"ch"
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
from PyQt6.QtCore import QSignalBlocker
|
||||||
|
from PyQt6.QtGui import QValidator
|
||||||
|
|
||||||
from pyqtgraph import SpinBox
|
from pyqtgraph import SpinBox
|
||||||
import pyqtgraph.functions as fn
|
import pyqtgraph.functions as fn
|
||||||
from pyqtgraph.parametertree.parameterTypes import (
|
from pyqtgraph.parametertree.parameterTypes import (
|
||||||
@ -7,18 +10,74 @@ from pyqtgraph.parametertree.parameterTypes import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PinSIPrefixSpinBox(SpinBox):
|
class UnitfulSpinBox(SpinBox):
|
||||||
"""
|
"""
|
||||||
Extension of PyQtGraph's SpinBox widget.
|
Extension of PyQtGraph's SpinBox widget.
|
||||||
Adds:
|
Adds:
|
||||||
|
|
||||||
* The "pinSiPrefix" option, where the siPrefix could be fixed to a
|
* The "pinSiPrefix" option, where the siPrefix could be fixed to a
|
||||||
particular scale instead of as determined by its value.
|
particular scale instead of as determined by its value.
|
||||||
|
* The "noUnitEditing" option, where the unit portion of the
|
||||||
|
SpinBox text, including the siPrefix, is fixed and uneditable.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None, value=0.0, **kwargs):
|
||||||
|
super().__init__(parent, value, **kwargs)
|
||||||
|
|
||||||
|
self._current_si_prefix = ""
|
||||||
|
self.lineEdit().cursorPositionChanged.connect(
|
||||||
|
self.editor_cursor_position_changed
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate(self, strn, pos):
|
||||||
|
ret, strn, pos = super().validate(strn, pos)
|
||||||
|
|
||||||
|
if self.opts.get("noUnitEditing") is True:
|
||||||
|
suffix = self.opts["suffix"]
|
||||||
|
|
||||||
|
# When the unit is edited / removed
|
||||||
|
if not (
|
||||||
|
strn.endswith(suffix)
|
||||||
|
and strn.removesuffix(suffix).endswith(self._current_si_prefix)
|
||||||
|
):
|
||||||
|
# Then the input is invalid instead of incomplete, reject this change
|
||||||
|
ret = QValidator.State.Invalid
|
||||||
|
|
||||||
|
return ret, strn, pos
|
||||||
|
|
||||||
|
def editor_cursor_position_changed(self, oldpos, newpos):
|
||||||
|
"""
|
||||||
|
Modified from the original Qt C++ source,
|
||||||
|
QAbstractSpinBox::editorCursorPositionChanged
|
||||||
|
|
||||||
|
Their suffix is different than our suffix; there's no obvious
|
||||||
|
way to set that one here.
|
||||||
|
"""
|
||||||
|
if self.opts.get("noUnitEditing") is True:
|
||||||
|
edit = self.lineEdit()
|
||||||
|
if edit.hasSelectedText():
|
||||||
|
return # Allow for selecting units, for copy-and-paste
|
||||||
|
|
||||||
|
unit_len = len(self._current_si_prefix) + len(self.opts["suffix"])
|
||||||
|
text_len = len(edit.text())
|
||||||
|
|
||||||
|
pos = -1
|
||||||
|
# Cursor in unit
|
||||||
|
if text_len - unit_len < newpos < text_len:
|
||||||
|
if oldpos == text_len:
|
||||||
|
pos = text_len - unit_len
|
||||||
|
else:
|
||||||
|
pos = text_len
|
||||||
|
|
||||||
|
if pos != -1:
|
||||||
|
with QSignalBlocker(edit):
|
||||||
|
edit.setCursorPosition(pos)
|
||||||
|
|
||||||
def setOpts(self, **opts):
|
def setOpts(self, **opts):
|
||||||
if "pinSiPrefix" in opts:
|
if "pinSiPrefix" in opts:
|
||||||
self.opts["pinSiPrefix"] = opts.pop("pinSiPrefix")
|
self.opts["pinSiPrefix"] = opts.pop("pinSiPrefix")
|
||||||
|
if "noUnitEditing" in opts:
|
||||||
|
self.opts["noUnitEditing"] = opts.pop("noUnitEditing")
|
||||||
|
|
||||||
super().setOpts(**opts)
|
super().setOpts(**opts)
|
||||||
|
|
||||||
@ -34,7 +93,7 @@ class PinSIPrefixSpinBox(SpinBox):
|
|||||||
decimals = self.opts['decimals']
|
decimals = self.opts['decimals']
|
||||||
suffix = self.opts['suffix']
|
suffix = self.opts['suffix']
|
||||||
prefix = self.opts['prefix']
|
prefix = self.opts['prefix']
|
||||||
pin_si_prefix = self.opts["pinSiPrefix"]
|
pin_si_prefix = self.opts.get("pinSiPrefix")
|
||||||
|
|
||||||
# format the string
|
# format the string
|
||||||
val = self.value()
|
val = self.value()
|
||||||
@ -50,6 +109,7 @@ class PinSIPrefixSpinBox(SpinBox):
|
|||||||
else:
|
else:
|
||||||
(s, p) = fn.siScale(val)
|
(s, p) = fn.siScale(val)
|
||||||
parts = {'value': val, 'suffix': suffix, 'decimals': decimals, 'siPrefix': p, 'scaledValue': s*val, 'prefix':prefix}
|
parts = {'value': val, 'suffix': suffix, 'decimals': decimals, 'siPrefix': p, 'scaledValue': s*val, 'prefix':prefix}
|
||||||
|
self._current_si_prefix = p
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# no SI prefix /suffix requested; scale is 1
|
# no SI prefix /suffix requested; scale is 1
|
||||||
@ -61,10 +121,10 @@ class PinSIPrefixSpinBox(SpinBox):
|
|||||||
return self.opts['format'].format(**parts)
|
return self.opts['format'].format(**parts)
|
||||||
|
|
||||||
|
|
||||||
class PinSIPrefixNumericParameterItem(NumericParameterItem):
|
class UnitfulNumericParameterItem(NumericParameterItem):
|
||||||
"""
|
"""
|
||||||
Subclasses PyQtGraph's `NumericParameterItem` and uses
|
Subclasses PyQtGraph's `NumericParameterItem` and uses
|
||||||
PinSIPrefixSpinBox for editing.
|
UnitfulSpinBox for editing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def makeWidget(self):
|
def makeWidget(self):
|
||||||
@ -74,7 +134,7 @@ class PinSIPrefixNumericParameterItem(NumericParameterItem):
|
|||||||
'value': 0, 'min': None, 'max': None,
|
'value': 0, 'min': None, 'max': None,
|
||||||
'step': 1.0, 'dec': False,
|
'step': 1.0, 'dec': False,
|
||||||
'siPrefix': False, 'suffix': '', 'decimals': 3,
|
'siPrefix': False, 'suffix': '', 'decimals': 3,
|
||||||
'pinSiPrefix': None,
|
'pinSiPrefix': None, 'noUnitEditing': False,
|
||||||
}
|
}
|
||||||
if t == 'int':
|
if t == 'int':
|
||||||
defs['int'] = True
|
defs['int'] = True
|
||||||
@ -84,7 +144,7 @@ class PinSIPrefixNumericParameterItem(NumericParameterItem):
|
|||||||
defs[k] = opts[k]
|
defs[k] = opts[k]
|
||||||
if 'limits' in opts:
|
if 'limits' in opts:
|
||||||
defs['min'], defs['max'] = opts['limits']
|
defs['min'], defs['max'] = opts['limits']
|
||||||
w = PinSIPrefixSpinBox()
|
w = UnitfulSpinBox()
|
||||||
w.setOpts(**defs)
|
w.setOpts(**defs)
|
||||||
w.sigChanged = w.sigValueChanged
|
w.sigChanged = w.sigValueChanged
|
||||||
w.sigChanging = w.sigValueChanging
|
w.sigChanging = w.sigValueChanging
|
||||||
@ -92,8 +152,8 @@ class PinSIPrefixNumericParameterItem(NumericParameterItem):
|
|||||||
|
|
||||||
|
|
||||||
registerParameterItemType(
|
registerParameterItemType(
|
||||||
"float", PinSIPrefixNumericParameterItem, SimpleParameter, override=True
|
"float", UnitfulNumericParameterItem, SimpleParameter, override=True
|
||||||
)
|
)
|
||||||
registerParameterItemType(
|
registerParameterItemType(
|
||||||
"int", PinSIPrefixNumericParameterItem, SimpleParameter, override=True
|
"int", UnitfulNumericParameterItem, SimpleParameter, override=True
|
||||||
)
|
)
|
Loading…
Reference in New Issue
Block a user