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