From 66f82a13d3cac15752772409c437ed211d36c920 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 6 Oct 2015 00:30:41 +0800 Subject: [PATCH] language,gui: support scaling of number entries --- artiq/gui/explorer.py | 18 ++++---- artiq/gui/scan.py | 45 +++++++++++--------- artiq/gui/tools.py | 17 ++++++++ artiq/language/environment.py | 13 ++++-- artiq/language/scan.py | 14 ++++-- examples/master/repository/arguments_demo.py | 6 ++- 6 files changed, 77 insertions(+), 36 deletions(-) diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py index fe6cb58cb..75ea4f338 100644 --- a/artiq/gui/explorer.py +++ b/artiq/gui/explorer.py @@ -6,7 +6,7 @@ from pyqtgraph import LayoutWidget from artiq.protocols.sync_struct import Subscriber 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 @@ -74,26 +74,28 @@ class _EnumerationEntry(QtGui.QComboBox): class _NumberEntry(QtGui.QDoubleSpinBox): def __init__(self, procdesc): QtGui.QDoubleSpinBox.__init__(self) + self.scale = procdesc["scale"] self.setDecimals(procdesc["ndecimals"]) - self.setSingleStep(procdesc["step"]) + self.setSingleStep(procdesc["step"]/self.scale) if procdesc["min"] is not None: - self.setMinimum(procdesc["min"]) + self.setMinimum(procdesc["min"]/self.scale) else: self.setMinimum(float("-inf")) if procdesc["max"] is not None: - self.setMaximum(procdesc["max"]) + self.setMaximum(procdesc["max"]/self.scale) else: self.setMaximum(float("inf")) - if procdesc["unit"]: - self.setSuffix(" " + procdesc["unit"]) + suffix = si_prefix(self.scale) + procdesc["unit"] + if suffix: + self.setSuffix(" " + suffix) if "default" in procdesc: self.set_argument_value(procdesc["default"]) def get_argument_value(self): - return self.value() + return self.value()*self.scale def set_argument_value(self, value): - self.setValue(value) + self.setValue(value/self.scale) class _StringEntry(QtGui.QLineEdit): diff --git a/artiq/gui/scan.py b/artiq/gui/scan.py index 0065b421d..7ed69ed46 100644 --- a/artiq/gui/scan.py +++ b/artiq/gui/scan.py @@ -1,25 +1,28 @@ from quamash import QtGui from pyqtgraph import LayoutWidget +from artiq.gui.tools import si_prefix + 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) + self.scale = scale def apply_properties(spinbox): spinbox.setDecimals(ndecimals) if global_min is not None: - spinbox.setMinimum(global_min) + spinbox.setMinimum(global_min/self.scale) else: spinbox.setMinimum(float("-inf")) if global_max is not None: - spinbox.setMaximum(global_max) + spinbox.setMaximum(global_max/self.scale) else: spinbox.setMaximum(float("inf")) if global_step is not None: - spinbox.setSingleStep(global_step) - if unit: - spinbox.setSuffix(" " + unit) + spinbox.setSingleStep(global_step/self.scale) + if suffix: + spinbox.setSuffix(" " + suffix) self.addWidget(QtGui.QLabel("Min:"), 0, 0) self.min = QtGui.QDoubleSpinBox() @@ -38,8 +41,8 @@ class _Range(LayoutWidget): self.addWidget(self.npoints, 0, 5) def set_values(self, min, max, npoints): - self.min.setValue(min) - self.max.setValue(max) + self.min.setValue(min/self.scale) + self.max.setValue(max/self.scale) self.npoints.setValue(npoints) def get_values(self): @@ -48,8 +51,8 @@ class _Range(LayoutWidget): if min > max: raise ValueError("Minimum scan boundary must be less than maximum") return { - "min": min, - "max": max, + "min": min*self.scale, + "max": max*self.scale, "npoints": self.npoints.value() } @@ -61,33 +64,35 @@ class ScanController(LayoutWidget): self.stack = QtGui.QStackedWidget() self.addWidget(self.stack, 1, 0, colspan=4) + self.scale = procdesc["scale"] + gmin, gmax = procdesc["global_min"], procdesc["global_max"] gstep = procdesc["global_step"] - unit = procdesc["unit"] + suffix = si_prefix(self.scale) + procdesc["unit"] ndecimals = procdesc["ndecimals"] self.v_noscan = QtGui.QDoubleSpinBox() self.v_noscan.setDecimals(ndecimals) if gmin is not None: - self.v_noscan.setMinimum(gmin) + self.v_noscan.setMinimum(gmin/self.scale) else: self.v_noscan.setMinimum(float("-inf")) if gmax is not None: - self.v_noscan.setMaximum(gmax) + self.v_noscan.setMaximum(gmax/self.scale) else: self.v_noscan.setMaximum(float("inf")) - self.v_noscan.setSingleStep(gstep) - if unit: - self.v_noscan.setSuffix(" " + unit) + self.v_noscan.setSingleStep(gstep/self.scale) + if suffix: + self.v_noscan.setSuffix(" " + suffix) self.v_noscan_gr = LayoutWidget() self.v_noscan_gr.addWidget(QtGui.QLabel("Value:"), 0, 0) self.v_noscan_gr.addWidget(self.v_noscan, 0, 1) 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.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.v_explicit = QtGui.QLineEdit() @@ -124,7 +129,7 @@ class ScanController(LayoutWidget): def get_argument_value(self): 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(): d = {"ty": "LinearScan"} d.update(self.v_linear.get_values()) @@ -140,7 +145,7 @@ class ScanController(LayoutWidget): def set_argument_value(self, d): if d["ty"] == "NoScan": self.noscan.setChecked(True) - self.v_noscan.setValue(d["value"]) + self.v_noscan.setValue(d["value"]/self.scale) elif d["ty"] == "LinearScan": self.linear.setChecked(True) self.v_linear.set_values(d["min"], d["max"], d["npoints"]) diff --git a/artiq/gui/tools.py b/artiq/gui/tools.py index ecce285ed..265a46491 100644 --- a/artiq/gui/tools.py +++ b/artiq/gui/tools.py @@ -37,6 +37,23 @@ def short_format(v): 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: def __init__(self, update_cb, ref): self.update_cb = update_cb diff --git a/artiq/language/environment.py b/artiq/language/environment.py index c2fe8ab51..b2b1c2a1d 100644 --- a/artiq/language/environment.py +++ b/artiq/language/environment.py @@ -73,16 +73,22 @@ class NumberValue(_SimpleArgProcessor): :param unit: A string representing the unit of the value, for user 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 - 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 max: The maximum value of the argument. :param ndecimals: The number of decimals a UI should use. """ - def __init__(self, default=NoDefault, unit="", step=1.0, - min=None, max=None, ndecimals=2): + def __init__(self, default=NoDefault, unit="", scale=1.0, + step=None, min=None, max=None, ndecimals=2): + if step is None: + step = scale/10.0 _SimpleArgProcessor.__init__(self, default) self.unit = unit + self.scale = scale self.step = step self.min = min self.max = max @@ -91,6 +97,7 @@ class NumberValue(_SimpleArgProcessor): def describe(self): d = _SimpleArgProcessor.describe(self) d["unit"] = self.unit + d["scale"] = self.scale d["step"] = self.step d["min"] = self.min d["max"] = self.max diff --git a/artiq/language/scan.py b/artiq/language/scan.py index 9f4b9e468..dabd49af8 100644 --- a/artiq/language/scan.py +++ b/artiq/language/scan.py @@ -121,17 +121,24 @@ class Scannable: range of its input widgets. :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 - 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 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. """ - def __init__(self, default=NoDefault, unit="", - global_step=1.0, global_min=None, global_max=None, + def __init__(self, default=NoDefault, unit="", scale=1.0, + global_step=None, global_min=None, global_max=None, ndecimals=2): + if global_step is None: + global_step = scale/10.0 if default is not NoDefault: self.default_value = default self.unit = unit + self.scale = scale self.global_step = global_step self.global_min = global_min self.global_max = global_max @@ -155,6 +162,7 @@ class Scannable: if hasattr(self, "default_value"): d["default"] = self.default_value.describe() d["unit"] = self.unit + d["scale"] = self.scale d["global_step"] = self.global_step d["global_min"] = self.global_min d["global_max"] = self.global_max diff --git a/examples/master/repository/arguments_demo.py b/examples/master/repository/arguments_demo.py index dad66d8a2..d52083faa 100644 --- a/examples/master/repository/arguments_demo.py +++ b/examples/master/repository/arguments_demo.py @@ -3,7 +3,8 @@ from artiq import * class SubComponent1(HasEnvironment): 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") self.setattr_argument("sc1_enum", EnumerationValue(["1", "2", "3"]), "Flux capacitor") @@ -35,7 +36,8 @@ class SubComponent2(HasEnvironment): class ArgumentsDemo(EnvExperiment): def build(self): 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)) self.setattr_argument("string", StringValue("Hello World")) self.setattr_argument("scan", Scannable(global_max=400,