2015-07-14 23:31:18 +08:00
|
|
|
import asyncio
|
2015-08-01 19:37:16 +08:00
|
|
|
import logging
|
2015-07-14 23:31:18 +08:00
|
|
|
|
2016-06-17 16:45:26 +08:00
|
|
|
import numpy as np
|
2023-09-17 06:52:13 +08:00
|
|
|
from PyQt6 import QtCore, QtGui, QtWidgets
|
2021-07-13 12:53:35 +08:00
|
|
|
from sipyco import pyon
|
2015-07-14 23:31:18 +08:00
|
|
|
|
2023-07-20 12:29:42 +08:00
|
|
|
from artiq.tools import scale_from_metadata, short_format, exc_to_warning
|
2023-09-15 06:17:35 +08:00
|
|
|
from artiq.gui.tools import LayoutWidget
|
2015-11-17 19:46:17 +08:00
|
|
|
from artiq.gui.models import DictSyncTreeSepModel
|
2015-07-14 23:31:18 +08:00
|
|
|
|
|
|
|
|
2015-08-01 19:37:16 +08:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2023-07-10 13:13:34 +08:00
|
|
|
async def rename(key, new_key, value, metadata, persist, dataset_ctl):
|
2022-11-23 18:22:53 +08:00
|
|
|
if key != new_key:
|
2022-05-18 17:07:43 +08:00
|
|
|
await dataset_ctl.delete(key)
|
2023-07-10 13:13:34 +08:00
|
|
|
await dataset_ctl.set(new_key, value, metadata=metadata, persist=persist)
|
2022-05-18 17:07:43 +08:00
|
|
|
|
|
|
|
|
2022-11-23 18:22:53 +08:00
|
|
|
class CreateEditDialog(QtWidgets.QDialog):
|
2023-07-10 13:13:34 +08:00
|
|
|
def __init__(self, parent, dataset_ctl, key=None, value=None, metadata=None, persist=False):
|
2016-06-17 16:45:26 +08:00
|
|
|
QtWidgets.QDialog.__init__(self, parent=parent)
|
|
|
|
self.dataset_ctl = dataset_ctl
|
|
|
|
|
2022-11-23 18:22:53 +08:00
|
|
|
self.setWindowTitle("Create dataset" if key is None else "Edit dataset")
|
2021-07-13 12:53:35 +08:00
|
|
|
grid = QtWidgets.QGridLayout()
|
|
|
|
grid.setRowMinimumHeight(1, 40)
|
|
|
|
grid.setColumnMinimumWidth(2, 60)
|
|
|
|
self.setLayout(grid)
|
|
|
|
|
|
|
|
grid.addWidget(QtWidgets.QLabel("Name:"), 0, 0)
|
|
|
|
self.name_widget = QtWidgets.QLineEdit()
|
|
|
|
grid.addWidget(self.name_widget, 0, 1)
|
|
|
|
|
|
|
|
grid.addWidget(QtWidgets.QLabel("Value:"), 1, 0)
|
|
|
|
self.value_widget = QtWidgets.QLineEdit()
|
|
|
|
self.value_widget.setPlaceholderText('PYON (Python)')
|
|
|
|
grid.addWidget(self.value_widget, 1, 1)
|
|
|
|
self.data_type = QtWidgets.QLabel("data type")
|
|
|
|
grid.addWidget(self.data_type, 1, 2)
|
|
|
|
self.value_widget.textChanged.connect(self.dtype)
|
|
|
|
|
2023-07-10 13:13:34 +08:00
|
|
|
grid.addWidget(QtWidgets.QLabel("Unit:"), 2, 0)
|
|
|
|
self.unit_widget = QtWidgets.QLineEdit()
|
|
|
|
grid.addWidget(self.unit_widget, 2, 1)
|
|
|
|
|
|
|
|
grid.addWidget(QtWidgets.QLabel("Scale:"), 3, 0)
|
|
|
|
self.scale_widget = QtWidgets.QLineEdit()
|
|
|
|
grid.addWidget(self.scale_widget, 3, 1)
|
|
|
|
|
|
|
|
grid.addWidget(QtWidgets.QLabel("Precision:"), 4, 0)
|
|
|
|
self.precision_widget = QtWidgets.QLineEdit()
|
|
|
|
grid.addWidget(self.precision_widget, 4, 1)
|
|
|
|
|
|
|
|
grid.addWidget(QtWidgets.QLabel("Persist:"), 5, 0)
|
2021-07-13 12:53:35 +08:00
|
|
|
self.box_widget = QtWidgets.QCheckBox()
|
2023-07-10 13:13:34 +08:00
|
|
|
grid.addWidget(self.box_widget, 5, 1)
|
2021-07-13 12:53:35 +08:00
|
|
|
|
|
|
|
self.ok = QtWidgets.QPushButton('&Ok')
|
|
|
|
self.ok.setEnabled(False)
|
|
|
|
self.cancel = QtWidgets.QPushButton('&Cancel')
|
|
|
|
self.buttons = QtWidgets.QDialogButtonBox(self)
|
|
|
|
self.buttons.addButton(
|
2023-09-17 06:52:13 +08:00
|
|
|
self.ok, QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole)
|
2021-07-13 12:53:35 +08:00
|
|
|
self.buttons.addButton(
|
2023-09-17 06:52:13 +08:00
|
|
|
self.cancel, QtWidgets.QDialogButtonBox.ButtonRole.RejectRole)
|
2023-07-10 13:13:34 +08:00
|
|
|
grid.setRowStretch(6, 1)
|
2023-09-17 06:52:13 +08:00
|
|
|
grid.addWidget(self.buttons, 7, 0, 1, 3, alignment=QtCore.Qt.AlignmentFlag.AlignHCenter)
|
2021-07-13 12:53:35 +08:00
|
|
|
self.buttons.accepted.connect(self.accept)
|
|
|
|
self.buttons.rejected.connect(self.reject)
|
|
|
|
|
2022-11-23 18:22:53 +08:00
|
|
|
self.key = key
|
|
|
|
self.name_widget.setText(key)
|
2023-07-10 13:13:34 +08:00
|
|
|
|
2023-09-11 15:56:59 +08:00
|
|
|
value_edit_string = self.value_to_edit_string(value)
|
2023-07-10 13:13:34 +08:00
|
|
|
if metadata is not None:
|
2023-07-20 12:29:42 +08:00
|
|
|
scale = scale_from_metadata(metadata)
|
2023-09-11 15:56:59 +08:00
|
|
|
t = value.dtype if value is np.ndarray else type(value)
|
|
|
|
if scale != 1 and np.issubdtype(t, np.number):
|
|
|
|
# degenerates to float type
|
2024-02-27 11:09:37 +08:00
|
|
|
value_edit_string = self.value_to_edit_string(value / scale)
|
2023-07-10 13:13:34 +08:00
|
|
|
self.unit_widget.setText(metadata.get('unit', ''))
|
|
|
|
self.scale_widget.setText(str(metadata.get('scale', '')))
|
|
|
|
self.precision_widget.setText(str(metadata.get('precision', '')))
|
|
|
|
|
2023-09-11 15:56:59 +08:00
|
|
|
self.value_widget.setText(value_edit_string)
|
2022-11-23 18:22:53 +08:00
|
|
|
self.box_widget.setChecked(persist)
|
|
|
|
|
2021-07-13 12:53:35 +08:00
|
|
|
def accept(self):
|
|
|
|
key = self.name_widget.text()
|
|
|
|
value = self.value_widget.text()
|
|
|
|
persist = self.box_widget.isChecked()
|
2023-07-10 13:13:34 +08:00
|
|
|
unit = self.unit_widget.text()
|
|
|
|
scale = self.scale_widget.text()
|
|
|
|
precision = self.precision_widget.text()
|
|
|
|
metadata = {}
|
|
|
|
if unit != "":
|
|
|
|
metadata['unit'] = unit
|
|
|
|
if scale != "":
|
|
|
|
metadata['scale'] = float(scale)
|
|
|
|
if precision != "":
|
|
|
|
metadata['precision'] = int(precision)
|
2023-07-20 12:29:42 +08:00
|
|
|
scale = scale_from_metadata(metadata)
|
2023-09-11 15:56:59 +08:00
|
|
|
value = self.parse_edit_string(value)
|
2023-09-01 13:59:17 +08:00
|
|
|
t = value.dtype if value is np.ndarray else type(value)
|
2023-09-11 15:56:59 +08:00
|
|
|
if scale != 1 and np.issubdtype(t, np.number):
|
|
|
|
# degenerates to float type
|
2024-02-27 11:09:37 +08:00
|
|
|
value = float(value * scale)
|
2022-11-23 18:22:53 +08:00
|
|
|
if self.key and self.key != key:
|
2024-04-10 12:18:24 +08:00
|
|
|
asyncio.ensure_future(exc_to_warning(rename(self.key, key, value, metadata, persist,
|
|
|
|
self.dataset_ctl)))
|
2022-11-23 18:22:53 +08:00
|
|
|
else:
|
2024-04-10 12:18:24 +08:00
|
|
|
asyncio.ensure_future(exc_to_warning(self.dataset_ctl.set(key, value, metadata=metadata,
|
|
|
|
persist=persist)))
|
2022-11-23 18:22:53 +08:00
|
|
|
self.key = key
|
2021-07-13 12:53:35 +08:00
|
|
|
QtWidgets.QDialog.accept(self)
|
|
|
|
|
|
|
|
def dtype(self):
|
|
|
|
txt = self.value_widget.text()
|
|
|
|
try:
|
2023-09-11 15:56:59 +08:00
|
|
|
result = self.parse_edit_string(txt)
|
|
|
|
# ensure only pyon compatible types are permissable
|
|
|
|
pyon.encode(result)
|
2021-07-13 12:53:35 +08:00
|
|
|
except:
|
|
|
|
pixmap = self.style().standardPixmap(
|
2023-09-17 06:52:13 +08:00
|
|
|
QtWidgets.QStyle.StandardPixmap.SP_MessageBoxWarning)
|
2021-07-13 12:53:35 +08:00
|
|
|
self.data_type.setPixmap(pixmap)
|
|
|
|
self.ok.setEnabled(False)
|
|
|
|
else:
|
|
|
|
self.data_type.setText(type(result).__name__)
|
|
|
|
self.ok.setEnabled(True)
|
|
|
|
|
2023-09-11 15:56:59 +08:00
|
|
|
@staticmethod
|
|
|
|
def parse_edit_string(s):
|
|
|
|
if s == "":
|
|
|
|
raise TypeError
|
|
|
|
_eval_dict = {
|
|
|
|
"__builtins__": {},
|
|
|
|
"array": np.array,
|
|
|
|
"null": np.nan,
|
|
|
|
"inf": np.inf
|
|
|
|
}
|
|
|
|
for t_ in pyon._numpy_scalar:
|
|
|
|
_eval_dict[t_] = eval("np.{}".format(t_), {"np": np})
|
|
|
|
return eval(s, _eval_dict, {})
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def value_to_edit_string(v):
|
|
|
|
t = type(v)
|
|
|
|
r = ""
|
|
|
|
if isinstance(v, np.generic):
|
|
|
|
r += t.__name__
|
|
|
|
r += "("
|
|
|
|
r += repr(v)
|
|
|
|
r += ")"
|
|
|
|
elif v is None:
|
|
|
|
return r
|
|
|
|
else:
|
|
|
|
r += repr(v)
|
|
|
|
return r
|
|
|
|
|
2021-07-13 12:53:35 +08:00
|
|
|
|
2015-11-17 19:46:17 +08:00
|
|
|
class Model(DictSyncTreeSepModel):
|
2024-04-10 12:18:24 +08:00
|
|
|
def __init__(self, init):
|
2022-01-25 10:02:15 +08:00
|
|
|
DictSyncTreeSepModel.__init__(self, ".",
|
|
|
|
["Dataset", "Persistent", "Value"],
|
|
|
|
init)
|
2015-07-14 23:31:18 +08:00
|
|
|
|
|
|
|
def convert(self, k, v, column):
|
2015-11-17 19:46:17 +08:00
|
|
|
if column == 1:
|
2022-01-25 10:02:15 +08:00
|
|
|
return "Y" if v[0] else "N"
|
2015-10-12 17:18:23 +08:00
|
|
|
elif column == 2:
|
2023-07-03 12:27:47 +08:00
|
|
|
return short_format(v[1], v[2])
|
2015-07-14 23:31:18 +08:00
|
|
|
else:
|
2016-04-05 18:15:09 +08:00
|
|
|
raise ValueError
|
2015-07-14 23:31:18 +08:00
|
|
|
|
|
|
|
|
2016-02-14 19:15:57 +08:00
|
|
|
class DatasetsDock(QtWidgets.QDockWidget):
|
2023-05-30 15:44:30 +08:00
|
|
|
def __init__(self, dataset_sub, dataset_ctl):
|
2016-02-14 19:15:57 +08:00
|
|
|
QtWidgets.QDockWidget.__init__(self, "Datasets")
|
2016-02-14 20:46:15 +08:00
|
|
|
self.setObjectName("Datasets")
|
2023-09-17 06:52:13 +08:00
|
|
|
self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
|
|
|
|
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
|
2016-02-14 05:46:29 +08:00
|
|
|
self.dataset_ctl = dataset_ctl
|
2015-07-14 23:31:18 +08:00
|
|
|
|
|
|
|
grid = LayoutWidget()
|
2016-02-14 19:15:57 +08:00
|
|
|
self.setWidget(grid)
|
2015-07-14 23:31:18 +08:00
|
|
|
|
2016-02-15 07:23:47 +08:00
|
|
|
self.search = QtWidgets.QLineEdit()
|
2015-10-12 17:31:55 +08:00
|
|
|
self.search.setPlaceholderText("search...")
|
|
|
|
self.search.editingFinished.connect(self._search_datasets)
|
2015-10-30 19:58:15 +08:00
|
|
|
grid.addWidget(self.search, 0, 0)
|
2015-10-12 17:31:55 +08:00
|
|
|
|
2016-02-15 07:23:47 +08:00
|
|
|
self.table = QtWidgets.QTreeView()
|
2023-09-17 06:52:13 +08:00
|
|
|
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
|
2016-04-05 18:15:09 +08:00
|
|
|
self.table.setSelectionMode(
|
2023-09-17 06:52:13 +08:00
|
|
|
QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
|
2015-10-12 17:31:55 +08:00
|
|
|
grid.addWidget(self.table, 1, 0)
|
2015-07-14 23:31:18 +08:00
|
|
|
|
2023-09-17 06:52:13 +08:00
|
|
|
self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
|
|
|
|
create_action = QtGui.QAction("New dataset", self.table)
|
2021-07-13 12:53:35 +08:00
|
|
|
create_action.triggered.connect(self.create_clicked)
|
|
|
|
create_action.setShortcut("CTRL+N")
|
2023-09-17 06:52:13 +08:00
|
|
|
create_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
|
2021-07-13 12:53:35 +08:00
|
|
|
self.table.addAction(create_action)
|
2023-09-17 06:52:13 +08:00
|
|
|
edit_action = QtGui.QAction("Edit dataset", self.table)
|
2016-06-17 16:45:26 +08:00
|
|
|
edit_action.triggered.connect(self.edit_clicked)
|
|
|
|
edit_action.setShortcut("RETURN")
|
2023-09-17 06:52:13 +08:00
|
|
|
edit_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
|
2016-06-17 16:45:26 +08:00
|
|
|
self.table.doubleClicked.connect(self.edit_clicked)
|
|
|
|
self.table.addAction(edit_action)
|
2023-09-17 06:52:13 +08:00
|
|
|
delete_action = QtGui.QAction("Delete dataset", self.table)
|
2016-02-14 05:46:29 +08:00
|
|
|
delete_action.triggered.connect(self.delete_clicked)
|
|
|
|
delete_action.setShortcut("DELETE")
|
2023-09-17 06:52:13 +08:00
|
|
|
delete_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
|
2016-02-14 05:46:29 +08:00
|
|
|
self.table.addAction(delete_action)
|
|
|
|
|
2015-11-11 12:13:19 +08:00
|
|
|
self.table_model = Model(dict())
|
2023-05-30 15:44:30 +08:00
|
|
|
dataset_sub.add_setmodel_callback(self.set_model)
|
2015-07-14 23:31:18 +08:00
|
|
|
|
2015-10-12 17:31:55 +08:00
|
|
|
def _search_datasets(self):
|
2015-11-11 12:13:19 +08:00
|
|
|
if hasattr(self, "table_model_filter"):
|
|
|
|
self.table_model_filter.setFilterFixedString(
|
|
|
|
self.search.displayText())
|
2015-10-12 17:31:55 +08:00
|
|
|
|
2015-11-11 12:13:19 +08:00
|
|
|
def set_model(self, model):
|
|
|
|
self.table_model = model
|
2023-09-15 06:17:35 +08:00
|
|
|
self.table_model_filter = QtCore.QSortFilterProxyModel()
|
|
|
|
self.table_model_filter.setRecursiveFilteringEnabled(True)
|
2015-10-14 19:29:58 +08:00
|
|
|
self.table_model_filter.setSourceModel(self.table_model)
|
|
|
|
self.table.setModel(self.table_model_filter)
|
2016-02-14 05:46:29 +08:00
|
|
|
|
2021-07-13 12:53:35 +08:00
|
|
|
def create_clicked(self):
|
2022-11-23 18:22:53 +08:00
|
|
|
CreateEditDialog(self, self.dataset_ctl).open()
|
2021-07-13 12:53:35 +08:00
|
|
|
|
2016-06-17 16:45:26 +08:00
|
|
|
def edit_clicked(self):
|
|
|
|
idx = self.table.selectedIndexes()
|
|
|
|
if idx:
|
|
|
|
idx = self.table_model_filter.mapToSource(idx[0])
|
|
|
|
key = self.table_model.index_to_key(idx)
|
|
|
|
if key is not None:
|
2023-07-10 13:13:34 +08:00
|
|
|
persist, value, metadata = self.table_model.backing_store[key]
|
|
|
|
CreateEditDialog(self, self.dataset_ctl, key, value, metadata, persist).open()
|
2016-06-17 16:45:26 +08:00
|
|
|
|
2016-02-14 05:46:29 +08:00
|
|
|
def delete_clicked(self):
|
|
|
|
idx = self.table.selectedIndexes()
|
|
|
|
if idx:
|
|
|
|
idx = self.table_model_filter.mapToSource(idx[0])
|
|
|
|
key = self.table_model.index_to_key(idx)
|
|
|
|
if key is not None:
|
|
|
|
asyncio.ensure_future(self.dataset_ctl.delete(key))
|
2016-03-25 18:33:22 +08:00
|
|
|
|
|
|
|
def save_state(self):
|
|
|
|
return bytes(self.table.header().saveState())
|
|
|
|
|
|
|
|
def restore_state(self, state):
|
|
|
|
self.table.header().restoreState(QtCore.QByteArray(state))
|