From f8ad1a930497399f38fe6f5cedfe4a1a4c461cb6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 25 Oct 2023 16:43:04 +0300 Subject: [PATCH 1/6] gh-75666: Tkinter: "unbind(sequence, funcid)" now only unbinds "funcid" Previously, "widget.unbind(sequence, funcid)" destroyed the current binding for "sequence", leaving "sequence" unbound, and deleted the "funcid" command. Now it removes only "funcid" from the binding for "sequence", keeping other commands, and deletes the "funcid" command. It leaves "sequence" unbound only if "funcid" was the last bound command. Co-authored-by: GiovanniL <13402461+GiovaLomba@users.noreply.github.com> --- Lib/test/test_tkinter/test_misc.py | 2 ++ Lib/tkinter/__init__.py | 19 +++++++++++++++---- ...3-10-25-16-37-13.gh-issue-75666.BpsWut.rst | 6 ++++++ 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index ca99caaf88b80d..1ead1dfe3aa937 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -491,6 +491,8 @@ def test2(e): pass f.unbind(event, funcid) script = f.bind(event) self.assertNotIn(funcid, script) + self.assertIn(funcid2, script) + self.assertEqual(f.bind(), (event,)) self.assertCommandNotExist(funcid) self.assertCommandExist(funcid2) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 47b93d31fd3ffd..5de78ac46e3dc8 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -1527,10 +1527,21 @@ def bind(self, sequence=None, func=None, add=None): return self._bind(('bind', self._w), sequence, func, add) def unbind(self, sequence, funcid=None): - """Unbind for this widget for event SEQUENCE the - function identified with FUNCID.""" - self.tk.call('bind', self._w, sequence, '') - if funcid: + """Unbind for this widget the event SEQUENCE. + + If FUNCID is given, only unbind the function identified with FUNCID + and also delete that command. + """ + if funcid is None: + self.tk.call('bind', self._w, sequence, '') + else: + lines = self.tk.call('bind', self._w, sequence).split('\n') + prefix = f'if {{"[{funcid} ' + keep = '\n'.join(line for line in lines + if not line.startswith(prefix)) + if not keep.strip(): + keep = '' + self.tk.call('bind', self._w, sequence, keep) self.deletecommand(funcid) def bind_all(self, sequence=None, func=None, add=None): diff --git a/Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst b/Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst new file mode 100644 index 00000000000000..8f3f950ec3aafc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst @@ -0,0 +1,6 @@ +Change the behavior of :mod:`tkinter` widget's ``unbind()`` method with two +arguments. Previously, ``widget.unbind(sequence, funcid)`` destroyed the +current binding for *sequence*, leaving *sequence* unbound, and deleted the +*funcid* command. Now it removes only *funcid* from the binding for +*sequence*, keeping other commands, and deletes the *funcid* command. It +leaves *sequence* unbound only if *funcid* was the last bound command. From ba530207e336a8ea1d4d0809f1776129958813ed Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 26 Oct 2023 19:13:33 +0300 Subject: [PATCH 2/6] Address some of review comments. --- Lib/test/test_tkinter/test_misc.py | 33 ++++++++++++++----- ...3-10-25-16-37-13.gh-issue-75666.BpsWut.rst | 2 +- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 1ead1dfe3aa937..57d414784bb849 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -433,6 +433,8 @@ def setUp(self): self.frame = tkinter.Frame(self.root, class_='Test', width=150, height=100) self.frame.pack() + self.frame.focus_set() + self.frame.wait_visibility() def assertCommandExist(self, funcid): self.assertEqual(_info_commands(self.root, funcid), (funcid,)) @@ -482,25 +484,40 @@ def test_unbind2(self): event = '' self.assertEqual(f.bind(), ()) self.assertEqual(f.bind(event), '') - def test1(e): pass - def test2(e): pass + def test1(e): events.append('a') + def test2(e): events.append('b') + def test3(e): events.append('c') funcid = f.bind(event, test1) funcid2 = f.bind(event, test2, add=True) + funcid3 = f.bind(event, test3, add=True) + events = [] + f.event_generate(event) + self.assertEqual(events, ['a', 'b', 'c']) - f.unbind(event, funcid) + f.unbind(event, funcid2) script = f.bind(event) - self.assertNotIn(funcid, script) - self.assertIn(funcid2, script) + self.assertNotIn(funcid2, script) + self.assertIn(funcid, script) + self.assertIn(funcid3, script) self.assertEqual(f.bind(), (event,)) - self.assertCommandNotExist(funcid) - self.assertCommandExist(funcid2) + self.assertCommandNotExist(funcid2) + self.assertCommandExist(funcid) + self.assertCommandExist(funcid3) + events = [] + f.event_generate(event) + self.assertEqual(events, ['a', 'c']) - f.unbind(event, funcid2) + f.unbind(event, funcid) + f.unbind(event, funcid3) self.assertEqual(f.bind(event), '') self.assertEqual(f.bind(), ()) self.assertCommandNotExist(funcid) self.assertCommandNotExist(funcid2) + self.assertCommandNotExist(funcid3) + events = [] + f.event_generate(event) + self.assertEqual(events, []) # non-idempotent self.assertRaises(tkinter.TclError, f.unbind, event, funcid2) diff --git a/Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst b/Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst index 8f3f950ec3aafc..d774cc4f7c687f 100644 --- a/Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst +++ b/Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst @@ -1,4 +1,4 @@ -Change the behavior of :mod:`tkinter` widget's ``unbind()`` method with two +Fix the behavior of :mod:`tkinter` widget's ``unbind()`` method with two arguments. Previously, ``widget.unbind(sequence, funcid)`` destroyed the current binding for *sequence*, leaving *sequence* unbound, and deleted the *funcid* command. Now it removes only *funcid* from the binding for From 3e71c69409c185ab65b26ccb0e406bc8631571d0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 28 Oct 2023 09:13:31 +0300 Subject: [PATCH 3/6] Update the docstring. --- Lib/tkinter/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 5de78ac46e3dc8..bd5a6850323a02 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -1530,7 +1530,10 @@ def unbind(self, sequence, funcid=None): """Unbind for this widget the event SEQUENCE. If FUNCID is given, only unbind the function identified with FUNCID - and also delete that command. + and also delete the corresponding Tcl command. + + Otherwise destroy the current binding for SEQUENCE, leaving SEQUENCE + unbound. """ if funcid is None: self.tk.call('bind', self._w, sequence, '') From f8b58bc0dc7ef1ace9f1560ed1c19ac34825be86 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 28 Oct 2023 09:21:44 +0300 Subject: [PATCH 4/6] Try to fix tests on Windows. --- Lib/test/test_tkinter/test_misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 57d414784bb849..506710588b82b2 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -433,8 +433,6 @@ def setUp(self): self.frame = tkinter.Frame(self.root, class_='Test', width=150, height=100) self.frame.pack() - self.frame.focus_set() - self.frame.wait_visibility() def assertCommandExist(self, funcid): self.assertEqual(_info_commands(self.root, funcid), (funcid,)) @@ -481,6 +479,8 @@ def test2(e): pass def test_unbind2(self): f = self.frame + f.focus_set() + f.wait_visibility() event = '' self.assertEqual(f.bind(), ()) self.assertEqual(f.bind(event), '') From be3ba6a9d2a8d113ccf6ee7f984d2afbeb599a2e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 28 Oct 2023 10:03:13 +0300 Subject: [PATCH 5/6] Fix tests on Ubuntu. --- Lib/test/test_tkinter/test_misc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 506710588b82b2..e4410ce375154e 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -481,6 +481,7 @@ def test_unbind2(self): f = self.frame f.focus_set() f.wait_visibility() + f.update_idletasks() event = '' self.assertEqual(f.bind(), ()) self.assertEqual(f.bind(event), '') From c5b05e22bfb132e43539d0e6d6049bdf876d27cc Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 28 Oct 2023 10:57:54 +0300 Subject: [PATCH 6/6] Try to rearrange the code. --- Lib/test/test_tkinter/test_misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index e4410ce375154e..6639eaaa59936a 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -479,8 +479,8 @@ def test2(e): pass def test_unbind2(self): f = self.frame - f.focus_set() f.wait_visibility() + f.focus_force() f.update_idletasks() event = '' self.assertEqual(f.bind(), ())