0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 03:31:50 +02:00

wip - namespace

This commit is contained in:
2025-03-13 10:20:16 +01:00
parent bf060b3aba
commit d1a41752c4

View File

@ -1,25 +1,33 @@
from __future__ import annotations 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 pydantic import Field
from pyqtgraph.dockarea import Dock, DockLabel from pyqtgraph.dockarea import Dock, DockLabel
from qtpy import QtCore, QtGui 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.cli.rpc.rpc_widget_handler import widget_handler
from bec_widgets.utils import ConnectionConfig, GridLayoutManager from bec_widgets.utils import ConnectionConfig, GridLayoutManager
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.container_utils import WidgetContainerUtils
logger = bec_logger.logger
if TYPE_CHECKING: if TYPE_CHECKING:
from qtpy.QtWidgets import QWidget from qtpy.QtWidgets import QWidget
from bec_widgets.widgets.containers.dock.dock_area import BECDockArea
class DockConfig(ConnectionConfig): class DockConfig(ConnectionConfig):
widgets: dict[str, Any] = Field({}, description="The widgets in the dock.") widgets: dict[str, Any] = Field({}, description="The widgets in the dock.")
position: Literal["bottom", "top", "left", "right", "above", "below"] = Field( position: Literal["bottom", "top", "left", "right", "above", "below"] = Field(
"bottom", description="The position of the dock." "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." None, description="The GUI ID of parent dock area of the dock."
) )
@ -103,16 +111,17 @@ class BECDock(BECWidget, Dock):
ICON_NAME = "widgets" ICON_NAME = "widgets"
USER_ACCESS = [ USER_ACCESS = [
"_config_dict", "_config_dict",
"_rpc_id", "element_list",
"widget_list", "elements",
"new",
"show",
"hide",
"show_title_bar", "show_title_bar",
"hide_title_bar",
"get_widgets_positions",
"set_title", "set_title",
"add_widget", "hide_title_bar",
"list_eligible_widgets", "available_widgets",
"move_widget", "delete",
"remove_widget", "delete_all",
"remove", "remove",
"attach", "attach",
"detach", "detach",
@ -121,7 +130,7 @@ class BECDock(BECWidget, Dock):
def __init__( def __init__(
self, self,
parent: QWidget | None = None, parent: QWidget | None = None,
parent_dock_area: QWidget | None = None, parent_dock_area: BECDockArea | None = None,
config: DockConfig | None = None, config: DockConfig | None = None,
name: str | None = None, name: str | None = None,
client=None, client=None,
@ -129,21 +138,24 @@ class BECDock(BECWidget, Dock):
closable: bool = True, closable: bool = True,
**kwargs, **kwargs,
) -> None: ) -> None:
if config is None: if config is None:
config = DockConfig( 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: else:
if isinstance(config, dict): if isinstance(config, dict):
config = DockConfig(**config) config = DockConfig(**config)
self.config = 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) 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) # Dock.__init__(self, name=name, **kwargs)
self.parent_dock_area = parent_dock_area self.parent_dock_area = parent_dock_area
# Layout Manager # Layout Manager
self.layout_manager = GridLayoutManager(self.layout) self.layout_manager = GridLayoutManager(self.layout)
@ -173,7 +185,18 @@ class BECDock(BECWidget, Dock):
super().float() super().float()
@property @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. Get the widgets in the dock.
@ -182,10 +205,6 @@ class BECDock(BECWidget, Dock):
""" """
return self.widgets return self.widgets
@widget_list.setter
def widget_list(self, value: list[BECWidget]):
self.widgets = value
def hide_title_bar(self): def hide_title_bar(self):
""" """
Hide the title bar of the dock. Hide the title bar of the dock.
@ -194,6 +213,20 @@ class BECDock(BECWidget, Dock):
self.label.hide() self.label.hide()
self.labelHidden = True 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): def show_title_bar(self):
""" """
Hide the title bar of the dock. 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.orig_area.docks[title] = self.orig_area.docks.pop(self.name())
self.setTitle(title) self.setTitle(title)
self._name = title
def get_widgets_positions(self) -> dict: def get_widgets_positions(self) -> dict:
""" """
@ -222,7 +254,7 @@ class BECDock(BECWidget, Dock):
""" """
return self.layout_manager.get_widgets_positions() return self.layout_manager.get_widgets_positions()
def list_eligible_widgets( def available_widgets(
self, self,
) -> list: # TODO can be moved to some util mixin like container class for rpc widgets ) -> 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()) 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, self,
widget: BECWidget | str, widget: BECWidget | str,
row=None, name: str | None = None,
col=0, row: int | None = None,
rowspan=1, col: int = 0,
colspan=1, rowspan: int = 1,
colspan: int = 1,
shift: Literal["down", "up", "left", "right"] = "down", shift: Literal["down", "up", "left", "right"] = "down",
) -> BECWidget: ) -> BECWidget:
""" """
Add a widget to the dock. Add a widget to the dock.
Args: 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. 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. col(int): The column to add the widget to.
rowspan(int): The number of rows the widget should span. 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. shift(Literal["down", "up", "left", "right"]): The direction to shift the widgets if the position is occupied.
""" """
if row is None: if row is None:
# row = cast(int, self.layout.rowCount()) # type:ignore
row = self.layout.rowCount() row = self.layout.rowCount()
# row = cast(int, row)
if self.layout_manager.is_position_occupied(row, col): if self.layout_manager.is_position_occupied(row, col):
self.layout_manager.shift_widgets(shift, start_row=row) 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): 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: else:
widget = widget widget._name = name # pylint: disable=protected-access
self.addWidget(widget, row=row, col=col, rowspan=rowspan, colspan=colspan) self.addWidget(widget, row=row, col=col, rowspan=rowspan, colspan=colspan)
@ -294,37 +359,72 @@ class BECDock(BECWidget, Dock):
""" """
self.float() 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): def remove(self):
""" """
Remove the dock from the parent dock area. Remove the dock from the parent dock area.
""" """
# self.cleanup() self.parent_dock_area.delete(self._name)
self.parent_dock_area.remove_dock(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): def cleanup(self):
""" """
Clean up the dock, including all its widgets. Clean up the dock, including all its widgets.
""" """
for widget in self.widgets: # Remove the dock from the parent dock area
if hasattr(widget, "cleanup"): if self.parent_dock_area:
widget.cleanup() 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.widgets.clear()
self.label.close() self.label.close()
self.label.deleteLater() self.label.deleteLater()
super().cleanup() 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): def close(self):
""" """
Close the dock area and cleanup. Close the dock area and cleanup.
@ -332,4 +432,15 @@ class BECDock(BECWidget, Dock):
""" """
self.cleanup() self.cleanup()
super().close() 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_())