diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index 9ce3aa151..079eedb51 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -11,10 +11,10 @@ from quamash import QEventLoop, QtGui, QtCore from pyqtgraph import dockarea from artiq.tools import * -from artiq.protocols.pc_rpc import AsyncioClient +from artiq.protocols.pc_rpc import AsyncioClient, Server from artiq.gui.models import ModelSubscriber from artiq.gui import (state, experiments, shortcuts, explorer, - moninj, datasets, schedule, log, console) + moninj, datasets, applets, schedule, log, console) def get_argparser(): @@ -111,6 +111,9 @@ def main(): d_datasets = datasets.DatasetsDock(win, dock_area, sub_clients["datasets"]) smgr.register(d_datasets) + appletmgr = applets.AppletManager(dock_area) + smgr.register(appletmgr) + if os.name != "nt": d_ttl_dds = moninj.MonInj() loop.run_until_complete(d_ttl_dds.start(args.server, args.port_notify)) @@ -129,9 +132,11 @@ def main(): if os.name != "nt": dock_area.addDock(d_ttl_dds.dds_dock, "top") dock_area.addDock(d_ttl_dds.ttl_dock, "above", d_ttl_dds.dds_dock) - dock_area.addDock(d_datasets, "above", d_ttl_dds.ttl_dock) + dock_area.addDock(appletmgr.main_dock, "above", d_ttl_dds.ttl_dock) + dock_area.addDock(d_datasets, "above", appletmgr.main_dock) else: - dock_area.addDock(d_datasets, "top") + dock_area.addDock(appletmgr.main_dock, "top") + dock_area.addDock(d_datasets, "above", appletmgr.main_dock) dock_area.addDock(d_shortcuts, "above", d_datasets) dock_area.addDock(d_explorer, "above", d_shortcuts) dock_area.addDock(d_console, "bottom") @@ -147,6 +152,11 @@ def main(): if d_log0 is not None: dock_area.addDock(d_log0, "right", d_explorer) + # start RPC server + rpc_server = Server({"applets": appletmgr.rpc}) + loop.run_until_complete(rpc_server.start("::1", 6501)) + atexit_register_coroutine(rpc_server.stop) + # run win.show() loop.run_until_complete(win.exit_request.wait()) diff --git a/artiq/gui/applets.py b/artiq/gui/applets.py new file mode 100644 index 000000000..4eb79fb53 --- /dev/null +++ b/artiq/gui/applets.py @@ -0,0 +1,135 @@ +import logging + +from quamash import QtCore, QtGui, QtWidgets +from pyqtgraph import dockarea + + +logger = logging.getLogger(__name__) + + +class AppletDock(dockarea.Dock): + def __init__(self, token, name): + dockarea.Dock.__init__(self, "applet" + str(token), + label="Applet: " + name, + closable=True) + self.setMinimumSize(QtCore.QSize(500, 400)) + + def capture(self, win_id): + self.captured_window = QtGui.QWindow.fromWinId(win_id) + self.captured_widget = QtWidgets.QWidget.createWindowContainer(captured_window) + self.addWidget(captured_widget) + + def terminate(self): + if hasattr(self, "captured_window"): + self.captured_window.close() + self.captured_widget.deleteLater() + del self.captured_window + del self.captured_widget + + +class AppletsDock(dockarea.Dock): + def __init__(self, manager): + self.manager = manager + + dockarea.Dock.__init__(self, "Applets") + self.setMinimumSize(QtCore.QSize(850, 450)) + + self.table = QtWidgets.QTableWidget(0, 3) + self.table.setHorizontalHeaderLabels(["Enable", "Name", "Command"]) + self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.table.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) + self.table.horizontalHeader().setStretchLastSection(True) + self.table.horizontalHeader().setResizeMode( + QtGui.QHeaderView.ResizeToContents) + self.table.verticalHeader().setResizeMode( + QtGui.QHeaderView.ResizeToContents) + self.table.verticalHeader().hide() + self.table.setTextElideMode(QtCore.Qt.ElideNone) + self.addWidget(self.table) + + self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) + new_action = QtGui.QAction("New applet", self.table) + new_action.triggered.connect(self.new) + self.table.addAction(new_action) + restart_action = QtGui.QAction("Restart selected applet", self.table) + self.table.addAction(restart_action) + delete_action = QtGui.QAction("Delete selected applet", self.table) + delete_action.triggered.connect(self.delete) + self.table.addAction(delete_action) + + self.table.cellChanged.connect(self.cell_changed) + + def cell_changed(self, row, column): + if column == 0: + item = self.table.item(row, column) + if item.checkState() == QtCore.Qt.Checked: + command = self.table.item(row, 2) + if command: + command = command.text() + name = self.table.item(row, 1) + if name is None: + name = "" + else: + name = name.text() + token = self.manager.create(name, command) + item.applet_token = token + else: + token = getattr(item, "applet_token", None) + if token is not None: + # cell_changed is emitted at row creation + self.manager.delete(token) + item.applet_token = None + + def new(self): + row = self.table.rowCount() + self.table.insertRow(row) + checkbox = QtWidgets.QTableWidgetItem() + checkbox.setFlags(QtCore.Qt.ItemIsSelectable | + QtCore.Qt.ItemIsUserCheckable | + QtCore.Qt.ItemIsEnabled) + checkbox.setCheckState(QtCore.Qt.Unchecked) + self.table.setItem(row, 0, checkbox) + + def delete(self): + selection = self.table.selectedRanges() + if selection: + self.table.deleteRow(selection[0].topRow()) + + +class AppletManagerRPC: + def __init__(self, parent): + self.parent = parent + + def embed(self, token, win_id): + self.parent.embed(token, win_id) + + +class AppletManager: + def __init__(self, dock_area): + self.dock_area = dock_area + self.main_dock = AppletsDock(self) + self.rpc = AppletManagerRPC(self) + self.applet_docks = dict() + + def embed(self, token, win_id): + if token not in self.applet_docks: + logger.warning("Ignored incorrect embed token %d for winid 0x%x", + token, win_id) + return + + def create(self, name, command): + token = next(iter(set(range(len(self.applet_docks) + 1)) + - self.applet_docks.keys())) + dock = AppletDock(token, name) + self.applet_docks[token] = dock + self.dock_area.floatDock(dock) + return token + + def delete(self, token): + del self.applet_docks[token] + + def save_state(self): + return dict() + + def restore_state(self, state): + pass