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
4 changes: 4 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
-->

# Version History
- 0.2.221 - Inspect registered Tcl commands when cancelling ``after`` callbacks
so root-level events without widget paths are removed.
- Add regression test ensuring unstored root callbacks referencing
detached widgets produce no ``invalid command name`` logs.
- 0.2.220 - Include script-bearing options when rewriting widget configuration
references so commands target cloned widgets.
- Add grouped tests verifying button and menu commands operate on
Expand Down
21 changes: 21 additions & 0 deletions gui/utils/closable_notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ def cancel_after_events(widget: tk.Widget, cancelled: set[str] | None = None) ->
if cancelled is None:
cancelled = set()

def _callback_references_widget(func: t.Any) -> bool:
if getattr(func, "__self__", None) is widget:
return True
try:
closure = inspect.getclosurevars(func)
if widget in closure.nonlocals.values() or widget in closure.globals.values():
return True
except Exception:
pass
return False

def _cancel_ident(ident: str) -> None:
tkapp_local = getattr(widget, "tk", None)
if tkapp_local is None:
Expand Down Expand Up @@ -83,6 +94,16 @@ def _cancel_ident(ident: str) -> None:
continue
if str(widget) in script:
_cancel_ident(ident)
continue
try:
root = widget._root()
except Exception:
root = None
if root is not None:
tcl_cmds = getattr(root, "_tclCommands", {}) or {}
cb = tcl_cmds.get(script)
if cb is not None and _callback_references_widget(cb):
_cancel_ident(ident)

try:
for name in dir(widget):
Expand Down
10 changes: 10 additions & 0 deletions tests/detachment/after_callbacks/test_cancel_root_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,13 @@ def test_detach_and_close_no_invalid_command(self, capsys):
root.update()
assert "invalid command name" not in capsys.readouterr().err
root.destroy()

def test_unstored_command_references_widget(self, capsys):
root = tk.Tk(); root.withdraw()
btn = tk.Button(root, text="Go")
btn.pack()
cmd = btn.register(lambda: btn.config(text="Foo"))
root.tk.call("after", "1", cmd)
cancel_after_events(btn)
root.destroy()
assert "invalid command name" not in capsys.readouterr().err