]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-113951: Tkinter: "tag_unbind(tag, sequence, funcid)" now only unbinds "funcid...
authorSerhiy Storchaka <storchaka@gmail.com>
Sun, 4 Feb 2024 15:49:42 +0000 (17:49 +0200)
committerGitHub <noreply@github.com>
Sun, 4 Feb 2024 15:49:42 +0000 (17:49 +0200)
Previously, "tag_unbind(tag, sequence, funcid)" methods of Text and
Canvas widgets destroyed the current binding for "sequence", leaving
"sequence" unbound, and deleted the "funcid" command.

Now they remove only "funcid" from the binding for "sequence", keeping
other commands, and delete the "funcid" command.
They leave "sequence" unbound only if "funcid" was the last bound command.

Lib/test/test_tkinter/test_misc.py
Lib/tkinter/__init__.py
Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst [new file with mode: 0644]

index dc8a810235fc9bd5566e948317afedad5cc4d0a7..71553503005c48b19fbdaf712f4af3d76f2942e0 100644 (file)
@@ -706,6 +706,101 @@ class BindTest(AbstractTkTest, unittest.TestCase):
         self.assertCommandExist(funcid2)
         self.assertCommandExist(funcid3)
 
+    def _test_tag_bind(self, w):
+        tag = 'sel'
+        event = '<Control-Alt-Key-a>'
+        w.pack()
+        self.assertRaises(TypeError, w.tag_bind)
+        tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind
+        if isinstance(w, tkinter.Text):
+            self.assertRaises(TypeError, w.tag_bind, tag)
+            self.assertRaises(TypeError, w.tag_bind, tag, event)
+        self.assertEqual(tag_bind(tag), ())
+        self.assertEqual(tag_bind(tag, event), '')
+        def test1(e): pass
+        def test2(e): pass
+
+        funcid = w.tag_bind(tag, event, test1)
+        self.assertEqual(tag_bind(tag), (event,))
+        script = tag_bind(tag, event)
+        self.assertIn(funcid, script)
+        self.assertCommandExist(funcid)
+
+        funcid2 = w.tag_bind(tag, event, test2, add=True)
+        script = tag_bind(tag, event)
+        self.assertIn(funcid, script)
+        self.assertIn(funcid2, script)
+        self.assertCommandExist(funcid)
+        self.assertCommandExist(funcid2)
+
+    def _test_tag_unbind(self, w):
+        tag = 'sel'
+        event = '<Control-Alt-Key-b>'
+        w.pack()
+        tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind
+        self.assertEqual(tag_bind(tag), ())
+        self.assertEqual(tag_bind(tag, event), '')
+        def test1(e): pass
+        def test2(e): pass
+
+        funcid = w.tag_bind(tag, event, test1)
+        funcid2 = w.tag_bind(tag, event, test2, add=True)
+
+        self.assertRaises(TypeError, w.tag_unbind, tag)
+        w.tag_unbind(tag, event)
+        self.assertEqual(tag_bind(tag, event), '')
+        self.assertEqual(tag_bind(tag), ())
+
+    def _test_tag_bind_rebind(self, w):
+        tag = 'sel'
+        event = '<Control-Alt-Key-d>'
+        w.pack()
+        tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind
+        self.assertEqual(tag_bind(tag), ())
+        self.assertEqual(tag_bind(tag, event), '')
+        def test1(e): pass
+        def test2(e): pass
+        def test3(e): pass
+
+        funcid = w.tag_bind(tag, event, test1)
+        funcid2 = w.tag_bind(tag, event, test2, add=True)
+        script = tag_bind(tag, event)
+        self.assertIn(funcid2, script)
+        self.assertIn(funcid, script)
+        self.assertCommandExist(funcid)
+        self.assertCommandExist(funcid2)
+
+        funcid3 = w.tag_bind(tag, event, test3)
+        script = tag_bind(tag, event)
+        self.assertNotIn(funcid, script)
+        self.assertNotIn(funcid2, script)
+        self.assertIn(funcid3, script)
+        self.assertCommandExist(funcid3)
+
+    def test_canvas_tag_bind(self):
+        c = tkinter.Canvas(self.frame)
+        self._test_tag_bind(c)
+
+    def test_canvas_tag_unbind(self):
+        c = tkinter.Canvas(self.frame)
+        self._test_tag_unbind(c)
+
+    def test_canvas_tag_bind_rebind(self):
+        c = tkinter.Canvas(self.frame)
+        self._test_tag_bind_rebind(c)
+
+    def test_text_tag_bind(self):
+        t = tkinter.Text(self.frame)
+        self._test_tag_bind(t)
+
+    def test_text_tag_unbind(self):
+        t = tkinter.Text(self.frame)
+        self._test_tag_unbind(t)
+
+    def test_text_tag_bind_rebind(self):
+        t = tkinter.Text(self.frame)
+        self._test_tag_bind_rebind(t)
+
     def test_bindtags(self):
         f = self.frame
         self.assertEqual(self.root.bindtags(), ('.', 'Tk', 'all'))
index e0db41dd915ece99c4512c48df87ff7f303a2e8d..a1567d332ae6efc0ed3c41ff09604ef572a5399e 100644 (file)
@@ -1537,16 +1537,19 @@ class Misc:
         Otherwise destroy the current binding for SEQUENCE, leaving SEQUENCE
         unbound.
         """
+        self._unbind(('bind', self._w, sequence), funcid)
+
+    def _unbind(self, what, funcid=None):
         if funcid is None:
-            self.tk.call('bind', self._w, sequence, '')
+            self.tk.call(*what, '')
         else:
-            lines = self.tk.call('bind', self._w, sequence).split('\n')
+            lines = self.tk.call(what).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.tk.call(*what, keep)
             self.deletecommand(funcid)
 
     def bind_all(self, sequence=None, func=None, add=None):
@@ -1558,7 +1561,7 @@ class Misc:
 
     def unbind_all(self, sequence):
         """Unbind for all widgets for event SEQUENCE all functions."""
-        self.tk.call('bind', 'all' , sequence, '')
+        self._root()._unbind(('bind', 'all', sequence))
 
     def bind_class(self, className, sequence=None, func=None, add=None):
         """Bind to widgets with bindtag CLASSNAME at event
@@ -1573,7 +1576,7 @@ class Misc:
     def unbind_class(self, className, sequence):
         """Unbind for all widgets with bindtag CLASSNAME for event SEQUENCE
         all functions."""
-        self.tk.call('bind', className , sequence, '')
+        self._root()._unbind(('bind', className, sequence))
 
     def mainloop(self, n=0):
         """Call the mainloop of Tk."""
@@ -2885,9 +2888,7 @@ class Canvas(Widget, XView, YView):
     def tag_unbind(self, tagOrId, sequence, funcid=None):
         """Unbind for all items with TAGORID for event SEQUENCE  the
         function identified with FUNCID."""
-        self.tk.call(self._w, 'bind', tagOrId, sequence, '')
-        if funcid:
-            self.deletecommand(funcid)
+        self._unbind((self._w, 'bind', tagOrId, sequence), funcid)
 
     def tag_bind(self, tagOrId, sequence=None, func=None, add=None):
         """Bind to all items with TAGORID at event SEQUENCE a call to function FUNC.
@@ -3997,9 +3998,7 @@ class Text(Widget, XView, YView):
     def tag_unbind(self, tagName, sequence, funcid=None):
         """Unbind for all characters with TAGNAME for event SEQUENCE  the
         function identified with FUNCID."""
-        self.tk.call(self._w, 'tag', 'bind', tagName, sequence, '')
-        if funcid:
-            self.deletecommand(funcid)
+        return self._unbind((self._w, 'tag', 'bind', tagName, sequence), funcid)
 
     def tag_bind(self, tagName, sequence, func, add=None):
         """Bind to all characters with TAGNAME at event SEQUENCE a call to function FUNC.
@@ -4010,6 +4009,11 @@ class Text(Widget, XView, YView):
         return self._bind((self._w, 'tag', 'bind', tagName),
                   sequence, func, add)
 
+    def _tag_bind(self, tagName, sequence=None, func=None, add=None):
+        # For tests only
+        return self._bind((self._w, 'tag', 'bind', tagName),
+                  sequence, func, add)
+
     def tag_cget(self, tagName, option):
         """Return the value of OPTION for tag TAGNAME."""
         if option[:1] != '-':
diff --git a/Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst b/Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst
new file mode 100644 (file)
index 0000000..e683472
--- /dev/null
@@ -0,0 +1,7 @@
+Fix the behavior of ``tag_unbind()`` methods of :class:`tkinter.Text` and
+:class:`tkinter.Canvas` classes with three arguments. Previously,
+``widget.tag_unbind(tag, 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.