diff --git a/bec_widgets/examples/device_manager_view/device_manager_view.py b/bec_widgets/examples/device_manager_view/device_manager_view.py index a82079ec..bf2fbd36 100644 --- a/bec_widgets/examples/device_manager_view/device_manager_view.py +++ b/bec_widgets/examples/device_manager_view/device_manager_view.py @@ -30,9 +30,6 @@ from bec_widgets.widgets.control.device_manager.components.available_device_reso AvailableDeviceResources, ) -if TYPE_CHECKING: - from bec_lib.client import BECClient - logger = bec_logger.logger @@ -157,6 +154,8 @@ class DeviceManagerView(BECWidget, QWidget): # Connect slots self.device_table_view.selected_devices.connect(self.dm_config_view.on_select_config) self.device_table_view.selected_devices.connect(self.dm_docs_view.on_select_config) + self.available_devices.selected_devices.connect(self.dm_config_view.on_select_config) + self.available_devices.selected_devices.connect(self.dm_docs_view.on_select_config) self.ophyd_test_view.device_validated.connect( self.device_table_view.update_device_validation ) @@ -260,6 +259,15 @@ class DeviceManagerView(BECWidget, QWidget): # IO actions + def _coming_soon(self): + return QMessageBox.question( + self, + "Not implemented yet", + "This feature has not been implemented yet, will be coming soon...!!", + QMessageBox.StandardButton.Cancel, + QMessageBox.StandardButton.Cancel, + ) + @SafeSlot() def _load_file_action(self): """Action for the 'load' action to load a config from disk for the io_bundle of the toolbar.""" @@ -282,7 +290,7 @@ class DeviceManagerView(BECWidget, QWidget): ) if file_path: try: - config = yaml_load(file_path) + config = [{"name": k, **v} for k, v in yaml_load(file_path).items()] except Exception as e: logger.error(f"Failed to load config from file {file_path}. Error: {e}") return @@ -297,18 +305,14 @@ class DeviceManagerView(BECWidget, QWidget): reply = QMessageBox.question( self, "Load currently active config", - "Do you really want to flush the current config and reload?", + "Do you really want to discard the current config and reload?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No, ) if reply == QMessageBox.StandardButton.Yes and self.client.device_manager is not None: - cfg = {} - config_list = self.client.device_manager._get_redis_device_config() - for item in config_list: - k = item["name"] - item.pop("name") - cfg[k] = item - self.device_table_view.set_device_config(cfg) + self.device_table_view.set_device_config( + self.client.device_manager._get_redis_device_config() + ) else: return @@ -328,7 +332,7 @@ class DeviceManagerView(BECWidget, QWidget): self, caption="Save Config File", dir=config_path ) if file_path: - config = self.device_table_view.get_device_config() + config = {cfg.pop("name"): cfg for cfg in self.device_table_view.get_device_config()} with open(file_path, "w") as file: file.write(yaml.dump(config)) @@ -337,13 +341,7 @@ class DeviceManagerView(BECWidget, QWidget): def _update_redis_action(self): """Action for the 'update_redis' action to update the current config in Redis.""" config = self.device_table_view.get_device_config() - reply = QMessageBox.question( - self, - "Not implemented yet", - "This feature has not been implemented yet, will be coming soon...!!", - QMessageBox.StandardButton.Cancel, - QMessageBox.StandardButton.Cancel, - ) + reply = self._coming_soon() # Table actions @@ -368,13 +366,7 @@ class DeviceManagerView(BECWidget, QWidget): def _add_device_action(self): """Action for the 'add_device' action to add a new device.""" # Implement the logic to add a new device - reply = QMessageBox.question( - self, - "Not implemented yet", - "This feature has not been implemented yet, will be coming soon...!!", - QMessageBox.StandardButton.Cancel, - QMessageBox.StandardButton.Cancel, - ) + reply = self._coming_soon() @SafeSlot() def _remove_device_action(self): @@ -387,13 +379,7 @@ class DeviceManagerView(BECWidget, QWidget): def _rerun_validation_action(self): """Action for the 'rerun_validation' action to rerun validation on selected devices.""" # Implement the logic to rerun validation on selected devices - reply = QMessageBox.question( - self, - "Not implemented yet", - "This feature has not been implemented yet, will be coming soon...!!", - QMessageBox.StandardButton.Cancel, - QMessageBox.StandardButton.Cancel, - ) + reply = self._coming_soon() ####### Default view has to be done with setting up splitters ######## def set_default_view(self, horizontal_weights: list, vertical_weights: list): diff --git a/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_group.py b/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_group.py index d25037b4..9b93304b 100644 --- a/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_group.py +++ b/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_group.py @@ -3,7 +3,7 @@ from typing import NamedTuple from uuid import uuid4 from bec_qthemes import material_icon -from qtpy.QtCore import QItemSelection, QSize +from qtpy.QtCore import QItemSelection, QSize, Signal from qtpy.QtWidgets import QFrame, QHBoxLayout, QLabel, QListWidgetItem, QVBoxLayout, QWidget from bec_widgets.utils.error_popups import SafeSlot @@ -110,6 +110,9 @@ class _DeviceEntry(NamedTuple): class AvailableDeviceGroup(ExpandableGroupFrame, Ui_AvailableDeviceGroup): + + selected_devices = Signal(list) + def __init__( self, parent=None, @@ -182,6 +185,8 @@ class AvailableDeviceGroup(ExpandableGroupFrame, Ui_AvailableDeviceGroup): @SafeSlot(QItemSelection, QItemSelection) def _on_selection_changed(self, selected: QItemSelection, deselected: QItemSelection) -> None: self._shared_selection_signal.proc.emit(self._shared_selection_uuid) + config = [dev.as_normal_device().model_dump() for dev in self.get_selection()] + self.selected_devices.emit(config) @SafeSlot(str) def _handle_shared_selection_signal(self, uuid: str): @@ -198,9 +203,6 @@ class AvailableDeviceGroup(ExpandableGroupFrame, Ui_AvailableDeviceGroup): widgets = (w.widget for _, w in self._devices.items() if w.list_item in selection) return set(w._device_spec for w in widgets) - def test(self, *args): - print(self.get_selection()) - def __repr__(self) -> str: return f"{self.__class__.__name__}: {self.title_text}" diff --git a/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_group_ui.py b/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_group_ui.py index 0dafc4d0..c45f1131 100644 --- a/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_group_ui.py +++ b/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_group_ui.py @@ -12,6 +12,13 @@ from bec_widgets.widgets.control.device_manager.components.constants import ( class _DeviceListWiget(QListWidget): + + def _item_iter(self): + return (self.item(i) for i in range(self.count())) + + def all_configs(self): + return [item.data(CONFIG_DATA_ROLE) for item in self._item_iter()] + def mimeTypes(self): return [MIME_DEVICE_CONFIG] diff --git a/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_resources.py b/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_resources.py index 87ed14ce..f6bf6616 100644 --- a/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_resources.py +++ b/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_resources.py @@ -2,7 +2,7 @@ from random import randint from typing import Any, Iterable from uuid import uuid4 -from qtpy.QtCore import QItemSelection +from qtpy.QtCore import QItemSelection, Signal from qtpy.QtWidgets import QWidget from bec_widgets.utils.bec_widget import BECWidget @@ -22,6 +22,9 @@ from bec_widgets.widgets.control.device_manager.components.constants import CONF class AvailableDeviceResources(BECWidget, QWidget, Ui_availableDeviceResources): + + selected_devices = Signal(list) # list[dict[str,Any]] of device configs currently selected + def __init__(self, parent=None, shared_selection_signal=SharedSelectionSignal(), **kwargs): super().__init__(parent=parent, **kwargs) self.setupUi(self) @@ -56,6 +59,8 @@ class AvailableDeviceResources(BECWidget, QWidget, Ui_availableDeviceResources): expanded=False, ) item.setData(CONFIG_DATA_ROLE, widget.create_mime_data()) + # Re-emit the selected items from a subgroup - all other selections should be disabled anyway + widget.selected_devices.connect(self.selected_devices) def resizeEvent(self, event): super().resizeEvent(event) @@ -64,6 +69,7 @@ class AvailableDeviceResources(BECWidget, QWidget, Ui_availableDeviceResources): @SafeSlot(QItemSelection, QItemSelection) def _on_selection_changed(self, selected: QItemSelection, deselected: QItemSelection) -> None: + self.selected_devices.emit(self.device_groups_list.selected_devices()) self._shared_selection_signal.proc.emit(self._shared_selection_uuid) @SafeSlot(str) diff --git a/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_resources_ui.py b/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_resources_ui.py index 03fa5252..3b9ebb22 100644 --- a/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_resources_ui.py +++ b/bec_widgets/widgets/control/device_manager/components/available_device_resources/available_device_resources_ui.py @@ -1,6 +1,7 @@ from __future__ import annotations import itertools +from typing import Generator from qtpy.QtCore import QMetaObject, Qt from qtpy.QtWidgets import ( @@ -26,6 +27,14 @@ from bec_widgets.widgets.control.device_manager.components.constants import ( class _ListOfDeviceGroups(ListOfExpandableFrames[AvailableDeviceGroup]): + + def selected_devices(self): + selected_items = (self.item(r.row()) for r in self.selectionModel().selectedRows()) + widgets: Generator[AvailableDeviceGroup, None, None] = ( + self.itemWidget(item) for item in selected_items # type: ignore + ) + return list(itertools.chain.from_iterable(w.device_list.all_configs() for w in widgets)) + def mimeTypes(self): return [MIME_DEVICE_CONFIG] diff --git a/bec_widgets/widgets/control/device_manager/components/device_table_view.py b/bec_widgets/widgets/control/device_manager/components/device_table_view.py index add78973..2b87707b 100644 --- a/bec_widgets/widgets/control/device_manager/components/device_table_view.py +++ b/bec_widgets/widgets/control/device_manager/components/device_table_view.py @@ -147,8 +147,10 @@ class WrappingTextDelegate(DictToolTipDelegate): painter.save() painter.setClipRect(option.rect) - text_option = Qt.TextWordWrap | Qt.AlignLeft | Qt.AlignTop - painter.drawText(option.rect.adjusted(4, 2, -4, -2), text_option, text) + text_option = ( + Qt.TextFlag.TextWordWrap | Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop + ) + painter.drawText(option.rect.adjusted(4, 2, -5, -2), text_option, text) painter.restore() def sizeHint(self, option, index):