forked from M-Labs/artiq
gui: shortcut support
This commit is contained in:
parent
9f2ff32948
commit
967d4eda63
|
@ -9,6 +9,7 @@ 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 DictSyncModel
|
||||||
from artiq.gui.scan import ScanController
|
from artiq.gui.scan import ScanController
|
||||||
|
from artiq.gui.shortcuts import ShortcutManager
|
||||||
|
|
||||||
|
|
||||||
class _ExplistModel(DictSyncModel):
|
class _ExplistModel(DictSyncModel):
|
||||||
|
@ -122,14 +123,14 @@ _procty_to_entry = {
|
||||||
|
|
||||||
|
|
||||||
class _ArgumentEditor(QtGui.QTreeWidget):
|
class _ArgumentEditor(QtGui.QTreeWidget):
|
||||||
def __init__(self, dialog_parent):
|
def __init__(self, main_window):
|
||||||
QtGui.QTreeWidget.__init__(self)
|
QtGui.QTreeWidget.__init__(self)
|
||||||
self.setColumnCount(2)
|
self.setColumnCount(2)
|
||||||
self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)
|
self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)
|
||||||
self.header().setVisible(False)
|
self.header().setVisible(False)
|
||||||
self.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
|
self.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
|
||||||
|
|
||||||
self.dialog_parent = dialog_parent
|
self.main_window = main_window
|
||||||
self._groups = dict()
|
self._groups = dict()
|
||||||
self.set_arguments([])
|
self.set_arguments([])
|
||||||
|
|
||||||
|
@ -176,7 +177,7 @@ class _ArgumentEditor(QtGui.QTreeWidget):
|
||||||
r[arg] = entry.get_argument_value()
|
r[arg] = entry.get_argument_value()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if show_error_message:
|
if show_error_message:
|
||||||
msgbox = QtGui.QMessageBox(self.dialog_parent)
|
msgbox = QtGui.QMessageBox(self.main_window)
|
||||||
msgbox.setWindowTitle("Error")
|
msgbox.setWindowTitle("Error")
|
||||||
msgbox.setText("Failed to obtain value for argument '{}':\n{}"
|
msgbox.setText("Failed to obtain value for argument '{}':\n{}"
|
||||||
.format(arg, str(e)))
|
.format(arg, str(e)))
|
||||||
|
@ -215,10 +216,10 @@ class _ArgumentEditor(QtGui.QTreeWidget):
|
||||||
|
|
||||||
|
|
||||||
class ExplorerDock(dockarea.Dock):
|
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))
|
dockarea.Dock.__init__(self, "Explorer", size=(1500, 500))
|
||||||
|
|
||||||
self.dialog_parent = dialog_parent
|
self.main_window = main_window
|
||||||
self.status_bar = status_bar
|
self.status_bar = status_bar
|
||||||
self.schedule_ctl = schedule_ctl
|
self.schedule_ctl = schedule_ctl
|
||||||
|
|
||||||
|
@ -268,20 +269,27 @@ class ExplorerDock(dockarea.Dock):
|
||||||
grid.addWidget(submit, 4, 0, colspan=4)
|
grid.addWidget(submit, 4, 0, colspan=4)
|
||||||
submit.clicked.connect(self.submit_clicked)
|
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.addWidget(self.argeditor)
|
||||||
self.splitter.setSizes([grid.minimumSizeHint().width(), 1000])
|
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):
|
def update_selection(self, selected, deselected):
|
||||||
if deselected:
|
if deselected:
|
||||||
self.state[deselected] = self.argeditor.save_state()
|
self.argeditor_states[deselected] = self.argeditor.save_state()
|
||||||
|
|
||||||
if selected:
|
if selected:
|
||||||
expinfo = self.explist_model.backing_store[selected]
|
expinfo = self.explist_model.backing_store[selected]
|
||||||
self.argeditor.set_arguments(expinfo["arguments"])
|
self.argeditor.set_arguments(expinfo["arguments"])
|
||||||
if selected in self.state:
|
if selected in self.argeditor_states:
|
||||||
self.argeditor.restore_state(self.state[selected])
|
self.argeditor.restore_state(self.argeditor_states[selected])
|
||||||
self.splitter.insertWidget(1, self.argeditor)
|
self.splitter.insertWidget(1, self.argeditor)
|
||||||
self.selected_key = selected
|
self.selected_key = selected
|
||||||
|
|
||||||
|
@ -302,11 +310,20 @@ class ExplorerDock(dockarea.Dock):
|
||||||
if idx:
|
if idx:
|
||||||
row = idx[0].row()
|
row = idx[0].row()
|
||||||
key = self.explist_model.row_to_key[row]
|
key = self.explist_model.row_to_key[row]
|
||||||
self.state[key] = self.argeditor.save_state()
|
self.argeditor_states[key] = self.argeditor.save_state()
|
||||||
return self.state
|
return {
|
||||||
|
"argeditor": self.argeditor_states,
|
||||||
|
"shortcuts": self.shortcuts.save_state()
|
||||||
|
}
|
||||||
|
|
||||||
def restore_state(self, 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):
|
def enable_duedate(self):
|
||||||
self.datetime_en.setChecked(True)
|
self.datetime_en.setChecked(True)
|
||||||
|
@ -324,7 +341,7 @@ class ExplorerDock(dockarea.Dock):
|
||||||
self.el.setModel(self.explist_model)
|
self.el.setModel(self.explist_model)
|
||||||
return self.explist_model
|
return self.explist_model
|
||||||
|
|
||||||
async def submit(self, pipeline_name, file, class_name, arguments,
|
async def submit_task(self, pipeline_name, file, class_name, arguments,
|
||||||
priority, due_date, flush):
|
priority, due_date, flush):
|
||||||
expid = {
|
expid = {
|
||||||
"log_level": getattr(logging, self.log_level.currentText()),
|
"log_level": getattr(logging, self.log_level.currentText()),
|
||||||
|
@ -337,20 +354,41 @@ class ExplorerDock(dockarea.Dock):
|
||||||
priority, due_date, flush)
|
priority, due_date, flush)
|
||||||
self.status_bar.showMessage("Submitted RID {}".format(rid))
|
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):
|
def submit_clicked(self):
|
||||||
if self.selected_key is not None:
|
if self.selected_key is not None:
|
||||||
expinfo = self.explist_model.backing_store[self.selected_key]
|
|
||||||
if self.datetime_en.isChecked():
|
if self.datetime_en.isChecked():
|
||||||
due_date = self.datetime.dateTime().toMSecsSinceEpoch()/1000
|
due_date = self.datetime.dateTime().toMSecsSinceEpoch()/1000
|
||||||
else:
|
else:
|
||||||
due_date = None
|
due_date = None
|
||||||
arguments = self.argeditor.get_argument_values(True)
|
self.submit(self.pipeline.text(),
|
||||||
if arguments is None:
|
self.selected_key,
|
||||||
return
|
|
||||||
asyncio.ensure_future(self.submit(self.pipeline.text(),
|
|
||||||
expinfo["file"],
|
|
||||||
expinfo["class_name"],
|
|
||||||
arguments,
|
|
||||||
self.priority.value(),
|
self.priority.value(),
|
||||||
due_date,
|
due_date,
|
||||||
self.flush.isChecked()))
|
self.flush.isChecked())
|
||||||
|
|
||||||
|
def edit_shortcuts(self):
|
||||||
|
experiments = sorted(self.explist_model.backing_store.keys())
|
||||||
|
self.shortcuts.edit(experiments)
|
||||||
|
|
|
@ -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("<b>" + title + "</b")
|
||||||
|
grid.addWidget(label, 0, n)
|
||||||
|
label.setMaximumHeight(label.sizeHint().height())
|
||||||
|
grid.setColumnStretch(1, 1)
|
||||||
|
grid.setColumnStretch(3, 1)
|
||||||
|
|
||||||
|
for i in range(12):
|
||||||
|
row = i + 1
|
||||||
|
existing_shortcut = self.shortcuts.get(i, dict())
|
||||||
|
|
||||||
|
grid.addWidget(QtGui.QLabel("F" + str(i+1)), row, 0)
|
||||||
|
|
||||||
|
experiment = QtGui.QComboBox()
|
||||||
|
grid.addWidget(experiment, row, 1)
|
||||||
|
experiment.addItem("<None>")
|
||||||
|
experiment.addItems(experiments)
|
||||||
|
experiment.setEditable(True)
|
||||||
|
experiment.setEditText(
|
||||||
|
existing_shortcut.get("experiment", "<None>"))
|
||||||
|
|
||||||
|
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 != "<None>":
|
||||||
|
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
|
Loading…
Reference in New Issue