diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py index 66dc9ce36..81b491d52 100644 --- a/artiq/gui/explorer.py +++ b/artiq/gui/explorer.py @@ -119,14 +119,32 @@ class _ArgumentEditor(QtGui.QTreeWidget): def __init__(self, dialog_parent): QtGui.QTreeWidget.__init__(self) self.setColumnCount(2) - self.header().setResizeMode( - QtGui.QHeaderView.ResizeToContents) + self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents) self.header().setVisible(False) self.setSelectionMode(QtGui.QAbstractItemView.NoSelection) self.dialog_parent = dialog_parent + self._groups = dict() self.set_arguments([]) + def clear(self): + QtGui.QTreeWidget.clear(self) + self._groups.clear() + + def _get_group(self, name): + if name in self._groups: + return self._groups[name] + group = QtGui.QTreeWidgetItem([name, ""]) + for c in 0, 1: + group.setBackground(c, QtGui.QBrush(QtGui.QColor(100, 100, 100))) + group.setForeground(c, QtGui.QBrush(QtGui.QColor(220, 220, 255))) + font = group.font(c) + font.setBold(True) + group.setFont(c, font) + self.addTopLevelItem(group) + self._groups[name] = group + return group + def set_arguments(self, arguments): self.clear() @@ -134,12 +152,15 @@ class _ArgumentEditor(QtGui.QTreeWidget): self.addTopLevelItem(QtGui.QTreeWidgetItem(["No arguments", ""])) self._args_to_entries = dict() - for n, (name, procdesc) in enumerate(arguments): + for n, (name, (procdesc, group)) in enumerate(arguments): entry = _procty_to_entry[procdesc["ty"]](procdesc) self._args_to_entries[name] = entry widget_item = QtGui.QTreeWidgetItem([name, ""]) - self.addTopLevelItem(widget_item) + if group is None: + self.addTopLevelItem(widget_item) + else: + self._get_group(group).addChild(widget_item) self.setItemWidget(widget_item, 1, entry) def get_argument_values(self, show_error_message): @@ -167,6 +188,25 @@ class _ArgumentEditor(QtGui.QTreeWidget): if not ignore_errors: raise + def save_state(self): + expanded = [] + for k, v in self._groups.items(): + if v.isExpanded(): + expanded.append(k) + argument_values = self.get_argument_values(False) + return { + "expanded": expanded, + "argument_values": argument_values + } + + def restore_state(self, state): + self.set_argument_values(state["argument_values"], True) + for e in state["expanded"]: + try: + self._groups[e].setExpanded(True) + except KeyError: + pass + class ExplorerDock(dockarea.Dock): def __init__(self, dialog_parent, status_bar, schedule_ctl): @@ -220,15 +260,13 @@ class ExplorerDock(dockarea.Dock): def update_selection(self, selected, deselected): if deselected: - self.state[deselected] = self.argeditor.get_argument_values(False) + self.state[deselected] = self.argeditor.save_state() if selected: expinfo = self.explist_model.backing_store[selected] self.argeditor.set_arguments(expinfo["arguments"]) if selected in self.state: - arguments = self.state[selected] - if arguments is not None: - self.argeditor.set_argument_values(arguments, True) + self.argeditor.restore_state(self.state[selected]) self.splitter.insertWidget(1, self.argeditor) self.selected_key = selected @@ -249,7 +287,7 @@ class ExplorerDock(dockarea.Dock): if idx: row = idx[0].row() key = self.explist_model.row_to_key[row] - self.state[key] = self.argeditor.get_argument_values(False) + self.state[key] = self.argeditor.save_state() return self.state def restore_state(self, state): diff --git a/artiq/language/environment.py b/artiq/language/environment.py index 8a90f51cf..c75bd7d4e 100644 --- a/artiq/language/environment.py +++ b/artiq/language/environment.py @@ -144,19 +144,21 @@ class HasEnvironment: """ return self.__dmgr, self.__pdb, self.__rdb - def get_argument(self, key, processor=None): + def get_argument(self, key, processor=None, group=None): """Retrieves and returns the value of an argument. :param key: Name of the argument. :param processor: A description of how to process the argument, such as instances of ``BooleanValue`` and ``NumberValue``. + :param group: An optional string that defines what group the argument + belongs to, for user interface purposes. """ if not self.__in_build: raise TypeError("get_argument() should only " "be called from build()") if processor is None: processor = FreeValue() - self.requested_args[key] = processor + self.requested_args[key] = processor, group try: argval = self.__kwargs[key] except KeyError: @@ -169,10 +171,10 @@ class HasEnvironment: raise return processor.process(argval) - def attr_argument(self, key, processor=None): + def attr_argument(self, key, processor=None, group=None): """Sets an argument as attribute. The names of the argument and of the attribute are the same.""" - setattr(self, key, self.get_argument(key, processor)) + setattr(self, key, self.get_argument(key, processor, group)) def get_device(self, key): """Creates and returns a device driver.""" diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index 6fea52513..f8ff39746 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -147,8 +147,8 @@ def examine(dmgr, pdb, rdb, file): if name[-1] == ".": name = name[:-1] exp_inst = exp_class(dmgr, pdb, rdb, default_arg_none=True) - arguments = [(k, v.describe()) - for k, v in exp_inst.requested_args.items()] + arguments = [(k, (proc.describe(), group)) + for k, (proc, group) in exp_inst.requested_args.items()] register_experiment(class_name, name, arguments)