From ea53ed1af75bb7657505d29c8bb9fd722bd1c559 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 22 May 2015 17:05:15 +0800 Subject: [PATCH] gui: switch to Qt --- artiq/__init__.py | 1 - artiq/frontend/artiq_gui.py | 85 +++----- artiq/gui/explib.py | 35 ---- artiq/gui/explorer.py | 152 -------------- artiq/gui/icon.png | Bin 13730 -> 0 bytes artiq/gui/parameters.py | 83 +++----- artiq/gui/rt_result_views.py | 79 -------- artiq/gui/rt_results.py | 120 ----------- artiq/gui/schedule.py | 69 +++++++ artiq/gui/scheduler.py | 75 ------- artiq/gui/tools.py | 191 +++++++----------- .../repository/flopping_f_simulation.py | 1 - .../flopping_f_simulation_gui.glade | 58 ------ .../repository/flopping_f_simulation_gui.py | 20 -- setup.py | 17 +- 15 files changed, 195 insertions(+), 791 deletions(-) delete mode 100644 artiq/gui/explib.py delete mode 100644 artiq/gui/explorer.py delete mode 100644 artiq/gui/icon.png delete mode 100644 artiq/gui/rt_result_views.py delete mode 100644 artiq/gui/rt_results.py create mode 100644 artiq/gui/schedule.py delete mode 100644 artiq/gui/scheduler.py delete mode 100644 examples/master/repository/flopping_f_simulation_gui.glade delete mode 100644 examples/master/repository/flopping_f_simulation_gui.py diff --git a/artiq/__init__.py b/artiq/__init__.py index d1a05b941..934cfbf49 100644 --- a/artiq/__init__.py +++ b/artiq/__init__.py @@ -4,4 +4,3 @@ from artiq.language.db import * from artiq.language.units import check_unit from artiq.language.units import ps, ns, us, ms, s from artiq.language.units import Hz, kHz, MHz, GHz -from artiq.gui.explib import * diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index 05dee0d96..e1549c6fd 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -4,17 +4,14 @@ import argparse import asyncio import atexit -import gbulb -from gi.repository import Gtk +# Quamash must be imported first so that pyqtgraph picks up the Qt binding +# it has chosen. +from quamash import QEventLoop, QtGui +from pyqtgraph.dockarea import DockArea from artiq.protocols.file_db import FlatFileDB -from artiq.protocols.pc_rpc import AsyncioClient -from artiq.protocols.sync_struct import Subscriber -from artiq.gui.tools import LayoutManager -from artiq.gui.scheduler import SchedulerWindow -from artiq.gui.parameters import ParametersWindow -from artiq.gui.rt_results import RTResults -from artiq.gui.explorer import ExplorerWindow +from artiq.gui.schedule import ScheduleDock +from artiq.gui.parameters import ParametersDock def get_argparser(): @@ -38,67 +35,31 @@ def main(): args = get_argparser().parse_args() db = FlatFileDB(args.db_file, default_data=dict()) - lmgr = LayoutManager(db) - asyncio.set_event_loop_policy(gbulb.GtkEventLoopPolicy()) - loop = asyncio.get_event_loop() + app = QtGui.QApplication([]) + loop = QEventLoop(app) + asyncio.set_event_loop(loop) atexit.register(lambda: loop.close()) - # share the schedule control and repository connections - schedule_ctl = AsyncioClient() - loop.run_until_complete(schedule_ctl.connect_rpc( - args.server, args.port_control, "master_schedule")) - atexit.register(lambda: schedule_ctl.close_rpc()) - repository = AsyncioClient() - loop.run_until_complete(repository.connect_rpc( - args.server, args.port_control, "master_repository")) - atexit.register(lambda: repository.close_rpc()) + win = QtGui.QMainWindow() + area = DockArea() + win.setCentralWidget(area) + win.resize(1000, 500) + win.setWindowTitle("ARTIQ") - scheduler_win = lmgr.create_window(SchedulerWindow, - "scheduler", - schedule_ctl) - loop.run_until_complete(scheduler_win.sub_connect( + d_params = ParametersDock(area) + area.addDock(d_params, "left") + loop.run_until_complete(d_params.sub_connect( args.server, args.port_notify)) - atexit.register( - lambda: loop.run_until_complete(scheduler_win.sub_close())) + atexit.register(lambda: loop.run_until_complete(d_params.sub_close())) - parameters_win = lmgr.create_window(ParametersWindow, "parameters") - loop.run_until_complete(parameters_win.sub_connect( + d_schedule = ScheduleDock(area) + area.addDock(d_schedule, "top", d_params) + loop.run_until_complete(d_schedule.sub_connect( args.server, args.port_notify)) - atexit.register( - lambda: loop.run_until_complete(parameters_win.sub_close())) - - def exit(*args): - lmgr.save() - Gtk.main_quit(*args) - explorer_win = lmgr.create_window(ExplorerWindow, - "explorer", - exit, - schedule_ctl, - repository) - loop.run_until_complete(explorer_win.sub_connect( - args.server, args.port_notify)) - atexit.register( - lambda: loop.run_until_complete(explorer_win.sub_close())) - - parameters_sub = Subscriber("parameters", - [parameters_win.init_parameters_store, - explorer_win.init_parameters_dict]) - loop.run_until_complete( - parameters_sub.connect(args.server, args.port_notify)) - atexit.register( - lambda: loop.run_until_complete(parameters_sub.close())) - - scheduler_win.show_all() - parameters_win.show_all() - explorer_win.show_all() - - rtr = RTResults() - loop.run_until_complete(rtr.sub_connect( - args.server, args.port_notify)) - atexit.register( - lambda: loop.run_until_complete(rtr.sub_close())) + atexit.register(lambda: loop.run_until_complete(d_schedule.sub_close())) + win.show() loop.run_forever() if __name__ == "__main__": diff --git a/artiq/gui/explib.py b/artiq/gui/explib.py deleted file mode 100644 index 0f1ee708b..000000000 --- a/artiq/gui/explib.py +++ /dev/null @@ -1,35 +0,0 @@ -import asyncio as _aio - - -class BaseControls: - def __init__(self, facilities): - self.facilities = facilities - - @_aio.coroutine - def build(self): - self.finalize() - - def finalize(self): - pass - - -class GladeControls(BaseControls): - def __init__(self, facilities, glade_file, top_widget_name="top"): - BaseControls.__init__(self, facilities) - self.glade_file = glade_file - self.top_widget_name = top_widget_name - - @_aio.coroutine - def build(self): - # lazy import GTK so that the artiq top-level - # (which imports from us) can be imported on systems - # without GTK installed - from gi.repository import Gtk - - self.builder = Gtk.Builder() - data = yield from self.facilities.get_data(self.glade_file) - self.builder.add_from_string(data) - self.finalize() - - def get_top_widget(self): - return self.builder.get_object(self.top_widget_name) diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py deleted file mode 100644 index 1736acf16..000000000 --- a/artiq/gui/explorer.py +++ /dev/null @@ -1,152 +0,0 @@ -import asyncio -import types - -from gi.repository import Gtk - -from artiq.gui.tools import Window, getitem, DictSyncer -from artiq.protocols.sync_struct import Subscriber - - -class _ExplistStoreSyncer(DictSyncer): - def order_key(self, kv_pair): - return kv_pair[0] - - def convert(self, name, value): - return [name] - - -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, - self.init_explist_data]) - yield from self.explist_subscriber.connect(host, port) - - @asyncio.coroutine - def sub_close(self): - yield from self.explist_subscriber.close() - - def init_parameters_dict(self, init): - self.parameters = init - return init - - 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): - return _ExplistStoreSyncer(self.explist_store, init) - - def init_explist_data(self, init): - self.explist_data = init - return init - - def explist_row_activated(self, widget, index, column): - self.controls = None - name = self.explist_store[index][0] - gui_file = self.explist_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) - facilities = types.SimpleNamespace( - get_data=self.repository.get_data, - get_parameter=lambda p: self.parameters[p]) - self.controls = gui_mod["Controls"](facilities) - yield from self.controls.build() - 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_data[name] - if self.controls is None: - arguments = {} - else: - arguments = self.controls.get_arguments() - expid = { - "file": data["file"], - "experiment": data["experiment"], - "arguments": arguments - } - asyncio.async(self.schedule_ctl.submit("main", expid, None)) diff --git a/artiq/gui/icon.png b/artiq/gui/icon.png deleted file mode 100644 index 33c071886b3da87c51f7cfaad0dde65b4cb82551..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13730 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 Fe*lkXeuMx3 diff --git a/artiq/gui/parameters.py b/artiq/gui/parameters.py index 3e4800ff8..4602373c9 100644 --- a/artiq/gui/parameters.py +++ b/artiq/gui/parameters.py @@ -1,74 +1,47 @@ import asyncio -from operator import itemgetter -import time -from gi.repository import Gtk +from quamash import QtGui +from pyqtgraph.dockarea import Dock -from artiq.gui.tools import Window, ListSyncer, DictSyncer from artiq.protocols.sync_struct import Subscriber +from artiq.gui.tools import DictSyncModel -class _ParameterStoreSyncer(DictSyncer): - def order_key(self, kv_pair): - return kv_pair[0] +class ParametersModel(DictSyncModel): + def __init__(self, parent, init): + DictSyncModel.__init__(self, ["Parameter", "Value"], + parent, init) - def convert(self, name, value): - return [name, str(value)] + def sort_key(self, k, v): + return k - -class _LastChangesStoreSyncer(ListSyncer): - def convert(self, x): - if len(x) == 3: - timestamp, name, value = x + def convert(self, k, v, column): + if column == 0: + return k + elif column == 1: + return str(v) else: - timestamp, name = x - value = "" - return [time.strftime("%m/%d %H:%M:%S", time.localtime(timestamp)), - name, str(value)] + raise ValueError -class ParametersWindow(Window): - def __init__(self, **kwargs): - Window.__init__(self, - title="Parameters", - default_size=(500, 500), - **kwargs) +class ParametersDock(Dock): + def __init__(self, parent): + Dock.__init__(self, "Parameters", size=(500, 300)) - 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) + self.table = QtGui.QTableView() + self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.addWidget(self.table) @asyncio.coroutine def sub_connect(self, host, port): - self.lastchanges_subscriber = Subscriber( - "parameters_simplehist", self.init_lastchanges_store) - yield from self.lastchanges_subscriber.connect(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.lastchanges_subscriber.close() + yield from self.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) + def init_parameters_model(self, init): + table_model = ParametersModel(self.table, init) + self.table.setModel(table_model) + return table_model diff --git a/artiq/gui/rt_result_views.py b/artiq/gui/rt_result_views.py deleted file mode 100644 index c3f593ad4..000000000 --- a/artiq/gui/rt_result_views.py +++ /dev/null @@ -1,79 +0,0 @@ -from gi.repository import Gtk -import cairoplot - -from artiq.gui.tools import Window - - -class RawWindow(Window): - def __init__(self, group_name, set_names): - self.labels = dict() - - Window.__init__(self, title=group_name + ": Raw values", - default_size=(200, 150)) - - grid = Gtk.Grid(row_spacing=6, column_spacing=6) - self.add(grid) - for i, name in enumerate(set_names): - grid.attach(Gtk.Label(name), 0, i, 1, 1) - label = Gtk.Label("-") - self.labels[name] = label - grid.attach(label, 1, i, 1, 1) - - def delete(self): - self.close() - - def set_data(self, data): - for name, label in self.labels.items(): - if name in data: - label.set_text(str(data[name])) - - -class PlotWindow(Window): - def __init__(self, group_name, set_names): - self.set_names = set_names - self.data = None - - Window.__init__(self, title=group_name + ": " + "/".join(set_names), - default_size=(700, 500)) - - self.darea = Gtk.DrawingArea() - self.darea.set_size_request(100, 100) - self.darea.connect("draw", self.on_draw) - self.add(self.darea) - - def delete(self): - self.close() - - -class XYWindow(PlotWindow): - def on_draw(self, widget, ctx): - if self.data is not None: - data = self.filter_data() - cairoplot.scatter_plot( - ctx, - data=data, - width=widget.get_allocated_width(), - height=widget.get_allocated_height(), - x_bounds=(min(data[0])*0.98, max(data[0])*1.02), - y_bounds=(min(data[1])*0.98, max(data[1])*1.02), - border=20, axis=True, grid=True, - dots=1, discrete=True, - series_colors=[(0.0, 0.0, 0.0)], - background="white" - ) - - def filter_data(self): - return [ - self.data[self.set_names[0]], - self.data[self.set_names[1]], - ] - - def set_data(self, data): - self.data = data - if not self.data: - return - # The two axes are not updated simultaneously. - # Redraw only after receiving a new point for each. - x, y = self.filter_data() - if len(x) == len(y): - self.darea.queue_draw() diff --git a/artiq/gui/rt_results.py b/artiq/gui/rt_results.py deleted file mode 100644 index e97c15409..000000000 --- a/artiq/gui/rt_results.py +++ /dev/null @@ -1,120 +0,0 @@ -import asyncio -from collections import defaultdict - -from artiq.protocols.sync_struct import Subscriber -from artiq.gui.rt_result_views import RawWindow, XYWindow - - -def _create_view(group_name, set_names, view_description): - if view_description == "raw": - r = RawWindow(group_name, set_names) - elif view_description == "xy": - r = XYWindow(group_name, set_names) - else: - raise ValueError("Unknown view description: " + view_description) - r.show_all() - return r - - -class _Group: - def __init__(self, name, init): - self.name = name - # data key -> list of views using it - self.views = defaultdict(list) - # original data - self.data = dict() - for k, v in init.items(): - self[k] = v - - def all_views(self): - r = set() - for view_list in self.views.values(): - for view in view_list: - r.add(view) - return r - - def delete(self): - for view in self.all_views(): - view.delete() - - def __getitem__(self, key): - if key == "data": - return self.data - else: - raise KeyError - - def __setitem__(self, key, value): - if key == "description": - self.delete() - self.views = defaultdict(list) - for set_names, view_description in value.items(): - if not isinstance(set_names, tuple): - set_names = (set_names, ) - view = _create_view(self.name, set_names, view_description) - view.set_data(self.data) - for set_name in set_names: - self.views[set_name].append(view) - elif key == "data": - self.data = value - for view in self.all_views(): - view.set_data(self.data) - else: - raise KeyError - - def on_data_modified(self, key): - for view in self.views[key]: - view.set_data(self.data) - - -class _Groups: - def __init__(self, init): - self.groups = dict() - for k, v in init.items(): - self[k] = v - - def delete(self): - for s in self.groups.values(): - s.delete() - - def __getitem__(self, key): - return self.groups[key] - - def __setitem__(self, key, value): - if key in self.groups: - self.groups[key].delete() - self.groups[key] = _Group(key, value) - - def __delitem__(self, key): - self.groups[key].delete() - del self.groups[key] - - -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"]) >= 2: - path = mod["path"] - group = self.current_groups[path[0]] - if path[1] == "data": - if len(mod["path"]) >= 3: - group.on_data_modified(path[2]) - else: - group.on_data_modified(mod["key"]) diff --git a/artiq/gui/schedule.py b/artiq/gui/schedule.py new file mode 100644 index 000000000..66d1cc19d --- /dev/null +++ b/artiq/gui/schedule.py @@ -0,0 +1,69 @@ +import asyncio +import time + +from quamash import QtGui +from pyqtgraph.dockarea import Dock + +from artiq.protocols.sync_struct import Subscriber +from artiq.gui.tools import DictSyncModel +from artiq.tools import format_arguments + + +class _ScheduleModel(DictSyncModel): + def __init__(self, parent, init): + DictSyncModel.__init__(self, + ["RID", "Pipeline", "Status", "Due date", + "File", "Experiment", "Arguments"], + parent, init) + + def sort_key(self, k, v): + # order by due date, and then by RID + return (v["due_date"] or 0, k) + + def convert(self, k, v, column): + if column == 0: + return k + elif column == 1: + return v["pipeline"] + elif column == 2: + return v["status"] + elif column == 3: + if v["due_date"] is None: + return "" + else: + return time.strftime("%m/%d %H:%M:%S", + time.localtime(v["due_date"])) + elif column == 4: + return v["expid"]["file"] + elif column == 5: + if v["expid"]["experiment"] is None: + return "" + else: + return v["expid"]["experiment"] + elif column == 6: + return format_arguments(v["expid"]["arguments"]) + else: + raise ValueError + + +class ScheduleDock(Dock): + def __init__(self, parent): + Dock.__init__(self, "Schedule", size=(1000, 300)) + + self.table = QtGui.QTableView() + self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.addWidget(self.table) + + @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): + table_model = _ScheduleModel(self.table, init) + self.table.setModel(table_model) + return table_model diff --git a/artiq/gui/scheduler.py b/artiq/gui/scheduler.py deleted file mode 100644 index 3bd8bbb11..000000000 --- a/artiq/gui/scheduler.py +++ /dev/null @@ -1,75 +0,0 @@ -import time -import asyncio - -from gi.repository import Gtk - -from artiq.gui.tools import Window, DictSyncer -from artiq.protocols.sync_struct import Subscriber -from artiq.tools import format_arguments - - -class _ScheduleStoreSyncer(DictSyncer): - def order_key(self, kv_pair): - # order by due date, and then by RID - return (kv_pair[1]["due_date"] or 0, kv_pair[0]) - - def convert(self, rid, v): - row = [rid, v["pipeline"], v["status"]] - if v["due_date"] is None: - row.append("") - else: - row.append(time.strftime("%m/%d %H:%M:%S", - time.localtime(v["due_date"]))) - row.append(v["expid"]["file"]) - if v["expid"]["experiment"] is None: - row.append("") - else: - row.append(v["expid"]["experiment"]) - row.append(format_arguments(v["expid"]["arguments"])) - return row - - -class SchedulerWindow(Window): - def __init__(self, schedule_ctl, **kwargs): - self.schedule_ctl = schedule_ctl - - Window.__init__(self, - title="Scheduler", - default_size=(950, 570), - **kwargs) - - topvbox = Gtk.VBox(spacing=6) - self.add(topvbox) - - self.schedule_store = Gtk.ListStore(int, str, str, str, str, str, str) - self.schedule_tree = Gtk.TreeView(self.schedule_store) - for i, title in enumerate(["RID", "Pipeline", "Status", "Due date", - "File", "Experiment", "Arguments"]): - renderer = Gtk.CellRendererText() - column = Gtk.TreeViewColumn(title, renderer, text=i) - self.schedule_tree.append_column(column) - scroll = Gtk.ScrolledWindow() - scroll.add(self.schedule_tree) - topvbox.pack_start(scroll, True, True, 0) - button = Gtk.Button("Delete") - button.connect("clicked", self.delete) - topvbox.pack_start(button, False, False, 0) - topvbox.set_border_width(6) - - def delete(self, widget): - store, selected = self.schedule_tree.get_selection().get_selected() - if selected is not None: - rid = store[selected][0] - asyncio.async(self.schedule_ctl.delete(rid)) - - @asyncio.coroutine - def sub_connect(self, host, port): - self.schedule_subscriber = Subscriber("schedule", self.init_schedule_store) - yield from self.schedule_subscriber.connect(host, port) - - @asyncio.coroutine - def sub_close(self): - yield from self.schedule_subscriber.close() - - def init_schedule_store(self, init): - return _ScheduleStoreSyncer(self.schedule_store, init) diff --git a/artiq/gui/tools.py b/artiq/gui/tools.py index aa329d3db..99af51dca 100644 --- a/artiq/gui/tools.py +++ b/artiq/gui/tools.py @@ -1,143 +1,96 @@ -import os - -from gi.repository import Gtk +from quamash import QtCore -data_dir = os.path.abspath(os.path.dirname(__file__)) - - -def getitem(d, item, default): - try: - return d[item] - except KeyError: - return default - - -class Window(Gtk.Window): - def __init__(self, title, default_size, layout_dict=dict()): - Gtk.Window.__init__(self, title=title + " - ARTIQ") - - self.set_wmclass("ARTIQ", "ARTIQ") - self.set_icon_from_file(os.path.join(data_dir, "icon.png")) - self.set_border_width(6) - - size = getitem(layout_dict, "size", default_size) - self.set_default_size(size[0], size[1]) - try: - position = layout_dict["position"] - except KeyError: - pass - else: - self.move(position[0], position[1]) - - def get_layout_dict(self): - return { - "size": self.get_size(), - "position": self.get_position() - } - - -class LayoutManager: - def __init__(self, db): - self.db = db - self.windows = dict() - - def create_window(self, cls, name, *args, **kwargs): - try: - win_layouts = self.db.request("win_layouts") - layout_dict = win_layouts[name] - except KeyError: - layout_dict = dict() - win = cls(*args, layout_dict=layout_dict, **kwargs) - self.windows[name] = win - return win - - def save(self): - win_layouts = {name: window.get_layout_dict() - for name, window in self.windows.items()} - self.db.set("win_layouts", win_layouts) - - -class ListSyncer: - def __init__(self, store, init): - self.store = store - self.store.clear() - for x in init: - self.append(x) - - def append(self, x): - self.store.append(self.convert(x)) - - def insert(self, i, x): - self.store.insert(i, self.convert(x)) - - def __delitem__(self, key): - del self.store[key] - - def convert(self, x): - raise NotImplementedError - - -class _DictSyncerSubstruct: +class _DictSyncSubstruct: def __init__(self, update_cb, ref): self.update_cb = update_cb self.ref = ref def __getitem__(self, key): - return _DictSyncerSubstruct(self.update_cb, self.ref[key]) + return _DictSyncSubstruct(self.update_cb, self.ref[key]) def __setitem__(self, key, value): self.ref[key] = value self.update_cb() -class DictSyncer: - def __init__(self, store, init): - self.store = store - self.store.clear() - self.order = [] - for k, v in sorted(init.items(), key=self.order_key): - self.store.append(self.convert(k, v)) - self.order.append((k, self.order_key((k, v)))) - self.local_copy = init +class DictSyncModel(QtCore.QAbstractTableModel): + def __init__(self, headers, parent, init): + self.headers = headers + self.data = init + self.row_to_key = sorted(self.data.keys(), + key=lambda k: self.sort_key(k, self.data[k])) + QtCore.QAbstractTableModel.__init__(self, parent) - def _find_index(self, key): - for i, e in enumerate(self.order): - if e[0] == key: - return i - raise KeyError + def rowCount(self, parent): + return len(self.data) - def __setitem__(self, key, value): - try: - i = self._find_index(key) - except KeyError: - pass + def columnCount(self, parent): + return len(self.headers) + + def data(self, index, role): + if not index.isValid(): + return None + elif role != QtCore.Qt.DisplayRole: + return None + k = self.row_to_key[index.row()] + return self.convert(k, self.data[k], index.column()) + + def headerData(self, col, orientation, role): + if (orientation == QtCore.Qt.Horizontal + and role == QtCore.Qt.DisplayRole): + return self.headers[col] + return None + + def _find_row(self, k, v): + lo = 0 + hi = len(self.row_to_key) + while lo < hi: + mid = (lo + hi)//2 + if (self.sort_key(self.row_to_key[mid], + self.data[self.row_to_key[mid]]) + < self.sort_key(k, v)): + lo = mid + 1 + else: + hi = mid + return lo + + def __setitem__(self, k, v): + if k in self.data: + old_row = self.row_to_key.index(k) + new_row = self._find_row(k, v) + if old_row == new_row: + self.dataChanged.emit(self.index(old_row, 0), + self.index(old_row, len(self.headers))) + else: + self.beginMoveRows(QtCore.QModelIndex(), old_row, old_row, + QtCore.QModelIndex(), new_row) + self.data[k] = v + self.row_to_key[old_row], self.row_to_key[new_row] = \ + self.row_to_key[new_row], self.row_to_key[old_row] + if old_row != new_row: + self.endMoveRows() else: - del self.store[i] - del self.order[i] - ord_el = self.order_key((key, value)) - j = len(self.order) - for i, (k, o) in enumerate(self.order): - if o > ord_el: - j = i - break - self.store.insert(j, self.convert(key, value)) - self.order.insert(j, (key, ord_el)) - self.local_copy[key] = value + row = self._find_row(k, v) + self.beginInsertRows(QtCore.QModelIndex(), row, row) + self.data[k] = v + self.row_to_key.insert(row, k) + self.endInsertRows() - def __delitem__(self, key): - i = self._find_index(key) - del self.store[i] - del self.order[i] - del self.local_copy[key] + def __delitem__(self, k): + row = self.row_to_key.index(k) + self.beginRemoveRows(QtCore.QModelIndex(), row, row) + del self.row_to_key[row] + del self.data[k] + self.endRemoveRows() def __getitem__(self, key): def update(): - self[key] = self.local_copy[key] - return _DictSyncerSubstruct(update, self.local_copy[key]) + self[key] = self.data[key] + return _DictSyncSubstruct(update, self.data[key]) - def order_key(self, kv_pair): + def sort_key(self, k, v): raise NotImplementedError - def convert(self, key, value): + def convert(self, k, v, column): raise NotImplementedError diff --git a/examples/master/repository/flopping_f_simulation.py b/examples/master/repository/flopping_f_simulation.py index 299e4f45c..9999165e6 100644 --- a/examples/master/repository/flopping_f_simulation.py +++ b/examples/master/repository/flopping_f_simulation.py @@ -25,7 +25,6 @@ def model_numpy(xdata, F0): class FloppingF(Experiment, AutoDB): """Flopping F simulation""" - __artiq_gui_file__ = "flopping_f_simulation_gui.py" class DBKeys: npoints = Argument(100) diff --git a/examples/master/repository/flopping_f_simulation_gui.glade b/examples/master/repository/flopping_f_simulation_gui.glade deleted file mode 100644 index 8b763ff94..000000000 --- a/examples/master/repository/flopping_f_simulation_gui.glade +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - 1000 - 2000 - 1500 - 1 - 10 - - - True - False - center - center - vertical - 6 - 6 - - - True - True - number - F0 - 1500 - - - 1 - 0 - - - - - Get from parameter DB - True - True - True - - - 0 - 1 - 2 - - - - - True - False - Simulated flopping frequency: - - - 0 - 0 - - - - diff --git a/examples/master/repository/flopping_f_simulation_gui.py b/examples/master/repository/flopping_f_simulation_gui.py deleted file mode 100644 index e948061a4..000000000 --- a/examples/master/repository/flopping_f_simulation_gui.py +++ /dev/null @@ -1,20 +0,0 @@ -from artiq import GladeControls - - -class Controls(GladeControls): - def __init__(self, facilities): - GladeControls.__init__(self, facilities, - "flopping_f_simulation_gui.glade") - - def finalize(self): - getparam = self.builder.get_object("getparam") - getparam.connect("clicked", self.getparam) - - def getparam(self, widget): - F0 = self.facilities.get_parameter("flopping_freq") - self.builder.get_object("F0").set_value(F0) - - def get_arguments(self): - return { - "F0": self.builder.get_object("F0").get_value() - } diff --git a/setup.py b/setup.py index e356e6146..13f35efd8 100755 --- a/setup.py +++ b/setup.py @@ -1,18 +1,19 @@ #!/usr/bin/env python3 from setuptools import setup, find_packages -import os requirements = [ "sphinx", "sphinx-argparse", "pyserial", "numpy", "scipy", - "python-dateutil", "prettytable", "h5py", "pydaqmx", "pyelftools" + "python-dateutil", "prettytable", "h5py", "pydaqmx", "pyelftools", + "quamash", "pyqtgraph" ] scripts = [ "artiq_client=artiq.frontend.artiq_client:main", "artiq_compile=artiq.frontend.artiq_compile:main", "artiq_ctlmgr=artiq.frontend.artiq_ctlmgr:main", + "artiq_gui=artiq.frontend.artiq_gui:main", "artiq_master=artiq.frontend.artiq_master:main", "artiq_mkfs=artiq.frontend.artiq_mkfs:main", "artiq_rpctool=artiq.frontend.artiq_rpctool:main", @@ -25,13 +26,6 @@ scripts = [ "thorlabs_tcube_controller=artiq.frontend.thorlabs_tcube_controller:main", ] -if os.getenv("ARTIQ_GUI") == "1": - requirements += ["pygobject", "gbulb", "cairoplot"] - scripts += [ - "artiq_gui=artiq.frontend.artiq_gui:main" - ] - - setup( name="artiq", version="0.0+dev", @@ -43,14 +37,9 @@ setup( license="BSD", install_requires=requirements, extras_require={}, - dependency_links=[ - "git+https://github.com/m-labs/gbulb.git#egg=gbulb", - "git+https://github.com/m-labs/cairoplot3.git#egg=cairoplot" - ], packages=find_packages(), namespace_packages=[], test_suite="artiq.test", - package_data={"artiq": [os.path.join("gui", "icon.png")]}, ext_modules=[], entry_points={ "console_scripts": scripts,