Skip to content

Commit ea53ed1

Browse files
committedMay 22, 2015
gui: switch to Qt
1 parent c91cd0a commit ea53ed1

15 files changed

+198
-794
lines changed
 

‎artiq/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@
44
from artiq.language.units import check_unit
55
from artiq.language.units import ps, ns, us, ms, s
66
from artiq.language.units import Hz, kHz, MHz, GHz
7-
from artiq.gui.explib import *

‎artiq/frontend/artiq_gui.py

+23-62
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,14 @@
44
import asyncio
55
import atexit
66

7-
import gbulb
8-
from gi.repository import Gtk
7+
# Quamash must be imported first so that pyqtgraph picks up the Qt binding
8+
# it has chosen.
9+
from quamash import QEventLoop, QtGui
10+
from pyqtgraph.dockarea import DockArea
911

1012
from artiq.protocols.file_db import FlatFileDB
11-
from artiq.protocols.pc_rpc import AsyncioClient
12-
from artiq.protocols.sync_struct import Subscriber
13-
from artiq.gui.tools import LayoutManager
14-
from artiq.gui.scheduler import SchedulerWindow
15-
from artiq.gui.parameters import ParametersWindow
16-
from artiq.gui.rt_results import RTResults
17-
from artiq.gui.explorer import ExplorerWindow
13+
from artiq.gui.schedule import ScheduleDock
14+
from artiq.gui.parameters import ParametersDock
1815

1916

2017
def get_argparser():
@@ -38,67 +35,31 @@ def main():
3835
args = get_argparser().parse_args()
3936

4037
db = FlatFileDB(args.db_file, default_data=dict())
41-
lmgr = LayoutManager(db)
4238

43-
asyncio.set_event_loop_policy(gbulb.GtkEventLoopPolicy())
44-
loop = asyncio.get_event_loop()
39+
app = QtGui.QApplication([])
40+
loop = QEventLoop(app)
41+
asyncio.set_event_loop(loop)
4542
atexit.register(lambda: loop.close())
4643

47-
# share the schedule control and repository connections
48-
schedule_ctl = AsyncioClient()
49-
loop.run_until_complete(schedule_ctl.connect_rpc(
50-
args.server, args.port_control, "master_schedule"))
51-
atexit.register(lambda: schedule_ctl.close_rpc())
52-
repository = AsyncioClient()
53-
loop.run_until_complete(repository.connect_rpc(
54-
args.server, args.port_control, "master_repository"))
55-
atexit.register(lambda: repository.close_rpc())
44+
win = QtGui.QMainWindow()
45+
area = DockArea()
46+
win.setCentralWidget(area)
47+
win.resize(1000, 500)
48+
win.setWindowTitle("ARTIQ")
5649

57-
scheduler_win = lmgr.create_window(SchedulerWindow,
58-
"scheduler",
59-
schedule_ctl)
60-
loop.run_until_complete(scheduler_win.sub_connect(
50+
d_params = ParametersDock(area)
51+
area.addDock(d_params, "left")
52+
loop.run_until_complete(d_params.sub_connect(
6153
args.server, args.port_notify))
62-
atexit.register(
63-
lambda: loop.run_until_complete(scheduler_win.sub_close()))
54+
atexit.register(lambda: loop.run_until_complete(d_params.sub_close()))
6455

65-
parameters_win = lmgr.create_window(ParametersWindow, "parameters")
66-
loop.run_until_complete(parameters_win.sub_connect(
56+
d_schedule = ScheduleDock(area)
57+
area.addDock(d_schedule, "top", d_params)
58+
loop.run_until_complete(d_schedule.sub_connect(
6759
args.server, args.port_notify))
68-
atexit.register(
69-
lambda: loop.run_until_complete(parameters_win.sub_close()))
70-
71-
def exit(*args):
72-
lmgr.save()
73-
Gtk.main_quit(*args)
74-
explorer_win = lmgr.create_window(ExplorerWindow,
75-
"explorer",
76-
exit,
77-
schedule_ctl,
78-
repository)
79-
loop.run_until_complete(explorer_win.sub_connect(
80-
args.server, args.port_notify))
81-
atexit.register(
82-
lambda: loop.run_until_complete(explorer_win.sub_close()))
83-
84-
parameters_sub = Subscriber("parameters",
85-
[parameters_win.init_parameters_store,
86-
explorer_win.init_parameters_dict])
87-
loop.run_until_complete(
88-
parameters_sub.connect(args.server, args.port_notify))
89-
atexit.register(
90-
lambda: loop.run_until_complete(parameters_sub.close()))
91-
92-
scheduler_win.show_all()
93-
parameters_win.show_all()
94-
explorer_win.show_all()
95-
96-
rtr = RTResults()
97-
loop.run_until_complete(rtr.sub_connect(
98-
args.server, args.port_notify))
99-
atexit.register(
100-
lambda: loop.run_until_complete(rtr.sub_close()))
60+
atexit.register(lambda: loop.run_until_complete(d_schedule.sub_close()))
10161

62+
win.show()
10263
loop.run_forever()
10364

10465
if __name__ == "__main__":

‎artiq/gui/explib.py

-35
This file was deleted.

‎artiq/gui/explorer.py

-152
This file was deleted.

‎artiq/gui/icon.png

-13.4 KB
Binary file not shown.

‎artiq/gui/parameters.py

+28-55
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,47 @@
11
import asyncio
2-
from operator import itemgetter
3-
import time
42

5-
from gi.repository import Gtk
3+
from quamash import QtGui
4+
from pyqtgraph.dockarea import Dock
65

7-
from artiq.gui.tools import Window, ListSyncer, DictSyncer
86
from artiq.protocols.sync_struct import Subscriber
7+
from artiq.gui.tools import DictSyncModel
98

109

11-
class _ParameterStoreSyncer(DictSyncer):
12-
def order_key(self, kv_pair):
13-
return kv_pair[0]
10+
class ParametersModel(DictSyncModel):
11+
def __init__(self, parent, init):
12+
DictSyncModel.__init__(self, ["Parameter", "Value"],
13+
parent, init)
1414

15-
def convert(self, name, value):
16-
return [name, str(value)]
15+
def sort_key(self, k, v):
16+
return k
1717

18-
19-
class _LastChangesStoreSyncer(ListSyncer):
20-
def convert(self, x):
21-
if len(x) == 3:
22-
timestamp, name, value = x
18+
def convert(self, k, v, column):
19+
if column == 0:
20+
return k
21+
elif column == 1:
22+
return str(v)
2323
else:
24-
timestamp, name = x
25-
value = "<deleted>"
26-
return [time.strftime("%m/%d %H:%M:%S", time.localtime(timestamp)),
27-
name, str(value)]
28-
24+
raise ValueError
2925

30-
class ParametersWindow(Window):
31-
def __init__(self, **kwargs):
32-
Window.__init__(self,
33-
title="Parameters",
34-
default_size=(500, 500),
35-
**kwargs)
3626

37-
notebook = Gtk.Notebook()
38-
self.add(notebook)
27+
class ParametersDock(Dock):
28+
def __init__(self, parent):
29+
Dock.__init__(self, "Parameters", size=(500, 300))
3930

40-
self.parameters_store = Gtk.ListStore(str, str)
41-
tree = Gtk.TreeView(self.parameters_store)
42-
for i, title in enumerate(["Parameter", "Value"]):
43-
renderer = Gtk.CellRendererText()
44-
column = Gtk.TreeViewColumn(title, renderer, text=i)
45-
tree.append_column(column)
46-
scroll = Gtk.ScrolledWindow()
47-
scroll.add(tree)
48-
notebook.insert_page(scroll, Gtk.Label("Current values"), -1)
49-
50-
self.lastchanges_store = Gtk.ListStore(str, str, str)
51-
tree = Gtk.TreeView(self.lastchanges_store)
52-
for i, title in enumerate(["Time", "Parameter", "Value"]):
53-
renderer = Gtk.CellRendererText()
54-
column = Gtk.TreeViewColumn(title, renderer, text=i)
55-
tree.append_column(column)
56-
scroll = Gtk.ScrolledWindow()
57-
scroll.add(tree)
58-
notebook.insert_page(scroll, Gtk.Label("Last changes"), -1)
31+
self.table = QtGui.QTableView()
32+
self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
33+
self.addWidget(self.table)
5934

6035
@asyncio.coroutine
6136
def sub_connect(self, host, port):
62-
self.lastchanges_subscriber = Subscriber(
63-
"parameters_simplehist", self.init_lastchanges_store)
64-
yield from self.lastchanges_subscriber.connect(host, port)
37+
self.subscriber = Subscriber("parameters", self.init_parameters_model)
38+
yield from self.subscriber.connect(host, port)
6539

6640
@asyncio.coroutine
6741
def sub_close(self):
68-
yield from self.lastchanges_subscriber.close()
69-
70-
def init_parameters_store(self, init):
71-
return _ParameterStoreSyncer(self.parameters_store, init)
42+
yield from self.subscriber.close()
7243

73-
def init_lastchanges_store(self, init):
74-
return _LastChangesStoreSyncer(self.lastchanges_store, init)
44+
def init_parameters_model(self, init):
45+
table_model = ParametersModel(self.table, init)
46+
self.table.setModel(table_model)
47+
return table_model

‎artiq/gui/rt_result_views.py

-79
This file was deleted.

‎artiq/gui/rt_results.py

-120
This file was deleted.

‎artiq/gui/schedule.py

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import asyncio
2+
import time
3+
4+
from quamash import QtGui
5+
from pyqtgraph.dockarea import Dock
6+
7+
from artiq.protocols.sync_struct import Subscriber
8+
from artiq.gui.tools import DictSyncModel
9+
from artiq.tools import format_arguments
10+
11+
12+
class _ScheduleModel(DictSyncModel):
13+
def __init__(self, parent, init):
14+
DictSyncModel.__init__(self,
15+
["RID", "Pipeline", "Status", "Due date",
16+
"File", "Experiment", "Arguments"],
17+
parent, init)
18+
19+
def sort_key(self, k, v):
20+
# order by due date, and then by RID
21+
return (v["due_date"] or 0, k)
22+
23+
def convert(self, k, v, column):
24+
if column == 0:
25+
return k
26+
elif column == 1:
27+
return v["pipeline"]
28+
elif column == 2:
29+
return v["status"]
30+
elif column == 3:
31+
if v["due_date"] is None:
32+
return ""
33+
else:
34+
return time.strftime("%m/%d %H:%M:%S",
35+
time.localtime(v["due_date"]))
36+
elif column == 4:
37+
return v["expid"]["file"]
38+
elif column == 5:
39+
if v["expid"]["experiment"] is None:
40+
return ""
41+
else:
42+
return v["expid"]["experiment"]
43+
elif column == 6:
44+
return format_arguments(v["expid"]["arguments"])
45+
else:
46+
raise ValueError
47+
48+
49+
class ScheduleDock(Dock):
50+
def __init__(self, parent):
51+
Dock.__init__(self, "Schedule", size=(1000, 300))
52+
53+
self.table = QtGui.QTableView()
54+
self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
55+
self.addWidget(self.table)
56+
57+
@asyncio.coroutine
58+
def sub_connect(self, host, port):
59+
self.subscriber = Subscriber("schedule", self.init_schedule_model)
60+
yield from self.subscriber.connect(host, port)
61+
62+
@asyncio.coroutine
63+
def sub_close(self):
64+
yield from self.subscriber.close()
65+
66+
def init_schedule_model(self, init):
67+
table_model = _ScheduleModel(self.table, init)
68+
self.table.setModel(table_model)
69+
return table_model

‎artiq/gui/scheduler.py

-75
This file was deleted.

‎artiq/gui/tools.py

+75-122
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,96 @@
1-
import os
1+
from quamash import QtCore
22

3-
from gi.repository import Gtk
43

5-
6-
data_dir = os.path.abspath(os.path.dirname(__file__))
7-
8-
9-
def getitem(d, item, default):
10-
try:
11-
return d[item]
12-
except KeyError:
13-
return default
14-
15-
16-
class Window(Gtk.Window):
17-
def __init__(self, title, default_size, layout_dict=dict()):
18-
Gtk.Window.__init__(self, title=title + " - ARTIQ")
19-
20-
self.set_wmclass("ARTIQ", "ARTIQ")
21-
self.set_icon_from_file(os.path.join(data_dir, "icon.png"))
22-
self.set_border_width(6)
23-
24-
size = getitem(layout_dict, "size", default_size)
25-
self.set_default_size(size[0], size[1])
26-
try:
27-
position = layout_dict["position"]
28-
except KeyError:
29-
pass
30-
else:
31-
self.move(position[0], position[1])
32-
33-
def get_layout_dict(self):
34-
return {
35-
"size": self.get_size(),
36-
"position": self.get_position()
37-
}
38-
39-
40-
class LayoutManager:
41-
def __init__(self, db):
42-
self.db = db
43-
self.windows = dict()
44-
45-
def create_window(self, cls, name, *args, **kwargs):
46-
try:
47-
win_layouts = self.db.request("win_layouts")
48-
layout_dict = win_layouts[name]
49-
except KeyError:
50-
layout_dict = dict()
51-
win = cls(*args, layout_dict=layout_dict, **kwargs)
52-
self.windows[name] = win
53-
return win
54-
55-
def save(self):
56-
win_layouts = {name: window.get_layout_dict()
57-
for name, window in self.windows.items()}
58-
self.db.set("win_layouts", win_layouts)
59-
60-
61-
class ListSyncer:
62-
def __init__(self, store, init):
63-
self.store = store
64-
self.store.clear()
65-
for x in init:
66-
self.append(x)
67-
68-
def append(self, x):
69-
self.store.append(self.convert(x))
70-
71-
def insert(self, i, x):
72-
self.store.insert(i, self.convert(x))
73-
74-
def __delitem__(self, key):
75-
del self.store[key]
76-
77-
def convert(self, x):
78-
raise NotImplementedError
79-
80-
81-
class _DictSyncerSubstruct:
4+
class _DictSyncSubstruct:
825
def __init__(self, update_cb, ref):
836
self.update_cb = update_cb
847
self.ref = ref
858

869
def __getitem__(self, key):
87-
return _DictSyncerSubstruct(self.update_cb, self.ref[key])
10+
return _DictSyncSubstruct(self.update_cb, self.ref[key])
8811

8912
def __setitem__(self, key, value):
9013
self.ref[key] = value
9114
self.update_cb()
9215

9316

94-
class DictSyncer:
95-
def __init__(self, store, init):
96-
self.store = store
97-
self.store.clear()
98-
self.order = []
99-
for k, v in sorted(init.items(), key=self.order_key):
100-
self.store.append(self.convert(k, v))
101-
self.order.append((k, self.order_key((k, v))))
102-
self.local_copy = init
103-
104-
def _find_index(self, key):
105-
for i, e in enumerate(self.order):
106-
if e[0] == key:
107-
return i
108-
raise KeyError
109-
110-
def __setitem__(self, key, value):
111-
try:
112-
i = self._find_index(key)
113-
except KeyError:
114-
pass
17+
class DictSyncModel(QtCore.QAbstractTableModel):
18+
def __init__(self, headers, parent, init):
19+
self.headers = headers
20+
self.data = init
21+
self.row_to_key = sorted(self.data.keys(),
22+
key=lambda k: self.sort_key(k, self.data[k]))
23+
QtCore.QAbstractTableModel.__init__(self, parent)
24+
25+
def rowCount(self, parent):
26+
return len(self.data)
27+
28+
def columnCount(self, parent):
29+
return len(self.headers)
30+
31+
def data(self, index, role):
32+
if not index.isValid():
33+
return None
34+
elif role != QtCore.Qt.DisplayRole:
35+
return None
36+
k = self.row_to_key[index.row()]
37+
return self.convert(k, self.data[k], index.column())
38+
39+
def headerData(self, col, orientation, role):
40+
if (orientation == QtCore.Qt.Horizontal
41+
and role == QtCore.Qt.DisplayRole):
42+
return self.headers[col]
43+
return None
44+
45+
def _find_row(self, k, v):
46+
lo = 0
47+
hi = len(self.row_to_key)
48+
while lo < hi:
49+
mid = (lo + hi)//2
50+
if (self.sort_key(self.row_to_key[mid],
51+
self.data[self.row_to_key[mid]])
52+
< self.sort_key(k, v)):
53+
lo = mid + 1
54+
else:
55+
hi = mid
56+
return lo
57+
58+
def __setitem__(self, k, v):
59+
if k in self.data:
60+
old_row = self.row_to_key.index(k)
61+
new_row = self._find_row(k, v)
62+
if old_row == new_row:
63+
self.dataChanged.emit(self.index(old_row, 0),
64+
self.index(old_row, len(self.headers)))
65+
else:
66+
self.beginMoveRows(QtCore.QModelIndex(), old_row, old_row,
67+
QtCore.QModelIndex(), new_row)
68+
self.data[k] = v
69+
self.row_to_key[old_row], self.row_to_key[new_row] = \
70+
self.row_to_key[new_row], self.row_to_key[old_row]
71+
if old_row != new_row:
72+
self.endMoveRows()
11573
else:
116-
del self.store[i]
117-
del self.order[i]
118-
ord_el = self.order_key((key, value))
119-
j = len(self.order)
120-
for i, (k, o) in enumerate(self.order):
121-
if o > ord_el:
122-
j = i
123-
break
124-
self.store.insert(j, self.convert(key, value))
125-
self.order.insert(j, (key, ord_el))
126-
self.local_copy[key] = value
127-
128-
def __delitem__(self, key):
129-
i = self._find_index(key)
130-
del self.store[i]
131-
del self.order[i]
132-
del self.local_copy[key]
74+
row = self._find_row(k, v)
75+
self.beginInsertRows(QtCore.QModelIndex(), row, row)
76+
self.data[k] = v
77+
self.row_to_key.insert(row, k)
78+
self.endInsertRows()
79+
80+
def __delitem__(self, k):
81+
row = self.row_to_key.index(k)
82+
self.beginRemoveRows(QtCore.QModelIndex(), row, row)
83+
del self.row_to_key[row]
84+
del self.data[k]
85+
self.endRemoveRows()
13386

13487
def __getitem__(self, key):
13588
def update():
136-
self[key] = self.local_copy[key]
137-
return _DictSyncerSubstruct(update, self.local_copy[key])
89+
self[key] = self.data[key]
90+
return _DictSyncSubstruct(update, self.data[key])
13891

139-
def order_key(self, kv_pair):
92+
def sort_key(self, k, v):
14093
raise NotImplementedError
14194

142-
def convert(self, key, value):
95+
def convert(self, k, v, column):
14396
raise NotImplementedError

‎examples/master/repository/flopping_f_simulation.py

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ def model_numpy(xdata, F0):
2525

2626
class FloppingF(Experiment, AutoDB):
2727
"""Flopping F simulation"""
28-
__artiq_gui_file__ = "flopping_f_simulation_gui.py"
2928

3029
class DBKeys:
3130
npoints = Argument(100)

‎examples/master/repository/flopping_f_simulation_gui.glade

-58
This file was deleted.

‎examples/master/repository/flopping_f_simulation_gui.py

-20
This file was deleted.

‎setup.py

+3-14
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
#!/usr/bin/env python3
22

33
from setuptools import setup, find_packages
4-
import os
54

65

76
requirements = [
87
"sphinx", "sphinx-argparse", "pyserial", "numpy", "scipy",
9-
"python-dateutil", "prettytable", "h5py", "pydaqmx", "pyelftools"
8+
"python-dateutil", "prettytable", "h5py", "pydaqmx", "pyelftools",
9+
"quamash", "pyqtgraph"
1010
]
1111

1212
scripts = [
1313
"artiq_client=artiq.frontend.artiq_client:main",
1414
"artiq_compile=artiq.frontend.artiq_compile:main",
1515
"artiq_ctlmgr=artiq.frontend.artiq_ctlmgr:main",
16+
"artiq_gui=artiq.frontend.artiq_gui:main",
1617
"artiq_master=artiq.frontend.artiq_master:main",
1718
"artiq_mkfs=artiq.frontend.artiq_mkfs:main",
1819
"artiq_rpctool=artiq.frontend.artiq_rpctool:main",
@@ -25,13 +26,6 @@
2526
"thorlabs_tcube_controller=artiq.frontend.thorlabs_tcube_controller:main",
2627
]
2728

28-
if os.getenv("ARTIQ_GUI") == "1":
29-
requirements += ["pygobject", "gbulb", "cairoplot"]
30-
scripts += [
31-
"artiq_gui=artiq.frontend.artiq_gui:main"
32-
]
33-
34-
3529
setup(
3630
name="artiq",
3731
version="0.0+dev",
@@ -43,14 +37,9 @@
4337
license="BSD",
4438
install_requires=requirements,
4539
extras_require={},
46-
dependency_links=[
47-
"git+https://github.com/m-labs/gbulb.git#egg=gbulb",
48-
"git+https://github.com/m-labs/cairoplot3.git#egg=cairoplot"
49-
],
5040
packages=find_packages(),
5141
namespace_packages=[],
5242
test_suite="artiq.test",
53-
package_data={"artiq": [os.path.join("gui", "icon.png")]},
5443
ext_modules=[],
5544
entry_points={
5645
"console_scripts": scripts,

0 commit comments

Comments
 (0)
Please sign in to comment.