gui: centralize subscribers

This commit is contained in:
Sebastien Bourdeauducq 2015-11-11 12:13:19 +08:00
parent ae99af27ee
commit c3f99eda8f
6 changed files with 124 additions and 133 deletions

View File

@ -12,13 +12,8 @@ from pyqtgraph import dockarea
from artiq.tools import verbosity_args, init_logger, artiq_dir
from artiq.protocols.pc_rpc import AsyncioClient
from artiq.gui.state import StateManager
from artiq.gui.explorer import ExplorerDock
from artiq.gui.moninj import MonInj
from artiq.gui.datasets import DatasetsDock
from artiq.gui.schedule import ScheduleDock
from artiq.gui.log import LogDock
from artiq.gui.console import ConsoleDock
from artiq.gui.models import ModelSubscriber
from artiq.gui import state, explorer, moninj, datasets, schedule, log, console
def get_argparser():
@ -57,6 +52,12 @@ class MainWindow(QtGui.QMainWindow):
self.restoreGeometry(QtCore.QByteArray(state))
def atexit_register_coroutine(coroutine, loop=None):
if loop is None:
loop = asyncio.get_event_loop()
atexit.register(lambda: loop.run_until_complete(coroutine()))
def main():
args = get_argparser().parse_args()
init_logger(args)
@ -74,7 +75,18 @@ def main():
atexit.register(client.close_rpc)
rpc_clients[target] = client
smgr = StateManager(args.db_file)
sub_clients = dict()
for notifier_name, module in (("explist", explorer),
("datasets", datasets),
("schedule", schedule),
("log", log)):
subscriber = ModelSubscriber(notifier_name, module.Model)
loop.run_until_complete(subscriber.connect(
args.server, args.port_notify))
atexit_register_coroutine(subscriber.close)
sub_clients[notifier_name] = subscriber
smgr = state.StateManager(args.db_file)
win = MainWindow(app, args.server)
area = dockarea.DockArea()
@ -85,24 +97,20 @@ def main():
status_bar.showMessage("Connected to {}".format(args.server))
win.setStatusBar(status_bar)
d_explorer = ExplorerDock(win, status_bar,
rpc_clients["schedule"],
rpc_clients["repository"])
d_explorer = explorer.ExplorerDock(win, status_bar,
sub_clients["explist"],
sub_clients["schedule"],
rpc_clients["schedule"],
rpc_clients["repository"])
smgr.register(d_explorer)
loop.run_until_complete(d_explorer.sub_connect(
args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_explorer.sub_close()))
d_datasets = DatasetsDock(win, area)
d_datasets = datasets.DatasetsDock(win, area, sub_clients["datasets"])
smgr.register(d_datasets)
loop.run_until_complete(d_datasets.sub_connect(
args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_datasets.sub_close()))
if os.name != "nt":
d_ttl_dds = MonInj()
d_ttl_dds = moninj.MonInj()
loop.run_until_complete(d_ttl_dds.start(args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_ttl_dds.stop()))
atexit_register_coroutine(d_ttl_dds.stop)
if os.name != "nt":
area.addDock(d_ttl_dds.dds_dock, "top")
@ -112,23 +120,17 @@ def main():
area.addDock(d_datasets, "top")
area.addDock(d_explorer, "above", d_datasets)
d_schedule = ScheduleDock(status_bar, rpc_clients["schedule"])
loop.run_until_complete(d_schedule.sub_connect(
args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_schedule.sub_close()))
d_explorer.get_current_schedule = d_schedule.get_current_schedule
d_schedule = schedule.ScheduleDock(
status_bar, rpc_clients["schedule"], sub_clients["schedule"])
d_log = LogDock()
d_log = log.LogDock(sub_clients["log"])
smgr.register(d_log)
loop.run_until_complete(d_log.sub_connect(
args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_log.sub_close()))
def _set_dataset(k, v):
asyncio.ensure_future(rpc_clients["dataset_db"].set(k, v))
def _del_dataset(k):
asyncio.ensure_future(rpc_clients["dataset_db"].delete(k))
d_console = ConsoleDock(
d_console = console.ConsoleDock(
d_datasets.get_dataset,
_set_dataset,
_del_dataset)
@ -139,7 +141,7 @@ def main():
smgr.load()
smgr.start()
atexit.register(lambda: loop.run_until_complete(smgr.stop()))
atexit_register_coroutine(smgr.stop)
win.show()
loop.run_until_complete(win.exit_request.wait())

View File

@ -7,9 +7,8 @@ from quamash import QtGui, QtCore
from pyqtgraph import dockarea
from pyqtgraph import LayoutWidget
from artiq.protocols.sync_struct import Subscriber
from artiq.tools import short_format
from artiq.gui.tools import DictSyncModel
from artiq.gui.models import DictSyncModel
from artiq.gui.displays import *
try:
@ -21,10 +20,9 @@ except AttributeError:
logger = logging.getLogger(__name__)
class DatasetsModel(DictSyncModel):
def __init__(self, parent, init):
DictSyncModel.__init__(self, ["Dataset", "Persistent", "Value"],
parent, init)
class Model(DictSyncModel):
def __init__(self, init):
DictSyncModel.__init__(self, ["Dataset", "Persistent", "Value"], init)
def sort_key(self, k, v):
return k
@ -47,7 +45,7 @@ def _get_display_type_name(display_cls):
class DatasetsDock(dockarea.Dock):
def __init__(self, dialog_parent, dock_area):
def __init__(self, dialog_parent, dock_area, datasets_sub):
dockarea.Dock.__init__(self, "Datasets", size=(1500, 500))
self.dialog_parent = dialog_parent
self.dock_area = dock_area
@ -66,6 +64,9 @@ class DatasetsDock(dockarea.Dock):
QtGui.QHeaderView.ResizeToContents)
grid.addWidget(self.table, 1, 0)
self.table_model = Model(dict())
datasets_sub.add_setmodel_callback(self.set_model)
add_display_box = QtGui.QGroupBox("Add display")
grid.addWidget(add_display_box, 1, 1)
display_grid = QtGui.QGridLayout()
@ -79,25 +80,18 @@ class DatasetsDock(dockarea.Dock):
self.displays = dict()
def _search_datasets(self):
self.table_model_filter.setFilterFixedString(self.search.displayText())
if hasattr(self, "table_model_filter"):
self.table_model_filter.setFilterFixedString(
self.search.displayText())
def get_dataset(self, key):
return self.table_model.backing_store[key][1]
async def sub_connect(self, host, port):
self.subscriber = Subscriber("datasets", self.init_datasets_model,
self.on_mod)
await self.subscriber.connect(host, port)
async def sub_close(self):
await self.subscriber.close()
def init_datasets_model(self, init):
self.table_model = DatasetsModel(self.table, init)
def set_model(self, model):
self.table_model = model
self.table_model_filter = QSortFilterProxyModel()
self.table_model_filter.setSourceModel(self.table_model)
self.table.setModel(self.table_model_filter)
return self.table_model
def update_display_data(self, dsp):
filtered_data = {k: self.table_model.backing_store[k][1]

View File

@ -5,19 +5,18 @@ from quamash import QtGui, QtCore
from pyqtgraph import dockarea
from pyqtgraph import LayoutWidget
from artiq.protocols.sync_struct import Subscriber
from artiq.protocols import pyon
from artiq.gui.tools import DictSyncModel
from artiq.gui.models import DictSyncModel
from artiq.gui.scan import ScanController
from artiq.gui.shortcuts import ShortcutManager
class _ExplistModel(DictSyncModel):
def __init__(self, explorer, parent, init):
self.explorer = explorer
class Model(DictSyncModel):
def __init__(self, init):
DictSyncModel.__init__(self,
["Experiment"],
parent, init)
init)
self.explorer = None
def sort_key(self, k, v):
return k
@ -27,8 +26,9 @@ class _ExplistModel(DictSyncModel):
def __setitem__(self, k, v):
DictSyncModel.__setitem__(self, k, v)
if k == self.explorer.selected_key:
self.explorer.update_selection(k, k)
if self.explorer is not None:
if k == self.explorer.selected_key:
self.explorer.update_selection(k, k)
class _FreeValueEntry(QtGui.QLineEdit):
@ -216,11 +216,14 @@ class _ArgumentEditor(QtGui.QTreeWidget):
class ExplorerDock(dockarea.Dock):
def __init__(self, main_window, status_bar, schedule_ctl, repository_ctl):
def __init__(self, main_window, status_bar,
explist_sub, schedule_sub,
schedule_ctl, repository_ctl):
dockarea.Dock.__init__(self, "Explorer", size=(1500, 500))
self.main_window = main_window
self.status_bar = status_bar
self.schedule_sub = schedule_sub
self.schedule_ctl = schedule_ctl
self.splitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
@ -271,6 +274,8 @@ class ExplorerDock(dockarea.Dock):
self.splitter.addWidget(self.argeditor)
self.splitter.setSizes([grid.minimumSizeHint().width(), 1000])
self.argeditor_states = dict()
self.explist_model = Model(dict())
explist_sub.add_setmodel_callback(self.set_model)
self.shortcuts = ShortcutManager(self.main_window, self)
@ -299,6 +304,11 @@ class ExplorerDock(dockarea.Dock):
scan_repository_action.triggered.connect(scan_repository)
self.el.addAction(scan_repository_action)
def set_model(self, model):
model.explorer = self
self.explist_model = model
self.el.setModel(model)
def update_selection(self, selected, deselected):
if deselected:
self.argeditor_states[deselected] = self.argeditor.save_state()
@ -346,19 +356,6 @@ class ExplorerDock(dockarea.Dock):
def enable_duedate(self):
self.datetime_en.setChecked(True)
async def sub_connect(self, host, port):
self.explist_subscriber = Subscriber("explist",
self.init_explist_model)
await self.explist_subscriber.connect(host, port)
async def sub_close(self):
await self.explist_subscriber.close()
def init_explist_model(self, init):
self.explist_model = _ExplistModel(self, self.el, init)
self.el.setModel(self.explist_model)
return self.explist_model
async def submit_task(self, pipeline_name, file, class_name, arguments,
priority, due_date, flush):
expid = {
@ -414,17 +411,16 @@ class ExplorerDock(dockarea.Dock):
def request_inst_term(self):
if self.selected_key is not None:
expinfo = self.explist_model.backing_store[self.selected_key]
# attribute get_current_schedule must be set externally after
# instance creation
current_schedule = self.get_current_schedule()
rids = []
for rid, desc in current_schedule.items():
expid = desc["expid"]
if ("repo_rev" in expid # only consider runs from repository
and expid["file"] == expinfo["file"]
and expid["class_name"] == expinfo["class_name"]):
rids.append(rid)
asyncio.ensure_future(self.request_term_multiple(rids))
if self.schedule_sub.model is not None:
current_schedule = self.schedule_sub.model.backing_store
rids = []
for rid, desc in current_schedule.items():
expid = desc["expid"]
if ("repo_rev" in expid # only consider runs from repository
and expid["file"] == expinfo["file"]
and expid["class_name"] == expinfo["class_name"]):
rids.append(rid)
asyncio.ensure_future(self.request_term_multiple(rids))
def edit_shortcuts(self):
experiments = sorted(self.explist_model.backing_store.keys())

View File

@ -5,8 +5,6 @@ import time
from quamash import QtGui, QtCore
from pyqtgraph import dockarea, LayoutWidget
from artiq.protocols.sync_struct import Subscriber
try:
QSortFilterProxyModel = QtCore.QSortFilterProxyModel
except AttributeError:
@ -25,9 +23,9 @@ def _level_to_name(level):
return "DEBUG"
class _LogModel(QtCore.QAbstractTableModel):
def __init__(self, parent, init):
QtCore.QAbstractTableModel.__init__(self, parent)
class Model(QtCore.QAbstractTableModel):
def __init__(self, init):
QtCore.QAbstractTableModel.__init__(self)
self.headers = ["Level", "Source", "Time", "Message"]
@ -153,7 +151,7 @@ class _LogFilterProxyModel(QSortFilterProxyModel):
class LogDock(dockarea.Dock):
def __init__(self):
def __init__(self, log_sub):
dockarea.Dock.__init__(self, "Log", size=(1000, 300))
grid = LayoutWidget()
@ -183,12 +181,7 @@ class LogDock(dockarea.Dock):
grid.addWidget(self.log, 1, 0, colspan=4)
self.scroll_at_bottom = False
async def sub_connect(self, host, port):
self.subscriber = Subscriber("log", self.init_log_model)
await self.subscriber.connect(host, port)
async def sub_close(self):
await self.subscriber.close()
log_sub.add_setmodel_callback(self.set_model)
def filter_level_changed(self):
if not hasattr(self, "table_model_filter"):
@ -223,8 +216,8 @@ class LogDock(dockarea.Dock):
scrollbar = self.log.verticalScrollBar()
scrollbar.setValue(self.scroll_value)
def init_log_model(self, init):
self.table_model = _LogModel(self.log, init)
def set_model(self, model):
self.table_model = model
self.table_model_filter = _LogFilterProxyModel(
getattr(logging, self.filter_level.currentText()),
self.filter_freetext.text())
@ -233,7 +226,6 @@ class LogDock(dockarea.Dock):
self.table_model_filter.rowsAboutToBeInserted.connect(self.rows_inserted_before)
self.table_model_filter.rowsInserted.connect(self.rows_inserted_after)
self.table_model_filter.rowsRemoved.connect(self.rows_removed)
return self.table_model
def save_state(self):
return {"min_level_idx": self.filter_level.currentIndex()}

View File

@ -1,5 +1,26 @@
from quamash import QtCore
from artiq.protocols.sync_struct import Subscriber
class ModelSubscriber(Subscriber):
def __init__(self, notifier_name, model_factory):
Subscriber.__init__(self, notifier_name, self._create_model)
self.model = None
self._model_factory = model_factory
self._setmodel_callbacks = []
def _create_model(self, init):
self.model = self._model_factory(init)
for cb in self._setmodel_callbacks:
cb(self.model)
return self.model
def add_setmodel_callback(self, cb):
self._setmodel_callbacks.append(cb)
if self.model is not None:
cb(self.model)
class _SyncSubstruct:
def __init__(self, update_cb, ref):
@ -31,12 +52,12 @@ class _SyncSubstruct:
class DictSyncModel(QtCore.QAbstractTableModel):
def __init__(self, headers, parent, init):
def __init__(self, headers, init):
self.headers = headers
self.backing_store = init
self.row_to_key = sorted(self.backing_store.keys(),
key=lambda k: self.sort_key(k, self.backing_store[k]))
QtCore.QAbstractTableModel.__init__(self, parent)
QtCore.QAbstractTableModel.__init__(self)
def rowCount(self, parent):
return len(self.backing_store)
@ -45,12 +66,11 @@ class DictSyncModel(QtCore.QAbstractTableModel):
return len(self.headers)
def data(self, index, role):
if not index.isValid():
if not index.isValid() or role != QtCore.Qt.DisplayRole:
return None
elif role != QtCore.Qt.DisplayRole:
return None
k = self.row_to_key[index.row()]
return self.convert(k, self.backing_store[k], index.column())
else:
k = self.row_to_key[index.row()]
return self.convert(k, self.backing_store[k], index.column())
def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Horizontal
@ -113,10 +133,10 @@ class DictSyncModel(QtCore.QAbstractTableModel):
class ListSyncModel(QtCore.QAbstractTableModel):
def __init__(self, headers, parent, init):
def __init__(self, headers, init):
self.headers = headers
self.backing_store = init
QtCore.QAbstractTableModel.__init__(self, parent)
QtCore.QAbstractTableModel.__init__(self)
def rowCount(self, parent):
return len(self.backing_store)
@ -125,11 +145,11 @@ class ListSyncModel(QtCore.QAbstractTableModel):
return len(self.headers)
def data(self, index, role):
if not index.isValid():
if not index.isValid() or role != QtCore.Qt.DisplayRole:
return None
elif role != QtCore.Qt.DisplayRole:
return None
return self.convert(self.backing_store[index.row()], index.column())
else:
return self.convert(self.backing_store[index.row()],
index.column())
def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Horizontal

View File

@ -5,17 +5,16 @@ from functools import partial
from quamash import QtGui, QtCore
from pyqtgraph import dockarea
from artiq.protocols.sync_struct import Subscriber
from artiq.gui.tools import DictSyncModel
from artiq.gui.models import DictSyncModel
from artiq.tools import elide
class _ScheduleModel(DictSyncModel):
def __init__(self, parent, init):
class Model(DictSyncModel):
def __init__(self, init):
DictSyncModel.__init__(self,
["RID", "Pipeline", "Status", "Prio", "Due date",
"Revision", "File", "Class name"],
parent, init)
init)
def sort_key(self, k, v):
# order by priority, and then by due date and RID
@ -57,7 +56,7 @@ class _ScheduleModel(DictSyncModel):
class ScheduleDock(dockarea.Dock):
def __init__(self, status_bar, schedule_ctl):
def __init__(self, status_bar, schedule_ctl, schedule_sub):
dockarea.Dock.__init__(self, "Schedule", size=(1000, 300))
self.status_bar = status_bar
@ -82,24 +81,12 @@ class ScheduleDock(dockarea.Dock):
delete_action.setShortcut("SHIFT+DELETE")
self.table.addAction(delete_action)
async def sub_connect(self, host, port):
self.subscriber = Subscriber("schedule", self.init_schedule_model)
await self.subscriber.connect(host, port)
self.table_model = Model(dict())
schedule_sub.add_setmodel_callback(self.set_model)
async def sub_close(self):
await self.subscriber.close()
def get_current_schedule(self):
try:
table_model = self.table_model
except AttributeError:
return dict()
return table_model.backing_store
def init_schedule_model(self, init):
self.table_model = _ScheduleModel(self.table, init)
def set_model(self, model):
self.table_model = model
self.table.setModel(self.table_model)
return self.table_model
async def delete(self, rid, graceful):
if graceful: