Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
-->

# Version History
- 0.2.247 - Cancel tab callbacks before forgetting or destroying widgets during
moves and detachment to prevent orphaned Tcl commands such as
``*_animate``.
- 0.2.246 - Guard tab rollback by checking target ownership and swallowing
race-condition errors when forgetting tabs.
- 0.2.245 - Verify tab parentage after moves so detached tabs skip
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
version: 0.2.246
version: 0.2.247
Author: Miguel Marina <karel.capek.robotics@gmail.com> - [LinkedIn](https://www.linkedin.com/in/progman32/)

This release safeguards tab rollback by verifying target ownership before
forgetting a tab and swallowing race-condition Tcl errors.
This release cancels tab callbacks before widgets are forgotten or destroyed,
preventing orphaned Tcl commands during tab moves and detachment.
# AutoML

AutoML is an automotive modeling and analysis tool built around a SysML-based metamodel. It lets you describe items, operating scenarios, functions, structure and interfaces in a single environment.
Expand Down
32 changes: 26 additions & 6 deletions gui/utils/closable_notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,15 +466,15 @@ def _handle_close(self, event: tk.Event) -> bool:
child = self.nametowidget(tab_id)
self._closing_tab = tab_id
self.event_generate("<<NotebookTabClosed>>")
try:
self._cancel_after_events(child)
except Exception:
pass
if tab_id in self.tabs():
try:
self.forget(tab_id)
except tk.TclError:
pass
try:
self._cancel_after_events(child)
except Exception:
pass
try:
child.destroy()
except Exception:
Expand Down Expand Up @@ -532,14 +532,20 @@ def _move_tab(self, tab_id: str, target: "ClosableNotebook") -> bool:

The method first tries Tk's ``winfo`` reparenting to avoid cloning. If
the operation fails, the widget is restored to its original notebook and
the caller may fall back to cloning.
the caller may fall back to cloning. Tk ``after`` callbacks are
cancelled before widgets are forgotten to avoid orphaned Tcl commands
such as ``*_animate``.
"""

text = self.tab(tab_id, "text")
child = self.nametowidget(tab_id)

def _safe_forget(nb: "ClosableNotebook", widget: tk.Widget) -> None:
if widget.master is nb or str(widget) in nb.tabs():
try:
self._cancel_after_events(widget)
except Exception:
pass
try:
nb.forget(widget)
except tk.TclError:
Expand Down Expand Up @@ -577,6 +583,10 @@ def _safe_forget(nb: "ClosableNotebook", widget: tk.Widget) -> None:
self.add(child, text=text)
self.select(child)
if isinstance(self.master, tk.Toplevel) and not self.tabs():
try:
self._cancel_after_events(self.master)
except Exception:
pass
self.master.destroy()
return moved

Expand Down Expand Up @@ -1284,7 +1294,11 @@ def _copy_tree_item(
def _cancel_after_events(
self, widget: tk.Widget, cancelled: set[str] | None = None
) -> None:
"""Wrapper for :func:`cancel_after_events` for backward compatibility."""
"""Wrapper for :func:`cancel_after_events`.

Cancels Tk ``after`` callbacks tied to *widget* to avoid orphaned Tcl
commands such as ``*_animate``.
"""

cancel_after_events(widget, cancelled)

Expand Down Expand Up @@ -1354,6 +1368,12 @@ def _raise_widgets(


def _detach_tab(self, tab_id: str, x: int, y: int) -> None:
"""Detach *tab_id* into a new floating window at *(x, y)*.

Tk ``after`` callbacks tied to the original tab are cancelled before it
is forgotten to prevent orphaned Tcl commands such as ``*_animate``.
"""

from .detached_window import DetachedWindow

self.update_idletasks()
Expand Down