forked from M-Labs/artiq
1
0
Fork 0

Compare commits

...

3 Commits

3 changed files with 236 additions and 4 deletions

View File

@ -0,0 +1,225 @@
import logging
import asyncio
from PyQt5 import QtCore, QtWidgets, QtGui
from artiq.tools import elide
from artiq.gui.tools import LayoutWidget
from artiq.gui.models import DictSyncModel
from artiq.gui.applets import EntryArea
logger = logging.getLogger(__name__)
class Model(DictSyncModel):
def __init__(self, init):
DictSyncModel.__init__(self, ["Request"], init)
def convert(self, k, v, column):
if column == 0:
txt = ":" + v["title"] if v["title"] != "" else ""
return elide(str(k) + txt, 25)
else:
raise ValueError
def sort_key(self, k, v):
return k
class _InputRequest(QtWidgets.QWidget):
def __init__(self, rid, title, arglist_desc):
QtWidgets.QWidget.__init__(self)
self.rid = rid
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
title_txt = title if title != "" else str(self.rid)
self.title_lbl = QtWidgets.QLabel(title_txt)
layout.addWidget(self.title_lbl)
self.entry_area = EntryArea()
layout.addWidget(self.entry_area)
for args in arglist_desc:
self.entry_area.setattr_argument_procdesc(*args)
def get_rid_and_values(self):
return (self.rid, self.entry_area.get_values())
class _InputRequestView(QtWidgets.QScrollArea):
supplied = QtCore.pyqtSignal(int, dict)
cancelled = QtCore.pyqtSignal(int)
def __init__(self, interactive_args_sub):
QtWidgets.QScrollArea.__init__(self)
self.setWidgetResizable(True)
self.interactive_args_sub = interactive_args_sub
self.request_stack = QtWidgets.QStackedWidget()
self.request_stack.addWidget(QtWidgets.QLabel("No input request selected"))
self.setWidget(self.request_stack)
self.model = Model({})
self.interactive_args_sub.add_setmodel_callback(self.setModel)
def setModel(self, model):
for i in range(self.request_stack.count() - 1):
widget = self.request_stack.widget(i)
self.request_stack.removeWidget(widget)
widget.deleteLater()
self.model = model
self.model.dataChanged.connect(self.dataChanged)
self.model.rowsInserted.connect(self.rowsInserted)
self.model.rowsMoved.connect(self.rowsMoved)
self.model.rowsRemoved.connect(self.rowsRemoved)
for i in range(len(self.model.row_to_key)):
self._insert_widget(i)
def _insert_widget(self, index):
rid = self.model.row_to_key[index]
title = self.model.backing_store[rid]["title"]
arglist_desc = self.model.backing_store[rid]["arglist_desc"]
input_request = _InputRequest(rid, title, arglist_desc)
self.request_stack.insertWidget(index, input_request)
def dataChanged(self, top_left, bottom_right, roles):
index = top_left.row()
widget = self.request_stack.widget(index)
self.request_stack.removeWidget(widget)
self._insert_widget(index)
widget.deleteLater()
def rowsInserted(self, parent, first, last):
self._insert_widget(first)
def rowsMoved(self, src_parent, src_start, src_end, dest_parent, dest_end):
widget = self.request_stack.widget(src_start)
self.request_stack.insertWidget(dest_end)
def rowsRemoved(self, parent, first, last):
widget = self.request_stack.widget(first)
self.request_stack.removeWidget(widget)
widget.deleteLater()
def set_selected_request(self, row):
self.request_stack.setCurrentIndex(row)
def supply_selected(self):
index = self.request_stack.currentIndex()
try:
self.supply(index)
except IndexError:
logger.error("no input request selected", exc_info=True)
def supply(self, index):
widget = self.request_stack.widget(index)
rid, d = widget.get_rid_and_values()
self.supplied.emit(rid, d)
def cancel_selected(self):
index = self.request_stack.currentIndex()
try:
self.cancel(index)
except IndexError:
logger.error("no input request selected", exc_info=True)
def cancel(self, index):
rid = self.request_stack.widget(index).rid
self.cancelled.emit(rid)
def supply_all(self):
rows = self.model.rowCount(QtCore.QModelIndex())
rid_and_values = [self.request_stack.widget(row).get_rid_and_values()
for row in range(rows)]
for rid, value in rid_and_values:
self.supplied.emit(rid, value)
def cancel_all(self):
rows = self.model.rowCount(QtCore.QModelIndex())
rids = [self.request_stack.widget(row).rid for row in range(rows)]
for rid in rids:
self.cancelled.emit(rid)
class _RequestList(QtWidgets.QListView):
selected = QtCore.pyqtSignal(int)
def __init__(self):
QtWidgets.QListView.__init__(self)
self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.setMaximumWidth(200)
def selectionChanged(self, selected, deselected):
indexes = selected.indexes()
row = selected.indexes()[0].row() if len(indexes) > 0 else 0
self.selected.emit(row)
class InteractiveArgsDock(QtWidgets.QDockWidget):
def __init__(self, interactive_args_sub, interactive_args_rpc):
QtWidgets.QDockWidget.__init__(self, "Inputs")
self.setObjectName("Inputs")
self.setFeatures(
QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable)
self.interactive_args_rpc = interactive_args_rpc
layout = LayoutWidget(self)
self.setWidget(layout)
self.request_list = _RequestList()
self.request_list.setModel(Model({}))
interactive_args_sub.add_setmodel_callback(self.request_list.setModel)
layout.addWidget(self.request_list, 0, 0, rowspan=2)
self.request_view = _InputRequestView(interactive_args_sub)
self.request_view.supplied.connect(self.supply)
self.request_view.cancelled.connect(self.cancel)
layout.addWidget(self.request_view, 0, 1, colspan=2)
self.request_list.selected.connect(self.request_view.set_selected_request)
self.cancel_btn = QtWidgets.QPushButton("Cancel")
self.cancel_btn.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogCancelButton))
self.cancel_btn.setToolTip("Cancel interactive argument request (Ctrl+Backspace)")
self.cancel_btn.setShortcut("CTRL+BACKSPACE")
self.cancel_btn.clicked.connect(self.request_view.cancel_selected)
layout.addWidget(self.cancel_btn, 1, 1)
self.supply_btn = QtWidgets.QPushButton("Supply")
self.supply_btn.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton))
self.supply_btn.setToolTip("Supply interactive argument request (Ctrl+Return)")
self.supply_btn.setShortcut("CTRL+RETURN")
self.supply_btn.clicked.connect(self.request_view.supply_selected)
layout.addWidget(self.supply_btn, 1, 2)
self.request_list.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
supply_all_action = QtWidgets.QAction("Supply all pending requests", self.request_list)
supply_all_action.triggered.connect(self.request_view.supply_all)
self.request_list.addAction(supply_all_action)
cancel_all_action = QtWidgets.QAction("Cancel all pending requests", self.request_list)
cancel_all_action.triggered.connect(self.request_view.cancel_all)
self.request_list.addAction(cancel_all_action)
def supply(self, rid, values):
asyncio.ensure_future(self._supply_task(rid, values))
async def _supply_task(self, rid, values):
try:
await self.interactive_args_rpc.supply(rid, values)
except:
logger.error("dashboard failed to supply interactive arguments for experiment: %d",
rid, exc_info=True)
def cancel(self, rid):
asyncio.ensure_future(self._cancel_task(rid))
async def _cancel_task(self, rid):
try:
await self.interactive_args_rpc.cancel(rid)
except:
logger.error("dashboard failed to cancel input request for experiment: %d",
rid, exc_info=True)

View File

@ -23,7 +23,7 @@ from artiq.gui.models import ModelSubscriber
from artiq.gui import state, log
from artiq.dashboard import (experiments, shortcuts, explorer,
moninj, datasets, schedule, applets_ccb,
waveform)
waveform, interactive_args)
def get_argparser():
@ -141,7 +141,7 @@ def main():
# create connections to master
rpc_clients = dict()
for target in "schedule", "experiment_db", "dataset_db", "device_db":
for target in "schedule", "experiment_db", "dataset_db", "device_db", "interactive_arg_db":
client = AsyncioClient()
loop.run_until_complete(client.connect_rpc(
args.server, args.port_control, target))
@ -166,7 +166,8 @@ def main():
for notifier_name, modelf in (("explist", explorer.Model),
("explist_status", explorer.StatusUpdater),
("datasets", datasets.Model),
("schedule", schedule.Model)):
("schedule", schedule.Model),
("interactive_args", interactive_args.Model)):
subscriber = ModelSubscriber(notifier_name, modelf,
report_disconnect)
loop.run_until_complete(subscriber.connect(
@ -244,6 +245,11 @@ def main():
loop.run_until_complete(devices_sub.connect(args.server, args.port_notify))
atexit_register_coroutine(devices_sub.close, loop=loop)
d_interactive_args = interactive_args.InteractiveArgsDock(
sub_clients["interactive_args"],
rpc_clients["interactive_arg_db"]
)
d_schedule = schedule.ScheduleDock(
rpc_clients["schedule"], sub_clients["schedule"])
smgr.register(d_schedule)
@ -257,7 +263,7 @@ def main():
right_docks = [
d_explorer, d_shortcuts,
d_ttl_dds.ttl_dock, d_ttl_dds.dds_dock, d_ttl_dds.dac_dock,
d_datasets, d_applets, d_waveform
d_datasets, d_applets, d_waveform, d_interactive_args
]
main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, right_docks[0])
for d1, d2 in zip(right_docks, right_docks[1:]):

View File

@ -35,6 +35,7 @@ mock_modules = ["artiq.gui.waitingspinnerwidget",
"artiq.compiler.module",
"artiq.compiler.embedding",
"artiq.dashboard.waveform",
"artiq.dashboard.interactive_args",
"qasync", "pyqtgraph", "matplotlib", "lmdb",
"numpy", "dateutil", "dateutil.parser", "prettytable", "PyQt5",
"h5py", "serial", "scipy", "scipy.interpolate",