Пример #1
0
class RTResults:
    def __init__(self):
        self.current_groups = None

    @asyncio.coroutine
    def sub_connect(self, host, port):
        self.sets_subscriber = Subscriber("rt_results", self.init_groups,
                                          self.on_mod)
        yield from self.sets_subscriber.connect(host, port)

    @asyncio.coroutine
    def sub_close(self):
        yield from self.sets_subscriber.close()

    def init_groups(self, init):
        if self.current_groups is not None:
            self.current_groups.delete()
        self.current_groups = _Groups(init)
        return self.current_groups

    def on_mod(self, mod):
        if mod["action"] != "init" and len(mod["path"]) >= 3:
            path = mod["path"]
            group = self.current_groups[path[0]]
            if path[1] == "data":
                group.on_data_modified(path[2])
Пример #2
0
    def _do(self):
        try:
            subscriber = Subscriber("devices",
                                    self.controller_db.sync_struct_init)
            while True:
                try:

                    def set_host_filter():
                        s = subscriber.writer.get_extra_info("socket")
                        localhost = s.getsockname()[0]
                        self.controller_db.set_host_filter(localhost)

                    yield from subscriber.connect(self.server, self.port,
                                                  set_host_filter)
                    try:
                        yield from asyncio.wait_for(subscriber.receive_task,
                                                    None)
                    finally:
                        yield from subscriber.close()
                except (ConnectionAbortedError, ConnectionError,
                        ConnectionRefusedError, ConnectionResetError) as e:
                    logger.warning("Connection to master failed (%s: %s)",
                                   e.__class__.__name__, str(e))
                else:
                    logger.warning("Connection to master lost")
                logger.warning("Retrying in %.1f seconds", self.retry_master)
                yield from asyncio.sleep(self.retry_master)
        except asyncio.CancelledError:
            pass
        finally:
            yield from self.controller_db.current_controllers.shutdown()
Пример #3
0
    def __init__(self):
        self.ttl_dock = _MonInjDock("TTL")
        self.dds_dock = _MonInjDock("DDS")

        self.subscriber = Subscriber("devices", self.init_devices)
        self.dm = _DeviceManager(self.send_to_device, dict())
        self.transport = None
Пример #4
0
 def subscribe(self):
     if self.args.embed is None:
         self.subscriber = Subscriber("datasets", self.sub_init,
                                      self.sub_mod)
         self.loop.run_until_complete(
             self.subscriber.connect(self.args.server, self.args.port))
     else:
         self.ipc.subscribe(self.datasets, self.sub_init, self.sub_mod)
Пример #5
0
class SimpleApplet:
    def __init__(self, main_widget_class, cmd_description=None):
        self.main_widget_class = main_widget_class

        self.argparser = argparse.ArgumentParser(description=cmd_description)
        group = self.argparser.add_argument_group("data server")
        group.add_argument(
            "--server", default="::1",
            help="hostname or IP to connect to")
        group.add_argument(
            "--port", default=3250, type=int,
            help="TCP port to connect to")
        self._arggroup_datasets = self.argparser.add_argument_group("datasets")

    def add_dataset(self, name, help=None):
        if help is None:
            self._arggroup_datasets.add_argument(name)
        else:
            self._arggroup_datasets.add_argument(name, help=help)

    def args_init(self):
        self.args = self.argparser.parse_args()

    def quamash_init(self):
        app = QtWidgets.QApplication([])
        self.loop = QEventLoop(app)
        asyncio.set_event_loop(self.loop)

    def create_main_widget(self):
        self.main_widget = self.main_widget_class(self.args)
        self.main_widget.show()

    def sub_init(self, data):
        self.data = data
        return data

    def sub_mod(self, mod):
        self.main_widget.data_changed(self.data, mod)

    def create_subscriber(self):
        self.subscriber = Subscriber("datasets",
                                     self.sub_init, self.sub_mod)
        self.loop.run_until_complete(self.subscriber.connect(
            self.args.server, self.args.port))

    def run(self):
        self.args_init()
        self.quamash_init()
        try:
            self.create_main_widget()
            self.create_subscriber()
            try:
                self.loop.run_forever()
            finally:
                self.loop.run_until_complete(self.subscriber.close())
        finally:
            self.loop.close()
Пример #6
0
 def sub_connect(self, host, port):
     self.queue_subscriber = Subscriber("queue", self.init_queue_store)
     yield from self.queue_subscriber.connect(host, port)
     try:
         self.timed_subscriber = Subscriber("timed", self.init_timed_store)
         yield from self.timed_subscriber.connect(host, port)
     except:
         yield from self.queue_subscriber.close()
         raise
Пример #7
0
 def sub_connect(self, host, port):
     self.parameters_subscriber = Subscriber("parameters",
                                             self.init_parameters_store)
     yield from self.parameters_subscriber.connect(host, port)
     try:
         self.lastchanges_subscriber = Subscriber(
             "parameters_simplehist", self.init_lastchanges_store)
         yield from self.lastchanges_subscriber.connect(host, port)
     except:
         yield from self.parameters_subscriber.close()
         raise
Пример #8
0
class ParametersWindow(Window):
    def __init__(self, **kwargs):
        Window.__init__(self,
                        title="Parameters",
                        default_size=(500, 500),
                        **kwargs)

        notebook = Gtk.Notebook()
        self.add(notebook)

        self.parameters_store = Gtk.ListStore(str, str)
        tree = Gtk.TreeView(self.parameters_store)
        for i, title in enumerate(["Parameter", "Value"]):
            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(title, renderer, text=i)
            tree.append_column(column)
        scroll = Gtk.ScrolledWindow()
        scroll.add(tree)
        notebook.insert_page(scroll, Gtk.Label("Current values"), -1)

        self.lastchanges_store = Gtk.ListStore(str, str, str)
        tree = Gtk.TreeView(self.lastchanges_store)
        for i, title in enumerate(["Time", "Parameter", "Value"]):
            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(title, renderer, text=i)
            tree.append_column(column)
        scroll = Gtk.ScrolledWindow()
        scroll.add(tree)
        notebook.insert_page(scroll, Gtk.Label("Last changes"), -1)

    @asyncio.coroutine
    def sub_connect(self, host, port):
        self.parameters_subscriber = Subscriber("parameters",
                                                self.init_parameters_store)
        yield from self.parameters_subscriber.connect(host, port)
        try:
            self.lastchanges_subscriber = Subscriber(
                "parameters_simplehist", self.init_lastchanges_store)
            yield from self.lastchanges_subscriber.connect(host, port)
        except:
            yield from self.parameters_subscriber.close()
            raise

    @asyncio.coroutine
    def sub_close(self):
        yield from self.lastchanges_subscriber.close()
        yield from self.parameters_subscriber.close()

    def init_parameters_store(self, init):
        return _ParameterStoreSyncer(self.parameters_store, init)

    def init_lastchanges_store(self, init):
        return _LastChangesStoreSyncer(self.lastchanges_store, init)
Пример #9
0
    def subscribe(self):
        # We want to subscribe only to the experiment-local datasets for our RID
        # (but always, even if using IPC – this can be optimised later).
        self.subscriber = Subscriber("datasets_rid_{}".format(self.args.rid),
                                     self.sub_init, self.sub_mod)
        self.loop.run_until_complete(
            self.subscriber.connect(self.args.server, self.args.port))

        # Make sure we still respond to non-dataset messages like `terminate` in
        # embed mode.
        if self.embed is not None:

            def ignore(*args):
                pass

            self.ipc.subscribe([], ignore, ignore)
Пример #10
0
class ParametersDock(dockarea.Dock):
    def __init__(self):
        dockarea.Dock.__init__(self, "Parameters", size=(400, 300))

        grid = LayoutWidget()
        self.addWidget(grid)

        self.search = QtGui.QLineEdit()
        self.search.setPlaceholderText("search...")
        self.search.editingFinished.connect(self._search_parameters)
        grid.addWidget(self.search, 0, 0)

        self.table = QtGui.QTableView()
        self.table.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
        self.table.horizontalHeader().setResizeMode(
            QtGui.QHeaderView.ResizeToContents)
        grid.addWidget(self.table, 1, 0)

    def get_parameter(self, key):
        return self.table_model.backing_store[key]

    def _search_parameters(self):
        model = self.table.model()
        parentIndex = model.index(0, 0)
        numRows = model.rowCount(parentIndex)

        for row in range(numRows):
            index = model.index(row, 0)
            parameter = model.data(index, QtCore.Qt.DisplayRole)
            if parameter.startswith(self.search.displayText()):
                self.table.showRow(row)
            else:
                self.table.hideRow(row)

    @asyncio.coroutine
    def sub_connect(self, host, port):
        self.subscriber = Subscriber("parameters", self.init_parameters_model)
        yield from self.subscriber.connect(host, port)

    @asyncio.coroutine
    def sub_close(self):
        yield from self.subscriber.close()

    def init_parameters_model(self, init):
        self.table_model = ParametersModel(self.table, init)
        self.table.setModel(self.table_model)
        return self.table_model
Пример #11
0
 def subscribe(self):
     if self.args.embed is None:
         self.subscriber = Subscriber("datasets",
                                      self.sub_init, self.sub_mod)
         self.loop.run_until_complete(self.subscriber.connect(
             self.args.server, self.args.port))
     else:
         self.ipc.subscribe(self.datasets, self.sub_init, self.sub_mod)
Пример #12
0
class ScheduleDock(dockarea.Dock):
    def __init__(self, status_bar, schedule_ctl):
        dockarea.Dock.__init__(self, "Schedule", size=(1000, 300))

        self.status_bar = status_bar
        self.schedule_ctl = schedule_ctl

        self.table = QtGui.QTableView()
        self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
        self.table.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
        self.table.horizontalHeader().setResizeMode(
            QtGui.QHeaderView.ResizeToContents)
        self.table.verticalHeader().setResizeMode(
            QtGui.QHeaderView.ResizeToContents)
        self.addWidget(self.table)

        self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
        delete_action = QtGui.QAction("Delete", self.table)
        delete_action.triggered.connect(self.delete_clicked)
        self.table.addAction(delete_action)

    @asyncio.coroutine
    def sub_connect(self, host, port):
        self.subscriber = Subscriber("schedule", self.init_schedule_model)
        yield from self.subscriber.connect(host, port)

    @asyncio.coroutine
    def sub_close(self):
        yield from self.subscriber.close()

    def init_schedule_model(self, init):
        self.table_model = _ScheduleModel(self.table, init)
        self.table.setModel(self.table_model)
        return self.table_model

    @asyncio.coroutine
    def delete(self, rid):
        yield from self.schedule_ctl.delete(rid)

    def delete_clicked(self):
        idx = self.table.selectedIndexes()
        if idx:
            row = idx[0].row()
            rid = self.table_model.row_to_key[row]
            self.status_bar.showMessage("Deleted RID {}".format(rid))
            asyncio.async(self.delete(rid))
Пример #13
0
def _show_list(args, notifier_name, display_fun):
    l = []

    def init_l(x):
        l[:] = x
        return l

    subscriber = Subscriber(notifier_name, init_l, lambda mod: display_fun(l))
    _run_subscriber(args.server, args.port, subscriber)
Пример #14
0
 def _do(self):
     subscriber = Subscriber(
         "parameters",
         partial(Parameters, self.filter_function, self.writer))
     while True:
         try:
             yield from subscriber.connect(self.server, self.port)
             try:
                 yield from asyncio.wait_for(subscriber.receive_task, None)
             finally:
                 yield from subscriber.close()
         except (ConnectionAbortedError, ConnectionError,
                 ConnectionRefusedError, ConnectionResetError) as e:
             logger.warning("Connection to master failed (%s: %s)",
                 e.__class__.__name__, str(e))
         else:
             logger.warning("Connection to master lost")
         logger.warning("Retrying in %.1f seconds", self.retry)
         yield from asyncio.sleep(self.retry)
Пример #15
0
def _show_dict(args, notifier_name, display_fun):
    d = dict()

    def init_d(x):
        d.clear()
        d.update(x)
        return d

    subscriber = Subscriber(notifier_name, init_d, lambda mod: display_fun(d))
    _run_subscriber(args.server, args.port, subscriber)
Пример #16
0
class LogDock(dockarea.Dock):
    def __init__(self):
        dockarea.Dock.__init__(self, "Log", size=(1000, 300))

        self.log = QtGui.QTableView()
        self.log.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
        self.log.horizontalHeader().setResizeMode(
            QtGui.QHeaderView.ResizeToContents)
        self.log.setHorizontalScrollMode(
            QtGui.QAbstractItemView.ScrollPerPixel)
        self.log.setShowGrid(False)
        self.log.setTextElideMode(QtCore.Qt.ElideNone)
        self.addWidget(self.log)
        self.scroll_at_bottom = False

    @asyncio.coroutine
    def sub_connect(self, host, port):
        self.subscriber = Subscriber("log", self.init_log_model)
        yield from self.subscriber.connect(host, port)

    @asyncio.coroutine
    def sub_close(self):
        yield from self.subscriber.close()

    def rows_inserted_before(self):
        scrollbar = self.log.verticalScrollBar()
        self.scroll_at_bottom = scrollbar.value() == scrollbar.maximum()

    def rows_inserted_after(self):
        if self.scroll_at_bottom:
            self.log.scrollToBottom()

    def init_log_model(self, init):
        table_model = _LogModel(self.log, init)
        self.log.setModel(table_model)
        table_model.rowsAboutToBeInserted.connect(self.rows_inserted_before)
        table_model.rowsInserted.connect(self.rows_inserted_after)
        return table_model
Пример #17
0
class NdscanApplet(SimpleApplet):
    def __init__(self):
        # Use a small update delay by default to avoid lagging out the UI by
        # continuous redraws for plots with a large number of points. (20 ms
        # is a pretty arbitrary choice for a latency not perceptible by the
        # user in a normal use case).
        super().__init__(_MainWidget, default_update_delay=20e-3)

        self.argparser.add_argument(
            "--port-control",
            default=3251,
            type=int,
            help="TCP port for master control commands")
        self.argparser.add_argument("--rid",
                                    help="RID of the experiment to plot")

    def subscribe(self):
        # We want to subscribe only to the experiment-local datasets for our RID
        # (but always, even if using IPC – this can be optimised later).
        self.subscriber = Subscriber("datasets_rid_{}".format(self.args.rid),
                                     self.sub_init, self.sub_mod)
        self.loop.run_until_complete(
            self.subscriber.connect(self.args.server, self.args.port))

        # Make sure we still respond to non-dataset messages like `terminate` in
        # embed mode.
        if self.embed is not None:

            def ignore(*args):
                pass

            self.ipc.subscribe([], ignore, ignore)

    def unsubscribe(self):
        self.loop.run_until_complete(self.subscriber.close())

    def filter_mod(self, *args):
        return True
Пример #18
0
    def __init__(self):
        self.ttl_dock = _MonInjDock("TTL")
        self.dds_dock = _MonInjDock("DDS")

        self.subscriber = Subscriber("devices", self.init_devices)
        self.dm = None
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.socket.bind(("", 0))
        # Never ceasing to disappoint, asyncio has an issue about UDP
        # not being supported on Windows (ProactorEventLoop) open since 2014.
        self.loop = asyncio.get_event_loop()
        self.thread = threading.Thread(target=self.receiver_thread,
                                       daemon=True)
        self.thread.start()
Пример #19
0
    def __init__(self):
        self.ttl_dock = _MonInjDock("TTL")
        self.dds_dock = _MonInjDock("DDS")
        self.dac_dock = _MonInjDock("DAC")

        self.dm = _DeviceManager()
        self.dm.ttl_cb = lambda: self.ttl_dock.layout_widgets(
            self.dm.ttl_widgets.values())
        self.dm.dds_cb = lambda: self.dds_dock.layout_widgets(
            self.dm.dds_widgets.values())
        self.dm.dac_cb = lambda: self.dac_dock.layout_widgets(
            self.dm.dac_widgets.values())

        self.subscriber = Subscriber("devices", self.dm.init_ddb,
                                     self.dm.notify)
Пример #20
0
 async def _do(self):
     subscriber = Subscriber(
         "datasets", partial(Datasets, self.filter_function, self.writer))
     while True:
         try:
             await subscriber.connect(self.server, self.port)
             try:
                 await asyncio.wait_for(subscriber.receive_task, None)
             finally:
                 await subscriber.close()
         except (ConnectionAbortedError, ConnectionError,
                 ConnectionRefusedError, ConnectionResetError) as e:
             logger.warning("Connection to master failed (%s: %s)",
                            e.__class__.__name__, str(e))
         else:
             logger.warning("Connection to master lost")
         logger.warning("Retrying in %.1f seconds", self.retry)
         await asyncio.sleep(self.retry)
Пример #21
0
 async def _do(self):
     subscriber = Subscriber("schedule",
                             target_builder=self.writer.init,
                             notify_cb=self.writer.notify_cb,
                             disconnect_cb=self.writer.disconnect_cb)
     while True:
         try:
             await subscriber.connect(self.server, self.port)
             try:
                 await asyncio.wait_for(subscriber.receive_task, None)
             finally:
                 await subscriber.close()
         except (ConnectionAbortedError, ConnectionError,
                 ConnectionRefusedError, ConnectionResetError) as e:
             logger.warning("Connection to master failed (%s: %s)",
                            e.__class__.__name__, str(e))
         else:
             logger.warning("Connection to master lost")
         logger.warning("Retrying in %.1f seconds", self.retry)
         await asyncio.sleep(self.retry)
Пример #22
0
class SimpleApplet:
    def __init__(self,
                 main_widget_class,
                 cmd_description=None,
                 default_update_delay=0.0):
        self.main_widget_class = main_widget_class

        self.argparser = argparse.ArgumentParser(description=cmd_description)

        self.argparser.add_argument(
            "--update-delay",
            type=float,
            default=default_update_delay,
            help="time to wait after a mod (buffering other mods) "
            "before updating (default: %(default).2f)")

        group = self.argparser.add_argument_group("standalone mode (default)")
        group.add_argument("--server",
                           default="::1",
                           help="hostname or IP of the master to connect to "
                           "for dataset notifications "
                           "(ignored in embedded mode)")
        group.add_argument("--port",
                           default=3250,
                           type=int,
                           help="TCP port to connect to")

        self.argparser.add_argument("--embed",
                                    default=None,
                                    help="embed into GUI",
                                    metavar="IPC_ADDRESS")

        self._arggroup_datasets = self.argparser.add_argument_group("datasets")

        self.dataset_args = set()

    def add_dataset(self, name, help=None, required=True):
        kwargs = dict()
        if help is not None:
            kwargs["help"] = help
        if required:
            self._arggroup_datasets.add_argument(name, **kwargs)
        else:
            self._arggroup_datasets.add_argument("--" + name, **kwargs)
        self.dataset_args.add(name)

    def args_init(self):
        self.args = self.argparser.parse_args()
        self.datasets = {
            getattr(self.args, arg.replace("-", "_"))
            for arg in self.dataset_args
        }

    def quamash_init(self):
        app = QtWidgets.QApplication([])
        self.loop = QEventLoop(app)
        asyncio.set_event_loop(self.loop)

    def ipc_init(self):
        if self.args.embed is not None:
            self.ipc = AppletIPCClient(self.args.embed)
            self.loop.run_until_complete(self.ipc.connect())

    def ipc_close(self):
        if self.args.embed is not None:
            self.ipc.close()

    def create_main_widget(self):
        self.main_widget = self.main_widget_class(self.args)
        if self.args.embed is not None:
            self.ipc.set_close_cb(self.main_widget.close)
            if os.name == "nt":
                # HACK: if the window has a frame, there will be garbage
                # (usually white) displayed at its right and bottom borders
                #  after it is embedded.
                self.main_widget.setWindowFlags(QtCore.Qt.FramelessWindowHint)
                self.main_widget.show()
                win_id = int(self.main_widget.winId())
                self.loop.run_until_complete(self.ipc.embed(win_id))
            else:
                # HACK:
                # Qt window embedding is ridiculously buggy, and empirical
                # testing has shown that the following procedure must be
                # followed exactly on Linux:
                # 1. applet creates widget
                # 2. applet creates native window without showing it, and
                #    gets its ID
                # 3. applet sends the ID to host, host embeds the widget
                # 4. applet shows the widget
                # 5. parent resizes the widget
                win_id = int(self.main_widget.winId())
                self.loop.run_until_complete(self.ipc.embed(win_id))
                self.main_widget.show()
                self.ipc.fix_initial_size()
        else:
            self.main_widget.show()

    def sub_init(self, data):
        self.data = data
        return data

    def filter_mod(self, mod):
        if self.args.embed is not None:
            # the parent already filters for us
            return True

        if mod["action"] == "init":
            return True
        if mod["path"]:
            return mod["path"][0] in self.datasets
        elif mod["action"] in {"setitem", "delitem"}:
            return mod["key"] in self.datasets
        else:
            return False

    def emit_data_changed(self, data, mod_buffer):
        self.main_widget.data_changed(data, mod_buffer)

    def flush_mod_buffer(self):
        self.emit_data_changed(self.data, self.mod_buffer)
        del self.mod_buffer

    def sub_mod(self, mod):
        if not self.filter_mod(mod):
            return

        if self.args.update_delay:
            if hasattr(self, "mod_buffer"):
                self.mod_buffer.append(mod)
            else:
                self.mod_buffer = [mod]
                asyncio.get_event_loop().call_later(self.args.update_delay,
                                                    self.flush_mod_buffer)
        else:
            self.emit_data_changed(self.data, [mod])

    def subscribe(self):
        if self.args.embed is None:
            self.subscriber = Subscriber("datasets", self.sub_init,
                                         self.sub_mod)
            self.loop.run_until_complete(
                self.subscriber.connect(self.args.server, self.args.port))
        else:
            self.ipc.subscribe(self.datasets, self.sub_init, self.sub_mod)

    def unsubscribe(self):
        if self.args.embed is None:
            self.loop.run_until_complete(self.subscriber.close())

    def run(self):
        self.args_init()
        self.quamash_init()
        try:
            self.ipc_init()
            try:
                self.create_main_widget()
                self.subscribe()
                try:
                    self.loop.run_forever()
                finally:
                    self.unsubscribe()
            finally:
                self.ipc_close()
        finally:
            self.loop.close()
Пример #23
0
    def __init__(self):
        self.ttl_dock = _MonInjDock("TTL")
        self.dds_dock = _MonInjDock("DDS")

        self.subscriber = Subscriber("devices", self.init_devices)
        self.dm = None
Пример #24
0
 def sub_connect(self, host, port):
     self.explist_subscriber = Subscriber("explist",
                                          self.init_explist_store)
     yield from self.explist_subscriber.connect(host, port)
Пример #25
0
class ExplorerWindow(Window):
    def __init__(self, exit_fn, schedule_ctl, repository, layout_dict=dict()):
        self.schedule_ctl = schedule_ctl
        self.repository = repository

        Window.__init__(self,
                        title="Explorer",
                        default_size=(800, 570),
                        layout_dict=layout_dict)
        self.connect("delete-event", exit_fn)

        topvbox = Gtk.VBox(spacing=6)
        self.add(topvbox)

        menubar = Gtk.MenuBar()
        topvbox.pack_start(menubar, False, False, 0)

        top_menuitem = Gtk.MenuItem("Windows")
        menu = Gtk.Menu()
        menuitem = Gtk.MenuItem("Scheduler")
        menu.append(menuitem)
        menuitem = Gtk.MenuItem("Parameters")
        menu.append(menuitem)
        menu.append(Gtk.SeparatorMenuItem())
        menuitem = Gtk.MenuItem("Quit")
        menuitem.connect("activate", exit_fn)
        menu.append(menuitem)
        top_menuitem.set_submenu(menu)
        menubar.append(top_menuitem)

        top_menuitem = Gtk.MenuItem("Registry")
        menu = Gtk.Menu()
        menuitem = Gtk.MenuItem("Run selected")
        menuitem.connect("activate", self.run)
        menu.append(menuitem)
        menu.append(Gtk.SeparatorMenuItem())
        menuitem = Gtk.MenuItem("Add experiment")
        menu.append(menuitem)
        menuitem = Gtk.MenuItem("Remove experiment")
        menu.append(menuitem)
        top_menuitem.set_submenu(menu)
        menubar.append(top_menuitem)

        self.pane = Gtk.HPaned(
            position=getitem(layout_dict, "pane_position", 180))
        topvbox.pack_start(self.pane, True, True, 0)

        explistvbox = Gtk.VBox(spacing=6)
        self.pane.pack1(explistvbox)
        self.explist_store = Gtk.ListStore(str)
        self.explist_tree = Gtk.TreeView(self.explist_store)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Registered experiments", renderer, text=0)
        self.explist_tree.append_column(column)
        self.explist_tree.connect("row-activated", self.explist_row_activated)
        self.explist_tree.set_activate_on_single_click(True)
        scroll = Gtk.ScrolledWindow()
        scroll.add(self.explist_tree)
        explistvbox.pack_start(scroll, True, True, 0)
        button = Gtk.Button("Run")
        button.connect("clicked", self.run)
        explistvbox.pack_start(button, False, False, 0)

        self.pane_contents = Gtk.Label("")
        self.pane.pack2(self.pane_contents)

    def get_layout_dict(self):
        r = Window.get_layout_dict(self)
        r["pane_position"] = self.pane.get_position()
        return r

    @asyncio.coroutine
    def sub_connect(self, host, port):
        self.explist_subscriber = Subscriber("explist",
                                             self.init_explist_store)
        yield from self.explist_subscriber.connect(host, port)

    @asyncio.coroutine
    def sub_close(self):
        yield from self.explist_subscriber.close()

    def set_pane_contents(self, widget):
        self.pane_contents.destroy()
        self.pane_contents = widget
        self.pane.pack2(self.pane_contents)
        self.pane_contents.show_all()

    def init_explist_store(self, init):
        self.explist_syncer = _ExplistStoreSyncer(self.explist_store,
                                                  init,
                                                  keep_data=True)
        return self.explist_syncer

    def explist_row_activated(self, widget, index, column):
        self.controls = None
        name = self.explist_store[index][0]
        gui_file = self.explist_syncer.data[name]["gui_file"]
        if gui_file is None:
            self.set_pane_contents(Gtk.Label("No GUI controls"))
        else:
            asyncio.Task(self.load_gui_file(gui_file))

    @asyncio.coroutine
    def load_gui_file(self, gui_file):
        gui_mod_data = yield from self.repository.get_data(gui_file)
        gui_mod = dict()
        exec(gui_mod_data, gui_mod)
        self.controls = gui_mod["Controls"]()
        yield from self.controls.build(self.repository.get_data)
        self.set_pane_contents(self.controls.get_top_widget())

    def run(self, widget):
        store, selected = self.explist_tree.get_selection().get_selected()
        if selected is not None:
            name = store[selected][0]
            data = self.explist_syncer.data[name]
            if self.controls is None:
                arguments = {}
            else:
                arguments = self.controls.get_arguments()
            run_params = {
                "file": data["file"],
                "unit": data["unit"],
                "arguments": arguments,
                "rtr_group": data["file"]
            }
            asyncio.Task(self.schedule_ctl.run_queued(run_params, None))
Пример #26
0
class SchedulerWindow(Window):
    def __init__(self, schedule_ctl, **kwargs):
        self.schedule_ctl = schedule_ctl

        Window.__init__(self,
                        title="Scheduler",
                        default_size=(720, 570),
                        **kwargs)

        topvbox = Gtk.VBox(spacing=6)
        self.add(topvbox)

        hbox = Gtk.HBox(spacing=6)
        enable = Gtk.Switch(active=True)
        label = Gtk.Label("Run experiments")
        hbox.pack_start(label, False, False, 0)
        hbox.pack_start(enable, False, False, 0)
        topvbox.pack_start(hbox, False, False, 0)

        notebook = Gtk.Notebook()
        topvbox.pack_start(notebook, True, True, 0)

        self.queue_store = Gtk.ListStore(int, str, str, str, str)
        self.queue_tree = Gtk.TreeView(self.queue_store)
        for i, title in enumerate(
            ["RID", "File", "Unit", "Timeout", "Arguments"]):
            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(title, renderer, text=i)
            self.queue_tree.append_column(column)
        scroll = Gtk.ScrolledWindow()
        scroll.add(self.queue_tree)
        vbox = Gtk.VBox(spacing=6)
        vbox.pack_start(scroll, True, True, 0)
        hbox = Gtk.HBox(spacing=6)
        button = Gtk.Button("Find")
        hbox.pack_start(button, True, True, 0)
        button = Gtk.Button("Move up")
        hbox.pack_start(button, True, True, 0)
        button = Gtk.Button("Move down")
        hbox.pack_start(button, True, True, 0)
        button = Gtk.Button("Remove")
        button.connect("clicked", self.remove_queued)
        hbox.pack_start(button, True, True, 0)
        vbox.pack_start(hbox, False, False, 0)
        vbox.set_border_width(6)
        notebook.insert_page(vbox, Gtk.Label("Queue"), -1)

        self.timed_store = Gtk.ListStore(str, int, str, str, str, str)
        self.timed_tree = Gtk.TreeView(self.timed_store)
        for i, title in enumerate(
            ["Next run", "TRID", "File", "Unit", "Timeout", "Arguments"]):
            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(title, renderer, text=i)
            self.timed_tree.append_column(column)
        scroll = Gtk.ScrolledWindow()
        scroll.add(self.timed_tree)
        vbox = Gtk.VBox(spacing=6)
        vbox.pack_start(scroll, True, True, 0)
        button = Gtk.Button("Remove")
        button.connect("clicked", self.remove_timed)
        vbox.pack_start(button, False, False, 0)
        vbox.set_border_width(6)
        notebook.insert_page(vbox, Gtk.Label("Timed schedule"), -1)

    def remove_queued(self, widget):
        store, selected = self.queue_tree.get_selection().get_selected()
        if selected is not None:
            rid = store[selected][0]
            asyncio.Task(self.schedule_ctl.cancel_queued(rid))

    def remove_timed(self, widget):
        store, selected = self.timed_tree.get_selection().get_selected()
        if selected is not None:
            trid = store[selected][1]
            asyncio.Task(self.schedule_ctl.cancel_timed(trid))

    @asyncio.coroutine
    def sub_connect(self, host, port):
        self.queue_subscriber = Subscriber("queue", self.init_queue_store)
        yield from self.queue_subscriber.connect(host, port)
        try:
            self.timed_subscriber = Subscriber("timed", self.init_timed_store)
            yield from self.timed_subscriber.connect(host, port)
        except:
            yield from self.queue_subscriber.close()
            raise

    @asyncio.coroutine
    def sub_close(self):
        yield from self.timed_subscriber.close()
        yield from self.queue_subscriber.close()

    def init_queue_store(self, init):
        return _QueueStoreSyncer(self.queue_store, init)

    def init_timed_store(self, init):
        return _TimedStoreSyncer(self.timed_store, init)
Пример #27
0
 def create_subscriber(self):
     self.subscriber = Subscriber("datasets",
                                  self.sub_init, self.sub_mod)
     self.loop.run_until_complete(self.subscriber.connect(
         self.args.server, self.args.port))
Пример #28
0
class ResultsDock(dockarea.Dock):
    def __init__(self, dialog_parent, dock_area):
        dockarea.Dock.__init__(self, "Results", size=(1500, 500))
        self.dialog_parent = dialog_parent
        self.dock_area = dock_area

        grid = LayoutWidget()
        self.addWidget(grid)

        self.table = QtGui.QTableView()
        self.table.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
        self.table.horizontalHeader().setResizeMode(
            QtGui.QHeaderView.ResizeToContents)
        grid.addWidget(self.table, 0, 0)

        add_display_box = QtGui.QGroupBox("Add display")
        grid.addWidget(add_display_box, 0, 1)
        display_grid = QtGui.QGridLayout()
        add_display_box.setLayout(display_grid)

        for n, name in enumerate(display_types.keys()):
            btn = QtGui.QPushButton(name)
            display_grid.addWidget(btn, n, 0)
            btn.clicked.connect(partial(self.create_dialog, name))

        self.displays = dict()

    def get_result(self, key):
        return self.table_model.backing_store[key]

    @asyncio.coroutine
    def sub_connect(self, host, port):
        self.subscriber = Subscriber("rt_results", self.init_results_model,
                                     self.on_mod)
        yield from self.subscriber.connect(host, port)

    @asyncio.coroutine
    def sub_close(self):
        yield from self.subscriber.close()

    def init_results_model(self, init):
        self.table_model = ResultsModel(self.table, init)
        self.table.setModel(self.table_model)
        return self.table_model

    def on_mod(self, mod):
        if mod["action"] == "init":
            for display in self.displays.values():
                display.update_data(self.table_model.backing_store)
            return

        if mod["action"] == "setitem":
            source = mod["key"]
        elif mod["path"]:
            source = mod["path"][0]
        else:
            return

        for display in self.displays.values():
            if source in display.data_sources():
                display.update_data(self.table_model.backing_store)

    def create_dialog(self, ty):
        dlg_class = display_types[ty][0]
        dlg = dlg_class(self.dialog_parent, None, dict(),
                        sorted(self.table_model.backing_store.keys()),
                        partial(self.create_display, ty, None))
        dlg.open()

    def create_display(self, ty, prev_name, name, settings):
        if prev_name is not None and prev_name in self.displays:
            raise NotImplementedError
        dsp_class = display_types[ty][1]
        dsp = dsp_class(name, settings)
        self.displays[name] = dsp
        dsp.update_data(self.table_model.backing_store)

        def on_close():
            del self.displays[name]

        dsp.sigClosed.connect(on_close)
        self.dock_area.addDock(dsp)
        self.dock_area.floatDock(dsp)
        return dsp

    def save_state(self):
        r = dict()
        for name, display in self.displays.items():
            r[name] = {
                "ty": _get_display_type_name(type(display)),
                "settings": display.settings,
                "state": display.save_state()
            }
        return r

    def restore_state(self, state):
        for name, desc in state.items():
            try:
                dsp = self.create_display(desc["ty"], None, name,
                                          desc["settings"])
            except:
                logger.warning("Failed to create display '%s'",
                               name,
                               exc_info=True)
            try:
                dsp.restore_state(desc["state"])
            except:
                logger.warning("Failed to restore display state of '%s'",
                               name,
                               exc_info=True)
Пример #29
0
 def __init__(self, notifier_name, model_factory,
              disconnect_cb=None):
     ModelManager.__init__(self, model_factory)
     Subscriber.__init__(self, notifier_name, self._create_model,
                         disconnect_cb=disconnect_cb)
Пример #30
0
class MonInj(TaskObject):
    def __init__(self):
        self.ttl_dock = _MonInjDock("TTL")
        self.dds_dock = _MonInjDock("DDS")

        self.subscriber = Subscriber("devices", self.init_devices)
        self.dm = _DeviceManager(self.send_to_device, dict())
        self.transport = None

    @asyncio.coroutine
    def start(self, server, port):
        loop = asyncio.get_event_loop()
        yield from loop.create_datagram_endpoint(lambda: self,
                                                 family=socket.AF_INET)
        try:
            yield from self.subscriber.connect(server, port)
            try:
                TaskObject.start(self)
            except:
                yield from self.subscriber.close()
                raise
        except:
            self.transport.close()
            raise

    @asyncio.coroutine
    def stop(self):
        yield from TaskObject.stop(self)
        yield from self.subscriber.close()
        if self.transport is not None:
            self.transport.close()
            self.transport = None

    def connection_made(self, transport):
        self.transport = transport

    def datagram_received(self, data, addr):
        try:
            ttl_levels, ttl_oes, ttl_overrides = \
                struct.unpack(">QQQ", data[:8*3])
            for channel, w in self.dm.ttl_widgets.items():
                w.set_value(ttl_levels & (1 << channel),
                            ttl_oes & (1 << channel),
                            ttl_overrides & (1 << channel))
            dds_data = data[8*3:]
            ndds = len(dds_data)//4
            ftws = struct.unpack(">" + "I"*ndds, dds_data)
            for channel, w in self.dm.dds_widgets.items():
                try:
                    ftw = ftws[channel]
                except KeyError:
                    pass
                else:
                    w.set_value(ftw)
        except:
            logger.warning("failed to process datagram", exc_info=True)

    def error_received(self, exc):
        logger.warning("datagram endpoint error")

    def connection_lost(self, exc):
        self.transport = None

    def send_to_device(self, data):
        ca = self.dm.get_core_addr()
        if ca is None:
            logger.warning("could not find core device address")
        elif self.transport is None:
            logger.warning("datagram endpoint not available")
        else:
            self.transport.sendto(data, (ca, 3250))

    @asyncio.coroutine
    def _do(self):
        while True:
            yield from asyncio.sleep(0.2)
            # MONINJ_REQ_MONITOR
            self.send_to_device(b"\x01")

    def init_devices(self, d):
        self.dm = _DeviceManager(self.send_to_device, d)
        self.dm.ttl_cb = lambda: self.ttl_dock.layout_widgets(
                            self.dm.ttl_widgets.items())
        self.dm.dds_cb = lambda: self.dds_dock.layout_widgets(
                            self.dm.dds_widgets.items())
        self.dm.ttl_cb()
        self.dm.dds_cb()
        return self.dm
Пример #31
0
 def sub_connect(self, host, port):
     self.explist_subscriber = Subscriber("explist",
                                          self.init_explist_model)
     yield from self.explist_subscriber.connect(host, port)
Пример #32
0
class ExplorerDock(dockarea.Dock):
    def __init__(self, dialog_parent, status_bar, schedule_ctl):
        dockarea.Dock.__init__(self, "Explorer", size=(1500, 500))

        self.dialog_parent = dialog_parent
        self.status_bar = status_bar
        self.schedule_ctl = schedule_ctl

        self.splitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
        self.addWidget(self.splitter)

        grid = LayoutWidget()
        self.splitter.addWidget(grid)

        self.el = QtGui.QListView()
        self.el.selectionChanged = self._selection_changed
        self.selected_key = None
        grid.addWidget(self.el, 0, 0, colspan=4)

        self.datetime = QtGui.QDateTimeEdit()
        self.datetime.setDisplayFormat("MMM d yyyy hh:mm:ss")
        self.datetime.setCalendarPopup(True)
        self.datetime.setDate(QtCore.QDate.currentDate())
        self.datetime.dateTimeChanged.connect(self.enable_duedate)
        self.datetime_en = QtGui.QCheckBox("Due date:")
        grid.addWidget(self.datetime_en, 1, 0)
        grid.addWidget(self.datetime, 1, 1)

        self.priority = QtGui.QSpinBox()
        self.priority.setRange(-99, 99)
        grid.addWidget(QtGui.QLabel("Priority:"), 1, 2)
        grid.addWidget(self.priority, 1, 3)

        self.pipeline = QtGui.QLineEdit()
        self.pipeline.setText("main")
        grid.addWidget(QtGui.QLabel("Pipeline:"), 2, 0)
        grid.addWidget(self.pipeline, 2, 1)

        self.flush = QtGui.QCheckBox("Flush")
        grid.addWidget(self.flush, 2, 2, colspan=2)

        submit = QtGui.QPushButton("Submit")
        grid.addWidget(submit, 3, 0, colspan=4)
        submit.clicked.connect(self.submit_clicked)

        self.argeditor = _ArgumentEditor(self.dialog_parent)
        self.splitter.addWidget(self.argeditor)
        self.splitter.setSizes([grid.minimumSizeHint().width(), 1000])
        self.state = dict()

    def update_selection(self, selected, deselected):
        if deselected:
            self.state[deselected] = self.argeditor.save_state()

        if selected:
            expinfo = self.explist_model.backing_store[selected]
            self.argeditor.set_arguments(expinfo["arguments"])
            if selected in self.state:
                self.argeditor.restore_state(self.state[selected])
            self.splitter.insertWidget(1, self.argeditor)
        self.selected_key = selected

    def _sel_to_key(self, selection):
        selection = selection.indexes()
        if selection:
            row = selection[0].row()
            return self.explist_model.row_to_key[row]
        else:
            return None

    def _selection_changed(self, selected, deselected):
        self.update_selection(self._sel_to_key(selected),
                              self._sel_to_key(deselected))

    def save_state(self):
        idx = self.el.selectedIndexes()
        if idx:
            row = idx[0].row()
            key = self.explist_model.row_to_key[row]
            self.state[key] = self.argeditor.save_state()
        return self.state

    def restore_state(self, state):
        self.state = state

    def enable_duedate(self):
        self.datetime_en.setChecked(True)

    @asyncio.coroutine
    def sub_connect(self, host, port):
        self.explist_subscriber = Subscriber("explist",
                                             self.init_explist_model)
        yield from self.explist_subscriber.connect(host, port)

    @asyncio.coroutine
    def sub_close(self):
        yield from 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

    @asyncio.coroutine
    def submit(self, pipeline_name, file, class_name, arguments,
               priority, due_date, flush):
        expid = {
            "repo_rev": None,
            "file": file,
            "class_name": class_name,
            "arguments": arguments,
        }
        rid = yield from self.schedule_ctl.submit(pipeline_name, expid,
                                                  priority, due_date, flush)
        self.status_bar.showMessage("Submitted RID {}".format(rid))

    def submit_clicked(self):
        if self.selected_key is not None:
            expinfo = self.explist_model.backing_store[self.selected_key]
            if self.datetime_en.isChecked():
                due_date = self.datetime.dateTime().toMSecsSinceEpoch()/1000
            else:
                due_date = None
            arguments = self.argeditor.get_argument_values(True)
            if arguments is None:
                return
            asyncio.async(self.submit(self.pipeline.text(),
                                      expinfo["file"], expinfo["class_name"],
                                      arguments, self.priority.value(),
                                      due_date, self.flush.isChecked()))
Пример #33
0
 def __init__(self, notifier_name, model_factory):
     ModelManager.__init__(self, model_factory)
     Subscriber.__init__(self, notifier_name, self._create_model)
Пример #34
0
 def sub_connect(self, host, port):
     self.subscriber = Subscriber("rt_results", self.init_results_model,
                                  self.on_mod)
     yield from self.subscriber.connect(host, port)
Пример #35
0
 def sub_connect(self, host, port):
     self.subscriber = Subscriber("log", self.init_log_model)
     yield from self.subscriber.connect(host, port)
Пример #36
0
 def sub_connect(self, host, port):
     self.subscriber = Subscriber("schedule", self.init_schedule_model)
     yield from self.subscriber.connect(host, port)
Пример #37
0
 def __init__(self, notifier_name, model_factory, disconnect_cb=None):
     ModelManager.__init__(self, model_factory)
     Subscriber.__init__(self,
                         notifier_name,
                         self._create_model,
                         disconnect_cb=disconnect_cb)
Пример #38
0
 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 = []
Пример #39
0
 async def sub_connect(self, host, port):
     self.subscriber = Subscriber("datasets", self.init_datasets_model,
                                  self.on_mod)
     await self.subscriber.connect(host, port)
Пример #40
0
 def sub_connect(self, host, port):
     self.sets_subscriber = Subscriber("rt_results", self.init_groups,
                                       self.on_mod)
     yield from self.sets_subscriber.connect(host, port)
Пример #41
0
def _show_log(args):
    subscriber = Subscriber("log", _LogPrinter)
    _run_subscriber(args.server, args.port, subscriber)
Пример #42
0
 def sub_connect(self, host, port):
     self.subscriber = Subscriber("parameters", self.init_parameters_model)
     yield from self.subscriber.connect(host, port)
Пример #43
0
class SimpleApplet:
    def __init__(self, main_widget_class, cmd_description=None,
                 default_update_delay=0.0):
        self.main_widget_class = main_widget_class

        self.argparser = argparse.ArgumentParser(description=cmd_description)

        self.argparser.add_argument(
            "--update-delay", type=float, default=default_update_delay,
            help="time to wait after a mod (buffering other mods) "
                 "before updating (default: %(default).2f)")

        group = self.argparser.add_argument_group("standalone mode (default)")
        group.add_argument(
            "--server", default="::1",
            help="hostname or IP of the master to connect to "
                 "for dataset notifications "
                 "(ignored in embedded mode)")
        group.add_argument(
            "--port", default=3250, type=int,
            help="TCP port to connect to")

        self.argparser.add_argument(
            "--embed", default=None, help="embed into GUI",
            metavar="IPC_ADDRESS")

        self._arggroup_datasets = self.argparser.add_argument_group("datasets")

        self.dataset_args = set()

    def add_dataset(self, name, help=None, required=True):
        kwargs = dict()
        if help is not None:
            kwargs["help"] = help
        if required:
            self._arggroup_datasets.add_argument(name, **kwargs)
        else:
            self._arggroup_datasets.add_argument("--" + name, **kwargs)
        self.dataset_args.add(name)

    def args_init(self):
        self.args = self.argparser.parse_args()
        self.datasets = {getattr(self.args, arg.replace("-", "_"))
                         for arg in self.dataset_args}

    def quamash_init(self):
        app = QtWidgets.QApplication([])
        self.loop = QEventLoop(app)
        asyncio.set_event_loop(self.loop)

    def ipc_init(self):
        if self.args.embed is not None:
            self.ipc = AppletIPCClient(self.args.embed)
            self.loop.run_until_complete(self.ipc.connect())

    def ipc_close(self):
        if self.args.embed is not None:
            self.ipc.close()

    def create_main_widget(self):
        self.main_widget = self.main_widget_class(self.args)
        if self.args.embed is not None:
            self.ipc.set_close_cb(self.main_widget.close)
            if os.name == "nt":
                # HACK: if the window has a frame, there will be garbage
                # (usually white) displayed at its right and bottom borders
                #  after it is embedded.
                self.main_widget.setWindowFlags(QtCore.Qt.FramelessWindowHint)
                self.main_widget.show()
                win_id = int(self.main_widget.winId())
                self.loop.run_until_complete(self.ipc.embed(win_id))
            else:
                # HACK:
                # Qt window embedding is ridiculously buggy, and empirical
                # testing has shown that the following procedure must be
                # followed exactly on Linux:
                # 1. applet creates widget
                # 2. applet creates native window without showing it, and
                #    gets its ID
                # 3. applet sends the ID to host, host embeds the widget
                # 4. applet shows the widget
                # 5. parent resizes the widget
                win_id = int(self.main_widget.winId())
                self.loop.run_until_complete(self.ipc.embed(win_id))
                self.main_widget.show()
                self.ipc.fix_initial_size()
        else:
            self.main_widget.show()

    def sub_init(self, data):
        self.data = data
        return data

    def filter_mod(self, mod):
        if self.args.embed is not None:
            # the parent already filters for us
            return True

        if mod["action"] == "init":
            return True
        if mod["path"]:
            return mod["path"][0] in self.datasets
        elif mod["action"] in {"setitem", "delitem"}:
            return mod["key"] in self.datasets
        else:
            return False

    def emit_data_changed(self, data, mod_buffer):
        self.main_widget.data_changed(data, mod_buffer)

    def flush_mod_buffer(self):
        self.emit_data_changed(self.data, self.mod_buffer)
        del self.mod_buffer

    def sub_mod(self, mod):
        if not self.filter_mod(mod):
            return

        if self.args.update_delay:
            if hasattr(self, "mod_buffer"):
                self.mod_buffer.append(mod)
            else:
                self.mod_buffer = [mod]
                asyncio.get_event_loop().call_later(self.args.update_delay,
                                                    self.flush_mod_buffer)
        else:
            self.emit_data_changed(self.data, [mod])

    def subscribe(self):
        if self.args.embed is None:
            self.subscriber = Subscriber("datasets",
                                         self.sub_init, self.sub_mod)
            self.loop.run_until_complete(self.subscriber.connect(
                self.args.server, self.args.port))
        else:
            self.ipc.subscribe(self.datasets, self.sub_init, self.sub_mod)

    def unsubscribe(self):
        if self.args.embed is None:
            self.loop.run_until_complete(self.subscriber.close())

    def run(self):
        self.args_init()
        self.quamash_init()
        try:
            self.ipc_init()
            try:
                self.create_main_widget()
                self.subscribe()
                try:
                    self.loop.run_forever()
                finally:
                    self.unsubscribe()
            finally:
                self.ipc_close()
        finally:
            self.loop.close()
Пример #44
0
class ResultsDock(dockarea.Dock):
    def __init__(self, dialog_parent, dock_area):
        dockarea.Dock.__init__(self, "Results", size=(1500, 500))
        self.dialog_parent = dialog_parent
        self.dock_area = dock_area

        grid = LayoutWidget()
        self.addWidget(grid)

        self.table = QtGui.QTableView()
        self.table.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
        self.table.horizontalHeader().setResizeMode(
            QtGui.QHeaderView.ResizeToContents)
        grid.addWidget(self.table, 0, 0)

        add_display_box = QtGui.QGroupBox("Add display")
        grid.addWidget(add_display_box, 0, 1)
        display_grid = QtGui.QGridLayout()
        add_display_box.setLayout(display_grid)

        for n, name in enumerate(display_types.keys()):
            btn = QtGui.QPushButton(name)
            display_grid.addWidget(btn, n, 0)
            btn.clicked.connect(partial(self.create_dialog, name))

        self.displays = dict()

    def get_result(self, key):
        return self.table_model.backing_store[key]

    @asyncio.coroutine
    def sub_connect(self, host, port):
        self.subscriber = Subscriber("rt_results", self.init_results_model,
                                     self.on_mod)
        yield from self.subscriber.connect(host, port)

    @asyncio.coroutine
    def sub_close(self):
        yield from self.subscriber.close()

    def init_results_model(self, init):
        self.table_model = ResultsModel(self.table, init)
        self.table.setModel(self.table_model)
        return self.table_model

    def on_mod(self, mod):
        if mod["action"] == "init":
            for display in self.displays.values():
                display.update_data(self.table_model.backing_store)
            return

        if mod["action"] == "setitem":
            source = mod["key"]
        elif mod["path"]:
            source = mod["path"][0]
        else:
            return

        for display in self.displays.values():
            if source in display.data_sources():
                display.update_data(self.table_model.backing_store)

    def create_dialog(self, ty):
        dlg_class = display_types[ty][0]
        dlg = dlg_class(self.dialog_parent, None, dict(),
            sorted(self.table_model.backing_store.keys()),
            partial(self.create_display, ty, None))
        dlg.open()

    def create_display(self, ty, prev_name, name, settings):
        if prev_name is not None and prev_name in self.displays:
            raise NotImplementedError
        dsp_class = display_types[ty][1]
        dsp = dsp_class(name, settings)
        self.displays[name] = dsp
        dsp.update_data(self.table_model.backing_store)

        def on_close():
            del self.displays[name]
        dsp.sigClosed.connect(on_close)
        self.dock_area.addDock(dsp)
        self.dock_area.floatDock(dsp)
        return dsp

    def save_state(self):
        r = dict()
        for name, display in self.displays.items():
            r[name] = {
                "ty": _get_display_type_name(type(display)),
                "settings": display.settings,
                "state": display.save_state()
            }
        return r

    def restore_state(self, state):
        for name, desc in state.items():
            try:
                dsp = self.create_display(desc["ty"], None, name,
                                          desc["settings"])
            except:
                logger.warning("Failed to create display '%s'", name,
                               exc_info=True)
            try:
                dsp.restore_state(desc["state"])
            except:
                logger.warning("Failed to restore display state of '%s'",
                               name, exc_info=True)