mirror of https://github.com/m-labs/artiq.git
language,gui: support scaling of number entries
This commit is contained in:
parent
342e72bed6
commit
66f82a13d3
|
@ -6,7 +6,7 @@ from pyqtgraph import LayoutWidget
|
||||||
|
|
||||||
from artiq.protocols.sync_struct import Subscriber
|
from artiq.protocols.sync_struct import Subscriber
|
||||||
from artiq.protocols import pyon
|
from artiq.protocols import pyon
|
||||||
from artiq.gui.tools import DictSyncModel
|
from artiq.gui.tools import si_prefix, DictSyncModel
|
||||||
from artiq.gui.scan import ScanController
|
from artiq.gui.scan import ScanController
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,26 +74,28 @@ class _EnumerationEntry(QtGui.QComboBox):
|
||||||
class _NumberEntry(QtGui.QDoubleSpinBox):
|
class _NumberEntry(QtGui.QDoubleSpinBox):
|
||||||
def __init__(self, procdesc):
|
def __init__(self, procdesc):
|
||||||
QtGui.QDoubleSpinBox.__init__(self)
|
QtGui.QDoubleSpinBox.__init__(self)
|
||||||
|
self.scale = procdesc["scale"]
|
||||||
self.setDecimals(procdesc["ndecimals"])
|
self.setDecimals(procdesc["ndecimals"])
|
||||||
self.setSingleStep(procdesc["step"])
|
self.setSingleStep(procdesc["step"]/self.scale)
|
||||||
if procdesc["min"] is not None:
|
if procdesc["min"] is not None:
|
||||||
self.setMinimum(procdesc["min"])
|
self.setMinimum(procdesc["min"]/self.scale)
|
||||||
else:
|
else:
|
||||||
self.setMinimum(float("-inf"))
|
self.setMinimum(float("-inf"))
|
||||||
if procdesc["max"] is not None:
|
if procdesc["max"] is not None:
|
||||||
self.setMaximum(procdesc["max"])
|
self.setMaximum(procdesc["max"]/self.scale)
|
||||||
else:
|
else:
|
||||||
self.setMaximum(float("inf"))
|
self.setMaximum(float("inf"))
|
||||||
if procdesc["unit"]:
|
suffix = si_prefix(self.scale) + procdesc["unit"]
|
||||||
self.setSuffix(" " + procdesc["unit"])
|
if suffix:
|
||||||
|
self.setSuffix(" " + suffix)
|
||||||
if "default" in procdesc:
|
if "default" in procdesc:
|
||||||
self.set_argument_value(procdesc["default"])
|
self.set_argument_value(procdesc["default"])
|
||||||
|
|
||||||
def get_argument_value(self):
|
def get_argument_value(self):
|
||||||
return self.value()
|
return self.value()*self.scale
|
||||||
|
|
||||||
def set_argument_value(self, value):
|
def set_argument_value(self, value):
|
||||||
self.setValue(value)
|
self.setValue(value/self.scale)
|
||||||
|
|
||||||
|
|
||||||
class _StringEntry(QtGui.QLineEdit):
|
class _StringEntry(QtGui.QLineEdit):
|
||||||
|
|
|
@ -1,25 +1,28 @@
|
||||||
from quamash import QtGui
|
from quamash import QtGui
|
||||||
from pyqtgraph import LayoutWidget
|
from pyqtgraph import LayoutWidget
|
||||||
|
|
||||||
|
from artiq.gui.tools import si_prefix
|
||||||
|
|
||||||
|
|
||||||
class _Range(LayoutWidget):
|
class _Range(LayoutWidget):
|
||||||
def __init__(self, global_min, global_max, global_step, unit, ndecimals):
|
def __init__(self, global_min, global_max, global_step, suffix, scale, ndecimals):
|
||||||
LayoutWidget.__init__(self)
|
LayoutWidget.__init__(self)
|
||||||
|
|
||||||
|
self.scale = scale
|
||||||
def apply_properties(spinbox):
|
def apply_properties(spinbox):
|
||||||
spinbox.setDecimals(ndecimals)
|
spinbox.setDecimals(ndecimals)
|
||||||
if global_min is not None:
|
if global_min is not None:
|
||||||
spinbox.setMinimum(global_min)
|
spinbox.setMinimum(global_min/self.scale)
|
||||||
else:
|
else:
|
||||||
spinbox.setMinimum(float("-inf"))
|
spinbox.setMinimum(float("-inf"))
|
||||||
if global_max is not None:
|
if global_max is not None:
|
||||||
spinbox.setMaximum(global_max)
|
spinbox.setMaximum(global_max/self.scale)
|
||||||
else:
|
else:
|
||||||
spinbox.setMaximum(float("inf"))
|
spinbox.setMaximum(float("inf"))
|
||||||
if global_step is not None:
|
if global_step is not None:
|
||||||
spinbox.setSingleStep(global_step)
|
spinbox.setSingleStep(global_step/self.scale)
|
||||||
if unit:
|
if suffix:
|
||||||
spinbox.setSuffix(" " + unit)
|
spinbox.setSuffix(" " + suffix)
|
||||||
|
|
||||||
self.addWidget(QtGui.QLabel("Min:"), 0, 0)
|
self.addWidget(QtGui.QLabel("Min:"), 0, 0)
|
||||||
self.min = QtGui.QDoubleSpinBox()
|
self.min = QtGui.QDoubleSpinBox()
|
||||||
|
@ -38,8 +41,8 @@ class _Range(LayoutWidget):
|
||||||
self.addWidget(self.npoints, 0, 5)
|
self.addWidget(self.npoints, 0, 5)
|
||||||
|
|
||||||
def set_values(self, min, max, npoints):
|
def set_values(self, min, max, npoints):
|
||||||
self.min.setValue(min)
|
self.min.setValue(min/self.scale)
|
||||||
self.max.setValue(max)
|
self.max.setValue(max/self.scale)
|
||||||
self.npoints.setValue(npoints)
|
self.npoints.setValue(npoints)
|
||||||
|
|
||||||
def get_values(self):
|
def get_values(self):
|
||||||
|
@ -48,8 +51,8 @@ class _Range(LayoutWidget):
|
||||||
if min > max:
|
if min > max:
|
||||||
raise ValueError("Minimum scan boundary must be less than maximum")
|
raise ValueError("Minimum scan boundary must be less than maximum")
|
||||||
return {
|
return {
|
||||||
"min": min,
|
"min": min*self.scale,
|
||||||
"max": max,
|
"max": max*self.scale,
|
||||||
"npoints": self.npoints.value()
|
"npoints": self.npoints.value()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,33 +64,35 @@ class ScanController(LayoutWidget):
|
||||||
self.stack = QtGui.QStackedWidget()
|
self.stack = QtGui.QStackedWidget()
|
||||||
self.addWidget(self.stack, 1, 0, colspan=4)
|
self.addWidget(self.stack, 1, 0, colspan=4)
|
||||||
|
|
||||||
|
self.scale = procdesc["scale"]
|
||||||
|
|
||||||
gmin, gmax = procdesc["global_min"], procdesc["global_max"]
|
gmin, gmax = procdesc["global_min"], procdesc["global_max"]
|
||||||
gstep = procdesc["global_step"]
|
gstep = procdesc["global_step"]
|
||||||
unit = procdesc["unit"]
|
suffix = si_prefix(self.scale) + procdesc["unit"]
|
||||||
ndecimals = procdesc["ndecimals"]
|
ndecimals = procdesc["ndecimals"]
|
||||||
|
|
||||||
self.v_noscan = QtGui.QDoubleSpinBox()
|
self.v_noscan = QtGui.QDoubleSpinBox()
|
||||||
self.v_noscan.setDecimals(ndecimals)
|
self.v_noscan.setDecimals(ndecimals)
|
||||||
if gmin is not None:
|
if gmin is not None:
|
||||||
self.v_noscan.setMinimum(gmin)
|
self.v_noscan.setMinimum(gmin/self.scale)
|
||||||
else:
|
else:
|
||||||
self.v_noscan.setMinimum(float("-inf"))
|
self.v_noscan.setMinimum(float("-inf"))
|
||||||
if gmax is not None:
|
if gmax is not None:
|
||||||
self.v_noscan.setMaximum(gmax)
|
self.v_noscan.setMaximum(gmax/self.scale)
|
||||||
else:
|
else:
|
||||||
self.v_noscan.setMaximum(float("inf"))
|
self.v_noscan.setMaximum(float("inf"))
|
||||||
self.v_noscan.setSingleStep(gstep)
|
self.v_noscan.setSingleStep(gstep/self.scale)
|
||||||
if unit:
|
if suffix:
|
||||||
self.v_noscan.setSuffix(" " + unit)
|
self.v_noscan.setSuffix(" " + suffix)
|
||||||
self.v_noscan_gr = LayoutWidget()
|
self.v_noscan_gr = LayoutWidget()
|
||||||
self.v_noscan_gr.addWidget(QtGui.QLabel("Value:"), 0, 0)
|
self.v_noscan_gr.addWidget(QtGui.QLabel("Value:"), 0, 0)
|
||||||
self.v_noscan_gr.addWidget(self.v_noscan, 0, 1)
|
self.v_noscan_gr.addWidget(self.v_noscan, 0, 1)
|
||||||
self.stack.addWidget(self.v_noscan_gr)
|
self.stack.addWidget(self.v_noscan_gr)
|
||||||
|
|
||||||
self.v_linear = _Range(gmin, gmax, gstep, unit, ndecimals)
|
self.v_linear = _Range(gmin, gmax, gstep, suffix, self.scale, ndecimals)
|
||||||
self.stack.addWidget(self.v_linear)
|
self.stack.addWidget(self.v_linear)
|
||||||
|
|
||||||
self.v_random = _Range(gmin, gmax, gstep, unit, ndecimals)
|
self.v_random = _Range(gmin, gmax, gstep, suffix, self.scale, ndecimals)
|
||||||
self.stack.addWidget(self.v_random)
|
self.stack.addWidget(self.v_random)
|
||||||
|
|
||||||
self.v_explicit = QtGui.QLineEdit()
|
self.v_explicit = QtGui.QLineEdit()
|
||||||
|
@ -124,7 +129,7 @@ class ScanController(LayoutWidget):
|
||||||
|
|
||||||
def get_argument_value(self):
|
def get_argument_value(self):
|
||||||
if self.noscan.isChecked():
|
if self.noscan.isChecked():
|
||||||
return {"ty": "NoScan", "value": self.v_noscan.value()}
|
return {"ty": "NoScan", "value": self.v_noscan.value()*self.scale}
|
||||||
elif self.linear.isChecked():
|
elif self.linear.isChecked():
|
||||||
d = {"ty": "LinearScan"}
|
d = {"ty": "LinearScan"}
|
||||||
d.update(self.v_linear.get_values())
|
d.update(self.v_linear.get_values())
|
||||||
|
@ -140,7 +145,7 @@ class ScanController(LayoutWidget):
|
||||||
def set_argument_value(self, d):
|
def set_argument_value(self, d):
|
||||||
if d["ty"] == "NoScan":
|
if d["ty"] == "NoScan":
|
||||||
self.noscan.setChecked(True)
|
self.noscan.setChecked(True)
|
||||||
self.v_noscan.setValue(d["value"])
|
self.v_noscan.setValue(d["value"]/self.scale)
|
||||||
elif d["ty"] == "LinearScan":
|
elif d["ty"] == "LinearScan":
|
||||||
self.linear.setChecked(True)
|
self.linear.setChecked(True)
|
||||||
self.v_linear.set_values(d["min"], d["max"], d["npoints"])
|
self.v_linear.set_values(d["min"], d["max"], d["npoints"])
|
||||||
|
|
|
@ -37,6 +37,23 @@ def short_format(v):
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def si_prefix(scale):
|
||||||
|
try:
|
||||||
|
return {
|
||||||
|
1e-12: "p",
|
||||||
|
1e-9: "n",
|
||||||
|
1e-6: "u",
|
||||||
|
1e-3: "m",
|
||||||
|
1.0: "",
|
||||||
|
1e3: "k",
|
||||||
|
1e6: "M",
|
||||||
|
1e9: "G",
|
||||||
|
1e12: "T"
|
||||||
|
}[scale]
|
||||||
|
except KeyError:
|
||||||
|
return "[x{}]".format(scale)
|
||||||
|
|
||||||
|
|
||||||
class _SyncSubstruct:
|
class _SyncSubstruct:
|
||||||
def __init__(self, update_cb, ref):
|
def __init__(self, update_cb, ref):
|
||||||
self.update_cb = update_cb
|
self.update_cb = update_cb
|
||||||
|
|
|
@ -73,16 +73,22 @@ class NumberValue(_SimpleArgProcessor):
|
||||||
|
|
||||||
:param unit: A string representing the unit of the value, for user
|
:param unit: A string representing the unit of the value, for user
|
||||||
interface (UI) purposes.
|
interface (UI) purposes.
|
||||||
|
:param scale: The scale of value for UI purposes. The corresponding SI
|
||||||
|
prefix is shown in front of the unit, and the displayed value is
|
||||||
|
divided by the scale.
|
||||||
:param step: The step with which the value should be modified by up/down
|
:param step: The step with which the value should be modified by up/down
|
||||||
buttons in a UI.
|
buttons in a UI. The default is the scale divided by 10.
|
||||||
:param min: The minimum value of the argument.
|
:param min: The minimum value of the argument.
|
||||||
:param max: The maximum value of the argument.
|
:param max: The maximum value of the argument.
|
||||||
:param ndecimals: The number of decimals a UI should use.
|
:param ndecimals: The number of decimals a UI should use.
|
||||||
"""
|
"""
|
||||||
def __init__(self, default=NoDefault, unit="", step=1.0,
|
def __init__(self, default=NoDefault, unit="", scale=1.0,
|
||||||
min=None, max=None, ndecimals=2):
|
step=None, min=None, max=None, ndecimals=2):
|
||||||
|
if step is None:
|
||||||
|
step = scale/10.0
|
||||||
_SimpleArgProcessor.__init__(self, default)
|
_SimpleArgProcessor.__init__(self, default)
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
|
self.scale = scale
|
||||||
self.step = step
|
self.step = step
|
||||||
self.min = min
|
self.min = min
|
||||||
self.max = max
|
self.max = max
|
||||||
|
@ -91,6 +97,7 @@ class NumberValue(_SimpleArgProcessor):
|
||||||
def describe(self):
|
def describe(self):
|
||||||
d = _SimpleArgProcessor.describe(self)
|
d = _SimpleArgProcessor.describe(self)
|
||||||
d["unit"] = self.unit
|
d["unit"] = self.unit
|
||||||
|
d["scale"] = self.scale
|
||||||
d["step"] = self.step
|
d["step"] = self.step
|
||||||
d["min"] = self.min
|
d["min"] = self.min
|
||||||
d["max"] = self.max
|
d["max"] = self.max
|
||||||
|
|
|
@ -121,17 +121,24 @@ class Scannable:
|
||||||
range of its input widgets.
|
range of its input widgets.
|
||||||
:param global_max: Same as global_min, but for the maximum value.
|
:param global_max: Same as global_min, but for the maximum value.
|
||||||
:param global_step: The step with which the value should be modified by
|
:param global_step: The step with which the value should be modified by
|
||||||
up/down buttons in a user interface.
|
up/down buttons in a user interface. The default is the scale divided
|
||||||
|
by 10.
|
||||||
:param unit: A string representing the unit of the scanned variable, for user
|
:param unit: A string representing the unit of the scanned variable, for user
|
||||||
interface (UI) purposes.
|
interface (UI) purposes.
|
||||||
|
:param scale: The scale of value for UI purposes. The corresponding SI
|
||||||
|
prefix is shown in front of the unit, and the displayed value is
|
||||||
|
divided by the scale.
|
||||||
:param ndecimals: The number of decimals a UI should use.
|
:param ndecimals: The number of decimals a UI should use.
|
||||||
"""
|
"""
|
||||||
def __init__(self, default=NoDefault, unit="",
|
def __init__(self, default=NoDefault, unit="", scale=1.0,
|
||||||
global_step=1.0, global_min=None, global_max=None,
|
global_step=None, global_min=None, global_max=None,
|
||||||
ndecimals=2):
|
ndecimals=2):
|
||||||
|
if global_step is None:
|
||||||
|
global_step = scale/10.0
|
||||||
if default is not NoDefault:
|
if default is not NoDefault:
|
||||||
self.default_value = default
|
self.default_value = default
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
|
self.scale = scale
|
||||||
self.global_step = global_step
|
self.global_step = global_step
|
||||||
self.global_min = global_min
|
self.global_min = global_min
|
||||||
self.global_max = global_max
|
self.global_max = global_max
|
||||||
|
@ -155,6 +162,7 @@ class Scannable:
|
||||||
if hasattr(self, "default_value"):
|
if hasattr(self, "default_value"):
|
||||||
d["default"] = self.default_value.describe()
|
d["default"] = self.default_value.describe()
|
||||||
d["unit"] = self.unit
|
d["unit"] = self.unit
|
||||||
|
d["scale"] = self.scale
|
||||||
d["global_step"] = self.global_step
|
d["global_step"] = self.global_step
|
||||||
d["global_min"] = self.global_min
|
d["global_min"] = self.global_min
|
||||||
d["global_max"] = self.global_max
|
d["global_max"] = self.global_max
|
||||||
|
|
|
@ -3,7 +3,8 @@ from artiq import *
|
||||||
|
|
||||||
class SubComponent1(HasEnvironment):
|
class SubComponent1(HasEnvironment):
|
||||||
def build(self):
|
def build(self):
|
||||||
self.setattr_argument("sc1_scan", Scannable(default=NoScan(325)),
|
self.setattr_argument("sc1_scan", Scannable(default=NoScan(3250),
|
||||||
|
scale=1e3, unit="Hz"),
|
||||||
"Flux capacitor")
|
"Flux capacitor")
|
||||||
self.setattr_argument("sc1_enum", EnumerationValue(["1", "2", "3"]),
|
self.setattr_argument("sc1_enum", EnumerationValue(["1", "2", "3"]),
|
||||||
"Flux capacitor")
|
"Flux capacitor")
|
||||||
|
@ -35,7 +36,8 @@ class SubComponent2(HasEnvironment):
|
||||||
class ArgumentsDemo(EnvExperiment):
|
class ArgumentsDemo(EnvExperiment):
|
||||||
def build(self):
|
def build(self):
|
||||||
self.setattr_argument("free_value", FreeValue(None))
|
self.setattr_argument("free_value", FreeValue(None))
|
||||||
self.setattr_argument("number", NumberValue(42, unit="s", step=0.1,
|
self.setattr_argument("number", NumberValue(42e-6,
|
||||||
|
unit="s", scale=1e-6,
|
||||||
ndecimals=4))
|
ndecimals=4))
|
||||||
self.setattr_argument("string", StringValue("Hello World"))
|
self.setattr_argument("string", StringValue("Hello World"))
|
||||||
self.setattr_argument("scan", Scannable(global_max=400,
|
self.setattr_argument("scan", Scannable(global_max=400,
|
||||||
|
|
Loading…
Reference in New Issue