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 33c071886..000000000 Binary files a/artiq/gui/icon.png and /dev/null differ 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,