diff --git a/bec_widgets/utils/compact_popup.py b/bec_widgets/utils/compact_popup.py index cb5203b8a..c7b0ac0fa 100644 --- a/bec_widgets/utils/compact_popup.py +++ b/bec_widgets/utils/compact_popup.py @@ -1,5 +1,6 @@ import time from types import SimpleNamespace +from typing import Literal from bec_qthemes import material_icon from qtpy.QtCore import Property, Qt, Signal @@ -39,7 +40,7 @@ def __init__(self, parent=None): self.setState("default") self.setFixedSize(20, 20) - def setState(self, state: str): + def setState(self, state: Literal["success", "default", "warning", "emergency"]): match state: case "success": r, g, b, a = self.palette.success.getRgb() diff --git a/bec_widgets/widgets/containers/dock/dock_area.py b/bec_widgets/widgets/containers/dock/dock_area.py index ca6a698b1..cff9d5d51 100644 --- a/bec_widgets/widgets/containers/dock/dock_area.py +++ b/bec_widgets/widgets/containers/dock/dock_area.py @@ -37,6 +37,7 @@ from bec_widgets.widgets.progress.ring_progress_bar.ring_progress_bar import RingProgressBar from bec_widgets.widgets.services.bec_queue.bec_queue import BECQueue from bec_widgets.widgets.services.bec_status_box.bec_status_box import BECStatusBox +from bec_widgets.widgets.services.device_browser.device_browser import DeviceBrowser from bec_widgets.widgets.utility.logpanel.logpanel import LogPanel from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton @@ -185,6 +186,12 @@ def _setup_toolbar(self): filled=True, parent=self, ), + "device_browser": MaterialIconAction( + icon_name=DeviceBrowser.ICON_NAME, + tooltip="Add Device Browser", + filled=True, + parent=self, + ), }, ), ) @@ -312,6 +319,9 @@ def _hook_toolbar(self): menu_devices.actions["positioner_box"].action.triggered.connect( lambda: self._create_widget_from_toolbar(widget_name="PositionerBox") ) + menu_devices.actions["device_browser"].action.triggered.connect( + lambda: self._create_widget_from_toolbar(widget_name="DeviceBrowser") + ) # Menu Utils menu_utils.actions["queue"].action.triggered.connect( diff --git a/bec_widgets/widgets/services/device_browser/device_browser.py b/bec_widgets/widgets/services/device_browser/device_browser.py index 55a066684..06fb68ca9 100644 --- a/bec_widgets/widgets/services/device_browser/device_browser.py +++ b/bec_widgets/widgets/services/device_browser/device_browser.py @@ -125,10 +125,8 @@ def on_device_update(self, action: ConfigAction, content: dict) -> None: action (str): The action that triggered the event. content (dict): The content of the config update. """ - if action in ["add", "remove", "reload"]: - self.devices_changed.emit() - if action in ["update", "reload"]: - self.device_update.emit(action, content) + self.devices_changed.emit() + self.device_update.emit(action, content) def init_device_list(self): self.dev_list.clear() diff --git a/bec_widgets/widgets/services/device_browser/device_item/device_item.py b/bec_widgets/widgets/services/device_browser/device_item/device_item.py index def709eb2..e3d7f435a 100644 --- a/bec_widgets/widgets/services/device_browser/device_item/device_item.py +++ b/bec_widgets/widgets/services/device_browser/device_item/device_item.py @@ -10,8 +10,17 @@ from bec_qthemes import material_icon from qtpy.QtCore import QMimeData, QSize, Qt, QThreadPool, Signal from qtpy.QtGui import QDrag -from qtpy.QtWidgets import QApplication, QHBoxLayout, QTabWidget, QToolButton, QVBoxLayout, QWidget +from qtpy.QtWidgets import ( + QApplication, + QHBoxLayout, + QLabel, + QTabWidget, + QToolButton, + QVBoxLayout, + QWidget, +) +from bec_widgets.utils.compact_popup import LedLabel from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.expandable_frame import ExpandableGroupFrame from bec_widgets.widgets.services.device_browser.device_item.config_communicator import ( @@ -56,6 +65,10 @@ def __init__( self._expanded_first_time = False self._data = None self.device = device + self._deleting = False + + self._ro_pixmap = material_icon(icon_name="keyboard_off", size=(15, 15)) + self._we_pixmap = material_icon(icon_name="keyboard", size=(15, 15)) self._layout = QHBoxLayout() self._layout.setContentsMargins(0, 0, 0, 0) @@ -78,6 +91,7 @@ def __init__( self.set_layout(self._layout) self.adjustSize() + self._reload_config() def _create_title_layout(self, title: str, icon: str): super()._create_title_layout(title, icon) @@ -92,6 +106,11 @@ def _create_title_layout(self, title: str, icon: str): self._title_layout.insertWidget(self._title_layout.count() - 1, self.delete_button) self.delete_button.clicked.connect(self._delete_device) + self.enabled_led = LedLabel() + self._title_layout.insertWidget(1, self.enabled_led) + self.readonly_label = QLabel() + self._title_layout.insertWidget(2, self.readonly_label) + @SafeSlot() def _create_edit_dialog(self): dialog = DeviceConfigDialog( @@ -106,6 +125,7 @@ def _create_edit_dialog(self): @SafeSlot() def _delete_device(self): + self._deleting = True self.expanded = False deleter = CommunicateConfigAction(self._config_helper, self.device, None, "remove") deleter.signals.error.connect(self._deletion_error) @@ -147,17 +167,23 @@ def set_editable(self, enabled: bool): @SafeSlot(str, dict) def config_update(self, action: ConfigAction, content: dict) -> None: - if self.device in content: + if (self.device in content or action == "reload") and not self._deleting: self._reload_config() @SafeSlot(popup_error=True) def _reload_config(self, *_): - self.set_display_config(self.dev[self.device]._config) + # Guard in case we attempt to reload config while a device is being removed/readded + if (dev := self.dev.get(self.device)) is not None: + self.set_display_config(dev._config) def set_display_config(self, config_dict: dict): """Set the displayed information from a device config dict, which must conform to the bec_lib.atlas_models.Device config model.""" self._data = DeviceConfigModel.model_validate(config_dict) + self.enabled_led.setState("success" if self._data.enabled else "emergency") + self.enabled_led.setToolTip("enabled" if self._data.enabled else "disabled") + self.readonly_label.setPixmap(self._ro_pixmap if self._data.readOnly else self._we_pixmap) + self.readonly_label.setToolTip("read only" if self._data.readOnly else "writing enabled") if self._expanded_first_time: self.form.set_data(self._data)