0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-13 11:11:49 +02:00

refactor(scan-history): update after message refactoring

This commit is contained in:
2025-07-10 07:39:54 +02:00
parent 9644c75e95
commit 2ae1c0e71c

View File

@ -2,6 +2,8 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING
from bec_lib.logger import bec_logger from bec_lib.logger import bec_logger
from bec_lib.messages import ScanHistoryMessage from bec_lib.messages import ScanHistoryMessage
from bec_qthemes import material_icon from bec_qthemes import material_icon
@ -9,54 +11,56 @@ from qtpy import QtCore, QtWidgets
from bec_widgets.utils.bec_widget import BECWidget, ConnectionConfig from bec_widgets.utils.bec_widget import BECWidget, ConnectionConfig
from bec_widgets.utils.colors import get_accent_colors from bec_widgets.utils.colors import get_accent_colors
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeSlot
if TYPE_CHECKING: # pragma: no cover
from bec_lib.messages import _StoredDataInfo
logger = bec_logger.logger logger = bec_logger.logger
# TODO check cleanup class SignalModel(QtCore.QAbstractListModel):
# Custom model """Custom model for displaying scan history signals in a combo box."""
class DeviceModel(QtCore.QAbstractListModel):
def __init__(self, parent=None, devices: dict[str, tuple[int,]] = None): def __init__(self, parent=None, signals: dict[str, _StoredDataInfo] = None):
super().__init__(parent) super().__init__(parent)
if devices is None: if signals is None:
devices = {} signals = {}
self._devices: list[tuple] = sorted(devices.items(), key=lambda x: -x[1]) self._signals: list[tuple[str, _StoredDataInfo]] = sorted(
signals.items(), key=lambda x: -x[1].shape[0]
)
# @SafeProperty(list) # TODO if SafeProperty, how to pass the type list[tuple[str, int]]?
@property @property
def devices(self): def signals(self) -> list[tuple[str, _StoredDataInfo]]:
"""Return the list of devices.""" """Return the list of devices."""
return self._devices return self._signals
@devices.setter @signals.setter
def devices(self, value: dict[str, tuple[int,]]): def signals(self, value: dict[str, _StoredDataInfo]):
self.beginResetModel() self.beginResetModel()
self._devices = sorted(value.items(), key=lambda x: -x[1][0]) self._signals = sorted(value.items(), key=lambda x: -x[1].shape[0])
self.endResetModel() self.endResetModel()
def rowCount(self, parent=QtCore.QModelIndex()): def rowCount(self, parent=QtCore.QModelIndex()):
return len(self._devices) return len(self._signals)
def data(self, index, role=QtCore.Qt.DisplayRole): def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid(): if not index.isValid():
return None return None
name, num_points = self.devices[index.row()] name, info = self.signals[index.row()]
# TODO how to display shape here nicely, mostly it's number of points, just occasionally it is a tuple
if len(num_points) == 1:
num_points = num_points[0] # Assuming num_points is a tuple, take the first element
if role == QtCore.Qt.DisplayRole: if role == QtCore.Qt.DisplayRole:
return f"{name} ({num_points})" # fallback display return f"{name} {info.shape}" # fallback display
elif role == QtCore.Qt.UserRole: elif role == QtCore.Qt.UserRole:
return name return name
elif role == QtCore.Qt.UserRole + 1: elif role == QtCore.Qt.UserRole + 1:
return num_points return info.shape
return None return None
# Custom delegate for better formatting # Custom delegate for better formatting
class DeviceDelegate(QtWidgets.QStyledItemDelegate): class SignalDelegate(QtWidgets.QStyledItemDelegate):
"""Custom delegate for displaying device names and points in the combo box."""
def paint(self, painter, option, index): def paint(self, painter, option, index):
name = index.data(QtCore.Qt.UserRole) name = index.data(QtCore.Qt.UserRole)
points = index.data(QtCore.Qt.UserRole + 1) points = index.data(QtCore.Qt.UserRole + 1)
@ -82,7 +86,9 @@ class ScanHistoryDeviceViewer(BECWidget, QtWidgets.QWidget):
RPC = False RPC = False
PLUGIN = False PLUGIN = False
request_history_plot = QtCore.Signal(str, object) # (str, ScanHistoryMessage.model_dump()) request_history_plot = QtCore.Signal(
str, str, object
) # (device_name, signal_name, ScanHistoryMessage)
def __init__( def __init__(
self, self,
@ -103,24 +109,43 @@ class ScanHistoryDeviceViewer(BECWidget, QtWidgets.QWidget):
) )
# Current scan history message # Current scan history message
self.scan_history_msg: ScanHistoryMessage | None = None self.scan_history_msg: ScanHistoryMessage | None = None
self._last_device_name: str | None = None
self._last_signal_name: str | None = None
# Init layout # Init layout
layout = QtWidgets.QHBoxLayout(self) layout = QtWidgets.QVBoxLayout(self)
self.setLayout(layout) self.setLayout(layout)
# Init ComboBox # Init widgets
self.device_combo = QtWidgets.QComboBox(parent=self) self.device_combo = QtWidgets.QComboBox(parent=self)
self.signal_combo = QtWidgets.QComboBox(parent=self)
colors = get_accent_colors() colors = get_accent_colors()
self.request_plotting_button = QtWidgets.QPushButton( self.request_plotting_button = QtWidgets.QPushButton(
material_icon("play_arrow", size=(24, 24), color=colors.success), material_icon("play_arrow", size=(24, 24), color=colors.success),
"Request Plotting", "Request Plotting",
self, self,
) )
self.device_model = DeviceModel(parent=self.device_combo) self.signal_model = SignalModel(parent=self.signal_combo)
self.device_combo.setModel(self.device_model) self.signal_combo.setModel(self.signal_model)
layout.addWidget(self.device_combo) self.signal_combo.setItemDelegate(SignalDelegate())
layout.addWidget(self.request_plotting_button) self._init_layout()
self.device_combo.setItemDelegate(DeviceDelegate())
# Connect signals # Connect signals
self.request_plotting_button.clicked.connect(self._on_request_plotting_clicked) self.request_plotting_button.clicked.connect(self._on_request_plotting_clicked)
self.device_combo.currentTextChanged.connect(self._signal_combo_update)
def _init_layout(self):
"""Initialize the layout for the device viewer."""
main_layout = self.layout()
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# horzizontal layout for device combo and signal combo boxes
widget = QtWidgets.QWidget(self)
hor_layout = QtWidgets.QHBoxLayout()
hor_layout.setContentsMargins(0, 0, 0, 0)
hor_layout.setSpacing(0)
widget.setLayout(hor_layout)
hor_layout.addWidget(self.device_combo)
hor_layout.addWidget(self.signal_combo)
main_layout.addWidget(widget)
main_layout.addWidget(self.request_plotting_button)
@SafeSlot() @SafeSlot()
def update_devices_from_scan_history(self, msg: ScanHistoryMessage) -> None: def update_devices_from_scan_history(self, msg: ScanHistoryMessage) -> None:
@ -132,21 +157,45 @@ class ScanHistoryDeviceViewer(BECWidget, QtWidgets.QWidget):
if not isinstance(msg, ScanHistoryMessage): if not isinstance(msg, ScanHistoryMessage):
logger.info(f"Received message of type {type(msg)} instead of ScanHistoryMessage.") logger.info(f"Received message of type {type(msg)} instead of ScanHistoryMessage.")
return return
current_index = self.device_combo.currentIndex() # Keep track of current device name
device_name = self.device_combo.model().data( self._last_device_name = self.device_combo.currentText()
self.device_combo.model().index(current_index, 0), QtCore.Qt.UserRole
current_signal_index = self.signal_combo.currentIndex()
self._last_signal_name = self.signal_combo.model().data(
self.signal_combo.model().index(current_signal_index, 0), QtCore.Qt.UserRole
) )
# Update the scan history message
self.scan_history_msg = msg self.scan_history_msg = msg
self.device_model.devices = self.scan_history_msg.device_data_info self.device_combo.clear()
index = self.device_combo.findData(device_name, QtCore.Qt.UserRole) self.device_combo.addItems(msg.stored_data_info.keys())
index = self.device_combo.findData(self._last_device_name, role=QtCore.Qt.DisplayRole)
if index != -1: if index != -1:
self.device_combo.setCurrentIndex(index) self.device_combo.setCurrentIndex(index)
@SafeSlot(str)
def _signal_combo_update(self, device_name: str) -> None:
"""Update the signal combo box based on the selected device."""
if not self.scan_history_msg:
logger.info("No scan history message available to update signals.")
return
if not device_name:
return
signal_data = self.scan_history_msg.stored_data_info.get(device_name, None)
if signal_data is None:
logger.warning(f"No signal data found for device {device_name}.")
return
self.signal_model.signals = self.scan_history_msg.stored_data_info[device_name]
if self._last_signal_name is not None:
# Try to restore the last selected signal
index = self.signal_combo.findData(self._last_signal_name, role=QtCore.Qt.UserRole)
if index != -1:
self.signal_combo.setCurrentIndex(index)
@SafeSlot() @SafeSlot()
def clear_view(self, msg: ScanHistoryMessage | None = None) -> None: def clear_view(self, msg: ScanHistoryMessage | None = None) -> None:
"""Clear the device combo box.""" """Clear the device combo box."""
self.scan_history_msg = None self.scan_history_msg = None
self.device_model.devices = {} self.signal_model.signals = {}
self.device_combo.clear() self.device_combo.clear()
@SafeSlot() @SafeSlot()
@ -155,17 +204,19 @@ class ScanHistoryDeviceViewer(BECWidget, QtWidgets.QWidget):
if self.scan_history_msg is None: if self.scan_history_msg is None:
logger.info("No scan history message available for plotting.") logger.info("No scan history message available for plotting.")
return return
current_index = self.device_combo.currentIndex() device_name = self.device_combo.currentText()
device_name = self.device_combo.model().data(
self.device_combo.model().index(current_index, 0), QtCore.Qt.UserRole signal_index = self.signal_combo.currentIndex()
signal_name = self.signal_combo.model().data(
self.device_combo.model().index(signal_index, 0), QtCore.Qt.UserRole
) )
logger.info( logger.info(
f"Requesting plotting for device: {device_name} with {self.scan_history_msg} points." f"Requesting plotting for device: {device_name}, {signal_name} with {self.scan_history_msg} points."
) )
self.request_history_plot.emit(device_name, self.scan_history_msg) self.request_history_plot.emit(device_name, signal_name, self.scan_history_msg)
if __name__ == "__main__": if __name__ == "__main__": # pragma: no cover
import sys import sys
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
@ -173,10 +224,11 @@ if __name__ == "__main__":
main_window = QtWidgets.QMainWindow() main_window = QtWidgets.QMainWindow()
central_widget = QtWidgets.QWidget() central_widget = QtWidgets.QWidget()
main_window.setCentralWidget(central_widget) main_window.setCentralWidget(central_widget)
layout = QtWidgets.QVBoxLayout(central_widget) ly = QtWidgets.QVBoxLayout(central_widget)
layout.setContentsMargins(0, 0, 0, 0) ly.setContentsMargins(0, 0, 0, 0)
viewer = ScanHistoryDeviceViewer() viewer = ScanHistoryDeviceViewer()
layout.addWidget(viewer) ly.addWidget(viewer)
main_window.show() main_window.show()
app.exec_() app.exec_()
app.exec_()