From a1bec7511549277da231928d989b16ecad0eed1b Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Mon, 7 Apr 2025 13:19:11 +0200 Subject: [PATCH] fix(widgets)!: BECConnector resolves hierarchy including objectName, parent, parent_id upon init; all widgets adjusted --- bec_widgets/applications/bw_launch.py | 4 +- bec_widgets/applications/launch_window.py | 5 +- bec_widgets/cli/client_utils.py | 40 +++-- bec_widgets/cli/rpc/rpc_base.py | 35 +++-- bec_widgets/cli/rpc/rpc_register.py | 4 +- bec_widgets/cli/rpc/rpc_widget_handler.py | 4 +- bec_widgets/cli/server.py | 6 +- bec_widgets/utils/bec_connector.py | 89 +++++++++-- bec_widgets/utils/bec_dispatcher.py | 16 +- bec_widgets/utils/bec_widget.py | 16 +- bec_widgets/utils/cli_server.py | 69 ++++++--- bec_widgets/utils/palette_viewer.py | 4 +- bec_widgets/utils/side_panel.py | 1 - bec_widgets/utils/ui_loader.py | 1 - bec_widgets/utils/widget_io.py | 144 ++++++++++++++---- bec_widgets/widgets/containers/dock/dock.py | 52 +++---- .../widgets/containers/dock/dock_area.py | 36 +++-- .../layout_manager/layout_manager.py | 1 - .../containers/main_window/main_window.py | 5 +- .../buttons/button_abort/button_abort.py | 5 +- .../buttons/button_reset/button_reset.py | 5 +- .../buttons/button_resume/button_resume.py | 4 +- .../buttons/stop_button/stop_button.py | 4 +- .../position_indicator/position_indicator.py | 4 +- .../_base/positioner_box_base.py | 4 +- .../positioner_group/positioner_group.py | 3 +- .../base_classes/device_signal_input_base.py | 1 + .../device_combobox/device_combobox.py | 3 +- .../device_line_edit/device_line_edit.py | 3 +- .../signal_combobox/signal_combobox.py | 3 +- .../signal_line_edit/signal_line_edit.py | 3 +- .../control/scan_control/scan_control.py | 3 +- .../dap/dap_combo_box/dap_combo_box.py | 3 +- .../widgets/dap/lmfit_dialog/lmfit_dialog.py | 5 +- .../editors/scan_metadata/scan_metadata.py | 4 +- .../widgets/editors/text_box/text_box.py | 3 +- .../widgets/editors/website/website.py | 3 +- bec_widgets/widgets/games/minesweeper.py | 4 +- bec_widgets/widgets/plots/image/image.py | 9 +- bec_widgets/widgets/plots/image/image_item.py | 17 ++- .../widgets/plots/motor_map/motor_map.py | 3 - .../motor_map/settings/motor_map_settings.py | 1 - .../plots/multi_waveform/multi_waveform.py | 3 - .../multi_waveform/settings/control_panel.py | 1 - bec_widgets/widgets/plots/plot_base.py | 6 +- .../plots/scatter_waveform/scatter_curve.py | 9 +- .../scatter_waveform/scatter_waveform.py | 2 - .../settings/scatter_curve_setting.py | 1 - .../plots/setting_menus/axis_settings.py | 1 - bec_widgets/widgets/plots/waveform/curve.py | 9 +- .../settings/curve_settings/curve_tree.py | 4 +- .../widgets/plots/waveform/waveform.py | 23 +-- .../bec_progressbar/bec_progressbar.py | 3 +- .../progress/ring_progress_bar/ring.py | 5 +- .../ring_progress_bar/ring_progress_bar.py | 3 +- .../widgets/services/bec_queue/bec_queue.py | 5 +- .../services/bec_status_box/bec_status_box.py | 3 +- .../services/device_browser/device_browser.py | 3 +- .../widgets/utility/logpanel/logpanel.py | 4 +- .../utility/spinbox/decimal_spinbox.py | 4 +- .../visual/colormap_widget/colormap_widget.py | 6 +- .../dark_mode_button/dark_mode_button.py | 8 +- tests/unit_tests/test_color_utils.py | 4 +- tests/unit_tests/test_device_input_base.py | 5 +- tests/unit_tests/test_device_signal_input.py | 3 +- tests/unit_tests/test_rpc_base.py | 2 +- 66 files changed, 467 insertions(+), 279 deletions(-) diff --git a/bec_widgets/applications/bw_launch.py b/bec_widgets/applications/bw_launch.py index c73bfaf6..d6e62506 100644 --- a/bec_widgets/applications/bw_launch.py +++ b/bec_widgets/applications/bw_launch.py @@ -1,6 +1,6 @@ from bec_widgets.widgets.containers.dock.dock_area import BECDockArea -def dock_area(name: str | None = None): - _dock_area = BECDockArea(name=name) +def dock_area(object_name: str | None = None): + _dock_area = BECDockArea(object_name=object_name) return _dock_area diff --git a/bec_widgets/applications/launch_window.py b/bec_widgets/applications/launch_window.py index 5618dde5..6c249d24 100644 --- a/bec_widgets/applications/launch_window.py +++ b/bec_widgets/applications/launch_window.py @@ -19,9 +19,8 @@ MODULE_PATH = os.path.dirname(bec_widgets.__file__) class LaunchWindow(BECWidget, QMainWindow): - def __init__(self, gui_id: str = None, *args, **kwargs): - BECWidget.__init__(self, gui_id=gui_id, **kwargs) - QMainWindow.__init__(self, *args, **kwargs) + def __init__(self, parent=None, gui_id: str = None, *args, **kwargs): + super().__init__(parent=parent, gui_id=gui_id, **kwargs) self.app = QApplication.instance() diff --git a/bec_widgets/cli/client_utils.py b/bec_widgets/cli/client_utils.py index adf32e43..2782a01c 100644 --- a/bec_widgets/cli/client_utils.py +++ b/bec_widgets/cli/client_utils.py @@ -10,7 +10,7 @@ import threading import time from contextlib import contextmanager from threading import Lock -from typing import TYPE_CHECKING, Literal, TypeAlias +from typing import TYPE_CHECKING, Literal, TypeAlias, cast from bec_lib.endpoints import MessageEndpoints from bec_lib.logger import bec_logger @@ -22,9 +22,9 @@ import bec_widgets.cli.client as client from bec_widgets.cli.rpc.rpc_base import RPCBase, RPCReference if TYPE_CHECKING: # pragma: no cover - from bec_lib.redis_connector import StreamMessage + from bec_lib.messages import GUIRegistryStateMessage else: - StreamMessage = lazy_import_from("bec_lib.redis_connector", ("StreamMessage",)) + GUIRegistryStateMessage = lazy_import_from("bec_lib.messages", "GUIRegistryStateMessage") logger = bec_logger.logger @@ -71,7 +71,11 @@ def _get_output(process, logger) -> None: def _start_plot_process( - gui_id: str, gui_class_id: str, config: dict | str, gui_class: str = "launcher", logger=None + gui_id: str, + gui_class_id: str, + config: dict | str, + gui_class: str = "dock_area", + logger=None, # FIXME change gui_class back to "launcher" later ) -> tuple[subprocess.Popen[str], threading.Thread | None]: """ Start the plot in a new process. @@ -199,7 +203,7 @@ class BECGuiClient(RPCBase): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self._lock = Lock() - self._default_dock_name = "bec" + self._anchor_widget = "launcher" self._auto_updates_enabled = True self._auto_updates = None self._killed = False @@ -220,7 +224,7 @@ class BECGuiClient(RPCBase): @property def launcher(self) -> RPCBase: """The launcher object.""" - return RPCBase(gui_id=f"{self._gui_id}:launcher", parent=self, name="launcher") + return RPCBase(gui_id=f"{self._gui_id}:launcher", parent=self, object_name="launcher") def connect_to_gui_server(self, gui_id: str) -> None: """Connect to a GUI server""" @@ -247,7 +251,7 @@ class BECGuiClient(RPCBase): @property def windows(self) -> dict: """Dictionary with dock areas in the GUI.""" - return {widget._name: widget for widget in self._top_level.values()} + return {widget.object_name: widget for widget in self._top_level.values()} @property def window_list(self) -> list: @@ -365,7 +369,7 @@ class BECGuiClient(RPCBase): # After 60s timeout. Should this raise an exception on timeout? while time.time() < time.time() + timeout: if len(list(self._server_registry.keys())) < 2 or not hasattr( - self, self._default_dock_name + self, self._anchor_widget ): time.sleep(0.1) else: @@ -383,7 +387,7 @@ class BECGuiClient(RPCBase): self._gui_started_event.clear() self._process, self._process_output_processing_thread = _start_plot_process( self._gui_id, - gui_class_id=self._default_dock_name, + gui_class_id="bec", config=self._client._service_config.config, # pylint: disable=protected-access logger=logger, ) @@ -413,11 +417,13 @@ class BECGuiClient(RPCBase): return self._start_server(wait=wait) @staticmethod - def _handle_registry_update(msg: StreamMessage, parent: BECGuiClient) -> None: + def _handle_registry_update( + msg: dict[str, GUIRegistryStateMessage], parent: BECGuiClient + ) -> None: # This was causing a deadlock during shutdown, not sure why. # with self._lock: self = parent - self._server_registry = msg["data"].state + self._server_registry = cast(dict[str, RegistryState], msg["data"].state) self._update_dynamic_namespace(self._server_registry) def _do_show_all(self): @@ -460,12 +466,11 @@ class BECGuiClient(RPCBase): for gui_id, widget in self._ipython_registry.items(): if gui_id not in server_registry: remove_from_registry.append(gui_id) - widget._refresh_references() for gui_id in remove_from_registry: self._ipython_registry.pop(gui_id) removed_widgets = [ - widget._name for widget in self._top_level.values() if widget._is_deleted() + widget.object_name for widget in self._top_level.values() if widget._is_deleted() ] for widget_name in removed_widgets: @@ -475,10 +480,13 @@ class BECGuiClient(RPCBase): delattr(self, widget_name) for gui_id, widget_ref in top_level_widgets.items(): - setattr(self, widget_ref._name, widget_ref) + setattr(self, widget_ref.object_name, widget_ref) self._top_level = top_level_widgets + for widget in self._ipython_registry.values(): + widget._refresh_references() + def _add_widget(self, state: dict, parent: object) -> RPCReference | None: """Add a widget to the namespace @@ -486,7 +494,7 @@ class BECGuiClient(RPCBase): state (dict): The state of the widget from the _server_registry. parent (object): The parent object. """ - name = state["name"] + object_name = state["object_name"] gui_id = state["gui_id"] if state["widget_class"] in IGNORE_WIDGETS: return @@ -495,7 +503,7 @@ class BECGuiClient(RPCBase): return obj = self._ipython_registry.get(gui_id) if obj is None: - widget = widget_class(gui_id=gui_id, name=name, parent=parent) + widget = widget_class(gui_id=gui_id, object_name=object_name, parent=parent) self._ipython_registry[gui_id] = widget else: widget = obj diff --git a/bec_widgets/cli/rpc/rpc_base.py b/bec_widgets/cli/rpc/rpc_base.py index 3424c806..c1d7bdf1 100644 --- a/bec_widgets/cli/rpc/rpc_base.py +++ b/bec_widgets/cli/rpc/rpc_base.py @@ -4,7 +4,7 @@ import inspect import threading import uuid from functools import wraps -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast from bec_lib.client import BECClient from bec_lib.endpoints import MessageEndpoints @@ -41,7 +41,7 @@ def rpc_call(func): def wrapper(self, *args, **kwargs): # we could rely on a strict type check here, but this is more flexible # moreover, it would anyway crash for objects... - caller_frame = inspect.currentframe().f_back + caller_frame = inspect.currentframe().f_back # type: ignore while caller_frame: if "jedi" in caller_frame.f_globals: # Jedi module is present, likely tab completion @@ -91,7 +91,7 @@ class RPCReference: def __init__(self, registry: dict, gui_id: str) -> None: self._registry = registry self._gui_id = gui_id - self._name = self._registry[self._gui_id]._name + self.object_name = self._registry[self._gui_id].object_name @check_for_deleted_widget def __getattr__(self, name): @@ -134,13 +134,13 @@ class RPCBase: self, gui_id: str | None = None, config: dict | None = None, - name: str | None = None, + object_name: str | None = None, parent=None, ) -> None: self._client = BECClient() # BECClient is a singleton; here, we simply get the instance self._config = config if config is not None else {} self._gui_id = gui_id if gui_id is not None else str(uuid.uuid4())[:5] - self._name = name if name is not None else str(uuid.uuid4())[:5] + self.object_name = object_name if object_name is not None else str(uuid.uuid4())[:5] self._parent = parent self._msg_wait_event = threading.Event() self._rpc_response = None @@ -163,7 +163,7 @@ class RPCBase: """ Get the widget name. """ - return self._name + return self.object_name @property def _root(self) -> BECGuiClient: @@ -175,7 +175,7 @@ class RPCBase: # pylint: disable=protected-access while parent._parent is not None: parent = parent._parent - return parent + return parent # type: ignore def _run_rpc(self, method, *args, wait_for_rpc_response=True, timeout=300, **kwargs) -> Any: """ @@ -219,7 +219,11 @@ class RPCBase: self._client.connector.unregister( MessageEndpoints.gui_instruction_response(request_id), cb=self._on_rpc_response ) - # get class name + + # we can assume that the response is a RequestResponseMessage, updated by + # the _on_rpc_response method + assert isinstance(self._rpc_response, messages.RequestResponseMessage) + if not self._rpc_response.accepted: raise ValueError(self._rpc_response.message["error"]) msg_result = self._rpc_response.message.get("result") @@ -227,8 +231,8 @@ class RPCBase: return self._create_widget_from_msg_result(msg_result) @staticmethod - def _on_rpc_response(msg: MessageObject, parent: RPCBase) -> None: - msg = msg.value + def _on_rpc_response(msg_obj: MessageObject, parent: RPCBase) -> None: + msg = cast(messages.RequestResponseMessage, msg_obj.value) parent._msg_wait_event.set() parent._rpc_response = msg @@ -283,12 +287,17 @@ class RPCBase: for key, val in self._root._server_registry.items(): parent_id = val["config"].get("parent_id") if parent_id == self._gui_id: - references[key] = {"gui_id": val["config"]["gui_id"], "name": val["name"]} + references[key] = { + "gui_id": val["config"]["gui_id"], + "object_name": val["object_name"], + } removed_references = set(self._rpc_references.keys()) - set(references.keys()) for key in removed_references: - delattr(self, self._rpc_references[key]["name"]) + delattr(self, self._rpc_references[key]["object_name"]) self._rpc_references = references for key, val in references.items(): setattr( - self, val["name"], RPCReference(self._root._ipython_registry, val["gui_id"]) + self, + val["object_name"], + RPCReference(self._root._ipython_registry, val["gui_id"]), ) diff --git a/bec_widgets/cli/rpc/rpc_register.py b/bec_widgets/cli/rpc/rpc_register.py index 75eb1b78..ee9a69a5 100644 --- a/bec_widgets/cli/rpc/rpc_register.py +++ b/bec_widgets/cli/rpc/rpc_register.py @@ -123,7 +123,7 @@ class RPCRegister: # This retrieves any rpc objects that are subclass of BECWidget, # i.e. curve and image items are excluded widgets = [rpc for rpc in self._rpc_register.values() if isinstance(rpc, cls)] - return [widget._name for widget in widgets] + return [widget.object_name for widget in widgets] def broadcast(self): """ @@ -172,6 +172,6 @@ class RPCRegisterBroadcast: """Exit the context manager""" self._call_depth -= 1 # Remove nested calls - if self._call_depth == 0: # Last one to exit is repsonsible for broadcasting + if self._call_depth == 0: # The Last one to exit is responsible for broadcasting self.rpc_register._skip_broadcast = False self.rpc_register.broadcast() diff --git a/bec_widgets/cli/rpc/rpc_widget_handler.py b/bec_widgets/cli/rpc/rpc_widget_handler.py index ed73e2ae..669db3fd 100644 --- a/bec_widgets/cli/rpc/rpc_widget_handler.py +++ b/bec_widgets/cli/rpc/rpc_widget_handler.py @@ -36,7 +36,7 @@ class RPCWidgetHandler: cls.__name__: cls for cls in clss.widgets if cls.__name__ not in IGNORE_WIDGETS } - def create_widget(self, widget_type, name: str | None = None, **kwargs) -> BECWidget: + def create_widget(self, widget_type, **kwargs) -> BECWidget: """ Create a widget from an RPC message. @@ -50,7 +50,7 @@ class RPCWidgetHandler: """ widget_class = self.widget_classes.get(widget_type) # type: ignore if widget_class: - return widget_class(name=name, **kwargs) + return widget_class(**kwargs) raise ValueError(f"Unknown widget type: {widget_type}") diff --git a/bec_widgets/cli/server.py b/bec_widgets/cli/server.py index 9f0480d7..6cd033e9 100644 --- a/bec_widgets/cli/server.py +++ b/bec_widgets/cli/server.py @@ -111,8 +111,8 @@ class GUIServer: self.setup_bec_icon() service_config = self._get_service_config() - self.dispatcher = BECDispatcher(config=service_config) - self.dispatcher.start_cli_server(gui_id=self.gui_id) + self.dispatcher = BECDispatcher(config=service_config, gui_id=self.gui_id) + # self.dispatcher.start_cli_server(gui_id=self.gui_id) self.launcher_window = LaunchWindow(gui_id=f"{self.gui_id}:launcher") self.launcher_window.setAttribute(Qt.WA_ShowWithoutActivating) # type: ignore @@ -139,8 +139,6 @@ class GUIServer: if self.app: self.app.quit() - # gui.bec.close() - # win.shutdown() signal.signal(signal.SIGINT, sigint_handler) signal.signal(signal.SIGTERM, sigint_handler) diff --git a/bec_widgets/utils/bec_connector.py b/bec_widgets/utils/bec_connector.py index 3c7a25d0..00d7c96b 100644 --- a/bec_widgets/utils/bec_connector.py +++ b/bec_widgets/utils/bec_connector.py @@ -10,21 +10,23 @@ from typing import TYPE_CHECKING, Optional from bec_lib.logger import bec_logger from bec_lib.utils.import_utils import lazy_import_from from pydantic import BaseModel, Field, field_validator -from qtpy.QtCore import QObject, QRunnable, QThreadPool, Signal +from qtpy.QtCore import QObject, QRunnable, Qt, QThreadPool, Signal from qtpy.QtWidgets import QApplication from bec_widgets.cli.rpc.rpc_register import RPCRegister from bec_widgets.utils.container_utils import WidgetContainerUtils from bec_widgets.utils.error_popups import ErrorPopupUtility from bec_widgets.utils.error_popups import SafeSlot as pyqtSlot +from bec_widgets.utils.widget_io import WidgetHierarchy from bec_widgets.utils.yaml_dialog import load_yaml, load_yaml_gui, save_yaml, save_yaml_gui if TYPE_CHECKING: # pragma: no cover from bec_widgets.utils.bec_dispatcher import BECDispatcher from bec_widgets.widgets.containers.dock import BECDock +else: + BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",)) logger = bec_logger.logger -BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",)) class ConnectionConfig(BaseModel): @@ -82,14 +84,21 @@ class BECConnector: client=None, config: ConnectionConfig | None = None, gui_id: str | None = None, - name: str | None = None, - parent_dock: BECDock | None = None, + object_name: str | None = None, + parent_dock: BECDock | None = None, # TODO should go away -> issue created #473 parent_id: str | None = None, + **kwargs, ): + # Extract object_name from kwargs to not pass it to Qt class + object_name = object_name or kwargs.pop("objectName", None) + # Ensure the parent is always the first argument for QObject + parent = kwargs.pop("parent", None) + # This initializes the QObject or any qt related class + super().__init__(parent=parent, **kwargs) # BEC related connections self.bec_dispatcher = BECDispatcher(client=client) self.client = self.bec_dispatcher.client if client is None else client - self._parent_dock = parent_dock + self._parent_dock = parent_dock # TODO also remove at some point -> issue created #473 if not self.client in BECConnector.EXIT_HANDLERS: # register function to clean connections at exit; @@ -122,12 +131,24 @@ class BECConnector: self.gui_id: str = gui_id # Keep namespace in sync else: self.gui_id: str = self.config.gui_id # type: ignore - if name is None: - name = self.__class__.__name__ - else: - if not WidgetContainerUtils.has_name_valid_chars(name): - raise ValueError(f"Name {name} contains invalid characters.") - self._name = name if name else self.__class__.__name__ + + # TODO Hierarchy can be refreshed upon creation -> also registry should be notified if objectName changes -> issue #472 + if object_name is not None: + self.setObjectName(object_name) + + # 1) If no objectName is set, set the initial name + if not self.objectName(): + self.setObjectName(self.__class__.__name__) + self.object_name = self.objectName() + + # 2) Enforce unique objectName among siblings with the same BECConnector parent + self.setParent(parent) + if parent_id is None: + connector_parent = WidgetHierarchy._get_becwidget_ancestor(self) + if connector_parent is not None: + self.parent_id = connector_parent.gui_id + + self._enforce_unique_sibling_name() self.rpc_register = RPCRegister() self.rpc_register.add_rpc(self) @@ -138,6 +159,49 @@ class BECConnector: # Store references to running workers so they're not garbage collected prematurely. self._workers = [] + def _enforce_unique_sibling_name(self): + """ + Enforce that this BECConnector has a unique objectName among its siblings. + + Sibling logic: + - If there's a nearest BECConnector parent, only compare with children of that parent. + - If parent is None (i.e., top-level object), compare with all other top-level BECConnectors. + """ + parent_bec = WidgetHierarchy._get_becwidget_ancestor(self) + + if parent_bec: + # We have a parent => only compare with siblings under that parent + siblings = parent_bec.findChildren(BECConnector) + else: + # No parent => treat all top-level BECConnectors as siblings + # 1) Gather all BECConnectors from QApplication + all_widgets = QApplication.allWidgets() + all_bec = [w for w in all_widgets if isinstance(w, BECConnector)] + # 2) "Top-level" means closest BECConnector parent is None + top_level_bec = [ + w for w in all_bec if WidgetHierarchy._get_becwidget_ancestor(w) is None + ] + # 3) We are among these top-level siblings + siblings = top_level_bec + + # Collect used names among siblings + used_names = {sib.objectName() for sib in siblings if sib is not self} + + base_name = self.objectName() + if base_name not in used_names: + # Name is already unique among siblings + return + + # Need a suffix to avoid collision + counter = 0 + while True: + trial_name = f"{base_name}_{counter}" + if trial_name not in used_names: + self.setObjectName(trial_name) + self.object_name = trial_name + break + counter += 1 + def submit_task(self, fn, *args, on_complete: pyqtSlot = None, **kwargs) -> Worker: """ Submit a task to run in a separate thread. The task will run the specified @@ -316,8 +380,9 @@ class BECConnector: def remove(self): """Cleanup the BECConnector""" # If the widget is attached to a dock, remove it from the dock. + # TODO this should be handled by dock and dock are not by BECConnector if self._parent_dock is not None: - self._parent_dock.delete(self._name) + self._parent_dock.delete(self.object_name) # If the widget is from Qt, trigger its close method. elif hasattr(self, "close"): self.close() diff --git a/bec_widgets/utils/bec_dispatcher.py b/bec_widgets/utils/bec_dispatcher.py index ed5bcf21..93c6c547 100644 --- a/bec_widgets/utils/bec_dispatcher.py +++ b/bec_widgets/utils/bec_dispatcher.py @@ -80,13 +80,21 @@ class BECDispatcher: client: BECClient cli_server: CLIServer | None = None - def __new__(cls, client=None, config: str | ServiceConfig | None = None, *args, **kwargs): + # TODO add custom gui id for server + def __new__( + cls, + client=None, + config: str | ServiceConfig | None = None, + gui_id: str = None, + *args, + **kwargs, + ): if cls._instance is None: cls._instance = super(BECDispatcher, cls).__new__(cls) cls._initialized = False return cls._instance - def __init__(self, client=None, config: str | ServiceConfig | None = None): + def __init__(self, client=None, config: str | ServiceConfig | None = None, gui_id: str = None): if self._initialized: return @@ -114,6 +122,8 @@ class BECDispatcher: logger.warning("Could not connect to Redis, skipping start of BECClient.") logger.success("Initialized BECDispatcher") + + self.start_cli_server(gui_id=gui_id) self._initialized = True @classmethod @@ -207,7 +217,7 @@ class BECDispatcher: logger.error("Cannot start CLI server without a running client") return self.cli_server = CLIServer(gui_id, dispatcher=self, client=self.client) - logger.success("Started CLI server with gui_id: {gui_id}") + logger.success(f"Started CLI server with gui_id: {gui_id}") def stop_cli_server(self): """ diff --git a/bec_widgets/utils/bec_widget.py b/bec_widgets/utils/bec_widget.py index f2c150ed..ae3456c5 100644 --- a/bec_widgets/utils/bec_widget.py +++ b/bec_widgets/utils/bec_widget.py @@ -1,11 +1,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import darkdetect from bec_lib.logger import bec_logger -from qtpy.QtCore import Slot -from qtpy.QtWidgets import QApplication, QWidget +from qtpy.QtCore import QObject, Slot +from qtpy.QtWidgets import QApplication from bec_widgets.cli.rpc.rpc_register import RPCRegister from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig @@ -32,8 +32,7 @@ class BECWidget(BECConnector): config: ConnectionConfig = None, gui_id: str | None = None, theme_update: bool = False, - name: str | None = None, - parent_dock: BECDock | None = None, + parent_dock: BECDock | None = None, # TODO should go away -> issue created #473 parent_id: str | None = None, **kwargs, ): @@ -54,16 +53,17 @@ class BECWidget(BECConnector): theme_update(bool, optional): Whether to subscribe to theme updates. Defaults to False. When set to True, the widget's apply_theme method will be called when the theme changes. """ - if not isinstance(self, QWidget): - raise RuntimeError(f"{repr(self)} is not a subclass of QWidget") + super().__init__( client=client, config=config, gui_id=gui_id, - name=name, parent_dock=parent_dock, parent_id=parent_id, + **kwargs, ) + if not isinstance(self, QObject): + raise RuntimeError(f"{repr(self)} is not a subclass of QWidget") app = QApplication.instance() if not hasattr(app, "theme"): # DO NOT SET THE THEME TO AUTO! Otherwise, the qwebengineview will segfault diff --git a/bec_widgets/utils/cli_server.py b/bec_widgets/utils/cli_server.py index ad06633b..eec57eec 100644 --- a/bec_widgets/utils/cli_server.py +++ b/bec_widgets/utils/cli_server.py @@ -11,12 +11,15 @@ from bec_lib.endpoints import MessageEndpoints from bec_lib.logger import bec_logger from bec_lib.utils.import_utils import lazy_import from qtpy.QtCore import QTimer +from qtpy.QtWidgets import QApplication from redis.exceptions import RedisError from bec_widgets.cli.rpc.rpc_register import RPCRegister from bec_widgets.utils import BECDispatcher from bec_widgets.utils.bec_connector import BECConnector from bec_widgets.utils.error_popups import ErrorPopupUtility +from bec_widgets.utils.widget_io import WidgetHierarchy +from bec_widgets.widgets.plots.plot_base import PlotBase if TYPE_CHECKING: from bec_lib import messages @@ -77,6 +80,7 @@ class CLIServer: self._heartbeat_timer = QTimer() self._heartbeat_timer.timeout.connect(self.emit_heartbeat) self._heartbeat_timer.start(200) + self._registry_update_callbacks = [] self.status = messages.BECStatus.RUNNING logger.success(f"Server started with gui_id: {self.gui_id}") @@ -141,17 +145,10 @@ class CLIServer: def serialize_object(self, obj): if isinstance(obj, BECConnector): - config = obj.config.model_dump() - config["parent_id"] = obj.parent_id # add parent_id to config - return { - "gui_id": obj.gui_id, - "name": ( - obj._name if hasattr(obj, "_name") else obj.__class__.__name__ - ), # pylint: disable=protected-access - "widget_class": obj.__class__.__name__, - "config": config, - "__rpc__": True, - } + # Respect RPC = False + if hasattr(obj, "RPC") and obj.RPC is False: + return None + return self._serialize_bec_connector(obj) return obj def emit_heartbeat(self): @@ -166,19 +163,57 @@ class CLIServer: logger.error(f"Error while emitting heartbeat: {exc}") def broadcast_registry_update(self, connections: dict): - """ - Broadcast the updated registry to all clients. - """ + data = {} + for key, val in connections.items(): + if not isinstance(val, BECConnector): + continue + if not getattr(val, "RPC", True): + continue + data[key] = self._serialize_bec_connector(val) - # We only need to broadcast the dock areas - data = {key: self.serialize_object(val) for key, val in connections.items()} logger.info(f"Broadcasting registry update: {data} for {self.gui_id}") self.client.connector.xadd( MessageEndpoints.gui_registry_state(self.gui_id), msg_dict={"data": messages.GUIRegistryStateMessage(state=data)}, - max_size=1, # only single message in stream + max_size=1, ) + def _serialize_bec_connector(self, connector: BECConnector) -> dict: + """ + Create the serialization dict for a single BECConnector, + setting 'parent_id' via the real nearest BECConnector parent. + """ + + config_dict = connector.config.model_dump() + config_dict["parent_id"] = getattr(connector, "parent_id", None) + + return { + "gui_id": connector.gui_id, + "object_name": connector.object_name or connector.__class__.__name__, + "widget_class": connector.__class__.__name__, + "config": config_dict, + "__rpc__": True, + } + + @staticmethod + def _get_becwidget_ancestor(widget): + """ + Traverse up the parent chain to find the nearest BECConnector. + Returns None if none is found. + """ + from bec_widgets.utils import BECConnector + + parent = widget.parent() + while parent is not None: + if isinstance(parent, BECConnector): + return parent + parent = parent.parent() + return None + + # Suppose clients register callbacks to receive updates + def add_registry_update_callback(self, cb): + self._registry_update_callbacks.append(cb) + def shutdown(self): # TODO not sure if needed when cleanup is done at level of BECConnector self.status = messages.BECStatus.IDLE self._heartbeat_timer.stop() diff --git a/bec_widgets/utils/palette_viewer.py b/bec_widgets/utils/palette_viewer.py index c9dd0c38..766cf96a 100644 --- a/bec_widgets/utils/palette_viewer.py +++ b/bec_widgets/utils/palette_viewer.py @@ -22,10 +22,10 @@ class PaletteViewer(BECWidget, QWidget): """ ICON_NAME = "palette" + RPC = False def __init__(self, *args, parent=None, **kwargs): - super().__init__(*args, theme_update=True, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, theme_update=True, **kwargs) self.setFixedSize(400, 600) layout = QVBoxLayout(self) dark_mode_button = DarkModeButton(self) diff --git a/bec_widgets/utils/side_panel.py b/bec_widgets/utils/side_panel.py index 32e46b93..955231f1 100644 --- a/bec_widgets/utils/side_panel.py +++ b/bec_widgets/utils/side_panel.py @@ -35,7 +35,6 @@ class SidePanel(QWidget): super().__init__(parent=parent) self.setProperty("skip_settings", True) - self.setObjectName("SidePanel") self._orientation = orientation self._panel_max_width = panel_max_width diff --git a/bec_widgets/utils/ui_loader.py b/bec_widgets/utils/ui_loader.py index 83299cfc..006070b4 100644 --- a/bec_widgets/utils/ui_loader.py +++ b/bec_widgets/utils/ui_loader.py @@ -31,7 +31,6 @@ if PYSIDE6: f"Custom widget {class_name} does not have a parent_id argument. " ) widget = self.custom_widgets[class_name](self.baseinstance) - widget.setObjectName(name) return widget return super().createWidget(class_name, self.baseinstance, name) diff --git a/bec_widgets/utils/widget_io.py b/bec_widgets/utils/widget_io.py index 75d090e5..c7fdad03 100644 --- a/bec_widgets/utils/widget_io.py +++ b/bec_widgets/utils/widget_io.py @@ -18,7 +18,6 @@ from qtpy.QtWidgets import ( QWidget, ) -from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.widgets.utility.toggle.toggle import ToggleSwitch @@ -291,62 +290,141 @@ class WidgetHierarchy: only_bec_widgets(bool, optional): Whether to print only widgets that are instances of BECWidget. show_parent(bool, optional): Whether to display which BECWidget is the parent of each discovered BECWidget. """ - # Decide if this particular widget is to be printed - is_bec = isinstance(widget, BECWidget) - print_this = (not only_bec_widgets) or is_bec + from bec_widgets.utils import BECConnector + from bec_widgets.widgets.plots.waveform.waveform import Waveform - # If it is a BECWidget and we're showing the parent, climb the chain to find the nearest BECWidget ancestor + # 1) Filter out widgets that are not BECConnectors (if 'only_bec_widgets' is True) + is_bec = isinstance(widget, BECConnector) + if only_bec_widgets and not is_bec: + return + + # 2) Determine and print the parent's info (closest BECConnector) + parent_info = "" if show_parent and is_bec: ancestor = WidgetHierarchy._get_becwidget_ancestor(widget) - if ancestor is not None: - parent_info = f" parent={ancestor.__class__.__name__}" + if ancestor: + parent_label = ancestor.objectName() or ancestor.__class__.__name__ + parent_info = f" parent={parent_label}" else: parent_info = " parent=None" - else: - parent_info = "" - if print_this: - widget_info = f"{widget.__class__.__name__} ({widget.objectName()}){parent_info}" - if grab_values: - value = WidgetIO.get_value(widget, ignore_errors=True) - value_str = f" [value: {value}]" if value is not None else "" - widget_info += value_str - print(prefix + widget_info) + widget_info = f"{widget.__class__.__name__} ({widget.objectName()}){parent_info}" + print(prefix + widget_info) - # Always recurse so we can discover deeper BECWidgets even if the current widget is not a BECWidget - children = widget.children() - for i, child in enumerate(children): - # Possibly skip known internal child widgets of a QComboBox - if ( - exclude_internal_widgets - and isinstance(widget, QComboBox) - and child.__class__.__name__ in ["QFrame", "QBoxLayout", "QListView"] - ): + # 3) If it's a Waveform, explicitly print the curves + if isinstance(widget, Waveform): + for curve in widget.curves: + curve_prefix = prefix + " └─ " + print( + f"{curve_prefix}{curve.__class__.__name__} ({curve.objectName()}) " + f"parent={widget.objectName()}" + ) + + # 4) Recursively handle each child if: + # - It's a QWidget + # - It is a BECConnector (or we don't care about filtering) + # - Its closest BECConnector parent is the current widget + for child in widget.findChildren(QWidget): + if only_bec_widgets and not isinstance(child, BECConnector): continue - child_prefix = prefix + " " - arrow = "├─ " if child != children[-1] else "└─ " - - # Regardless of whether child is BECWidget or not, keep recursing, or we might miss deeper BECWidgets + # if WidgetHierarchy._get_becwidget_ancestor(child) == widget: + child_prefix = prefix + " └─ " WidgetHierarchy.print_widget_hierarchy( child, - indent + 1, + indent=indent + 1, grab_values=grab_values, - prefix=child_prefix + arrow, + prefix=child_prefix, exclude_internal_widgets=exclude_internal_widgets, only_bec_widgets=only_bec_widgets, show_parent=show_parent, ) + @staticmethod + def print_becconnector_hierarchy_from_app(): + """ + Enumerate ALL BECConnector objects in the QApplication. + Also detect if a widget is a PlotBase, and add any data items + (PlotDataItem-like) that are also BECConnector objects. + + Build a parent->children graph where each child's 'parent' + is its closest BECConnector ancestor. Print the entire hierarchy + from the root(s). + + The result is a single, consolidated tree for your entire + running GUI, including PlotBase data items that are BECConnector. + """ + import sys + from collections import defaultdict + + from qtpy.QtWidgets import QApplication + + from bec_widgets.utils import BECConnector + from bec_widgets.widgets.plots.plot_base import PlotBase + + # 1) Gather ALL QWidget-based BECConnector objects + all_qwidgets = QApplication.allWidgets() + bec_widgets = set(w for w in all_qwidgets if isinstance(w, BECConnector)) + + # 2) Also gather any BECConnector-based data items from PlotBase widgets + for w in all_qwidgets: + if isinstance(w, PlotBase) and hasattr(w, "plot_item"): + plot_item = w.plot_item + if hasattr(plot_item, "listDataItems"): + for data_item in plot_item.listDataItems(): + if isinstance(data_item, BECConnector): + bec_widgets.add(data_item) + + # 3) Build a map of (closest BECConnector parent) -> list of children + parent_map = defaultdict(list) + for w in bec_widgets: + parent_bec = WidgetHierarchy._get_becwidget_ancestor(w) + parent_map[parent_bec].append(w) + + # 4) Define a recursive printer to show each object's children + def print_tree(parent, prefix=""): + children = parent_map[parent] + for i, child in enumerate(children): + connector_class = child.__class__.__name__ + connector_name = child.objectName() or connector_class + + if parent is None: + parent_label = "None" + else: + parent_label = parent.objectName() or parent.__class__.__name__ + + line = f"{connector_class} ({connector_name}) parent={parent_label}" + # Determine tree-branch symbols + is_last = i == len(children) - 1 + branch_str = "└─ " if is_last else "├─ " + print(prefix + branch_str + line) + + # Recurse deeper + next_prefix = prefix + (" " if is_last else "│ ") + print_tree(child, prefix=next_prefix) + + # 5) Print top-level items (roots) whose BECConnector parent is None + roots = parent_map[None] + for r_i, root in enumerate(roots): + root_class = root.__class__.__name__ + root_name = root.objectName() or root_class + line = f"{root_class} ({root_name}) parent=None" + is_last_root = r_i == len(roots) - 1 + print(line) + # Recurse into its children + print_tree(root, prefix=" ") + @staticmethod def _get_becwidget_ancestor(widget): """ - Climb the parent chain to find the nearest BECWidget above this widget. + Traverse up the parent chain to find the nearest BECConnector. Returns None if none is found. """ + from bec_widgets.utils import BECConnector + parent = widget.parent() while parent is not None: - if isinstance(parent, BECWidget): + if isinstance(parent, BECConnector): return parent parent = parent.parent() return None diff --git a/bec_widgets/widgets/containers/dock/dock.py b/bec_widgets/widgets/containers/dock/dock.py index 587b6be6..9dc0da24 100644 --- a/bec_widgets/widgets/containers/dock/dock.py +++ b/bec_widgets/widgets/containers/dock/dock.py @@ -133,6 +133,7 @@ class BECDock(BECWidget, Dock): parent_id: str | None = None, config: DockConfig | None = None, name: str | None = None, + object_name: str | None = None, client=None, gui_id: str | None = None, closable: bool = True, @@ -148,12 +149,17 @@ class BECDock(BECWidget, Dock): if isinstance(config, dict): config = DockConfig(**config) self.config = config - super().__init__( - client=client, config=config, gui_id=gui_id, name=name, parent_id=parent_id - ) # Name was checked and created in BEC Widget label = CustomDockLabel(text=name, closable=closable) - Dock.__init__(self, name=name, label=label, parent=self, **kwargs) - # Dock.__init__(self, name=name, **kwargs) + super().__init__( + parent=parent_dock_area, + name=name, + object_name=object_name, + client=client, + gui_id=gui_id, + config=config, + label=label, + **kwargs, + ) self.parent_dock_area = parent_dock_area # Layout Manager @@ -193,7 +199,7 @@ class BECDock(BECWidget, Dock): widgets(dict): The widgets in the dock. """ # pylint: disable=protected-access - return dict((widget._name, widget) for widget in self.element_list) + return dict((widget.object_name, widget) for widget in self.element_list) @property def element_list(self) -> list[BECWidget]: @@ -296,27 +302,11 @@ class BECDock(BECWidget, Dock): shift(Literal["down", "up", "left", "right"]): The direction to shift the widgets if the position is occupied. """ if row is None: - # row = cast(int, self.layout.rowCount()) # type:ignore row = self.layout.rowCount() - # row = cast(int, row) if self.layout_manager.is_position_occupied(row, col): self.layout_manager.shift_widgets(shift, start_row=row) - existing_widgets_parent_dock = self._get_list_of_widget_name_of_parent_dock_area() - - if name is not None: # Name is provided - if name in existing_widgets_parent_dock: - # pylint: disable=protected-access - raise ValueError( - f"Name {name} must be unique for widgets, but already exists in DockArea " - f"with name: {self.parent_dock_area._name} and id {self.parent_dock_area.gui_id}." - ) - else: # Name is not provided - widget_class_name = widget if isinstance(widget, str) else widget.__class__.__name__ - name = WidgetContainerUtils.generate_unique_name( - name=widget_class_name, list_of_names=existing_widgets_parent_dock - ) # Check that Widget is not BECDock or BECDockArea widget_class_name = widget if isinstance(widget, str) else widget.__class__.__name__ if widget_class_name in IGNORE_WIDGETS: @@ -326,16 +316,20 @@ class BECDock(BECWidget, Dock): widget = cast( BECWidget, widget_handler.create_widget( - widget_type=widget, name=name, parent_dock=self, parent_id=self.gui_id + widget_type=widget, + object_name=name, + parent_dock=self, + parent_id=self.gui_id, + parent=self, ), ) else: - widget._name = name # pylint: disable=protected-access + widget.object_name = name self.addWidget(widget, row=row, col=col, rowspan=rowspan, colspan=colspan) if hasattr(widget, "config"): widget.config.gui_id = widget.gui_id - self.config.widgets[widget._name] = widget.config # pylint: disable=protected-access + self.config.widgets[widget.object_name] = widget.config return widget def move_widget(self, widget: QWidget, new_row: int, new_col: int): @@ -365,7 +359,7 @@ class BECDock(BECWidget, Dock): """ Remove the dock from the parent dock area. """ - self.parent_dock_area.delete(self._name) + self.parent_dock_area.delete(self.object_name) def delete(self, widget_name: str) -> None: """ @@ -375,7 +369,7 @@ class BECDock(BECWidget, Dock): widget_name(str): Delete the widget with the given name. """ # pylint: disable=protected-access - widgets = [widget for widget in self.widgets if widget._name == widget_name] + widgets = [widget for widget in self.widgets if widget.object_name == widget_name] if len(widgets) == 0: logger.warning( f"Widget with name {widget_name} not found in dock {self.name()}. " @@ -391,7 +385,7 @@ class BECDock(BECWidget, Dock): else: widget = widgets[0] self.layout.removeWidget(widget) - self.config.widgets.pop(widget._name, None) + self.config.widgets.pop(widget.object_name, None) if widget in self.widgets: self.widgets.remove(widget) widget.close() @@ -401,7 +395,7 @@ class BECDock(BECWidget, Dock): Remove all widgets from the dock. """ for widget in self.widgets: - self.delete(widget._name) # pylint: disable=protected-access + self.delete(widget.object_name) def cleanup(self): """ diff --git a/bec_widgets/widgets/containers/dock/dock_area.py b/bec_widgets/widgets/containers/dock/dock_area.py index 5bcd87e6..473ff0f9 100644 --- a/bec_widgets/widgets/containers/dock/dock_area.py +++ b/bec_widgets/widgets/containers/dock/dock_area.py @@ -21,6 +21,7 @@ from bec_widgets.utils.toolbar import ( ModularToolBar, SeparatorAction, ) +from bec_widgets.utils.widget_io import WidgetHierarchy from bec_widgets.widgets.containers.dock.dock import BECDock, DockConfig from bec_widgets.widgets.control.device_control.positioner_box import PositionerBox from bec_widgets.widgets.control.scan_control.scan_control import ScanControl @@ -73,7 +74,7 @@ class BECDockArea(BECWidget, QWidget): config: DockAreaConfig | None = None, client=None, gui_id: str = None, - name: str | None = None, + object_name: str = None, **kwargs, ) -> None: if config is None: @@ -82,9 +83,15 @@ class BECDockArea(BECWidget, QWidget): if isinstance(config, dict): config = DockAreaConfig(**config) self.config = config - super().__init__(client=client, config=config, gui_id=gui_id, name=name, **kwargs) - QWidget.__init__(self, parent=parent) - self._parent = parent + super().__init__( + parent=parent, + object_name=object_name, + client=client, + gui_id=gui_id, + config=config, + **kwargs, + ) + self._parent = parent # TODO probably not needed self.layout = QVBoxLayout(self) self.layout.setSpacing(5) self.layout.setContentsMargins(0, 0, 0, 0) @@ -354,17 +361,26 @@ class BECDockArea(BECWidget, QWidget): Returns: BECDock: The created dock. """ - dock_names = [dock._name for dock in self.panel_list] # pylint: disable=protected-access + dock_names = [ + dock.object_name for dock in self.panel_list + ] # pylint: disable=protected-access if name is not None: # Name is provided if name in dock_names: raise ValueError( f"Name {name} must be unique for docks, but already exists in DockArea " - f"with name: {self._name} and id {self.gui_id}." + f"with name: {self.object_name} and id {self.gui_id}." ) else: # Name is not provided name = WidgetContainerUtils.generate_unique_name(name="dock", list_of_names=dock_names) - dock = BECDock(name=name, parent_dock_area=self, parent_id=self.gui_id, closable=closable) + dock = BECDock( + parent=self, + name=name, # this is dock name pyqtgraph property, this is displayed on label + object_name=name, # this is a real qt object name passed to BECConnector + parent_dock_area=self, + parent_id=self.gui_id, + closable=closable, + ) dock.config.position = position self.config.docks[dock.name()] = dock.config # The dock.name is equal to the name passed to BECDock @@ -499,11 +515,13 @@ if __name__ == "__main__": # pragma: no cover app = QApplication([]) set_theme("auto") dock_area = BECDockArea() - dock_1 = dock_area.new(name="dock_0", widget="Waveform") + dock_1 = dock_area.new(name="dock_0", widget="DarkModeButton") + dock_1.new(widget="DarkModeButton") # dock_1 = dock_area.new(name="dock_0", widget="Waveform") - dock_area.new(widget="Waveform") + dock_area.new(widget="DarkModeButton") dock_area.show() dock_area.setGeometry(100, 100, 800, 600) app.topLevelWidgets() + WidgetHierarchy.print_becconnector_hierarchy_from_app() app.exec_() sys.exit(app.exec_()) diff --git a/bec_widgets/widgets/containers/layout_manager/layout_manager.py b/bec_widgets/widgets/containers/layout_manager/layout_manager.py index f215e98f..a5238cca 100644 --- a/bec_widgets/widgets/containers/layout_manager/layout_manager.py +++ b/bec_widgets/widgets/containers/layout_manager/layout_manager.py @@ -34,7 +34,6 @@ class LayoutManagerWidget(QWidget): def __init__(self, parent=None, auto_reindex=True): super().__init__(parent) - self.setObjectName("LayoutManagerWidget") self.layout = QGridLayout(self) self.auto_reindex = auto_reindex diff --git a/bec_widgets/widgets/containers/main_window/main_window.py b/bec_widgets/widgets/containers/main_window/main_window.py index 4f2a2ab7..373900cd 100644 --- a/bec_widgets/widgets/containers/main_window/main_window.py +++ b/bec_widgets/widgets/containers/main_window/main_window.py @@ -20,9 +20,8 @@ logger = bec_logger.logger class BECMainWindow(BECWidget, QMainWindow): - def __init__(self, gui_id: str = None, *args, **kwargs): - BECWidget.__init__(self, gui_id=gui_id, **kwargs) - QMainWindow.__init__(self, *args, **kwargs) + def __init__(self, parent=None, gui_id: str = None, *args, **kwargs): + super().__init__(parent=parent, gui_id=gui_id, **kwargs) self.app = QApplication.instance() diff --git a/bec_widgets/widgets/control/buttons/button_abort/button_abort.py b/bec_widgets/widgets/control/buttons/button_abort/button_abort.py index 9af01292..dfae2de7 100644 --- a/bec_widgets/widgets/control/buttons/button_abort/button_abort.py +++ b/bec_widgets/widgets/control/buttons/button_abort/button_abort.py @@ -11,6 +11,7 @@ class AbortButton(BECWidget, QWidget): PLUGIN = True ICON_NAME = "cancel" + RPC = False def __init__( self, @@ -22,9 +23,7 @@ class AbortButton(BECWidget, QWidget): scan_id=None, **kwargs, ): - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent=parent) - + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self.get_bec_shortcuts() self.layout = QHBoxLayout(self) diff --git a/bec_widgets/widgets/control/buttons/button_reset/button_reset.py b/bec_widgets/widgets/control/buttons/button_reset/button_reset.py index 3b52160c..dc468a31 100644 --- a/bec_widgets/widgets/control/buttons/button_reset/button_reset.py +++ b/bec_widgets/widgets/control/buttons/button_reset/button_reset.py @@ -11,11 +11,10 @@ class ResetButton(BECWidget, QWidget): PLUGIN = True ICON_NAME = "restart_alt" + RPC = False def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs): - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent=parent) - + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self.get_bec_shortcuts() self.layout = QHBoxLayout(self) diff --git a/bec_widgets/widgets/control/buttons/button_resume/button_resume.py b/bec_widgets/widgets/control/buttons/button_resume/button_resume.py index a1256260..c27369ed 100644 --- a/bec_widgets/widgets/control/buttons/button_resume/button_resume.py +++ b/bec_widgets/widgets/control/buttons/button_resume/button_resume.py @@ -11,10 +11,10 @@ class ResumeButton(BECWidget, QWidget): PLUGIN = True ICON_NAME = "resume" + RPC = False def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs): - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self.get_bec_shortcuts() diff --git a/bec_widgets/widgets/control/buttons/stop_button/stop_button.py b/bec_widgets/widgets/control/buttons/stop_button/stop_button.py index 0d55a4bb..201a108a 100644 --- a/bec_widgets/widgets/control/buttons/stop_button/stop_button.py +++ b/bec_widgets/widgets/control/buttons/stop_button/stop_button.py @@ -11,10 +11,10 @@ class StopButton(BECWidget, QWidget): PLUGIN = True ICON_NAME = "dangerous" + RPC = False def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs): - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self.get_bec_shortcuts() diff --git a/bec_widgets/widgets/control/device_control/position_indicator/position_indicator.py b/bec_widgets/widgets/control/device_control/position_indicator/position_indicator.py index fd079a12..67230f4f 100644 --- a/bec_widgets/widgets/control/device_control/position_indicator/position_indicator.py +++ b/bec_widgets/widgets/control/device_control/position_indicator/position_indicator.py @@ -13,8 +13,8 @@ class PositionIndicator(BECWidget, QWidget): ICON_NAME = "horizontal_distribute" def __init__(self, parent=None, client=None, config=None, gui_id=None, **kwargs): - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) + self.position = 50 self.min_value = 0 self.max_value = 100 diff --git a/bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py b/bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py index c09d4c93..449ae7e3 100644 --- a/bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py +++ b/bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py @@ -47,6 +47,7 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget): current_path = "" ICON_NAME = "switch_right" + RPC = False def __init__(self, parent=None, **kwargs): """Initialize the PositionerBox widget. @@ -55,8 +56,7 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget): parent: The parent widget. device (Positioner): The device to control. """ - super().__init__(**kwargs) - CompactPopupWidget.__init__(self, parent=parent, layout=QVBoxLayout) + super().__init__(parent=parent, layout=QVBoxLayout, **kwargs) self._dialog = None self.get_bec_shortcuts() diff --git a/bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py b/bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py index 962eca8b..1cbf871f 100644 --- a/bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py +++ b/bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py @@ -69,8 +69,7 @@ class PositionerGroup(BECWidget, QWidget): Args: parent: The parent widget. """ - super().__init__(**kwargs) - QWidget.__init__(self, parent) + super().__init__(parent=parent, **kwargs) self.get_bec_shortcuts() diff --git a/bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py b/bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py index 06c03544..37f4c45d 100644 --- a/bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py +++ b/bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py @@ -29,6 +29,7 @@ class DeviceSignalInputBase(BECWidget): signal object based on the current text of the widget. """ + RPC = False _filter_handler = { Kind.hinted: "include_hinted_signals", Kind.normal: "include_normal_signals", diff --git a/bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py b/bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py index adf5161d..7be2c2fe 100644 --- a/bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py +++ b/bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py @@ -47,8 +47,7 @@ class DeviceComboBox(DeviceInputBase, QComboBox): arg_name: str | None = None, **kwargs, ): - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QComboBox.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) if arg_name is not None: self.config.arg_name = arg_name self.arg_name = arg_name diff --git a/bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py b/bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py index a97a9960..58fb0b7d 100644 --- a/bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py +++ b/bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py @@ -53,8 +53,7 @@ class DeviceLineEdit(DeviceInputBase, QLineEdit): self._callback_id = None self._is_valid_input = False self._accent_colors = get_accent_colors() - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QLineEdit.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self.completer = QCompleter(self) self.setCompleter(self.completer) diff --git a/bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py b/bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py index 43ea8cc5..babd2064 100644 --- a/bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py +++ b/bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py @@ -40,8 +40,7 @@ class SignalComboBox(DeviceSignalInputBase, QComboBox): arg_name: str | None = None, **kwargs, ): - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QComboBox.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) if arg_name is not None: self.config.arg_name = arg_name self.arg_name = arg_name diff --git a/bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py b/bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py index 5b49f454..15860f02 100644 --- a/bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py +++ b/bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py @@ -42,8 +42,7 @@ class SignalLineEdit(DeviceSignalInputBase, QLineEdit): **kwargs, ): self._is_valid_input = False - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QLineEdit.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self._accent_colors = get_accent_colors() self.completer = QCompleter(self) self.setCompleter(self.completer) diff --git a/bec_widgets/widgets/control/scan_control/scan_control.py b/bec_widgets/widgets/control/scan_control/scan_control.py index e5c6d95c..9672528f 100644 --- a/bec_widgets/widgets/control/scan_control/scan_control.py +++ b/bec_widgets/widgets/control/scan_control/scan_control.py @@ -65,8 +65,7 @@ class ScanControl(BECWidget, QWidget): config = ScanControlConfig( widget_class=self.__class__.__name__, allowed_scans=allowed_scans ) - super().__init__(client=client, gui_id=gui_id, config=config, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self._hide_add_remove_buttons = False diff --git a/bec_widgets/widgets/dap/dap_combo_box/dap_combo_box.py b/bec_widgets/widgets/dap/dap_combo_box/dap_combo_box.py index d8321185..df122446 100644 --- a/bec_widgets/widgets/dap/dap_combo_box/dap_combo_box.py +++ b/bec_widgets/widgets/dap/dap_combo_box/dap_combo_box.py @@ -44,8 +44,7 @@ class DapComboBox(BECWidget, QWidget): default_fit: str | None = None, **kwargs, ): - super().__init__(client=client, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, **kwargs) self.layout = QVBoxLayout(self) self.fit_model_combobox = QComboBox(self) self.layout.addWidget(self.fit_model_combobox) diff --git a/bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog.py b/bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog.py index a43d3247..05a5623c 100644 --- a/bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog.py +++ b/bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog.py @@ -17,6 +17,7 @@ class LMFitDialog(BECWidget, QWidget): PLUGIN = True ICON_NAME = "monitoring" + RPC = False # Signal to emit the currently selected fit curve_id selected_fit = Signal(str) # Signal to emit a move action in form of a tuple (param_name, value) @@ -43,10 +44,8 @@ class LMFitDialog(BECWidget, QWidget): gui_id (str): GUI ID. ui_file (str): The UI file to be loaded. """ - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self.setProperty("skip_settings", True) - self.setObjectName("LMFitDialog") self._ui_file = ui_file self.target_widget = target_widget diff --git a/bec_widgets/widgets/editors/scan_metadata/scan_metadata.py b/bec_widgets/widgets/editors/scan_metadata/scan_metadata.py index bc49a4d1..7bd5038c 100644 --- a/bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +++ b/bec_widgets/widgets/editors/scan_metadata/scan_metadata.py @@ -45,6 +45,7 @@ class ScanMetadata(BECWidget, QWidget): metadata_updated = Signal(dict) metadata_cleared = Signal(NoneType) + RPC = False def __init__( self, @@ -54,8 +55,7 @@ class ScanMetadata(BECWidget, QWidget): initial_extras: list[list[str]] | None = None, **kwargs, ): - super().__init__(client=client, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, **kwargs) self.set_schema(scan_name) diff --git a/bec_widgets/widgets/editors/text_box/text_box.py b/bec_widgets/widgets/editors/text_box/text_box.py index e1757f1a..cf0108d3 100644 --- a/bec_widgets/widgets/editors/text_box/text_box.py +++ b/bec_widgets/widgets/editors/text_box/text_box.py @@ -49,8 +49,7 @@ class TextBox(BECWidget, QWidget): if isinstance(config, dict): config = TextBoxConfig(**config) self.config = config - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self.layout = QVBoxLayout(self) self.text_box_text_edit = QTextEdit(parent=self) self.layout.addWidget(self.text_box_text_edit) diff --git a/bec_widgets/widgets/editors/website/website.py b/bec_widgets/widgets/editors/website/website.py index a2c4a6cf..059f5559 100644 --- a/bec_widgets/widgets/editors/website/website.py +++ b/bec_widgets/widgets/editors/website/website.py @@ -26,8 +26,7 @@ class WebsiteWidget(BECWidget, QWidget): def __init__( self, parent=None, url: str = None, config=None, client=None, gui_id=None, **kwargs ): - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.website = QWebEngineView() diff --git a/bec_widgets/widgets/games/minesweeper.py b/bec_widgets/widgets/games/minesweeper.py index d597c829..e730ad10 100644 --- a/bec_widgets/widgets/games/minesweeper.py +++ b/bec_widgets/widgets/games/minesweeper.py @@ -144,10 +144,10 @@ class Minesweeper(BECWidget, QWidget): PLUGIN = True ICON_NAME = "videogame_asset" USER_ACCESS = [] + RPC = False def __init__(self, parent=None, *args, **kwargs): - super().__init__(*args, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, *args, **kwargs) self._ui_initialised = False self._timer_start_num_seconds = 0 diff --git a/bec_widgets/widgets/plots/image/image.py b/bec_widgets/widgets/plots/image/image.py index 2939453f..c10f812d 100644 --- a/bec_widgets/widgets/plots/image/image.py +++ b/bec_widgets/widgets/plots/image/image.py @@ -125,16 +125,15 @@ class Image(PlotBase): popups: bool = True, **kwargs, ): - self._main_image = ImageItem(parent_image=self) - self._color_bar = None if config is None: config = ImageConfig(widget_class=self.__class__.__name__) + self.gui_id = config.gui_id + self._color_bar = None + self._main_image = ImageItem() super().__init__( parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs ) - - # For PropertyManager identification - self.setObjectName("Image") + self._main_image.parent_image = self self.plot_item.addItem(self._main_image) self.scan_id = None diff --git a/bec_widgets/widgets/plots/image/image_item.py b/bec_widgets/widgets/plots/image/image_item.py index 7ea9cf92..352a017d 100644 --- a/bec_widgets/widgets/plots/image/image_item.py +++ b/bec_widgets/widgets/plots/image/image_item.py @@ -82,10 +82,12 @@ class ImageItem(BECConnector, pg.ImageItem): self.config = config else: self.config = config - super().__init__(config=config, gui_id=gui_id) - pg.ImageItem.__init__(self) - - self.parent_image = parent_image + if parent_image is not None: + self.set_parent(parent_image) + else: + self.parent_image = None + self.parent_id = None + super().__init__(config=config, gui_id=gui_id, **kwargs) self.raw_data = None self.buffer = [] @@ -94,6 +96,13 @@ class ImageItem(BECConnector, pg.ImageItem): # Image processor will handle any setting of data self._image_processor = ImageProcessor(config=self.config.processing) + def set_parent(self, parent: BECConnector): + self.parent_image = parent + self.parent_id = parent.gui_id + + def parent(self): + return self.parent_image + def set_data(self, data: np.ndarray): self.raw_data = data self._process_image() diff --git a/bec_widgets/widgets/plots/motor_map/motor_map.py b/bec_widgets/widgets/plots/motor_map/motor_map.py index c837b1cb..6ff54e0b 100644 --- a/bec_widgets/widgets/plots/motor_map/motor_map.py +++ b/bec_widgets/widgets/plots/motor_map/motor_map.py @@ -161,9 +161,6 @@ class MotorMap(PlotBase): parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs ) - # For PropertyManager identification - self.setObjectName("MotorMap") - # Default values for PlotBase self.x_grid = True self.y_grid = True diff --git a/bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py b/bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py index f871e279..0cab9d3a 100644 --- a/bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py +++ b/bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py @@ -20,7 +20,6 @@ class MotorMapSettings(SettingWidget): super().__init__(parent=parent, *args, **kwargs) self.setProperty("skip_settings", True) - self.setObjectName("MotorMapSettings") current_path = os.path.dirname(__file__) form = UILoader().load_ui(os.path.join(current_path, "motor_map_settings.ui"), self) diff --git a/bec_widgets/widgets/plots/multi_waveform/multi_waveform.py b/bec_widgets/widgets/plots/multi_waveform/multi_waveform.py index f168e11f..fe195219 100644 --- a/bec_widgets/widgets/plots/multi_waveform/multi_waveform.py +++ b/bec_widgets/widgets/plots/multi_waveform/multi_waveform.py @@ -126,9 +126,6 @@ class MultiWaveform(PlotBase): parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs ) - # For PropertyManager identification - self.setObjectName("MultiWaveform") - # Scan Data self.old_scan_id = None self.scan_id = None diff --git a/bec_widgets/widgets/plots/multi_waveform/settings/control_panel.py b/bec_widgets/widgets/plots/multi_waveform/settings/control_panel.py index 9d4eb777..5ab4278b 100644 --- a/bec_widgets/widgets/plots/multi_waveform/settings/control_panel.py +++ b/bec_widgets/widgets/plots/multi_waveform/settings/control_panel.py @@ -20,7 +20,6 @@ class MultiWaveformControlPanel(SettingWidget): super().__init__(parent=parent, *args, **kwargs) self.setProperty("skip_settings", True) - self.setObjectName("MultiWaveformControlPanel") current_path = os.path.dirname(__file__) form = UILoader().load_ui(os.path.join(current_path, "multi_waveform_controls.ui"), self) diff --git a/bec_widgets/widgets/plots/plot_base.py b/bec_widgets/widgets/plots/plot_base.py index 1818be1a..1ffb1e81 100644 --- a/bec_widgets/widgets/plots/plot_base.py +++ b/bec_widgets/widgets/plots/plot_base.py @@ -74,11 +74,9 @@ class PlotBase(BECWidget, QWidget): ) -> None: if config is None: config = ConnectionConfig(widget_class=self.__class__.__name__) - super().__init__(client=client, gui_id=gui_id, config=config, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) # For PropertyManager identification - self.setObjectName("PlotBase") self.get_bec_shortcuts() # Layout Management @@ -1018,7 +1016,7 @@ if __name__ == "__main__": # pragma: no cover: from qtpy.QtWidgets import QApplication app = QApplication(sys.argv) - window = DemoPlotBase() + window = PlotBase() window.show() sys.exit(app.exec_()) diff --git a/bec_widgets/widgets/plots/scatter_waveform/scatter_curve.py b/bec_widgets/widgets/plots/scatter_waveform/scatter_curve.py index dc453b70..da92ed9b 100644 --- a/bec_widgets/widgets/plots/scatter_waveform/scatter_curve.py +++ b/bec_widgets/widgets/plots/scatter_waveform/scatter_curve.py @@ -77,13 +77,16 @@ class ScatterCurve(BECConnector, pg.PlotDataItem): else: self.config = config name = config.label - super().__init__(config=config, gui_id=gui_id) - pg.PlotDataItem.__init__(self, name=name) - self.parent_item = parent_item + self.parent_id = self.parent_item.gui_id + super().__init__(name=name, config=config, gui_id=gui_id, **kwargs) + self.data_z = None # color scaling needs to be cashed for changing colormap self.apply_config() + def parent(self): + return self.parent_item + def apply_config(self, config: dict | ScatterCurveConfig | None = None, **kwargs) -> None: """ Apply the configuration to the curve. diff --git a/bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py b/bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py index de8df872..42f40e82 100644 --- a/bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py +++ b/bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py @@ -111,8 +111,6 @@ class ScatterWaveform(PlotBase): parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs ) self._main_curve = ScatterCurve(parent_item=self) - # For PropertyManager identification - self.setObjectName("ScatterWaveform") # Specific GUI elements self.scatter_dialog = None diff --git a/bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_setting.py b/bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_setting.py index 2d6bf61c..2dd05ce4 100644 --- a/bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_setting.py +++ b/bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_setting.py @@ -15,7 +15,6 @@ class ScatterCurveSettings(SettingWidget): # and should mirror what is in the target widget. # Saving settings for this widget could result in recursively setting the target widget. self.setProperty("skip_settings", True) - self.setObjectName("ScatterCurveSettings") current_path = os.path.dirname(__file__) if popup: diff --git a/bec_widgets/widgets/plots/setting_menus/axis_settings.py b/bec_widgets/widgets/plots/setting_menus/axis_settings.py index 4474cacb..d5605ace 100644 --- a/bec_widgets/widgets/plots/setting_menus/axis_settings.py +++ b/bec_widgets/widgets/plots/setting_menus/axis_settings.py @@ -16,7 +16,6 @@ class AxisSettings(SettingWidget): # and should mirror what is in the target widget. # Saving settings for this widget could result in recursively setting the target widget. self.setProperty("skip_settings", True) - self.setObjectName("AxisSettings") current_path = os.path.dirname(__file__) if popup: form = UILoader().load_ui( diff --git a/bec_widgets/widgets/plots/waveform/curve.py b/bec_widgets/widgets/plots/waveform/curve.py index 454aa33b..261b73f9 100644 --- a/bec_widgets/widgets/plots/waveform/curve.py +++ b/bec_widgets/widgets/plots/waveform/curve.py @@ -91,10 +91,10 @@ class Curve(BECConnector, pg.PlotDataItem): self.config = config else: self.config = config - super().__init__(config=config, gui_id=gui_id) - pg.PlotDataItem.__init__(self, name=name) - self.parent_item = parent_item + self.parent_id = self.parent_item.gui_id + super().__init__(name=name, config=config, gui_id=gui_id, **kwargs) + self.apply_config() self.dap_params = None self.dap_summary = None @@ -104,6 +104,9 @@ class Curve(BECConnector, pg.PlotDataItem): # Activate setClipToView, to boost performance for large datasets per default self.setClipToView(True) + def parent(self): + return self.parent_item + def apply_config(self, config: dict | CurveConfig | None = None, **kwargs) -> None: """ Apply the configuration to the curve. diff --git a/bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py b/bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py index 417d7cc0..f16c864e 100644 --- a/bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py +++ b/bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py @@ -334,11 +334,11 @@ class CurveTree(BECWidget, QWidget): client=None, gui_id: str | None = None, waveform: Waveform | None = None, + **kwargs, ) -> None: if config is None: config = ConnectionConfig(widget_class=self.__class__.__name__) - super().__init__(client=client, gui_id=gui_id, config=config) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self.waveform = waveform if self.waveform and hasattr(self.waveform, "color_palette"): diff --git a/bec_widgets/widgets/plots/waveform/waveform.py b/bec_widgets/widgets/plots/waveform/waveform.py index d683e57c..f090da3d 100644 --- a/bec_widgets/widgets/plots/waveform/waveform.py +++ b/bec_widgets/widgets/plots/waveform/waveform.py @@ -9,8 +9,15 @@ import pyqtgraph as pg from bec_lib import bec_logger, messages from bec_lib.endpoints import MessageEndpoints from pydantic import Field, ValidationError, field_validator -from qtpy.QtCore import QTimer, Signal, Slot -from qtpy.QtWidgets import QDialog, QHBoxLayout, QMainWindow, QVBoxLayout, QWidget +from qtpy.QtCore import QTimer, Signal +from qtpy.QtWidgets import ( + QApplication, + QDialog, + QHBoxLayout, + QMainWindow, + QVBoxLayout, + QWidget, +) from bec_widgets.utils import ConnectionConfig from bec_widgets.utils.bec_signal_proxy import BECSignalProxy @@ -18,7 +25,6 @@ from bec_widgets.utils.colors import Colors, set_theme from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.settings_dialog import SettingsDialog from bec_widgets.utils.toolbar import MaterialIconAction -from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow from bec_widgets.widgets.dap.lmfit_dialog.lmfit_dialog import LMFitDialog from bec_widgets.widgets.plots.plot_base import PlotBase from bec_widgets.widgets.plots.waveform.curve import Curve, CurveConfig, DeviceSignal @@ -129,9 +135,6 @@ class Waveform(PlotBase): parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs ) - # For PropertyManager identification - self.setObjectName("Waveform") - # Curve data self._sync_curves = [] self._async_curves = [] @@ -1737,12 +1740,12 @@ class Waveform(PlotBase): super().cleanup() -class DemoApp(BECMainWindow): # pragma: no cover +class DemoApp(QMainWindow): # pragma: no cover def __init__(self): super().__init__() self.setWindowTitle("Waveform Demo") self.resize(800, 600) - self.main_widget = QWidget() + self.main_widget = QWidget(self) self.layout = QHBoxLayout(self.main_widget) self.setCentralWidget(self.main_widget) @@ -1760,9 +1763,7 @@ class DemoApp(BECMainWindow): # pragma: no cover if __name__ == "__main__": # pragma: no cover import sys - from bec_widgets.utils.bec_qapp import BECApplication - - app = BECApplication(sys.argv) + app = QApplication(sys.argv) set_theme("dark") widget = DemoApp() widget.show() diff --git a/bec_widgets/widgets/progress/bec_progressbar/bec_progressbar.py b/bec_widgets/widgets/progress/bec_progressbar/bec_progressbar.py index c4c6ef91..0c2eb045 100644 --- a/bec_widgets/widgets/progress/bec_progressbar/bec_progressbar.py +++ b/bec_widgets/widgets/progress/bec_progressbar/bec_progressbar.py @@ -25,8 +25,7 @@ class BECProgressBar(BECWidget, QWidget): ICON_NAME = "page_control" def __init__(self, parent=None, client=None, config=None, gui_id=None, **kwargs): - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) accent_colors = get_accent_colors() diff --git a/bec_widgets/widgets/progress/ring_progress_bar/ring.py b/bec_widgets/widgets/progress/ring_progress_bar/ring.py index 2327054d..4dbac973 100644 --- a/bec_widgets/widgets/progress/ring_progress_bar/ring.py +++ b/bec_widgets/widgets/progress/ring_progress_bar/ring.py @@ -6,6 +6,7 @@ from bec_lib.endpoints import EndpointInfo, MessageEndpoints from pydantic import BaseModel, Field, field_validator from pydantic_core import PydanticCustomError from qtpy import QtGui +from qtpy.QtCore import QObject from bec_widgets.utils import BECConnector, ConnectionConfig @@ -77,7 +78,7 @@ class RingConfig(ProgressbarConfig): ) -class Ring(BECConnector): +class Ring(BECConnector, QObject): USER_ACCESS = [ "_get_all_rpc", "_rpc_id", @@ -108,7 +109,7 @@ class Ring(BECConnector): if isinstance(config, dict): config = RingConfig(**config) self.config = config - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) + super().__init__(parent=parent, client=client, config=config, gui_id=gui_id, **kwargs) self.parent_progress_widget = parent_progress_widget self.color = None diff --git a/bec_widgets/widgets/progress/ring_progress_bar/ring_progress_bar.py b/bec_widgets/widgets/progress/ring_progress_bar/ring_progress_bar.py index 9c9789a0..234cf3be 100644 --- a/bec_widgets/widgets/progress/ring_progress_bar/ring_progress_bar.py +++ b/bec_widgets/widgets/progress/ring_progress_bar/ring_progress_bar.py @@ -110,8 +110,7 @@ class RingProgressBar(BECWidget, QWidget): if isinstance(config, dict): config = RingProgressBarConfig(**config, widget_class=self.__class__.__name__) self.config = config - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self.get_bec_shortcuts() self.entry_validator = EntryValidator(self.dev) diff --git a/bec_widgets/widgets/services/bec_queue/bec_queue.py b/bec_widgets/widgets/services/bec_queue/bec_queue.py index 1f1f7870..3f9ba3f7 100644 --- a/bec_widgets/widgets/services/bec_queue/bec_queue.py +++ b/bec_widgets/widgets/services/bec_queue/bec_queue.py @@ -44,8 +44,9 @@ class BECQueue(BECWidget, CompactPopupWidget): refresh_upon_start: bool = True, **kwargs, ): - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - CompactPopupWidget.__init__(self, parent=parent, layout=QVBoxLayout) + super().__init__( + parent=parent, layout=QVBoxLayout, client=client, gui_id=gui_id, config=config, **kwargs + ) self.layout.setSpacing(0) self.layout.setContentsMargins(0, 0, 0, 0) diff --git a/bec_widgets/widgets/services/bec_status_box/bec_status_box.py b/bec_widgets/widgets/services/bec_status_box/bec_status_box.py index c97e88ba..e003694e 100644 --- a/bec_widgets/widgets/services/bec_status_box/bec_status_box.py +++ b/bec_widgets/widgets/services/bec_status_box/bec_status_box.py @@ -89,8 +89,7 @@ class BECStatusBox(BECWidget, CompactPopupWidget): gui_id: str = None, **kwargs, ): - super().__init__(client=client, gui_id=gui_id, **kwargs) - CompactPopupWidget.__init__(self, parent=parent, layout=QHBoxLayout) + super().__init__(parent=parent, layout=QHBoxLayout, client=client, gui_id=gui_id, **kwargs) self.box_name = box_name self.status_container = defaultdict(lambda: {"info": None, "item": None, "widget": None}) diff --git a/bec_widgets/widgets/services/device_browser/device_browser.py b/bec_widgets/widgets/services/device_browser/device_browser.py index 5f31aa5d..ed9755b4 100644 --- a/bec_widgets/widgets/services/device_browser/device_browser.py +++ b/bec_widgets/widgets/services/device_browser/device_browser.py @@ -25,8 +25,7 @@ class DeviceBrowser(BECWidget, QWidget): gui_id: Optional[str] = None, **kwargs, ) -> None: - super().__init__(client=client, config=config, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self.get_bec_shortcuts() self.ui = None diff --git a/bec_widgets/widgets/utility/logpanel/logpanel.py b/bec_widgets/widgets/utility/logpanel/logpanel.py index f4d02ede..116da1c1 100644 --- a/bec_widgets/widgets/utility/logpanel/logpanel.py +++ b/bec_widgets/widgets/utility/logpanel/logpanel.py @@ -103,8 +103,8 @@ class BecLogsQueue: self._display_queue.append(self._line_formatter(_msg)) if self._new_message_signal: self._new_message_signal.emit() - except Exception as e: - logger.warning(f"Error in LogPanel incoming message callback: {e}") + except Exception: + pass def _set_formatter_and_update_filter(self, line_formatter: LineFormatter = noop_format): self._line_formatter: LineFormatter = line_formatter diff --git a/bec_widgets/widgets/utility/spinbox/decimal_spinbox.py b/bec_widgets/widgets/utility/spinbox/decimal_spinbox.py index 6bad7368..945e9ce4 100644 --- a/bec_widgets/widgets/utility/spinbox/decimal_spinbox.py +++ b/bec_widgets/widgets/utility/spinbox/decimal_spinbox.py @@ -30,10 +30,8 @@ class BECSpinBox(BECWidget, QDoubleSpinBox): ) -> None: if config is None: config = ConnectionConfig(widget_class=self.__class__.__name__) - super().__init__(client=client, gui_id=gui_id, config=config, **kwargs) - QDoubleSpinBox.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) - self.setObjectName("BECSpinBox") # Make the widget as compact as possible horizontally. self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.setAlignment(Qt.AlignHCenter) diff --git a/bec_widgets/widgets/utility/visual/colormap_widget/colormap_widget.py b/bec_widgets/widgets/utility/visual/colormap_widget/colormap_widget.py index f66d4235..47df2246 100644 --- a/bec_widgets/widgets/utility/visual/colormap_widget/colormap_widget.py +++ b/bec_widgets/widgets/utility/visual/colormap_widget/colormap_widget.py @@ -9,13 +9,11 @@ from bec_widgets.utils.bec_widget import BECWidget class BECColorMapWidget(BECWidget, QWidget): colormap_changed_signal = Signal(str) ICON_NAME = "palette" - USER_ACCESS = ["colormap"] PLUGIN = True + RPC = False def __init__(self, parent=None, cmap: str = "magma", **kwargs): - super().__init__(**kwargs) - QWidget.__init__(self, parent=parent) - + super().__init__(parent=parent, **kwargs) # Create the ColorMapButton self.button = ColorMapButton() diff --git a/bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py b/bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py index 4336abfb..28baa73c 100644 --- a/bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py +++ b/bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py @@ -9,10 +9,10 @@ from bec_widgets.utils.colors import set_theme class DarkModeButton(BECWidget, QWidget): - USER_ACCESS = ["toggle_dark_mode"] ICON_NAME = "dark_mode" PLUGIN = True + RPC = False def __init__( self, @@ -22,8 +22,7 @@ class DarkModeButton(BECWidget, QWidget): toolbar: bool = False, **kwargs, ) -> None: - super().__init__(client=client, gui_id=gui_id, theme_update=True, **kwargs) - QWidget.__init__(self, parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, theme_update=True, **kwargs) self._dark_mode_enabled = False self.layout = QHBoxLayout(self) @@ -99,9 +98,6 @@ class DarkModeButton(BECWidget, QWidget): if __name__ == "__main__": - from qtpy.QtWidgets import QApplication - - from bec_widgets.utils.colors import set_theme app = QApplication([]) set_theme("auto") diff --git a/tests/unit_tests/test_color_utils.py b/tests/unit_tests/test_color_utils.py index d36e0f03..7628e8ae 100644 --- a/tests/unit_tests/test_color_utils.py +++ b/tests/unit_tests/test_color_utils.py @@ -130,11 +130,11 @@ class ExamplePlotWidget(BECWidget, QWidget): config: ConnectionConfig | None = None, client=None, gui_id: str | None = None, + **kwargs, ) -> None: if config is None: config = ConnectionConfig(widget_class=self.__class__.__name__) - super().__init__(client=client, gui_id=gui_id, config=config) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) self.layout = QVBoxLayout(self) self.glw = pg.GraphicsLayoutWidget() diff --git a/tests/unit_tests/test_device_input_base.py b/tests/unit_tests/test_device_input_base.py index b7c806a5..c8e946c9 100644 --- a/tests/unit_tests/test_device_input_base.py +++ b/tests/unit_tests/test_device_input_base.py @@ -17,9 +17,8 @@ from .conftest import create_widget class DeviceInputWidget(DeviceInputBase, QWidget): """Thin wrapper around DeviceInputBase to make it a QWidget""" - def __init__(self, parent=None, client=None, config=None, gui_id=None): - super().__init__(client=client, config=config, gui_id=gui_id) - QWidget.__init__(self, parent=parent) + def __init__(self, parent=None, client=None, config=None, gui_id=None, **kwargs): + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) @pytest.fixture diff --git a/tests/unit_tests/test_device_signal_input.py b/tests/unit_tests/test_device_signal_input.py index deeb275c..fc89ec05 100644 --- a/tests/unit_tests/test_device_signal_input.py +++ b/tests/unit_tests/test_device_signal_input.py @@ -22,8 +22,7 @@ class DeviceInputWidget(DeviceSignalInputBase, QWidget): """Thin wrapper around DeviceInputBase to make it a QWidget""" def __init__(self, parent=None, client=None, config=None, gui_id=None): - super().__init__(client=client, config=config, gui_id=gui_id) - QWidget.__init__(self, parent=parent) + super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs) @pytest.fixture diff --git a/tests/unit_tests/test_rpc_base.py b/tests/unit_tests/test_rpc_base.py index cbb85c30..d842a9fe 100644 --- a/tests/unit_tests/test_rpc_base.py +++ b/tests/unit_tests/test_rpc_base.py @@ -5,7 +5,7 @@ from bec_widgets.cli.rpc.rpc_base import DeletedWidgetError, RPCBase, RPCReferen @pytest.fixture def rpc_base(): - yield RPCBase(gui_id="rpc_base_test", name="test") + yield RPCBase(gui_id="rpc_base_test", object_name="test") def test_rpc_base(rpc_base):