forked from M-Labs/artiq
Compare commits
4 Commits
13a36bf911
...
32eb03adb6
Author | SHA1 | Date |
---|---|---|
Simon Renblad | 32eb03adb6 | |
Simon Renblad | 33c7cda688 | |
Simon Renblad | ab3600b7b3 | |
Simon Renblad | f02c218f40 |
|
@ -0,0 +1,140 @@
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
|
from artiq.tools import elide
|
||||||
|
from artiq.gui.models import DictSyncModel
|
||||||
|
from artiq.gui.entries import EntryTreeWidget, procdesc_to_entry
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Model(DictSyncModel):
|
||||||
|
def __init__(self, init):
|
||||||
|
DictSyncModel.__init__(self, ["RID", "Title", "Args"], init)
|
||||||
|
|
||||||
|
def convert(self, k, v, column):
|
||||||
|
if column == 0:
|
||||||
|
return k
|
||||||
|
elif column == 1:
|
||||||
|
txt = ": " + v["title"] if v["title"] != "" else ""
|
||||||
|
return str(k) + txt
|
||||||
|
elif column == 2:
|
||||||
|
return v["arglist_desc"]
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
def sort_key(self, k, v):
|
||||||
|
return k
|
||||||
|
|
||||||
|
|
||||||
|
class _InteractiveArgsRequest(QtWidgets.QWidget):
|
||||||
|
supplied = QtCore.pyqtSignal(int, dict)
|
||||||
|
cancelled = QtCore.pyqtSignal(int)
|
||||||
|
|
||||||
|
def __init__(self, rid, arglist_desc):
|
||||||
|
QtWidgets.QWidget.__init__(self)
|
||||||
|
self.rid = rid
|
||||||
|
self.arguments = dict()
|
||||||
|
layout = QtWidgets.QGridLayout()
|
||||||
|
self.setLayout(layout)
|
||||||
|
self.entry_tree = EntryTreeWidget()
|
||||||
|
layout.addWidget(self.entry_tree, 0, 0, 1, 2)
|
||||||
|
for key, procdesc, group, tooltip in arglist_desc:
|
||||||
|
self.arguments[key] = {"desc": procdesc, "group": group, "tooltip": tooltip}
|
||||||
|
self.entry_tree.set_argument(key, self.arguments[key])
|
||||||
|
self.cancel_btn = QtWidgets.QPushButton("Cancel")
|
||||||
|
self.cancel_btn.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.SP_DialogCancelButton))
|
||||||
|
self.cancel_btn.clicked.connect(self.cancel)
|
||||||
|
layout.addWidget(self.cancel_btn, 1, 0, 1, 1)
|
||||||
|
self.supply_btn = QtWidgets.QPushButton("Supply")
|
||||||
|
self.supply_btn.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.SP_DialogOkButton))
|
||||||
|
self.supply_btn.clicked.connect(self.supply)
|
||||||
|
layout.addWidget(self.supply_btn, 1, 1, 1, 1)
|
||||||
|
|
||||||
|
def supply(self):
|
||||||
|
argument_values = dict()
|
||||||
|
for key, argument in self.arguments.items():
|
||||||
|
entry_cls = procdesc_to_entry(argument["desc"])
|
||||||
|
argument_values[key] = entry_cls.state_to_value(argument["state"])
|
||||||
|
self.supplied.emit(self.rid, argument_values)
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self.cancelled.emit(self.rid)
|
||||||
|
|
||||||
|
|
||||||
|
class _InteractiveArgsView(QtWidgets.QTabWidget):
|
||||||
|
supplied = QtCore.pyqtSignal(int, dict)
|
||||||
|
cancelled = QtCore.pyqtSignal(int)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
QtWidgets.QTabWidget.__init__(self)
|
||||||
|
self.model = Model({})
|
||||||
|
|
||||||
|
def setModel(self, model):
|
||||||
|
for i in range(self.count()):
|
||||||
|
widget = self.widget(i)
|
||||||
|
self.removeTab(i)
|
||||||
|
widget.deleteLater()
|
||||||
|
self.model = model
|
||||||
|
self.model.rowsInserted.connect(self.rowsInserted)
|
||||||
|
self.model.rowsRemoved.connect(self.rowsRemoved)
|
||||||
|
for i in range(self.model.rowCount(QtCore.QModelIndex())):
|
||||||
|
self._insert_widget(i)
|
||||||
|
|
||||||
|
def _insert_widget(self, row):
|
||||||
|
rid = self.model.data(self.model.index(row, 0), QtCore.Qt.DisplayRole)
|
||||||
|
title = self.model.data(self.model.index(row, 1), QtCore.Qt.DisplayRole)
|
||||||
|
arglist_desc = self.model.data(self.model.index(row, 2), QtCore.Qt.DisplayRole)
|
||||||
|
inter_args_request = _InteractiveArgsRequest(rid, arglist_desc)
|
||||||
|
inter_args_request.supplied.connect(self.supplied)
|
||||||
|
inter_args_request.cancelled.connect(self.cancelled)
|
||||||
|
self.insertTab(row, inter_args_request, title)
|
||||||
|
|
||||||
|
def rowsInserted(self, parent, first, last):
|
||||||
|
assert first == last
|
||||||
|
self._insert_widget(first)
|
||||||
|
|
||||||
|
def rowsRemoved(self, parent, first, last):
|
||||||
|
assert first == last
|
||||||
|
widget = self.widget(first)
|
||||||
|
self.removeTab(first)
|
||||||
|
widget.deleteLater()
|
||||||
|
|
||||||
|
|
||||||
|
class InteractiveArgsDock(QtWidgets.QDockWidget):
|
||||||
|
def __init__(self, interactive_args_sub, interactive_args_rpc):
|
||||||
|
QtWidgets.QDockWidget.__init__(self, "Interactive Args")
|
||||||
|
self.setObjectName("Interactive Args")
|
||||||
|
self.setFeatures(
|
||||||
|
QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable)
|
||||||
|
self.interactive_args_rpc = interactive_args_rpc
|
||||||
|
self.request_view = _InteractiveArgsView()
|
||||||
|
self.request_view.supplied.connect(self.supply)
|
||||||
|
self.request_view.cancelled.connect(self.cancel)
|
||||||
|
self.setWidget(self.request_view)
|
||||||
|
interactive_args_sub.add_setmodel_callback(self.request_view.setModel)
|
||||||
|
|
||||||
|
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 Exception:
|
||||||
|
logger.error("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 Exception:
|
||||||
|
logger.error("failed to cancel interactive args request for experiment: %d",
|
||||||
|
rid, exc_info=True)
|
|
@ -23,7 +23,7 @@ from artiq.gui.models import ModelSubscriber
|
||||||
from artiq.gui import state, log
|
from artiq.gui import state, log
|
||||||
from artiq.dashboard import (experiments, shortcuts, explorer,
|
from artiq.dashboard import (experiments, shortcuts, explorer,
|
||||||
moninj, datasets, schedule, applets_ccb,
|
moninj, datasets, schedule, applets_ccb,
|
||||||
waveform)
|
waveform, interactive_args)
|
||||||
|
|
||||||
|
|
||||||
def get_argparser():
|
def get_argparser():
|
||||||
|
@ -141,7 +141,7 @@ def main():
|
||||||
|
|
||||||
# create connections to master
|
# create connections to master
|
||||||
rpc_clients = dict()
|
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()
|
client = AsyncioClient()
|
||||||
loop.run_until_complete(client.connect_rpc(
|
loop.run_until_complete(client.connect_rpc(
|
||||||
args.server, args.port_control, target))
|
args.server, args.port_control, target))
|
||||||
|
@ -166,7 +166,8 @@ def main():
|
||||||
for notifier_name, modelf in (("explist", explorer.Model),
|
for notifier_name, modelf in (("explist", explorer.Model),
|
||||||
("explist_status", explorer.StatusUpdater),
|
("explist_status", explorer.StatusUpdater),
|
||||||
("datasets", datasets.Model),
|
("datasets", datasets.Model),
|
||||||
("schedule", schedule.Model)):
|
("schedule", schedule.Model),
|
||||||
|
("interactive_args", interactive_args.Model)):
|
||||||
subscriber = ModelSubscriber(notifier_name, modelf,
|
subscriber = ModelSubscriber(notifier_name, modelf,
|
||||||
report_disconnect)
|
report_disconnect)
|
||||||
loop.run_until_complete(subscriber.connect(
|
loop.run_until_complete(subscriber.connect(
|
||||||
|
@ -244,6 +245,11 @@ def main():
|
||||||
loop.run_until_complete(devices_sub.connect(args.server, args.port_notify))
|
loop.run_until_complete(devices_sub.connect(args.server, args.port_notify))
|
||||||
atexit_register_coroutine(devices_sub.close, loop=loop)
|
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(
|
d_schedule = schedule.ScheduleDock(
|
||||||
rpc_clients["schedule"], sub_clients["schedule"])
|
rpc_clients["schedule"], sub_clients["schedule"])
|
||||||
smgr.register(d_schedule)
|
smgr.register(d_schedule)
|
||||||
|
@ -257,7 +263,7 @@ def main():
|
||||||
right_docks = [
|
right_docks = [
|
||||||
d_explorer, d_shortcuts,
|
d_explorer, d_shortcuts,
|
||||||
d_ttl_dds.ttl_dock, d_ttl_dds.dds_dock, d_ttl_dds.dac_dock,
|
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])
|
main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, right_docks[0])
|
||||||
for d1, d2 in zip(right_docks, right_docks[1:]):
|
for d1, d2 in zip(right_docks, right_docks[1:]):
|
||||||
|
|
|
@ -350,8 +350,9 @@ class HasEnvironment:
|
||||||
yield namespace
|
yield namespace
|
||||||
del namespace.setattr_argument
|
del namespace.setattr_argument
|
||||||
argdict = self.__argument_mgr.get_interactive(interactive_arglist, title)
|
argdict = self.__argument_mgr.get_interactive(interactive_arglist, title)
|
||||||
for key, value in argdict.items():
|
if argdict is not None:
|
||||||
setattr(namespace, key, value)
|
for key, value in argdict.items():
|
||||||
|
setattr(namespace, key, value)
|
||||||
|
|
||||||
def get_device_db(self):
|
def get_device_db(self):
|
||||||
"""Returns the full contents of the device database."""
|
"""Returns the full contents of the device database."""
|
||||||
|
|
|
@ -144,4 +144,4 @@ class InteractiveArgDB:
|
||||||
if rid not in self.futures:
|
if rid not in self.futures:
|
||||||
raise ValueError("no experiment with this RID is "
|
raise ValueError("no experiment with this RID is "
|
||||||
"waiting for interactive arguments")
|
"waiting for interactive arguments")
|
||||||
self.futures[rid].cancel()
|
self.futures[rid].set_result(None)
|
||||||
|
|
|
@ -222,8 +222,9 @@ class ArgumentManager(ProcessArgumentManager):
|
||||||
arglist_desc = [(k, p.describe(), g, t)
|
arglist_desc = [(k, p.describe(), g, t)
|
||||||
for k, p, g, t in interactive_arglist]
|
for k, p, g, t in interactive_arglist]
|
||||||
arguments = ArgumentManager._get_interactive(arglist_desc, title)
|
arguments = ArgumentManager._get_interactive(arglist_desc, title)
|
||||||
for key, processor, _, _ in interactive_arglist:
|
if arguments is not None:
|
||||||
arguments[key] = processor.process(arguments[key])
|
for key, processor, _, _ in interactive_arglist:
|
||||||
|
arguments[key] = processor.process(arguments[key])
|
||||||
return arguments
|
return arguments
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ mock_modules = ["artiq.gui.waitingspinnerwidget",
|
||||||
"artiq.compiler.module",
|
"artiq.compiler.module",
|
||||||
"artiq.compiler.embedding",
|
"artiq.compiler.embedding",
|
||||||
"artiq.dashboard.waveform",
|
"artiq.dashboard.waveform",
|
||||||
|
"artiq.dashboard.interactive_args",
|
||||||
"qasync", "pyqtgraph", "matplotlib", "lmdb",
|
"qasync", "pyqtgraph", "matplotlib", "lmdb",
|
||||||
"numpy", "dateutil", "dateutil.parser", "prettytable", "PyQt5",
|
"numpy", "dateutil", "dateutil.parser", "prettytable", "PyQt5",
|
||||||
"h5py", "serial", "scipy", "scipy.interpolate",
|
"h5py", "serial", "scipy", "scipy.interpolate",
|
||||||
|
|
Loading…
Reference in New Issue