Skip to content

Commit

Permalink
gui: switch to Qt
Browse files Browse the repository at this point in the history
sbourdeauducq committed May 22, 2015
1 parent c91cd0a commit ea53ed1
Showing 15 changed files with 198 additions and 794 deletions.
1 change: 0 additions & 1 deletion artiq/__init__.py
Original file line number Diff line number Diff line change
@@ -4,4 +4,3 @@
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 *
85 changes: 23 additions & 62 deletions artiq/frontend/artiq_gui.py
Original file line number Diff line number Diff line change
@@ -4,17 +4,14 @@
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__":
35 changes: 0 additions & 35 deletions artiq/gui/explib.py

This file was deleted.

152 changes: 0 additions & 152 deletions artiq/gui/explorer.py

This file was deleted.

Binary file removed artiq/gui/icon.png
Binary file not shown.
83 changes: 28 additions & 55 deletions artiq/gui/parameters.py
Original file line number Diff line number Diff line change
@@ -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)

notebook = Gtk.Notebook()
self.add(notebook)
class ParametersDock(Dock):
def __init__(self, parent):
Dock.__init__(self, "Parameters", size=(500, 300))

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()

def init_parameters_store(self, init):
return _ParameterStoreSyncer(self.parameters_store, init)
yield from self.subscriber.close()

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
79 changes: 0 additions & 79 deletions artiq/gui/rt_result_views.py

This file was deleted.

120 changes: 0 additions & 120 deletions artiq/gui/rt_results.py

This file was deleted.

69 changes: 69 additions & 0 deletions artiq/gui/schedule.py
Original file line number Diff line number Diff line change
@@ -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
75 changes: 0 additions & 75 deletions artiq/gui/scheduler.py

This file was deleted.

197 changes: 75 additions & 122 deletions artiq/gui/tools.py
Original file line number Diff line number Diff line change
@@ -1,143 +1,96 @@
import os
from quamash import QtCore

from gi.repository import Gtk


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

def _find_index(self, key):
for i, e in enumerate(self.order):
if e[0] == key:
return i
raise KeyError

def __setitem__(self, key, value):
try:
i = self._find_index(key)
except KeyError:
pass
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 rowCount(self, parent):
return len(self.data)

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

def __delitem__(self, key):
i = self._find_index(key)
del self.store[i]
del self.order[i]
del self.local_copy[key]
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, 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
1 change: 0 additions & 1 deletion examples/master/repository/flopping_f_simulation.py
Original file line number Diff line number Diff line change
@@ -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)
58 changes: 0 additions & 58 deletions examples/master/repository/flopping_f_simulation_gui.glade

This file was deleted.

20 changes: 0 additions & 20 deletions examples/master/repository/flopping_f_simulation_gui.py

This file was deleted.

17 changes: 3 additions & 14 deletions setup.py
Original file line number Diff line number Diff line change
@@ -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 @@
"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 @@
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,

0 comments on commit ea53ed1

Please sign in to comment.