diff --git a/bec_widgets/widgets/dock_area/__init__.py b/bec_widgets/widgets/dock_area/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bec_widgets/widgets/dock_area/dock/__init__.py b/bec_widgets/widgets/dock_area/dock/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bec_widgets/widgets/dock_area/dock/dock.py b/bec_widgets/widgets/dock_area/dock/dock.py new file mode 100644 index 00000000..6592b99c --- /dev/null +++ b/bec_widgets/widgets/dock_area/dock/dock.py @@ -0,0 +1,116 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Literal, Optional +from weakref import WeakValueDictionary + +from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout, QGridLayout +from PyQt6.QtCore import Qt +from pydantic import Field +from pyqtgraph.dockarea import Dock + + +from bec_widgets.cli.rpc_wigdet_handler import RPCWidgetHandler +from bec_widgets.utils import BECConnector, ConnectionConfig, GridLayoutManager +from qtpy.QtWidgets import QWidget, QMainWindow, QDockWidget, QSizePolicy + +if TYPE_CHECKING: + from qtpy.QtWidgets import QWidget + from bec_widgets.widgets.dock_area.dock_area import BECDockAreaAlt + + +class DockConfig(ConnectionConfig): + widgets: dict[str, ConnectionConfig] = 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( + None, description="The GUI ID of parent dock area of the dock." + ) + + +class BECDockAlt(BECConnector, QDockWidget): + USER_ACCESS = [] + + def __init__( + self, + parent: QWidget | None = None, + parent_dock_area: BECDockAreaAlt | None = None, + config: DockConfig | None = None, + title: str | None = None, # TODO maybe rename to title + client=None, + gui_id: str | None = None, + layout: Literal["horizontal", "vertical", "grid"] = "vertical", + ) -> None: + if config is None: + config = DockConfig( + widget_class=self.__class__.__name__, parent_dock_area=parent_dock_area.gui_id + ) + else: + if isinstance(config, dict): + config = DockConfig(**config) + super().__init__(client=client, config=config, gui_id=gui_id) + QDockWidget.__init__(self, title, parent) + + self._parent_dock_area = parent_dock_area + self._init_setup() + self.setup_layout(layout) + + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + # self._grid_layout_manager = GridLayoutManager(self) + # self._widget_handler.init() + # self._grid_layout_manager.init + + def _init_setup(self): + self.setAllowedAreas(Qt.DockWidgetArea.AllDockWidgetAreas) + self._widget_handler = RPCWidgetHandler() + self._base_widget = QWidget() + self._widgets = WeakValueDictionary() + + def setup_layout(self, layout: Literal["horizontal", "vertical", "grid"] = "grid"): + if layout == "horizontal": + self.layout = QHBoxLayout() + elif layout == "vertical": + self.layout = QVBoxLayout() + elif layout == "grid": + self.layout = QGridLayout() + self.layout.setContentsMargins(0, 0, 0, 0) + self._base_widget.setLayout(self.layout) + self.setWidget(self._base_widget) + + def add_widget(self, widget: QWidget, row: int = None, col: int = None): + + if isinstance(self.layout, QVBoxLayout) or isinstance(self.layout, QHBoxLayout): + self.layout.addWidget(widget) + if row or col: + print(f"Warning: row and column are not used in this layout - {self.layout}") + elif isinstance(self.layout, QGridLayout): + if row is None: + row = self.layout.rowCount() + if col is None: + col = self.layout.columnCount() + self.layout.addWidget(widget, row, col) + + return widget + + def add_widget_bec(self, widget_type: str, row: int = None, col: int = None): + widget = self._widget_handler.create_widget(widget_type) + self.add_widget(widget, row, col) + return widget + + def remove_widget(self, widget_id: str): + widget = self.widgets.pop(widget_id) + widget.deleteLater() + return widget + + def cleanup(self): + """ + Clean up the dock, including all its widgets. + """ + for widget in self.widgets: + if hasattr(widget, "cleanup"): + widget.cleanup() + super().cleanup() + + def close(self): + self.cleanup() + super().close() diff --git a/bec_widgets/widgets/dock_area/dock_area.py b/bec_widgets/widgets/dock_area/dock_area.py new file mode 100644 index 00000000..19d786da --- /dev/null +++ b/bec_widgets/widgets/dock_area/dock_area.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +import sys +from typing import Literal, Optional +from weakref import WeakValueDictionary + +import qdarktheme +from PyQt6.QtWidgets import QApplication, QVBoxLayout +from pydantic import Field +from pyqtgraph.dockarea.DockArea import DockArea +from qtpy.QtCore import Qt +from qtpy.QtGui import QPainter, QPaintEvent +from qtpy.QtWidgets import QWidget, QMainWindow, QDockWidget + +from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils +from bec_widgets.widgets import BECFigure +from bec_widgets.widgets.dock_area.dock.dock import BECDockAlt, DockConfig + + +class DockAreaConfig(ConnectionConfig): + docks: dict[str, DockConfig] = Field({}, description="The docks in the dock area.") + + +class BECDockAreaAlt(BECConnector, QMainWindow): + USER_ACCESS = [] + positions = { + "top": Qt.DockWidgetArea.TopDockWidgetArea, + "bottom": Qt.DockWidgetArea.BottomDockWidgetArea, + "left": Qt.DockWidgetArea.LeftDockWidgetArea, + "right": Qt.DockWidgetArea.RightDockWidgetArea, + } + + def __init__( + self, + parent: QWidget | None = None, + config: DockAreaConfig | None = None, + client=None, + gui_id: str = None, + ) -> None: + if config is None: + config = DockAreaConfig(widget_class=self.__class__.__name__) + else: + if isinstance(config, dict): + config = DockAreaConfig(**config) + self.config = config + super().__init__(client=client, config=config, gui_id=gui_id) + QMainWindow.__init__(self, parent=parent) + # TODO experimetn with the options and features + # self.setDockNestingEnabled(True) + # self.setDockOptions( + # QMainWindow.DockOption.AllowTabbedDocks | QMainWindow.DockOption.AllowNestedDocks + # ) + self._instructions_visible = True # TODO do not know how to translate yet to native qt + + self._docks = WeakValueDictionary() + + @property + def docks(self) -> dict: + """ + Get the docks in the dock area. + Returns: + dock_dict(dict): The docks in the dock area. + """ + return dict(self._docks) + + @docks.setter + def docks(self, value: dict): + self._docks = WeakValueDictionary(value) + + def add_dock( + self, + title: str = None, + layout: Literal["horizontal", "vertical", "grid"] = "vertical", + prefix: str = "dock", + position: Literal["top", "bottom", "left", "right"] = "bottom", + ): + + if title is None: + title = WidgetContainerUtils.generate_unique_widget_id(self._docks, prefix=prefix) + + if title in set(self.docks.keys()): + raise ValueError(f"Dock with name {title} already exists.") + + dock = BECDockAlt(title=title, parent=self, parent_dock_area=self, layout=layout) + self.addDockWidget(self.positions[position], dock) + self._docks[title] = dock + + return dock