Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/artiq
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: adbb217d556b^
Choose a base ref
...
head repository: m-labs/artiq
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: e106ee3f9003
Choose a head ref
  • 2 commits
  • 3 files changed
  • 1 contributor

Commits on Jan 7, 2016

  1. Copy the full SHA
    adbb217 View commit details
  2. Copy the full SHA
    e106ee3 View commit details
Showing with 181 additions and 10 deletions.
  1. +32 −6 artiq/applets/simple.py
  2. +14 −4 artiq/frontend/artiq_gui.py
  3. +135 −0 artiq/gui/applets.py
38 changes: 32 additions & 6 deletions artiq/applets/simple.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import argparse
import asyncio

from quamash import QEventLoop, QtWidgets, QtCore
from quamash import QEventLoop, QtWidgets, QtGui, QtCore

from artiq.protocols.sync_struct import Subscriber
from artiq.protocols.pc_rpc import Client


class SimpleApplet:
@@ -13,11 +14,20 @@ def __init__(self, main_widget_class, cmd_description=None):
self.argparser = argparse.ArgumentParser(description=cmd_description)
group = self.argparser.add_argument_group("data server")
group.add_argument(
"--server", default="::1",
help="hostname or IP to connect to")
"--server-notify", default="::1",
help="hostname or IP to connect to for dataset notifications")
group.add_argument(
"--port", default=3250, type=int,
help="TCP port to connect to")
"--port-notify", default=3250, type=int,
help="TCP port to connect to for dataset notifications")
group = self.argparser.add_argument_group("GUI server")
group.add_argument(
"--server-gui", default="::1",
help="hostname or IP to connect to for GUI control")
group.add_argument(
"--port-gui", default=6501, type=int,
help="TCP port to connect to for GUI control")
group.add_argument("--embed", default=None, type=int,
help="embed main widget into existing window")
self._arggroup_datasets = self.argparser.add_argument_group("datasets")

def add_dataset(self, name, help=None):
@@ -36,6 +46,22 @@ def quamash_init(self):

def create_main_widget(self):
self.main_widget = self.main_widget_class(self.args)
# Qt window embedding is ridiculously buggy, and empirical testing
# has shown that the following procedure must be followed exactly:
# 1. applet creates widget
# 2. applet creates native window without showing it, and get its ID
# 3. applet sends the ID to host, host embeds the widget
# 4. applet shows the widget
# Doing embedding the other way around (using QWindow.setParent in the
# applet) breaks resizing; furthermore the host needs to know our
# window ID to request graceful termination by closing the window.
if self.args.embed is not None:
win_id = int(self.main_widget.winId())
remote = Client(self.args.server_gui, self.args.port_gui, "applets")
try:
remote.embed(self.args.embed, win_id)
finally:
remote.close_rpc()
self.main_widget.show()

def sub_init(self, data):
@@ -49,7 +75,7 @@ def create_subscriber(self):
self.subscriber = Subscriber("datasets",
self.sub_init, self.sub_mod)
self.loop.run_until_complete(self.subscriber.connect(
self.args.server, self.args.port))
self.args.server_notify, self.args.port_notify))

def run(self):
self.args_init()
18 changes: 14 additions & 4 deletions artiq/frontend/artiq_gui.py
Original file line number Diff line number Diff line change
@@ -11,10 +11,10 @@
from pyqtgraph import dockarea

from artiq.tools import *
from artiq.protocols.pc_rpc import AsyncioClient
from artiq.protocols.pc_rpc import AsyncioClient, Server
from artiq.gui.models import ModelSubscriber
from artiq.gui import (state, experiments, shortcuts, explorer,
moninj, datasets, schedule, log, console)
moninj, datasets, applets, schedule, log, console)


def get_argparser():
@@ -111,6 +111,9 @@ def main():
d_datasets = datasets.DatasetsDock(win, dock_area, sub_clients["datasets"])
smgr.register(d_datasets)

appletmgr = applets.AppletManager(dock_area)
smgr.register(appletmgr)

if os.name != "nt":
d_ttl_dds = moninj.MonInj()
loop.run_until_complete(d_ttl_dds.start(args.server, args.port_notify))
@@ -129,9 +132,11 @@ def main():
if os.name != "nt":
dock_area.addDock(d_ttl_dds.dds_dock, "top")
dock_area.addDock(d_ttl_dds.ttl_dock, "above", d_ttl_dds.dds_dock)
dock_area.addDock(d_datasets, "above", d_ttl_dds.ttl_dock)
dock_area.addDock(appletmgr.main_dock, "above", d_ttl_dds.ttl_dock)
dock_area.addDock(d_datasets, "above", appletmgr.main_dock)
else:
dock_area.addDock(d_datasets, "top")
dock_area.addDock(appletmgr.main_dock, "top")
dock_area.addDock(d_datasets, "above", appletmgr.main_dock)
dock_area.addDock(d_shortcuts, "above", d_datasets)
dock_area.addDock(d_explorer, "above", d_shortcuts)
dock_area.addDock(d_console, "bottom")
@@ -147,6 +152,11 @@ def main():
if d_log0 is not None:
dock_area.addDock(d_log0, "right", d_explorer)

# start RPC server
rpc_server = Server({"applets": appletmgr.rpc})
loop.run_until_complete(rpc_server.start("::1", 6501))
atexit_register_coroutine(rpc_server.stop)

# run
win.show()
loop.run_until_complete(win.exit_request.wait())
135 changes: 135 additions & 0 deletions artiq/gui/applets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import logging

from quamash import QtCore, QtGui, QtWidgets
from pyqtgraph import dockarea


logger = logging.getLogger(__name__)


class AppletDock(dockarea.Dock):
def __init__(self, token, name):
dockarea.Dock.__init__(self, "applet" + str(token),
label="Applet: " + name,
closable=True)
self.setMinimumSize(QtCore.QSize(500, 400))

def capture(self, win_id):
self.captured_window = QtGui.QWindow.fromWinId(win_id)
self.captured_widget = QtWidgets.QWidget.createWindowContainer(captured_window)
self.addWidget(captured_widget)

def terminate(self):
if hasattr(self, "captured_window"):
self.captured_window.close()
self.captured_widget.deleteLater()
del self.captured_window
del self.captured_widget


class AppletsDock(dockarea.Dock):
def __init__(self, manager):
self.manager = manager

dockarea.Dock.__init__(self, "Applets")
self.setMinimumSize(QtCore.QSize(850, 450))

self.table = QtWidgets.QTableWidget(0, 3)
self.table.setHorizontalHeaderLabels(["Enable", "Name", "Command"])
self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.table.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.table.horizontalHeader().setStretchLastSection(True)
self.table.horizontalHeader().setResizeMode(
QtGui.QHeaderView.ResizeToContents)
self.table.verticalHeader().setResizeMode(
QtGui.QHeaderView.ResizeToContents)
self.table.verticalHeader().hide()
self.table.setTextElideMode(QtCore.Qt.ElideNone)
self.addWidget(self.table)

self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
new_action = QtGui.QAction("New applet", self.table)
new_action.triggered.connect(self.new)
self.table.addAction(new_action)
restart_action = QtGui.QAction("Restart selected applet", self.table)
self.table.addAction(restart_action)
delete_action = QtGui.QAction("Delete selected applet", self.table)
delete_action.triggered.connect(self.delete)
self.table.addAction(delete_action)

self.table.cellChanged.connect(self.cell_changed)

def cell_changed(self, row, column):
if column == 0:
item = self.table.item(row, column)
if item.checkState() == QtCore.Qt.Checked:
command = self.table.item(row, 2)
if command:
command = command.text()
name = self.table.item(row, 1)
if name is None:
name = ""
else:
name = name.text()
token = self.manager.create(name, command)
item.applet_token = token
else:
token = getattr(item, "applet_token", None)
if token is not None:
# cell_changed is emitted at row creation
self.manager.delete(token)
item.applet_token = None

def new(self):
row = self.table.rowCount()
self.table.insertRow(row)
checkbox = QtWidgets.QTableWidgetItem()
checkbox.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsUserCheckable |
QtCore.Qt.ItemIsEnabled)
checkbox.setCheckState(QtCore.Qt.Unchecked)
self.table.setItem(row, 0, checkbox)

def delete(self):
selection = self.table.selectedRanges()
if selection:
self.table.deleteRow(selection[0].topRow())


class AppletManagerRPC:
def __init__(self, parent):
self.parent = parent

def embed(self, token, win_id):
self.parent.embed(token, win_id)


class AppletManager:
def __init__(self, dock_area):
self.dock_area = dock_area
self.main_dock = AppletsDock(self)
self.rpc = AppletManagerRPC(self)
self.applet_docks = dict()

def embed(self, token, win_id):
if token not in self.applet_docks:
logger.warning("Ignored incorrect embed token %d for winid 0x%x",
token, win_id)
return

def create(self, name, command):
token = next(iter(set(range(len(self.applet_docks) + 1))
- self.applet_docks.keys()))
dock = AppletDock(token, name)
self.applet_docks[token] = dock
self.dock_area.floatDock(dock)
return token

def delete(self, token):
del self.applet_docks[token]

def save_state(self):
return dict()

def restore_state(self, state):
pass