]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-75666: Tkinter: "unbind(sequence, funcid)" now only unbinds "funcid" (GH-111322)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 6 Dec 2023 14:42:15 +0000 (16:42 +0200)
committerGitHub <noreply@github.com>
Wed, 6 Dec 2023 14:42:15 +0000 (16:42 +0200)
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
Lib/tkinter/__init__.py
Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst [new file with mode: 0644]

index ca99caaf88b80dd89efc62fc7a524167f5a80997..6639eaaa59936a9633cd296ee67e8c6cf397afe7 100644 (file)
@@ -479,26 +479,46 @@ class BindTest(AbstractTkTest, unittest.TestCase):
 
     def test_unbind2(self):
         f = self.frame
+        f.wait_visibility()
+        f.focus_force()
+        f.update_idletasks()
         event = '<Control-Alt-Key-c>'
         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.assertCommandNotExist(funcid)
-        self.assertCommandExist(funcid2)
+        self.assertNotIn(funcid2, script)
+        self.assertIn(funcid, script)
+        self.assertIn(funcid3, script)
+        self.assertEqual(f.bind(), (event,))
+        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)
index 0df7f9d889413c803d9687e42134d00ccc79df04..124882420c255c3754ef4b59d0b69977701db8ed 100644 (file)
@@ -1527,10 +1527,24 @@ class Misc:
         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 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, '')
+        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 (file)
index 0000000..d774cc4
--- /dev/null
@@ -0,0 +1,6 @@
+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
+*sequence*, keeping other commands, and deletes the *funcid* command. It
+leaves *sequence* unbound only if *funcid* was the last bound command.