From 3cbd7c4c13cccc6e7944f298a4dc5f3c7ea0cdf9 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 12 Nov 2015 01:13:57 +0800 Subject: [PATCH] gui: support multiple log docks --- artiq/frontend/artiq_gui.py | 38 +++++++++++------- artiq/gui/log.py | 78 +++++++++++++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 17 deletions(-) diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index fc8442512..0e1cf5dad 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -53,6 +53,7 @@ class MainWindow(QtGui.QMainWindow): def main(): + # initialize application args = get_argparser().parse_args() init_logger(args) @@ -60,7 +61,9 @@ def main(): loop = QEventLoop(app) asyncio.set_event_loop(loop) atexit.register(loop.close) + smgr = state.StateManager(args.db_file) + # create connections to master rpc_clients = dict() for target in "schedule", "repository", "dataset_db": client = AsyncioClient() @@ -80,8 +83,7 @@ def main(): atexit_register_coroutine(subscriber.close) sub_clients[notifier_name] = subscriber - smgr = state.StateManager(args.db_file) - + # initialize main window win = MainWindow(app, args.server) area = dockarea.DockArea() smgr.register(area) @@ -91,6 +93,7 @@ def main(): status_bar.showMessage("Connected to {}".format(args.server)) win.setStatusBar(status_bar) + # create UI components d_explorer = explorer.ExplorerDock(win, status_bar, sub_clients["explist"], sub_clients["schedule"], @@ -106,6 +109,16 @@ def main(): loop.run_until_complete(d_ttl_dds.start(args.server, args.port_notify)) atexit_register_coroutine(d_ttl_dds.stop) + d_schedule = schedule.ScheduleDock( + status_bar, rpc_clients["schedule"], sub_clients["schedule"]) + + logmgr = log.LogDockManager(area, sub_clients["log"]) + smgr.register(logmgr) + + d_console = console.ConsoleDock(sub_clients["datasets"], + rpc_clients["dataset_db"]) + + # lay out docks if os.name != "nt": area.addDock(d_ttl_dds.dds_dock, "top") area.addDock(d_ttl_dds.ttl_dock, "above", d_ttl_dds.dds_dock) @@ -113,23 +126,20 @@ def main(): else: area.addDock(d_datasets, "top") area.addDock(d_explorer, "above", d_datasets) - - d_schedule = schedule.ScheduleDock( - status_bar, rpc_clients["schedule"], sub_clients["schedule"]) - - d_log = log.LogDock(sub_clients["log"]) - smgr.register(d_log) - - d_console = console.ConsoleDock(sub_clients["datasets"], - rpc_clients["dataset_db"]) - area.addDock(d_console, "bottom") - area.addDock(d_log, "above", d_console) - area.addDock(d_schedule, "above", d_log) + area.addDock(d_schedule, "above", d_console) + # load/initialize state smgr.load() smgr.start() atexit_register_coroutine(smgr.stop) + + # create first log dock if not already in state + d_log0 = logmgr.first_log_dock() + if d_log0 is not None: + area.addDock(d_log0, "right", d_explorer) + + # run win.show() loop.run_until_complete(win.exit_request.wait()) diff --git a/artiq/gui/log.py b/artiq/gui/log.py index d275b142b..a85c9d1c9 100644 --- a/artiq/gui/log.py +++ b/artiq/gui/log.py @@ -1,6 +1,7 @@ import asyncio import logging import time +from functools import partial from quamash import QtGui, QtCore from pyqtgraph import dockarea, LayoutWidget @@ -151,8 +152,8 @@ class _LogFilterProxyModel(QSortFilterProxyModel): class LogDock(dockarea.Dock): - def __init__(self, log_sub): - dockarea.Dock.__init__(self, "Log", size=(1000, 300)) + def __init__(self, manager, name, log_sub): + dockarea.Dock.__init__(self, name, label="Log", size=(1000, 300)) grid = LayoutWidget() self.addWidget(grid) @@ -180,6 +181,13 @@ class LogDock(dockarea.Dock): self.log.setTextElideMode(QtCore.Qt.ElideNone) grid.addWidget(self.log, 1, 0, colspan=4) self.scroll_at_bottom = False + self.scroll_value = 0 + + self.log.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) + newlog_action = QtGui.QAction("Create new log dock", self.log) + # note the lambda, the default parameter is overriden otherwise + newlog_action.triggered.connect(lambda: manager.create_new_dock()) + self.log.addAction(newlog_action) log_sub.add_setmodel_callback(self.set_model) @@ -228,7 +236,10 @@ class LogDock(dockarea.Dock): self.table_model_filter.rowsRemoved.connect(self.rows_removed) def save_state(self): - return {"min_level_idx": self.filter_level.currentIndex()} + return { + "min_level_idx": self.filter_level.currentIndex(), + "freetext_filter": self.filter_freetext.text() + } def restore_state(self, state): try: @@ -237,3 +248,64 @@ class LogDock(dockarea.Dock): pass else: self.filter_level.setCurrentIndex(idx) + + try: + freetext = state["freetext_filter"] + except KeyError: + pass + else: + self.filter_freetext.setText(freetext) + # Note that editingFinished is not emitted when calling setText, + # (unlike currentIndexChanged) so we need to call the callback + # manually here, unlike for the combobox. + self.filter_freetext_changed() + + +class LogDockManager: + def __init__(self, dock_area, log_sub): + self.dock_area = dock_area + self.log_sub = log_sub + self.docks = dict() + + def create_new_dock(self, add_to_area=True): + n = 0 + name = "log0" + while name in self.docks: + n += 1 + name = "log" + str(n) + + dock = LogDock(self, name, self.log_sub) + self.docks[name] = dock + if add_to_area: + self.dock_area.addDock(dock) + self.dock_area.floatDock(dock) + dock.sigClosed.connect(partial(self.on_dock_closed, name)) + self.update_closable() + return dock + + def on_dock_closed(self, name): + del self.docks[name] + self.update_closable() + + def update_closable(self): + closable = len(self.docks) > 1 + for dock in self.docks.values(): + dock.setClosable(closable) + + def save_state(self): + return {name: dock.save_state() for name, dock in self.docks.items()} + + def restore_state(self, state): + if self.docks: + raise NotImplementedError + for name, dock_state in state.items(): + dock = LogDock(self, name, self.log_sub) + dock.restore_state(dock_state) + self.dock_area.addDock(dock) + self.docks[name] = dock + + def first_log_dock(self): + if self.docks: + return None + dock = self.create_new_dock(False) + return dock