diff --git a/artiq/dashboard/moninj.py b/artiq/dashboard/moninj.py index ac49619ac..c12f2cf7a 100644 --- a/artiq/dashboard/moninj.py +++ b/artiq/dashboard/moninj.py @@ -2,12 +2,13 @@ import asyncio import logging import textwrap from collections import namedtuple +from functools import partial from PyQt5 import QtCore, QtWidgets from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe from artiq.coredevice.ad9912_reg import AD9912_SER_CONF -from artiq.gui.tools import LayoutWidget +from artiq.gui.tools import LayoutWidget, QDockWidgetCloseDetect from artiq.gui.flowlayout import FlowLayout @@ -782,17 +783,26 @@ class _DeviceManager: await self.mi_connection.close() -class _MonInjDock(QtWidgets.QDockWidget): - def __init__(self, name): - QtWidgets.QDockWidget.__init__(self, name) +class _MonInjDock(QDockWidgetCloseDetect): + def __init__(self, name, manager): + QtWidgets.QDockWidget.__init__(self, "MonInj") self.setObjectName(name) self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable) + self.name = name + grid = LayoutWidget() + newdock = QtWidgets.QToolButton() + newdock.setToolTip("Create new moninj dock") + newdock.setIcon(QtWidgets.QApplication.style().standardIcon( + QtWidgets.QStyle.SP_FileDialogNewFolder)) + # note the lambda, the default parameter is overriden otherwise + newdock.clicked.connect(lambda: manager.create_new_dock()) + grid.addWidget(newdock, 0, 0) + self.setWidget(grid) + self.scroll_area = QtWidgets.QScrollArea() + grid.addWidget(self.scroll_area, 1, 0) def layout_widgets(self, handlers): - scroll_area = QtWidgets.QScrollArea() - self.setWidget(scroll_area) - grid = FlowLayout() grid_widget = QtWidgets.QWidget() grid_widget.setLayout(grid) @@ -800,16 +810,56 @@ class _MonInjDock(QtWidgets.QDockWidget): for handler in sorted(handlers, key=lambda h: h.sort_key()): grid.addWidget(handler.widget) - scroll_area.setWidgetResizable(True) - scroll_area.setWidget(grid_widget) + self.scroll_area.setWidgetResizable(True) + self.scroll_area.setWidget(grid_widget) class MonInj: - def __init__(self, schedule_ctl): - self.dock = _MonInjDock("MonInj") + def __init__(self, schedule_ctl, main_window): + self.docks = dict() + self.main_window = main_window self.dm = _DeviceManager(schedule_ctl) - self.dm.channels_cb = lambda: self.dock.layout_widgets(self.dm.handlers_by_uid.values()) + self.dm.channels_cb = self.add_widgets + + def add_widgets(self): + self.docks["moninj0"].layout_widgets(self.dm.handlers_by_uid.values()) + + def create_new_dock(self, add_to_area=True): + n = 0 + name = "moninj0" + while name in self.docks: + n += 1 + name = "moninj" + str(n) + + dock = _MonInjDock(name, self) + self.docks[name] = dock + if add_to_area: + self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) + dock.setFloating(True) + dock.sigClosed.connect(partial(self.on_dock_closed, name)) + self.update_closable() + return dock + + def on_dock_closed(self, name): + dock = self.docks[name] + dock.deleteLater() + del self.docks[name] + self.update_closable() + + def update_closable(self): + flags = (QtWidgets.QDockWidget.DockWidgetMovable | + QtWidgets.QDockWidget.DockWidgetFloatable) + if len(self.docks) > 1: + flags |= QtWidgets.QDockWidget.DockWidgetClosable + for dock in self.docks.values(): + dock.setFeatures(flags) + + def first_moninj_dock(self): + if self.docks: + return None + dock = self.create_new_dock(False) + return dock async def stop(self): if self.dm is not None: diff --git a/artiq/frontend/artiq_dashboard.py b/artiq/frontend/artiq_dashboard.py index 1e95def0b..2f48db3e3 100755 --- a/artiq/frontend/artiq_dashboard.py +++ b/artiq/frontend/artiq_dashboard.py @@ -226,8 +226,8 @@ def main(): smgr.register(d_applets) broadcast_clients["ccb"].notify_cbs.append(d_applets.ccb_notify) - d_ttl_dds = moninj.MonInj(rpc_clients["schedule"]) - atexit_register_coroutine(d_ttl_dds.stop, loop=loop) + moninj_mgr = moninj.MonInj(rpc_clients["schedule"], main_window) + atexit_register_coroutine(moninj_mgr.stop, loop=loop) d_waveform = waveform.WaveformDock( args.analyzer_proxy_timeout, @@ -237,10 +237,10 @@ def main(): atexit_register_coroutine(d_waveform.stop, loop=loop) def init_cbs(ddb): - d_ttl_dds.dm.init_ddb(ddb) + moninj_mgr.dm.init_ddb(ddb) d_waveform.init_ddb(ddb) return ddb - devices_sub = Subscriber("devices", init_cbs, [d_ttl_dds.dm.notify_ddb, d_waveform.notify_ddb]) + devices_sub = Subscriber("devices", init_cbs, [moninj_mgr.dm.notify_ddb, d_waveform.notify_ddb]) loop.run_until_complete(devices_sub.connect(args.server, args.port_notify)) atexit_register_coroutine(devices_sub.close, loop=loop) @@ -261,12 +261,15 @@ def main(): # lay out docks right_docks = [ d_explorer, d_shortcuts, - d_ttl_dds.dock, d_datasets, d_applets, + 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:]): main_window.tabifyDockWidget(d1, d2) + d_moninj0 = moninj_mgr.first_moninj_dock() + if d_moninj0 is not None: + main_window.tabifyDockWidget(right_docks[-1], d_moninj0) main_window.addDockWidget(QtCore.Qt.BottomDockWidgetArea, d_schedule) # load/initialize state @@ -279,9 +282,6 @@ def main(): smgr.start(loop=loop) atexit_register_coroutine(smgr.stop, loop=loop) - # work around for https://github.com/m-labs/artiq/issues/1307 - d_ttl_dds.dock.show() - # create first log dock if not already in state d_log0 = logmgr.first_log_dock() if d_log0 is not None: