1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-03-05 00:12:49 +01:00

feat(advanced_dock_area): ads has default direction

This commit is contained in:
2025-08-18 23:03:51 +02:00
parent 166b56b560
commit 0756ebb389
3 changed files with 99 additions and 80 deletions

View File

@@ -128,20 +128,21 @@ class AdvancedDockArea(RPCBase):
floatable: "bool" = True,
movable: "bool" = True,
start_floating: "bool" = False,
where: "Literal['left', 'right', 'top', 'bottom'] | None" = None,
) -> "BECWidget":
"""
Creates a new widget or reuses an existing one and schedules its dock creation.
Create a new widget (or reuse an instance) and add it as a dock.
Args:
widget (BECWidget | str): The widget instance or a string specifying the
type of widget to create.
closable (bool): Whether the dock should be closable. Defaults to True.
floatable (bool): Whether the dock should be floatable. Defaults to True.
movable (bool): Whether the dock should be movable. Defaults to True.
start_floating (bool): Whether to start the dock in a floating state. Defaults to False.
widget: Widget instance or a string widget type (factory-created).
closable: Whether the dock is closable.
floatable: Whether the dock is floatable.
movable: Whether the dock is movable.
start_floating: Start the dock in a floating state.
where: Preferred area to add the dock: "left" | "right" | "top" | "bottom".
If None, uses the instance default passed at construction time.
Returns:
widget: The widget instance.
The widget instance.
"""
@rpc_call
@@ -184,6 +185,20 @@ class AdvancedDockArea(RPCBase):
Delete all docks and widgets.
"""
@property
@rpc_call
def mode(self) -> "str":
"""
None
"""
@mode.setter
@rpc_call
def mode(self) -> "str":
"""
None
"""
class AutoUpdates(RPCBase):
@property

View File

@@ -1,11 +1,11 @@
from __future__ import annotations
import os
from typing import cast
from typing import cast, Literal
import PySide6QtAds as QtAds
from PySide6QtAds import CDockManager, CDockWidget
from qtpy.QtCore import Property, QSettings, QSize, Signal
from qtpy.QtCore import QSettings, Signal
from qtpy.QtWidgets import (
QApplication,
QCheckBox,
@@ -202,12 +202,28 @@ class SaveProfileDialog(QDialog):
class AdvancedDockArea(BECWidget, QWidget):
RPC = True
PLUGIN = False
USER_ACCESS = ["new", "widget_map", "widget_list", "lock_workspace", "attach_all", "delete_all"]
USER_ACCESS = [
"new",
"widget_map",
"widget_list",
"lock_workspace",
"attach_all",
"delete_all",
"mode",
"mode.setter",
]
# Define a signal for mode changes
mode_changed = Signal(str)
def __init__(self, parent=None, mode: str = "developer", *args, **kwargs):
def __init__(
self,
parent=None,
mode: str = "developer",
default_add_direction: Literal["left", "right", "top", "bottom"] = "right",
*args,
**kwargs,
):
super().__init__(parent=parent, *args, **kwargs)
# Title (as a top-level QWidget it can have a window title)
@@ -219,10 +235,10 @@ class AdvancedDockArea(BECWidget, QWidget):
self._root_layout.setSpacing(0)
# Setting the dock manager with flags
QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.eConfigFlag.FocusHighlighting, True)
QtAds.CDockManager.setConfigFlag(
QtAds.CDockManager.eConfigFlag.RetainTabSizeWhenCloseButtonHidden, True
)
# QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.eConfigFlag.FocusHighlighting, True)
# QtAds.CDockManager.setConfigFlag(
# QtAds.CDockManager.eConfigFlag.RetainTabSizeWhenCloseButtonHidden, True
# )
self.dock_manager = CDockManager(self)
# Dock manager helper variables
@@ -230,6 +246,11 @@ class AdvancedDockArea(BECWidget, QWidget):
# Initialize mode property first (before toolbar setup)
self._mode = "developer"
self._default_add_direction = (
default_add_direction
if default_add_direction in ("left", "right", "top", "bottom")
else "right"
)
# Toolbar
self.dark_mode_button = DarkModeButton(parent=self, toolbar=True)
@@ -258,9 +279,6 @@ class AdvancedDockArea(BECWidget, QWidget):
# Apply the requested mode after everything is set up
self.mode = mode
def minimumSizeHint(self):
return QSize(1200, 800)
def _make_dock(
self,
widget: QWidget,
@@ -332,6 +350,19 @@ class AdvancedDockArea(BECWidget, QWidget):
dock.closeDockWidget()
dock.deleteDockWidget()
def _area_from_where(self, where: str | None) -> QtAds.DockWidgetArea:
"""Return ADS DockWidgetArea from a human-friendly direction string.
If *where* is None, fall back to instance default.
"""
d = (where or getattr(self, "_default_add_direction", "right") or "right").lower()
mapping = {
"left": QtAds.DockWidgetArea.LeftDockWidgetArea,
"right": QtAds.DockWidgetArea.RightDockWidgetArea,
"top": QtAds.DockWidgetArea.TopDockWidgetArea,
"bottom": QtAds.DockWidgetArea.BottomDockWidgetArea,
}
return mapping.get(d, QtAds.DockWidgetArea.RightDockWidgetArea)
################################################################################
# Toolbar Setup
################################################################################
@@ -553,22 +584,25 @@ class AdvancedDockArea(BECWidget, QWidget):
floatable: bool = True,
movable: bool = True,
start_floating: bool = False,
where: Literal["left", "right", "top", "bottom"] | None = None,
) -> BECWidget:
"""
Creates a new widget or reuses an existing one and schedules its dock creation.
Create a new widget (or reuse an instance) and add it as a dock.
Args:
widget (BECWidget | str): The widget instance or a string specifying the
type of widget to create.
closable (bool): Whether the dock should be closable. Defaults to True.
floatable (bool): Whether the dock should be floatable. Defaults to True.
movable (bool): Whether the dock should be movable. Defaults to True.
start_floating (bool): Whether to start the dock in a floating state. Defaults to False.
widget: Widget instance or a string widget type (factory-created).
closable: Whether the dock is closable.
floatable: Whether the dock is floatable.
movable: Whether the dock is movable.
start_floating: Start the dock in a floating state.
where: Preferred area to add the dock: "left" | "right" | "top" | "bottom".
If None, uses the instance default passed at construction time.
Returns:
widget: The widget instance.
The widget instance.
"""
# 1) Instantiate or look up the widget (this schedules the BECConnector naming logic)
target_area = self._area_from_where(where)
# 1) Instantiate or look up the widget
if isinstance(widget, str):
widget = cast(BECWidget, widget_handler.create_widget(widget_type=widget, parent=self))
widget.name_established.connect(
@@ -578,8 +612,20 @@ class AdvancedDockArea(BECWidget, QWidget):
floatable=floatable,
movable=movable,
start_floating=start_floating,
area=target_area,
)
)
return widget
# If a widget instance is passed, dock it immediately
self._create_dock_with_name(
widget=widget,
closable=closable,
floatable=floatable,
movable=movable,
start_floating=start_floating,
area=target_area,
)
return widget
def _create_dock_with_name(
@@ -589,13 +635,15 @@ class AdvancedDockArea(BECWidget, QWidget):
floatable: bool = False,
movable: bool = True,
start_floating: bool = False,
area: QtAds.DockWidgetArea | None = None,
):
target_area = area or self._area_from_where(None)
self._make_dock(
widget,
closable=closable,
floatable=floatable,
movable=movable,
area=QtAds.DockWidgetArea.RightDockWidgetArea,
area=target_area,
start_floating=start_floating,
)
self.dock_manager.setFocus()
@@ -906,21 +954,6 @@ class AdvancedDockArea(BECWidget, QWidget):
# Fallback to user mode
self.toolbar.show_bundles(["spacer_bundle", "workspace", "dock_actions"])
def switch_to_plot_mode(self):
self.mode = "plot"
def switch_to_device_mode(self):
self.mode = "device"
def switch_to_utils_mode(self):
self.mode = "utils"
def switch_to_developer_mode(self):
self.mode = "developer"
def switch_to_user_mode(self):
self.mode = "user"
def cleanup(self):
"""
Cleanup the dock area.
@@ -938,7 +971,7 @@ if __name__ == "__main__":
app = QApplication(sys.argv)
dispatcher = BECDispatcher(gui_id="ads")
window = BECMainWindowNoRPC()
ads = AdvancedDockArea(parent=window, mode="developer")
ads = AdvancedDockArea(mode="developer", root_widget=True)
window.setCentralWidget(ads)
window.show()
window.resize(800, 600)

View File

@@ -55,11 +55,6 @@ class TestAdvancedDockAreaInit:
assert hasattr(advanced_dock_area, "dark_mode_button")
assert hasattr(advanced_dock_area, "state_manager")
def test_minimum_size_hint(self, advanced_dock_area):
size_hint = advanced_dock_area.minimumSizeHint()
assert size_hint.width() == 1200
assert size_hint.height() == 800
def test_rpc_and_plugin_flags(self):
assert AdvancedDockArea.RPC is True
assert AdvancedDockArea.PLUGIN is False
@@ -97,7 +92,7 @@ class TestDockManagement:
assert widget is not None
assert hasattr(widget, "name_established")
def test_new_widget_instance(self, advanced_dock_area):
def test_new_widget_instance(self, advanced_dock_area, qtbot):
"""Test creating dock with existing widget instance."""
from bec_widgets.widgets.plots.waveform.waveform import Waveform
@@ -113,8 +108,9 @@ class TestDockManagement:
# Should return the same instance
assert result == widget_instance
# No new dock created since we passed an instance, not a string
assert len(advanced_dock_area.dock_list()) == initial_count
qtbot.wait(200)
assert len(advanced_dock_area.dock_list()) == initial_count + 1
def test_dock_map(self, advanced_dock_area, qtbot):
"""Test dock_map returns correct mapping."""
@@ -784,31 +780,6 @@ class TestModeSwitching:
# Check signal was emitted with correct argument
assert blocker.args == ["plot"]
def test_switch_to_plot_mode(self, advanced_dock_area):
"""Test switch_to_plot_mode method."""
advanced_dock_area.switch_to_plot_mode()
assert advanced_dock_area.mode == "plot"
def test_switch_to_device_mode(self, advanced_dock_area):
"""Test switch_to_device_mode method."""
advanced_dock_area.switch_to_device_mode()
assert advanced_dock_area.mode == "device"
def test_switch_to_utils_mode(self, advanced_dock_area):
"""Test switch_to_utils_mode method."""
advanced_dock_area.switch_to_utils_mode()
assert advanced_dock_area.mode == "utils"
def test_switch_to_developer_mode(self, advanced_dock_area):
"""Test switch_to_developer_mode method."""
advanced_dock_area.switch_to_developer_mode()
assert advanced_dock_area.mode == "developer"
def test_switch_to_user_mode(self, advanced_dock_area):
"""Test switch_to_user_mode method."""
advanced_dock_area.switch_to_user_mode()
assert advanced_dock_area.mode == "user"
class TestToolbarModeBundles:
"""Test toolbar bundle creation and visibility for different modes."""