forked from M-Labs/artiq
interactive_args: add InteractiveArgsDock
parent
65a57e1f8c
commit
f825df8a6f
|
@ -0,0 +1,227 @@
|
|||
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):
|
||||
QtWidgets.QScrollArea.__init__(self)
|
||||
self.setWidgetResizable(True)
|
||||
self.request_stack = QtWidgets.QStackedWidget()
|
||||
self.request_stack.addWidget(QtWidgets.QLabel("No pending input requests"))
|
||||
self.setWidget(self.request_stack)
|
||||
self.model = Model({})
|
||||
|
||||
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:
|
||||
if index >= self.request_stack.count() - 1:
|
||||
raise IndexError
|
||||
widget = self.request_stack.widget(index)
|
||||
rid, values = widget.get_rid_and_values()
|
||||
self.supplied.emit(rid, values)
|
||||
except IndexError:
|
||||
logger.error("no input request selected", exc_info=True)
|
||||
|
||||
def cancel_selected(self):
|
||||
index = self.request_stack.currentIndex()
|
||||
try:
|
||||
if index >= self.request_stack.count() - 1:
|
||||
raise IndexError
|
||||
rid = self.request_stack.widget(index).rid
|
||||
self.cancelled.emit(rid)
|
||||
except IndexError:
|
||||
logger.error("no input request selected", exc_info=True)
|
||||
|
||||
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, values in rid_and_values:
|
||||
self.supplied.emit(rid, values)
|
||||
|
||||
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 setModel(self, model):
|
||||
QtWidgets.QListView.setModel(self, model)
|
||||
if self.model().rowCount(QtCore.QModelIndex()) != 0:
|
||||
self.setCurrentIndex(self.model().index(0, 0))
|
||||
|
||||
def selectionChanged(self, selected, deselected):
|
||||
indexes = selected.indexes()
|
||||
row = selected.indexes()[0].row() if len(indexes) > 0 else 0
|
||||
self.selected.emit(row)
|
||||
|
||||
def rowsInserted(self, parent, first, last):
|
||||
QtWidgets.QListView.rowsInserted(self, parent, first, last)
|
||||
if len(self.selectionModel().selectedRows()) == 0:
|
||||
self.setCurrentIndex(self.model().index(0, 0))
|
||||
|
||||
|
||||
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({}))
|
||||
layout.addWidget(self.request_list, 0, 0, rowspan=2)
|
||||
|
||||
self.request_view = _InputRequestView()
|
||||
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.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.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)
|
||||
|
||||
interactive_args_sub.add_setmodel_callback(self.setmodel_callbacks)
|
||||
|
||||
def setmodel_callbacks(self, model):
|
||||
self.request_view.setModel(model)
|
||||
self.request_list.setModel(model)
|
||||
|
||||
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)
|
Loading…
Reference in New Issue