diff --git a/bec_widgets/utils/list_of_expandable_frames.py b/bec_widgets/utils/list_of_expandable_frames.py index 1eae99a0..7ad85a71 100644 --- a/bec_widgets/utils/list_of_expandable_frames.py +++ b/bec_widgets/utils/list_of_expandable_frames.py @@ -9,7 +9,7 @@ from qtpy.QtWidgets import QListWidget, QListWidgetItem, QWidget from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.expandable_frame import ExpandableGroupFrame -from bec_widgets.widgets.control.device_manager.components.available_device_resources._util import ( +from bec_widgets.widgets.control.device_manager.components._util import ( SORT_KEY_ROLE, SortableQListWidgetItem, ) diff --git a/bec_widgets/widgets/control/device_manager/components/available_device_resources/_util.py b/bec_widgets/widgets/control/device_manager/components/_util.py similarity index 83% rename from bec_widgets/widgets/control/device_manager/components/available_device_resources/_util.py rename to bec_widgets/widgets/control/device_manager/components/_util.py index c848da92..b6cad8d7 100644 --- a/bec_widgets/widgets/control/device_manager/components/available_device_resources/_util.py +++ b/bec_widgets/widgets/control/device_manager/components/_util.py @@ -1,7 +1,10 @@ from typing import Any, Callable, Generator, Iterable, TypeVar +from PySide6.QtCore import QObject, Signal from qtpy.QtWidgets import QListWidgetItem +from bec_widgets.widgets.control.device_manager.components.constants import SORT_KEY_ROLE + _T = TypeVar("_T") _RT = TypeVar("_RT") @@ -14,9 +17,6 @@ def yield_only_passing(fn: Callable[[_T], _RT], vals: Iterable[_T]) -> Generator pass -SORT_KEY_ROLE = 117 - - class SortableQListWidgetItem(QListWidgetItem): """Store a sorting string key with .setData(SORT_KEY_ROLE, key) to be able to sort a list with \ custom widgets and this item.""" @@ -34,3 +34,7 @@ class SortableQListWidgetItem(QListWidgetItem): ) is None: return False return self_key.lower() < other_key.lower() + + +class SharedSelectionSignal(QObject): + proc = Signal(str) 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 2fb2d360..d25037b4 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 @@ -1,11 +1,14 @@ from textwrap import dedent from typing import NamedTuple +from uuid import uuid4 from bec_qthemes import material_icon -from qtpy.QtCore import QSize +from qtpy.QtCore import QItemSelection, QSize from qtpy.QtWidgets import QFrame, QHBoxLayout, QLabel, QListWidgetItem, QVBoxLayout, QWidget +from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.expandable_frame import ExpandableGroupFrame +from bec_widgets.widgets.control.device_manager.components._util import SharedSelectionSignal from bec_widgets.widgets.control.device_manager.components.available_device_resources.available_device_group_ui import ( Ui_AvailableDeviceGroup, ) @@ -108,10 +111,21 @@ class _DeviceEntry(NamedTuple): class AvailableDeviceGroup(ExpandableGroupFrame, Ui_AvailableDeviceGroup): def __init__( - self, parent=None, name: str = "TagGroupTitle", data: set[HashableDevice] = set(), **kwargs + self, + parent=None, + name: str = "TagGroupTitle", + data: set[HashableDevice] = set(), + shared_selection_signal=SharedSelectionSignal(), + **kwargs, ): super().__init__(parent=parent, **kwargs) self.setupUi(self) + + self._shared_selection_signal = shared_selection_signal + self._shared_selection_uuid = str(uuid4()) + self._shared_selection_signal.proc.connect(self._handle_shared_selection_signal) + self.device_list.selectionModel().selectionChanged.connect(self._on_selection_changed) + self.title_text = name # type: ignore self._mime_data = [] self._devices: dict[str, _DeviceEntry] = {} @@ -165,6 +179,15 @@ class AvailableDeviceGroup(ExpandableGroupFrame, Ui_AvailableDeviceGroup): self.device_list.sizeHintForRow(0) * self.device_list.count() + 50, ) + @SafeSlot(QItemSelection, QItemSelection) + def _on_selection_changed(self, selected: QItemSelection, deselected: QItemSelection) -> None: + self._shared_selection_signal.proc.emit(self._shared_selection_uuid) + + @SafeSlot(str) + def _handle_shared_selection_signal(self, uuid: str): + if uuid != self._shared_selection_uuid: + self.device_list.clearSelection() + def resizeEvent(self, event): super().resizeEvent(event) self.setMinimumHeight(self.sizeHint().height()) 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 d98d9e5a..ed754f81 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 @@ -1,11 +1,14 @@ from random import randint from typing import Any, Iterable +from uuid import uuid4 +from PySide6.QtCore import QItemSelection from qtpy.QtWidgets import QWidget from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.error_popups import SafeSlot -from bec_widgets.widgets.control.device_manager.components.available_device_resources._util import ( +from bec_widgets.widgets.control.device_manager.components._util import ( + SharedSelectionSignal, yield_only_passing, ) from bec_widgets.widgets.control.device_manager.components.available_device_resources.available_device_resources_ui import ( @@ -19,10 +22,16 @@ from bec_widgets.widgets.control.device_manager.components.constants import CONF class AvailableDeviceResources(BECWidget, QWidget, Ui_availableDeviceResources): - def __init__(self, parent=None, **kwargs): + def __init__(self, parent=None, shared_selection_signal=SharedSelectionSignal(), **kwargs): super().__init__(parent=parent, **kwargs) self.setupUi(self) self._backend = get_backend() + self._shared_selection_signal = shared_selection_signal + self._shared_selection_uuid = str(uuid4()) + self._shared_selection_signal.proc.connect(self._handle_shared_selection_signal) + self.device_groups_list.selectionModel().selectionChanged.connect( + self._on_selection_changed + ) self.grouping_selector.addItem("deviceTags") self.grouping_selector.addItems(self._backend.allowed_sort_keys) self._grouping_selection_changed("deviceTags") @@ -39,7 +48,12 @@ class AvailableDeviceResources(BECWidget, QWidget, Ui_availableDeviceResources): def _add_device_group(self, device_group: str, devices: set[HashableDevice]): item, widget = self.device_groups_list.add_item( - device_group, self.device_groups_list, device_group, devices, expanded=False + device_group, + self.device_groups_list, + device_group, + devices, + shared_selection_signal=self._shared_selection_signal, + expanded=False, ) item.setData(CONFIG_DATA_ROLE, widget.create_mime_data()) @@ -57,6 +71,15 @@ class AvailableDeviceResources(BECWidget, QWidget, Ui_availableDeviceResources): for list_item, device_group_widget in self.device_groups_list.item_widget_pairs(): list_item.setSizeHint(device_group_widget.sizeHint()) + @SafeSlot(QItemSelection, QItemSelection) + def _on_selection_changed(self, selected: QItemSelection, deselected: QItemSelection) -> None: + self._shared_selection_signal.proc.emit(self._shared_selection_uuid) + + @SafeSlot(str) + def _handle_shared_selection_signal(self, uuid: str): + if uuid != self._shared_selection_uuid: + self.device_groups_list.clearSelection() + @SafeSlot(dict) def update_devices_state_name_outside(self, configs: dict): self.update_devices_state([{"name": k, **v} for k, v in configs.items()]) 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 0b7a56d9..29217f36 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 @@ -27,7 +27,6 @@ from bec_widgets.widgets.control.device_manager.components.constants import ( class _ListOfDeviceGroups(ListOfExpandableFrames[AvailableDeviceGroup]): - def mimeTypes(self): return [MIME_DEVICE_CONFIG] diff --git a/bec_widgets/widgets/control/device_manager/components/constants.py b/bec_widgets/widgets/control/device_manager/components/constants.py index a75950ab..b438470e 100644 --- a/bec_widgets/widgets/control/device_manager/components/constants.py +++ b/bec_widgets/widgets/control/device_manager/components/constants.py @@ -2,4 +2,7 @@ from typing import Final # Denotes a MIME type for JSON-encoded list of device config dictionaries MIME_DEVICE_CONFIG: Final[str] = "application/x-bec_device_config" + +# Custom user roles +SORT_KEY_ROLE: Final[int] = 117 CONFIG_DATA_ROLE: Final[int] = 118