From b70bac3900cec5018034b9eaa4045b4470ddf699 Mon Sep 17 00:00:00 2001
From: Karel Capek Robotics <96583804+MelkorBalrog@users.noreply.github.com>
Date: Thu, 30 Oct 2025 08:33:55 -0400
Subject: [PATCH] Ensure detached tabs register resize helpers
---
HISTORY.md | 9 +++
gui/utils/closable_notebook.py | 8 +-
.../test_detached_window_moved_widget.py | 74 +++++++++++++++++++
3 files changed, 89 insertions(+), 2 deletions(-)
diff --git a/HISTORY.md b/HISTORY.md
index 43d24069..626e1fba 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -18,6 +18,15 @@
# along with this program. If not, see .
-->
+## 0.2.283 - 2025-09-19
+
+- Ensure detached notebook tabs register with the floating window helper so
+ resize tracking and lifecycle hooks initialise together, preserving toolbox
+ activation after detachment.
+- Extend the detachment window regression suite with a resize scenario that
+ confirms floating windows propagate geometry updates to their hosted content
+ independently of the originating notebook.
+
## 0.2.282 - 2025-09-18
- Harden ``cancel_after_events`` so it cancels pending Tk ``after`` jobs before
diff --git a/gui/utils/closable_notebook.py b/gui/utils/closable_notebook.py
index cbfa4de8..7d666fa4 100644
--- a/gui/utils/closable_notebook.py
+++ b/gui/utils/closable_notebook.py
@@ -1,3 +1,4 @@
+# GNU disclaimer
# Author: Miguel Marina
# SPDX-License-Identifier: GPL-3.0-or-later
#
@@ -941,8 +942,11 @@ def _on_destroy(_e, w=dw.win) -> None:
except tk.TclError:
logger.exception("Failed to detach tab %s", tab_id)
return
- dw._ensure_toolbox(child)
- dw._activate_hooks(child)
+ try:
+ text = dw.nb.tab(child, "text")
+ except Exception:
+ text = ""
+ dw.add_moved_widget(child, text)
def rewrite_option_references(self, mapping: dict[tk.Widget, tk.Widget]) -> None:
"""Rewrite widget configuration options to point at cloned widgets."""
diff --git a/tests/detachment/window/test_detached_window_moved_widget.py b/tests/detachment/window/test_detached_window_moved_widget.py
index de07b85c..c3107747 100644
--- a/tests/detachment/window/test_detached_window_moved_widget.py
+++ b/tests/detachment/window/test_detached_window_moved_widget.py
@@ -1,3 +1,4 @@
+# GNU disclaimer
# Author: Miguel Marina
# SPDX-License-Identifier: GPL-3.0-or-later
#
@@ -24,6 +25,7 @@
import pytest
+from gui.utils.closable_notebook import ClosableNotebook
from gui.utils.detached_window import DetachedWindow
@@ -81,3 +83,75 @@ def test_selector_event_triggers_switch(self) -> None:
diagram.toolbox_selector.event_generate("<>")
assert diagram.log.count("switch") == count + 1
root.destroy()
+
+ def test_detached_content_tracks_floating_window_resize(
+ self, monkeypatch: pytest.MonkeyPatch
+ ) -> None:
+ try:
+ root = tk.Tk()
+ except tk.TclError:
+ pytest.skip("Tk not available")
+
+ nb = ClosableNotebook(root)
+ nb.pack(expand=True, fill="both")
+
+ class TrackingDetachedWindow(DetachedWindow):
+ created: list["TrackingDetachedWindow"] = []
+
+ def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 - signature from parent
+ super().__init__(*args, **kwargs)
+ TrackingDetachedWindow.created.append(self)
+
+ monkeypatch.setattr(
+ "gui.utils.closable_notebook.DetachedWindow", TrackingDetachedWindow
+ )
+ TrackingDetachedWindow.created.clear()
+
+ class LoggedFrame(tk.Frame):
+ def __init__(self, master: tk.Misc) -> None:
+ super().__init__(master)
+ self.events: list[str] = []
+ self.bind("<>", self._on_host_resize, add="+")
+
+ def _on_host_resize(self, event: tk.Event) -> None: # pragma: no cover - Tk callback
+ data = getattr(event, "data", "")
+ if not data:
+ width = getattr(event, "width", 0)
+ height = getattr(event, "height", 0)
+ data = f"{width}x{height}"
+ self.events.append(str(data))
+
+ frame = LoggedFrame(nb)
+ nb.add(frame, text="Detached")
+ root.update_idletasks()
+
+ tabs = nb.tabs()
+ assert tabs
+ nb._detach_tab(tabs[0], 120, 160)
+
+ assert TrackingDetachedWindow.created
+ detached = TrackingDetachedWindow.created[-1]
+ win = detached.win
+ win.update_idletasks()
+
+ frame.events.clear()
+ win.geometry("420x315+40+40")
+ win.update()
+ win.update_idletasks()
+
+ assert frame.events, "Detached widget did not receive resize notification"
+ size_tokens = frame.events[-1].split("x")
+ assert len(size_tokens) == 2
+ width, height = map(int, size_tokens)
+ assert width == 420
+ assert height == 315
+ assert frame.winfo_width() == 420
+
+ nb.configure(width=200, height=180)
+ root.update_idletasks()
+ assert frame.events[-1] == "420x315"
+ assert frame.winfo_width() == 420
+
+ if detached.win.winfo_exists():
+ detached.win.destroy()
+ root.destroy()