diff --git a/bec_widgets/widgets/containers/dock_area/basic_dock_area.py b/bec_widgets/widgets/containers/dock_area/basic_dock_area.py index 848eeabd..97a51891 100644 --- a/bec_widgets/widgets/containers/dock_area/basic_dock_area.py +++ b/bec_widgets/widgets/containers/dock_area/basic_dock_area.py @@ -1,6 +1,7 @@ from __future__ import annotations import inspect +import itertools from dataclasses import dataclass from typing import Any, Callable, Literal, Mapping, Sequence, cast @@ -26,6 +27,11 @@ from bec_widgets.widgets.containers.qt_ads import ( logger = bec_logger.logger +# Dynamic property tagging a splitter with its latest layout-ratio request; deferred applies of +# older requests compare against it and skip themselves. +_RATIO_REQUEST_PROPERTY = "_bec_layout_ratio_request" +_ratio_requests = itertools.count(1) + class DockSettingsDialog(QDialog): """Generic settings editor shown from dock title bar actions.""" @@ -746,7 +752,9 @@ class DockAreaWidget(BECWidget, QWidget): The deferred callback is bound to the splitter as context object, so Qt drops it if the splitter is deleted (e.g. by ADS while the layout changes) before it runs. - While the splitter has no usable size, the ratios wait for its next resize. + While the splitter has no usable size, the ratios wait for its next resize. Every request + tags the splitter, so a deferred apply of an older request skips itself once a newer one + for the same splitter exists. Args: splitter(QtAds.CDockSplitter): Target splitter. @@ -759,7 +767,12 @@ class DockAreaWidget(BECWidget, QWidget): if not ratios: return + request = next(_ratio_requests) + splitter.setProperty(_RATIO_REQUEST_PROPERTY, request) + def apply(): + if splitter.property(_RATIO_REQUEST_PROPERTY) != request: + return # superseded by a newer request for this splitter count = splitter.count() if count != len(ratios): return diff --git a/tests/unit_tests/test_dock_area.py b/tests/unit_tests/test_dock_area.py index 3780f464..acbf4dff 100644 --- a/tests/unit_tests/test_dock_area.py +++ b/tests/unit_tests/test_dock_area.py @@ -433,6 +433,12 @@ class TestBasicDockArea: def findChildren(self, *_args, **_kwargs): return [] + def setProperty(self, name, value): + self.__dict__[name] = value + + def property(self, name): + return self.__dict__.get(name) + splitter = DummySplitter() basic_dock_area._schedule_splitter_weights(splitter, [1, 2, 1]) @@ -647,6 +653,19 @@ class TestBasicDockArea: assert left_px > 2 * right_px assert root.findChildren(basic_dock_module._ResizeWatcher) == [] + def test_layout_ratios_superseded_request_does_not_apply(self, collapsed_dock_area, qtbot): + """An older request still queued when a newer one arrives must not win either.""" + outer, area, root = collapsed_dock_area + area.set_layout_ratios(horizontal=[1, 3]) + # The area gets its width back between the two queued applies. + QTimer.singleShot(0, outer, lambda: outer.setSizes([400, 400])) + area.set_layout_ratios(horizontal=[3, 1]) + qtbot.waitUntil(lambda: root.width() > 0) + qtbot.wait(50) + + left_px, right_px = root.sizes() + assert left_px > 2 * right_px + def test_first_dock_title_bar_preference_survives_second_dock(self, basic_dock_area): first = QWidget(parent=basic_dock_area) first.setObjectName("first_panel")