forked from M-Labs/artiq
1
0
Fork 0

Compare commits

...

7 Commits

1 changed files with 278 additions and 113 deletions

View File

@ -1,15 +1,19 @@
import asyncio
import logging
import textwrap
import os
from collections import namedtuple
from PyQt5 import QtCore, QtWidgets
from PyQt5 import QtCore, QtWidgets, QtGui
import pyqtgraph as pg
from sipyco import pyon
from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe
from artiq.coredevice.ad9912_reg import AD9912_SER_CONF
from artiq.gui.tools import LayoutWidget
from artiq.gui.flowlayout import FlowLayout
from artiq.gui.tools import LayoutWidget, get_open_file_name, get_save_file_name
from artiq.gui.models import DictSyncTreeSepModel
from artiq.tools import exc_to_warning
logger = logging.getLogger(__name__)
@ -35,48 +39,40 @@ class _TTLWidget(QtWidgets.QFrame):
self.setFrameShape(QtWidgets.QFrame.Box)
self.setFrameShadow(QtWidgets.QFrame.Raised)
self.uid = title
grid = QtWidgets.QGridLayout()
grid.setContentsMargins(0, 0, 0, 0)
grid.setHorizontalSpacing(0)
grid.setContentsMargins(2, 2, 2, 2)
grid.setHorizontalSpacing(2)
grid.setVerticalSpacing(0)
self.setLayout(grid)
label = QtWidgets.QLabel(title)
label.setAlignment(QtCore.Qt.AlignCenter)
label.setSizePolicy(QtWidgets.QSizePolicy.Ignored,
QtWidgets.QSizePolicy.Preferred)
grid.addWidget(label, 1, 1)
grid.addWidget(label, 0, 0)
self.stack = QtWidgets.QStackedWidget()
grid.addWidget(self.stack, 2, 1)
self.direction = QtWidgets.QLabel()
self.direction.setAlignment(QtCore.Qt.AlignCenter)
self.stack.addWidget(self.direction)
grid_cb = LayoutWidget()
grid_cb.layout.setContentsMargins(0, 0, 0, 0)
grid_cb.layout.setHorizontalSpacing(0)
grid_cb.layout.setVerticalSpacing(0)
self.override = QtWidgets.QToolButton()
self.override.setText("OVR")
self.override.setCheckable(True)
self.override.setToolTip("Override")
grid_cb.addWidget(self.override, 3, 1)
grid.addWidget(self.override, 0, 1)
self.level = QtWidgets.QToolButton()
self.level.setText("LVL")
self.level.setCheckable(True)
self.level.setToolTip("Level")
grid_cb.addWidget(self.level, 3, 2)
self.stack.addWidget(grid_cb)
grid.addWidget(self.level, 0, 2)
self.direction = QtWidgets.QLabel()
self.direction.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(self.direction, 0, 3)
self.value = QtWidgets.QLabel()
self.value.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(self.value, 3, 1)
self.value.setAlignment(QtCore.Qt.AlignRight)
grid.addWidget(self.value, 0, 4)
grid.setRowStretch(1, 1)
grid.setRowStretch(2, 0)
grid.setRowStretch(3, 0)
grid.setRowStretch(4, 1)
grid.setColumnStretch(0, 1)
grid.setColumnStretch(1, 1)
grid.setColumnStretch(2, 1)
grid.setColumnStretch(3, 1)
grid.setColumnStretch(4, 1)
self.override.clicked.connect(self.override_toggled)
self.level.clicked.connect(self.level_toggled)
@ -98,18 +94,8 @@ class _TTLWidget(QtWidgets.QFrame):
def setButtonsState(self, override, level):
self.override.setChecked(override)
if override:
self.stack.setCurrentIndex(1)
self.level.setChecked(level)
def enterEvent(self, event):
self.stack.setCurrentIndex(1)
QtWidgets.QFrame.enterEvent(self, event)
def leaveEvent(self, event):
if not self.override.isChecked():
self.stack.setCurrentIndex(0)
QtWidgets.QFrame.leaveEvent(self, event)
class _TTLHandler:
def __init__(self, dm, channel, force_out, title):
@ -117,6 +103,7 @@ class _TTLHandler:
self.force_out = force_out
self.set_mode = dm.ttl_set_mode
self.title = title
self.uid = title
self.cur_level = False
self.cur_oe = False
self.cur_override = False
@ -170,89 +157,68 @@ class _DDSWidget(QtWidgets.QFrame):
def __init__(self, title, is_urukul):
QtWidgets.QFrame.__init__(self)
self.uid = title
self.setFrameShape(QtWidgets.QFrame.Box)
self.setFrameShadow(QtWidgets.QFrame.Raised)
grid = QtWidgets.QGridLayout()
grid.setContentsMargins(0, 0, 0, 0)
grid.setHorizontalSpacing(0)
grid.setVerticalSpacing(0)
grid.setContentsMargins(2, 2, 2, 2)
grid.setHorizontalSpacing(2)
self.setLayout(grid)
label = QtWidgets.QLabel(title)
label.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(label, 1, 1)
grid.addWidget(label, 0, 0)
# FREQ DATA/EDIT FIELD
self.data_stack = QtWidgets.QStackedWidget()
self.stack = QtWidgets.QStackedWidget()
# page 1: display data
grid_disp = LayoutWidget()
grid_disp.layout.setContentsMargins(0, 0, 0, 0)
grid_disp.layout.setHorizontalSpacing(0)
grid_disp.layout.setVerticalSpacing(0)
self.value_label = QtWidgets.QLabel()
self.value_label.setAlignment(QtCore.Qt.AlignCenter)
grid_disp.addWidget(self.value_label, 0, 1, 1, 2)
unit = QtWidgets.QLabel("MHz")
unit.setAlignment(QtCore.Qt.AlignCenter)
grid_disp.addWidget(unit, 0, 3, 1, 1)
self.data_stack.addWidget(grid_disp)
# page 2: edit data
grid_edit = LayoutWidget()
grid_edit.layout.setContentsMargins(0, 0, 0, 0)
grid_edit.layout.setHorizontalSpacing(0)
grid_edit.layout.setVerticalSpacing(0)
self.value_edit = _CancellableLineEdit(self)
self.value_edit.setAlignment(QtCore.Qt.AlignRight)
grid_edit.addWidget(self.value_edit, 0, 1, 1, 2)
unit = QtWidgets.QLabel("MHz")
unit.setAlignment(QtCore.Qt.AlignCenter)
grid_edit.addWidget(unit, 0, 3, 1, 1)
self.data_stack.addWidget(grid_edit)
grid.addWidget(self.data_stack, 2, 1)
# BUTTONS
self.button_stack = QtWidgets.QStackedWidget()
# page 1: SET button
set_grid = LayoutWidget()
set_btn = QtWidgets.QToolButton()
set_btn.setText("Set")
set_btn.setToolTip("Set frequency")
set_grid.addWidget(set_btn, 0, 1, 1, 1)
# for urukuls also allow switching off RF
if is_urukul:
grid_disp.addWidget(set_btn, 0, 0, 1, 1)
off_btn = QtWidgets.QToolButton()
off_btn.setText("Off")
off_btn.setToolTip("Switch off the output")
set_grid.addWidget(off_btn, 0, 2, 1, 1)
grid_disp.addWidget(off_btn, 0, 1, 1, 1)
else:
grid_disp.addWidget(set_btn, 0, 0, 1, 2)
self.button_stack.addWidget(set_grid)
self.value_label = QtWidgets.QLabel()
self.value_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
grid_disp.addWidget(self.value_label, 0, 2, 1, 4)
# page 2: apply/cancel buttons
apply_grid = LayoutWidget()
self.stack.addWidget(grid_disp)
# page 2: edit data
grid_edit = LayoutWidget()
apply = QtWidgets.QToolButton()
apply.setText("Apply")
apply.setToolTip("Apply changes")
apply_grid.addWidget(apply, 0, 1, 1, 1)
grid_edit.addWidget(apply, 0, 0, 1, 1)
cancel = QtWidgets.QToolButton()
cancel.setText("Cancel")
cancel.setToolTip("Cancel changes")
apply_grid.addWidget(cancel, 0, 2, 1, 1)
self.button_stack.addWidget(apply_grid)
grid.addWidget(self.button_stack, 3, 1)
grid_edit.addWidget(cancel, 0, 1, 1, 1)
grid_edit.layout.setContentsMargins(0, 0, 0, 0)
grid_edit.layout.setHorizontalSpacing(0)
grid_edit.layout.setVerticalSpacing(0)
self.value_edit = _CancellableLineEdit(self)
self.value_edit.setAlignment(QtCore.Qt.AlignRight)
grid_edit.addWidget(self.value_edit, 0, 2, 1, 4)
self.stack.addWidget(grid_edit)
grid.setRowStretch(1, 1)
grid.setRowStretch(2, 1)
grid.setRowStretch(3, 1)
grid.addWidget(self.stack, 0, 1, 1, 6)
unit = QtWidgets.QLabel("MHz")
unit.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
grid.addWidget(unit, 0, 7)
set_btn.clicked.connect(self.set_clicked)
apply.clicked.connect(self.apply_changes)
@ -270,8 +236,7 @@ class _DDSWidget(QtWidgets.QFrame):
cancel.clicked.connect(cancel_changes)
def set_page(self, page):
self.data_stack.setCurrentIndex(page)
self.button_stack.setCurrentIndex(page)
self.stack.setCurrentIndex(page)
def get_value(self):
return float(self.value_edit.text())
@ -293,6 +258,7 @@ class _DDSHandler:
ref_clk, cpld=None, pll=1, clk_div=0):
self.dm = dm
self.title = title
self.uid = title
self.bus_channel = bus_channel
self.channel = channel
self.cur_frequency = 0
@ -362,25 +328,22 @@ class _DACWidget(QtWidgets.QFrame):
def __init__(self, channel, title):
QtWidgets.QFrame.__init__(self)
self.uid = (title, channel)
self.setFrameShape(QtWidgets.QFrame.Box)
self.setFrameShadow(QtWidgets.QFrame.Raised)
grid = QtWidgets.QGridLayout()
grid.setContentsMargins(0, 0, 0, 0)
grid.setHorizontalSpacing(0)
grid.setVerticalSpacing(0)
grid.setContentsMargins(2, 2, 2, 2)
grid.setHorizontalSpacing(2)
self.setLayout(grid)
label = QtWidgets.QLabel("{} ch{}".format(title, channel))
label.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(label, 1, 1)
label.setAlignment(QtCore.Qt.AlignLeft)
grid.addWidget(label, 0, 1)
self.value = QtWidgets.QLabel()
self.value.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(self.value, 2, 1, 6, 1)
grid.setRowStretch(1, 1)
grid.setRowStretch(2, 0)
grid.setRowStretch(3, 1)
self.value.setAlignment(QtCore.Qt.AlignRight)
grid.addWidget(self.value, 0, 2, 1, 1)
def set_value(self, value):
self.value.setText("<font size=\"4\">{:.3f}</font><font size=\"2\"> %</font>"
@ -394,6 +357,7 @@ class _DACHandler:
self.spi_channel = spi_channel
self.channel = channel
self.title = title
self.uid = (title, channel)
def create_widget(self):
self.widget = _DACWidget(self.channel, self.title)
@ -835,6 +799,129 @@ class _DeviceManager:
await self.mi_connection.close()
class MoninjTreeWidget(pg.TreeWidget):
def __init__(self):
pg.TreeWidget.__init__(self)
self.setColumnCount(1)
self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.header().setVisible(False)
self.setHorizontalScrollMode(self.ScrollPerPixel)
self.setVerticalScrollMode(self.ScrollPerPixel)
palette = QtGui.QPalette()
palette.setColor(QtGui.QPalette.Highlight, QtGui.QColor("gray"))
self.setPalette(palette)
self._widgets = dict()
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
delete_action = QtWidgets.QAction("Delete", self)
delete_action.triggered.connect(self._remove_item_action)
delete_action.setShortcut("DEL")
delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.addAction(delete_action)
add_grp_action = QtWidgets.QAction("Insert group", self)
add_grp_action.triggered.connect(self._add_group_action)
add_grp_action.setShortcut("CTRL+SHIFT+N")
add_grp_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.addAction(add_grp_action)
def supportedDropActions(self):
# Moving items breaks widgets in pg.TreeWidget, perform only copies
return QtCore.Qt.CopyAction
def itemMoving(self, item, parent, index):
if parent is None:
return True
if self.indexOfTopLevelItem(parent) == -1:
return False
if self.itemWidget(item, 0) is None:
return False
if self.itemWidget(parent, 0) is not None:
return False
return True
def _find_group(self, group):
for i in range(self.topLevelItemCount()):
item = self.topLevelItem(i)
if item.text(0) == group:
return item
return self.invisibleRootItem()
def add_widget(self, widget, group=None):
item = pg.TreeWidgetItem()
item.setFlags(item.flags() & ~QtCore.Qt.ItemFlag.ItemIsDropEnabled)
if group is None:
self.addTopLevelItem(item)
else:
group_item = self._find_group(group)
group_item.addChild(item)
self.setItemWidget(item, 0, widget)
def _remove_item_action(self):
if len(self.selectedItems()) == 0:
return
item = self.selectedItems()[0]
self.remove_item(item)
def remove_item(self, item):
if self.itemWidget(item, 0) is None:
item.takeChildren()
root = self.invisibleRootItem()
(item.parent() or root).removeChild(item)
def _add_group_action(self):
if len(self.selectedItems()) == 0:
self.add_group()
return
item = self.selectedItems()[0]
index = self.indexOfTopLevelItem(item)
if index != -1:
self.insert_group(index + 1)
else:
parent = item.parent()
index = self.indexOfTopLevelItem(parent)
self.insert_group(index + 1)
def add_group(self, name="untitled"):
group_item = pg.TreeWidgetItem([name])
group_item.setFlags(group_item.flags() | QtCore.Qt.ItemFlag.ItemIsEditable)
self.addTopLevelItem(group_item)
return group_item
def insert_group(self, index, name="untitled"):
group_item = pg.TreeWidgetItem([name])
group_item.setFlags(group_item.flags() | QtCore.Qt.ItemFlag.ItemIsEditable)
self.insertTopLevelItem(index, group_item)
return group_item
def extend(self, d):
for item in d:
if isinstance(item, dict):
self.add_group(item["name"])
for widget in item["widgets"]:
self.add_widget(widget, group=item["name"])
else:
self.add_widget(item)
def export_list(self):
export_list = list()
for i in range(self.topLevelItemCount()):
item = self.topLevelItem(i)
widget = self.itemWidget(item, 0)
if widget is None:
group = dict()
group["name"] = item.text(0)
group["widgets"] = list()
for j in range(item.childCount()):
child = item.child(j)
widget = self.itemWidget(child, 0)
group["widgets"].append(widget.uid)
export_list.append(group)
else:
export_list.append(widget.uid)
return export_list
class MonInjDock(QtWidgets.QDockWidget):
def __init__(self, schedule_ctl):
QtWidgets.QDockWidget.__init__(self, "MonInj")
@ -847,6 +934,14 @@ class MonInjDock(QtWidgets.QDockWidget):
layout = LayoutWidget()
self.setWidget(layout)
self._current_dir = os.getcwd()
self._menu_btn = QtWidgets.QPushButton()
self._menu_btn.setIcon(
QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogStart))
layout.addWidget(self._menu_btn, 0, 0)
self._channel_model = Model({})
self.add_channel_dialog = _AddChannelDialog(self, self._channel_model)
self.add_channel_dialog.accepted.connect(
@ -857,22 +952,92 @@ class MonInjDock(QtWidgets.QDockWidget):
QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogListView))
add_channel_btn.clicked.connect(self.add_channel_dialog.open)
layout.addWidget(add_channel_btn, 0, 0, colspan=1)
scroll_area = QtWidgets.QScrollArea()
layout.addWidget(scroll_area, 1, 0, colspan=10)
self.grid = FlowLayout()
grid_widget = QtWidgets.QWidget()
grid_widget.setLayout(self.grid)
scroll_area.setWidgetResizable(True)
scroll_area.setWidget(grid_widget)
layout.addWidget(add_channel_btn, 0, 1)
self.tree = MoninjTreeWidget()
layout.addWidget(self.tree, 1, 0, colspan=10)
self._file_menu = QtWidgets.QMenu()
self._add_async_action("Open configuration...", self.load_configuration)
self._add_async_action("Save configuration...", self.save_configuration)
self._menu_btn.setMenu(self._file_menu)
def _add_async_action(self, label, coro):
action = QtWidgets.QAction(label, self)
action.triggered.connect(
lambda: asyncio.ensure_future(exc_to_warning(coro())))
self._file_menu.addAction(action)
def layout_widgets(self, handlers):
for handler in handlers:
self.grid.addWidget(handler.widget)
handler.create_widget()
self.tree.add_widget(handler.widget)
handler.refresh_display()
def set_channels(self, handlers):
self._channel_model.update(handlers)
def _get_widget_from_handler(self, uid):
if uid in self.dm.handlers_by_uid:
handler = self.dm.handlers_by_uid[uid]
handler.create_widget()
return handler.widget
else:
logger.warning("skipping moninj widget with uid {}:"
" description incorrect/missing from device db".format(uid))
return None
def _connect_config(self, config):
out_config = []
for item in config:
if isinstance(item, dict):
out_widgets = []
for uid in item["widgets"]:
widget = self._get_widget_from_handler(uid)
if widget is not None:
out_widgets.append(widget)
item["widgets"] = out_widgets
out_config.append(item)
else:
widget = self._get_widget_from_handler(item)
if widget is not None:
out_config.append(widget)
return out_config
async def load_configuration(self):
try:
filename = await get_open_file_name(
self,
"Load configuration",
self._current_dir,
"PYON files (*.pyon);;All files (*.*)")
except asyncio.CancelledError:
return
self._current_dir = os.path.dirname(filename)
try:
configuration = pyon.load_file(filename)
connected_config = self._connect_config(configuration)
self.tree.clear()
self.tree.extend(connected_config)
except Exception:
logger.error("Failed to load moninj configuration", exc_info=True)
async def save_configuration(self):
try:
filename = await get_save_file_name(
self,
"Save configuration",
self._current_dir,
"PYON files (*.pyon);;All files (*.*)")
except asyncio.CancelledError:
return
self._current_dir = os.path.dirname(filename)
try:
configuration = self.tree.export_list()
pyon.store_file(filename, configuration)
except Exception:
logger.error("Failed to save moninj configuration", exc_info=True)
async def stop(self):
if self.dm is not None:
await self.dm.close()