forked from M-Labs/artiq
gui: switch to Qt
This commit is contained in:
parent
c91cd0ab38
commit
ea53ed1af7
@ -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 *
|
||||
|
@ -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__":
|
||||
|
@ -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)
|
@ -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))
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
@ -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 = "<deleted>"
|
||||
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
|
||||
|
@ -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()
|
@ -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"])
|
69
artiq/gui/schedule.py
Normal file
69
artiq/gui/schedule.py
Normal file
@ -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
|
@ -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)
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.18.3 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.10"/>
|
||||
<object class="GtkAdjustment" id="F0">
|
||||
<property name="lower">1000</property>
|
||||
<property name="upper">2000</property>
|
||||
<property name="value">1500</property>
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkGrid" id="top">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="row_spacing">6</property>
|
||||
<property name="column_spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="spinbutton1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="input_purpose">number</property>
|
||||
<property name="adjustment">F0</property>
|
||||
<property name="value">1500</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="getparam">
|
||||
<property name="label" translatable="yes">Get from parameter DB</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Simulated flopping frequency:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
@ -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()
|
||||
}
|
17
setup.py
17
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,
|
||||
|
Loading…
Reference in New Issue
Block a user