forked from M-Labs/artiq
1
0
Fork 0

language,gui: support scaling of number entries

This commit is contained in:
Sebastien Bourdeauducq 2015-10-06 00:30:41 +08:00
parent 342e72bed6
commit 66f82a13d3
6 changed files with 77 additions and 36 deletions

View File

@ -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):

View File

@ -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"])

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,