diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py index ab216fd64..06dd9c3f6 100644 --- a/artiq/gui/explorer.py +++ b/artiq/gui/explorer.py @@ -9,6 +9,7 @@ from artiq.protocols.sync_struct import Subscriber from artiq.protocols import pyon from artiq.gui.tools import DictSyncModel from artiq.gui.scan import ScanController +from artiq.gui.shortcuts import ShortcutManager class _ExplistModel(DictSyncModel): @@ -122,14 +123,14 @@ _procty_to_entry = { class _ArgumentEditor(QtGui.QTreeWidget): - def __init__(self, dialog_parent): + def __init__(self, main_window): QtGui.QTreeWidget.__init__(self) self.setColumnCount(2) self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents) self.header().setVisible(False) self.setSelectionMode(QtGui.QAbstractItemView.NoSelection) - self.dialog_parent = dialog_parent + self.main_window = main_window self._groups = dict() self.set_arguments([]) @@ -176,7 +177,7 @@ class _ArgumentEditor(QtGui.QTreeWidget): r[arg] = entry.get_argument_value() except Exception as e: if show_error_message: - msgbox = QtGui.QMessageBox(self.dialog_parent) + msgbox = QtGui.QMessageBox(self.main_window) msgbox.setWindowTitle("Error") msgbox.setText("Failed to obtain value for argument '{}':\n{}" .format(arg, str(e))) @@ -215,10 +216,10 @@ class _ArgumentEditor(QtGui.QTreeWidget): class ExplorerDock(dockarea.Dock): - def __init__(self, dialog_parent, status_bar, schedule_ctl): + def __init__(self, main_window, status_bar, schedule_ctl): dockarea.Dock.__init__(self, "Explorer", size=(1500, 500)) - self.dialog_parent = dialog_parent + self.main_window = main_window self.status_bar = status_bar self.schedule_ctl = schedule_ctl @@ -268,20 +269,27 @@ class ExplorerDock(dockarea.Dock): grid.addWidget(submit, 4, 0, colspan=4) submit.clicked.connect(self.submit_clicked) - self.argeditor = _ArgumentEditor(self.dialog_parent) + self.argeditor = _ArgumentEditor(self.main_window) self.splitter.addWidget(self.argeditor) self.splitter.setSizes([grid.minimumSizeHint().width(), 1000]) - self.state = dict() + self.argeditor_states = dict() + + self.shortcuts = ShortcutManager(self.main_window, self) + + self.el.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) + edit_shortcuts_action = QtGui.QAction("Edit shortcuts", self.el) + edit_shortcuts_action.triggered.connect(self.edit_shortcuts) + self.el.addAction(edit_shortcuts_action) def update_selection(self, selected, deselected): if deselected: - self.state[deselected] = self.argeditor.save_state() + self.argeditor_states[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: - self.argeditor.restore_state(self.state[selected]) + if selected in self.argeditor_states: + self.argeditor.restore_state(self.argeditor_states[selected]) self.splitter.insertWidget(1, self.argeditor) self.selected_key = selected @@ -302,11 +310,20 @@ class ExplorerDock(dockarea.Dock): if idx: row = idx[0].row() key = self.explist_model.row_to_key[row] - self.state[key] = self.argeditor.save_state() - return self.state + self.argeditor_states[key] = self.argeditor.save_state() + return { + "argeditor": self.argeditor_states, + "shortcuts": self.shortcuts.save_state() + } def restore_state(self, state): - self.state = state + try: + argeditor_states = state["argeditor"] + shortcuts_state = state["shortcuts"] + except KeyError: + return + self.argeditor_states = argeditor_states + self.shortcuts.restore_state(shortcuts_state) def enable_duedate(self): self.datetime_en.setChecked(True) @@ -324,8 +341,8 @@ class ExplorerDock(dockarea.Dock): self.el.setModel(self.explist_model) return self.explist_model - async def submit(self, pipeline_name, file, class_name, arguments, - priority, due_date, flush): + async def submit_task(self, pipeline_name, file, class_name, arguments, + priority, due_date, flush): expid = { "log_level": getattr(logging, self.log_level.currentText()), "repo_rev": None, @@ -337,20 +354,41 @@ class ExplorerDock(dockarea.Dock): priority, due_date, flush) self.status_bar.showMessage("Submitted RID {}".format(rid)) + def submit(self, pipeline, key, priority, due_date, flush): + # TODO: refactor explorer and cleanup. + # Argument editors should immediately modify the global state. + expinfo = self.explist_model.backing_store[key] + if key == self.selected_key: + arguments = self.argeditor.get_argument_values(True) + if arguments is None: + # There has been an error. Displaying the error message box + # was done by argeditor. + return + else: + try: + arguments = self.argeditor_states[key]["argument_values"] + except KeyError: + arguments = dict() + asyncio.ensure_future(self.submit_task(self.pipeline.text(), + expinfo["file"], + expinfo["class_name"], + arguments, + priority, + due_date, + flush)) + def submit_clicked(self): if self.selected_key is not None: - expinfo = self.explist_model.backing_store[self.selected_key] if self.datetime_en.isChecked(): due_date = self.datetime.dateTime().toMSecsSinceEpoch()/1000 else: due_date = None - arguments = self.argeditor.get_argument_values(True) - if arguments is None: - return - asyncio.ensure_future(self.submit(self.pipeline.text(), - expinfo["file"], - expinfo["class_name"], - arguments, - self.priority.value(), - due_date, - self.flush.isChecked())) + self.submit(self.pipeline.text(), + self.selected_key, + self.priority.value(), + due_date, + self.flush.isChecked()) + + def edit_shortcuts(self): + experiments = sorted(self.explist_model.backing_store.keys()) + self.shortcuts.edit(experiments) diff --git a/artiq/gui/shortcuts.py b/artiq/gui/shortcuts.py new file mode 100644 index 000000000..82308f0be --- /dev/null +++ b/artiq/gui/shortcuts.py @@ -0,0 +1,98 @@ +from functools import partial + +from quamash import QtGui +try: + from quamash import QtWidgets + QShortcut = QtWidgets.QShortcut +except: + QShortcut = QtGui.QShortcut + + +class _ShortcutEditor(QtGui.QDialog): + def __init__(self, parent, experiments, shortcuts): + QtGui.QDialog.__init__(self, parent=parent) + self.setWindowTitle("Shortcuts") + + self.shortcuts = shortcuts + self.edit_widgets = dict() + + grid = QtGui.QGridLayout() + self.setLayout(grid) + + for n, title in enumerate(["Key", "Experiment", "Priority", "Pipeline"]): + label = QtGui.QLabel("" + title + "") + experiment.addItems(experiments) + experiment.setEditable(True) + experiment.setEditText( + existing_shortcut.get("experiment", "")) + + priority = QtGui.QSpinBox() + grid.addWidget(priority, row, 2) + priority.setRange(-99, 99) + priority.setValue(existing_shortcut.get("priority", 0)) + + pipeline = QtGui.QLineEdit() + grid.addWidget(pipeline, row, 3) + pipeline.setText(existing_shortcut.get("pipeline", "main")) + + self.edit_widgets[i] = { + "experiment": experiment, + "priority": priority, + "pipeline": pipeline + } + + buttons = QtGui.QDialogButtonBox( + QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) + grid.addWidget(buttons, 14, 0, 1, 4) + buttons.accepted.connect(self.accept) + buttons.rejected.connect(self.reject) + self.accepted.connect(self.on_accept) + + def on_accept(self): + for n, widgets in self.edit_widgets.items(): + self.shortcuts[n] = { + "experiment": widgets["experiment"].currentText(), + "priority": widgets["priority"].value(), + "pipeline": widgets["pipeline"].text() + } + + +class ShortcutManager: + def __init__(self, main_window, explorer): + for i in range(12): + shortcut = QShortcut("F" + str(i+1), main_window) + shortcut.activated.connect(partial(self._activated, i)) + self.main_window = main_window + self.explorer = explorer + self.shortcuts = dict() + + def edit(self, experiments): + dlg = _ShortcutEditor(self.main_window, experiments, self.shortcuts) + dlg.open() + + def _activated(self, nr): + info = self.shortcuts.get(nr, dict()) + experiment = info.get("experiment", "") + if experiment and experiment != "": + self.explorer.submit(info["pipeline"], experiment, + info["priority"], None, False) + + def save_state(self): + return self.shortcuts + + def restore_state(self, state): + self.shortcuts = state