1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-03-08 01:37:52 +01:00

fix(dock_area): tabbed dock have correct parent

This commit is contained in:
2026-02-25 15:12:26 +01:00
parent 3a5317be53
commit a632f35c40
2 changed files with 77 additions and 18 deletions

View File

@@ -113,6 +113,7 @@ class DockAreaWidget(BECWidget, QWidget):
)
self._root_layout.addWidget(self.dock_manager, 1)
self._install_manager_parent_guards()
################################################################################
# Dock Utility Helpers
@@ -255,6 +256,54 @@ class DockAreaWidget(BECWidget, QWidget):
return lambda dock: self._default_close_handler(dock, widget)
def _install_manager_parent_guards(self) -> None:
"""
Track ADS structural changes so drag/drop-created tab areas keep stable parenting.
"""
self.dock_manager.dockAreaCreated.connect(self._normalize_all_dock_parents)
self.dock_manager.dockWidgetAdded.connect(self._normalize_all_dock_parents)
self.dock_manager.stateRestored.connect(self._normalize_all_dock_parents)
self.dock_manager.restoringState.connect(self._normalize_all_dock_parents)
self.dock_manager.focusedDockWidgetChanged.connect(self._normalize_all_dock_parents)
self._normalize_all_dock_parents()
def _iter_all_dock_areas(self) -> list[CDockAreaWidget]:
"""Return all dock areas from all known dock containers."""
areas: list[CDockAreaWidget] = []
for i in range(self.dock_manager.dockAreaCount()):
area = self.dock_manager.dockArea(i)
if area is None or not isValid(area):
continue
areas.append(area)
return areas
def _connect_dock_area_parent_guards(self) -> None:
"""Bind area-level tab/view events to parent normalization."""
for area in self._iter_all_dock_areas():
try:
area.currentChanged.connect(
self._normalize_all_dock_parents, Qt.ConnectionType.UniqueConnection
)
area.viewToggled.connect(
self._normalize_all_dock_parents, Qt.ConnectionType.UniqueConnection
)
except TypeError:
area.currentChanged.connect(self._normalize_all_dock_parents)
area.viewToggled.connect(self._normalize_all_dock_parents)
def _normalize_all_dock_parents(self, *_args) -> None:
"""
Ensure each dock has a stable parent after tab switches, re-docking, or restore.
"""
self._connect_dock_area_parent_guards()
for dock in self.dock_list():
if dock is None or not isValid(dock):
continue
area_widget = dock.dockAreaWidget()
target_parent = area_widget if area_widget is not None else self.dock_manager
if dock.parent() is not target_parent:
dock.setParent(target_parent)
def _make_dock(
self,
widget: QWidget,
@@ -357,6 +406,7 @@ class DockAreaWidget(BECWidget, QWidget):
self._apply_floating_state_to_dock(dock, floating_state)
if resolved_icon is not None:
dock.setIcon(resolved_icon)
self._normalize_all_dock_parents()
return dock
def _delete_dock(self, dock: CDockWidget) -> None:
@@ -1335,29 +1385,13 @@ class DockAreaWidget(BECWidget, QWidget):
dock = self._create_dock_from_spec(spec)
return dock if return_dock else widget
def _iter_all_docks(self) -> list[CDockWidget]:
"""Return all docks, including those hosted in floating containers."""
docks = list(self.dock_manager.dockWidgets())
seen = {id(d) for d in docks}
for container in self.dock_manager.floatingWidgets():
if container is None:
continue
for dock in container.dockWidgets():
if dock is None:
continue
if id(dock) in seen:
continue
docks.append(dock)
seen.add(id(dock))
return docks
def dock_map(self) -> dict[str, CDockWidget]:
"""Return the dock widgets map as dictionary with names as keys."""
return {dock.objectName(): dock for dock in self._iter_all_docks() if dock.objectName()}
return self.dock_manager.dockWidgetsMap()
def dock_list(self) -> list[CDockWidget]:
"""Return the list of dock widgets."""
return self._iter_all_docks()
return list(self.dock_map().values())
def widget_map(self, bec_widgets_only: bool = True) -> dict[str, QWidget]:
"""

View File

@@ -331,6 +331,31 @@ class TestBasicDockArea:
assert manifest_entries[1]["object_name"] == "anchored_widget"
assert manifest_entries[1]["floating"] is False
def test_tabbed_docks_keep_parent_after_tab_switch(self, basic_dock_area, qtbot):
first = QWidget(parent=basic_dock_area)
first.setObjectName("tab_parent_first")
second = QWidget(parent=basic_dock_area)
second.setObjectName("tab_parent_second")
first_dock = basic_dock_area.new(first, return_dock=True)
second_dock = basic_dock_area.new(second, return_dock=True, tab_with=first_dock)
dock_area = first_dock.dockAreaWidget()
assert dock_area is not None
qtbot.waitUntil(lambda: second_dock.dockAreaWidget() is dock_area, timeout=1000)
dock_area.setCurrentDockWidget(second_dock)
qtbot.waitUntil(
lambda: first_dock.parent() is dock_area and second_dock.parent() is dock_area,
timeout=1000,
)
dock_area.setCurrentDockWidget(first_dock)
qtbot.waitUntil(
lambda: first_dock.parent() is dock_area and second_dock.parent() is dock_area,
timeout=1000,
)
def test_splitter_weight_coercion_supports_aliases(self, basic_dock_area):
weights = {"default": 0.5, "left": 2, "center": 3, "right": 4}