From d1a41752c48e6243763526865a6154af5817d885 Mon Sep 17 00:00:00 2001 From: wakonig_k Date: Thu, 13 Mar 2025 10:20:16 +0100 Subject: [PATCH] wip - namespace --- bec_widgets/widgets/containers/dock/dock.py | 207 +++++++++++++++----- 1 file changed, 159 insertions(+), 48 deletions(-) diff --git a/bec_widgets/widgets/containers/dock/dock.py b/bec_widgets/widgets/containers/dock/dock.py index 08235c07..26c9343d 100644 --- a/bec_widgets/widgets/containers/dock/dock.py +++ b/bec_widgets/widgets/containers/dock/dock.py @@ -1,25 +1,33 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Literal, Optional +from typing import TYPE_CHECKING, Any, Literal, Optional, cast +from bec_lib.logger import bec_logger from pydantic import Field from pyqtgraph.dockarea import Dock, DockLabel from qtpy import QtCore, QtGui +from bec_widgets.cli.client_utils import IGNORE_WIDGETS +from bec_widgets.cli.rpc.rpc_register import RPCRegister from bec_widgets.cli.rpc.rpc_widget_handler import widget_handler from bec_widgets.utils import ConnectionConfig, GridLayoutManager from bec_widgets.utils.bec_widget import BECWidget +from bec_widgets.utils.container_utils import WidgetContainerUtils + +logger = bec_logger.logger if TYPE_CHECKING: from qtpy.QtWidgets import QWidget + from bec_widgets.widgets.containers.dock.dock_area import BECDockArea + class DockConfig(ConnectionConfig): widgets: dict[str, Any] = Field({}, description="The widgets in the dock.") position: Literal["bottom", "top", "left", "right", "above", "below"] = Field( "bottom", description="The position of the dock." ) - parent_dock_area: Optional[str] = Field( + parent_dock_area: Optional[str] | None = Field( None, description="The GUI ID of parent dock area of the dock." ) @@ -103,16 +111,17 @@ class BECDock(BECWidget, Dock): ICON_NAME = "widgets" USER_ACCESS = [ "_config_dict", - "_rpc_id", - "widget_list", + "element_list", + "elements", + "new", + "show", + "hide", "show_title_bar", - "hide_title_bar", - "get_widgets_positions", "set_title", - "add_widget", - "list_eligible_widgets", - "move_widget", - "remove_widget", + "hide_title_bar", + "available_widgets", + "delete", + "delete_all", "remove", "attach", "detach", @@ -121,7 +130,7 @@ class BECDock(BECWidget, Dock): def __init__( self, parent: QWidget | None = None, - parent_dock_area: QWidget | None = None, + parent_dock_area: BECDockArea | None = None, config: DockConfig | None = None, name: str | None = None, client=None, @@ -129,21 +138,24 @@ class BECDock(BECWidget, Dock): closable: bool = True, **kwargs, ) -> None: + if config is None: config = DockConfig( - widget_class=self.__class__.__name__, parent_dock_area=parent_dock_area.gui_id + widget_class=self.__class__.__name__, + parent_dock_area=parent_dock_area.gui_id if parent_dock_area else None, ) else: if isinstance(config, dict): config = DockConfig(**config) self.config = config - super().__init__(client=client, config=config, gui_id=gui_id) + super().__init__( + client=client, config=config, gui_id=gui_id, name=name + ) # Name was checked and created in BEC Widget label = CustomDockLabel(text=name, closable=closable) - Dock.__init__(self, name=name, label=label, **kwargs) + Dock.__init__(self, name=name, label=label, parent=self, **kwargs) # Dock.__init__(self, name=name, **kwargs) self.parent_dock_area = parent_dock_area - # Layout Manager self.layout_manager = GridLayoutManager(self.layout) @@ -173,7 +185,18 @@ class BECDock(BECWidget, Dock): super().float() @property - def widget_list(self) -> list[BECWidget]: + def elements(self) -> dict[str, BECWidget]: + """ + Get the widgets in the dock. + + Returns: + widgets(dict): The widgets in the dock. + """ + # pylint: disable=protected-access + return dict((widget._name, widget) for widget in self.element_list) + + @property + def element_list(self) -> list[BECWidget]: """ Get the widgets in the dock. @@ -182,10 +205,6 @@ class BECDock(BECWidget, Dock): """ return self.widgets - @widget_list.setter - def widget_list(self, value: list[BECWidget]): - self.widgets = value - def hide_title_bar(self): """ Hide the title bar of the dock. @@ -194,6 +213,20 @@ class BECDock(BECWidget, Dock): self.label.hide() self.labelHidden = True + def show(self): + """ + Show the dock. + """ + super().show() + self.show_title_bar() + + def hide(self): + """ + Hide the dock. + """ + self.hide_title_bar() + super().hide() + def show_title_bar(self): """ Hide the title bar of the dock. @@ -211,7 +244,6 @@ class BECDock(BECWidget, Dock): """ self.orig_area.docks[title] = self.orig_area.docks.pop(self.name()) self.setTitle(title) - self._name = title def get_widgets_positions(self) -> dict: """ @@ -222,7 +254,7 @@ class BECDock(BECWidget, Dock): """ return self.layout_manager.get_widgets_positions() - def list_eligible_widgets( + def available_widgets( self, ) -> list: # TODO can be moved to some util mixin like container class for rpc widgets """ @@ -233,20 +265,29 @@ class BECDock(BECWidget, Dock): """ return list(widget_handler.widget_classes.keys()) - def add_widget( + def _get_list_of_widget_name_of_parent_dock_area(self): + docks = self.parent_dock_area.panel_list + widgets = [] + for dock in docks: + widgets.extend(dock.elements.keys()) + return widgets + + def new( self, widget: BECWidget | str, - row=None, - col=0, - rowspan=1, - colspan=1, + name: str | None = None, + row: int | None = None, + col: int = 0, + rowspan: int = 1, + colspan: int = 1, shift: Literal["down", "up", "left", "right"] = "down", ) -> BECWidget: """ Add a widget to the dock. Args: - widget(QWidget): The widget to add. + widget(QWidget): The widget to add. It can not be BECDock or BECDockArea. + name(str): The name of the widget. row(int): The row to add the widget to. If None, the widget will be added to the next available row. col(int): The column to add the widget to. rowspan(int): The number of rows the widget should span. @@ -254,15 +295,39 @@ 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: + raise ValueError(f"Widget {widget} can not be added to dock.") + if isinstance(widget, str): - widget = widget_handler.create_widget(widget) + widget = cast( + BECWidget, + widget_handler.create_widget(widget_type=widget, name=name, parent_dock=self), + ) else: - widget = widget + widget._name = name # pylint: disable=protected-access self.addWidget(widget, row=row, col=col, rowspan=rowspan, colspan=colspan) @@ -294,37 +359,72 @@ class BECDock(BECWidget, Dock): """ self.float() - def remove_widget(self, widget_rpc_id: str): - """ - Remove a widget from the dock. - - Args: - widget_rpc_id(str): The ID of the widget to remove. - """ - widget = self.rpc_register.get_rpc_by_id(widget_rpc_id) - self.layout.removeWidget(widget) - self.config.widgets.pop(widget_rpc_id, None) - widget.close() - def remove(self): """ Remove the dock from the parent dock area. """ - # self.cleanup() - self.parent_dock_area.remove_dock(self.name()) + self.parent_dock_area.delete(self._name) + + def delete(self, widget_name: str) -> None: + """ + Remove a widget from the dock. + + Args: + 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] + if len(widgets) == 0: + logger.warning( + f"Widget with name {widget_name} not found in dock {self.name()}. " + f"Checking if gui_id was passed as widget_name." + ) + # Try to find the widget in the RPC register, maybe the gui_id was passed as widget_name + widget = self.rpc_register.get_rpc_by_id(widget_name) + if widget is None: + logger.warning( + f"Widget not found for name or gui_id: {widget_name} in dock {self.name()}" + ) + return + else: + widget = widgets[0] + self.layout.removeWidget(widget) + self.config.widgets.pop(widget._name, None) + if widget in self.widgets: + self.widgets.remove(widget) + widget.close() + self._broadcast_update() + + def delete_all(self): + """ + Remove all widgets from the dock. + """ + for widget in self.widgets: + self.delete(widget._name) # pylint: disable=protected-access def cleanup(self): """ Clean up the dock, including all its widgets. """ - for widget in self.widgets: - if hasattr(widget, "cleanup"): - widget.cleanup() + # Remove the dock from the parent dock area + if self.parent_dock_area: + self.parent_dock_area.dock_area.docks.pop(self.name(), None) + self.parent_dock_area.config.docks.pop(self.name(), None) + self.delete_all() self.widgets.clear() self.label.close() self.label.deleteLater() super().cleanup() + # def closeEvent(self, event): # pylint: disable=uselsess-parent-delegation + # """Close Event for dock and cleanup. + + # This wrapper ensures that the BECWidget close event is triggered. + # If removed, the closeEvent from pyqtgraph will be triggered, which + # is not calling super().closeEvent(event) and will not trigger the BECWidget close event. + # """ + # return super().closeEvent(event) + def close(self): """ Close the dock area and cleanup. @@ -332,4 +432,15 @@ class BECDock(BECWidget, Dock): """ self.cleanup() super().close() - self.parent_dock_area.dock_area.docks.pop(self.name(), None) + + +if __name__ == "__main__": + import sys + + from qtpy.QtWidgets import QApplication + + app = QApplication([]) + dock = BECDock(name="dock") + dock.show() + app.exec_() + sys.exit(app.exec_())