Skip to content

Commit f18341f

Browse files
[3.11] gh-113951: Tkinter: "tag_unbind(tag, sequence, funcid)" now only unbinds "funcid" (GH-113955) (GH-114998)
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. (cherry picked from commit 7e42fdd) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent f8cba75 commit f18341f

File tree

3 files changed

+117
-11
lines changed

3 files changed

+117
-11
lines changed

Lib/tkinter/__init__.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,16 +1458,19 @@ def unbind(self, sequence, funcid=None):
14581458
Otherwise destroy the current binding for SEQUENCE, leaving SEQUENCE
14591459
unbound.
14601460
"""
1461+
self._unbind(('bind', self._w, sequence), funcid)
1462+
1463+
def _unbind(self, what, funcid=None):
14611464
if funcid is None:
1462-
self.tk.call('bind', self._w, sequence, '')
1465+
self.tk.call(*what, '')
14631466
else:
1464-
lines = self.tk.call('bind', self._w, sequence).split('\n')
1467+
lines = self.tk.call(what).split('\n')
14651468
prefix = f'if {{"[{funcid} '
14661469
keep = '\n'.join(line for line in lines
14671470
if not line.startswith(prefix))
14681471
if not keep.strip():
14691472
keep = ''
1470-
self.tk.call('bind', self._w, sequence, keep)
1473+
self.tk.call(*what, keep)
14711474
self.deletecommand(funcid)
14721475

14731476
def bind_all(self, sequence=None, func=None, add=None):
@@ -1479,7 +1482,7 @@ def bind_all(self, sequence=None, func=None, add=None):
14791482

14801483
def unbind_all(self, sequence):
14811484
"""Unbind for all widgets for event SEQUENCE all functions."""
1482-
self.tk.call('bind', 'all' , sequence, '')
1485+
self._root()._unbind(('bind', 'all', sequence))
14831486

14841487
def bind_class(self, className, sequence=None, func=None, add=None):
14851488
"""Bind to widgets with bindtag CLASSNAME at event
@@ -1494,7 +1497,7 @@ def bind_class(self, className, sequence=None, func=None, add=None):
14941497
def unbind_class(self, className, sequence):
14951498
"""Unbind for all widgets with bindtag CLASSNAME for event SEQUENCE
14961499
all functions."""
1497-
self.tk.call('bind', className , sequence, '')
1500+
self._root()._unbind(('bind', className, sequence))
14981501

14991502
def mainloop(self, n=0):
15001503
"""Call the mainloop of Tk."""
@@ -2805,9 +2808,7 @@ def bbox(self, *args):
28052808
def tag_unbind(self, tagOrId, sequence, funcid=None):
28062809
"""Unbind for all items with TAGORID for event SEQUENCE the
28072810
function identified with FUNCID."""
2808-
self.tk.call(self._w, 'bind', tagOrId, sequence, '')
2809-
if funcid:
2810-
self.deletecommand(funcid)
2811+
self._unbind((self._w, 'bind', tagOrId, sequence), funcid)
28112812

28122813
def tag_bind(self, tagOrId, sequence=None, func=None, add=None):
28132814
"""Bind to all items with TAGORID at event SEQUENCE a call to function FUNC.
@@ -3914,9 +3915,7 @@ def tag_add(self, tagName, index1, *args):
39143915
def tag_unbind(self, tagName, sequence, funcid=None):
39153916
"""Unbind for all characters with TAGNAME for event SEQUENCE the
39163917
function identified with FUNCID."""
3917-
self.tk.call(self._w, 'tag', 'bind', tagName, sequence, '')
3918-
if funcid:
3919-
self.deletecommand(funcid)
3918+
return self._unbind((self._w, 'tag', 'bind', tagName, sequence), funcid)
39203919

39213920
def tag_bind(self, tagName, sequence, func, add=None):
39223921
"""Bind to all characters with TAGNAME at event SEQUENCE a call to function FUNC.
@@ -3927,6 +3926,11 @@ def tag_bind(self, tagName, sequence, func, add=None):
39273926
return self._bind((self._w, 'tag', 'bind', tagName),
39283927
sequence, func, add)
39293928

3929+
def _tag_bind(self, tagName, sequence=None, func=None, add=None):
3930+
# For tests only
3931+
return self._bind((self._w, 'tag', 'bind', tagName),
3932+
sequence, func, add)
3933+
39303934
def tag_cget(self, tagName, option):
39313935
"""Return the value of OPTION for tag TAGNAME."""
39323936
if option[:1] != '-':

Lib/tkinter/test/test_tkinter/test_misc.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,101 @@ def test3(e): pass
652652
self.assertCommandExist(funcid2)
653653
self.assertCommandExist(funcid3)
654654

655+
def _test_tag_bind(self, w):
656+
tag = 'sel'
657+
event = '<Control-Alt-Key-a>'
658+
w.pack()
659+
self.assertRaises(TypeError, w.tag_bind)
660+
tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind
661+
if isinstance(w, tkinter.Text):
662+
self.assertRaises(TypeError, w.tag_bind, tag)
663+
self.assertRaises(TypeError, w.tag_bind, tag, event)
664+
self.assertEqual(tag_bind(tag), ())
665+
self.assertEqual(tag_bind(tag, event), '')
666+
def test1(e): pass
667+
def test2(e): pass
668+
669+
funcid = w.tag_bind(tag, event, test1)
670+
self.assertEqual(tag_bind(tag), (event,))
671+
script = tag_bind(tag, event)
672+
self.assertIn(funcid, script)
673+
self.assertCommandExist(funcid)
674+
675+
funcid2 = w.tag_bind(tag, event, test2, add=True)
676+
script = tag_bind(tag, event)
677+
self.assertIn(funcid, script)
678+
self.assertIn(funcid2, script)
679+
self.assertCommandExist(funcid)
680+
self.assertCommandExist(funcid2)
681+
682+
def _test_tag_unbind(self, w):
683+
tag = 'sel'
684+
event = '<Control-Alt-Key-b>'
685+
w.pack()
686+
tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind
687+
self.assertEqual(tag_bind(tag), ())
688+
self.assertEqual(tag_bind(tag, event), '')
689+
def test1(e): pass
690+
def test2(e): pass
691+
692+
funcid = w.tag_bind(tag, event, test1)
693+
funcid2 = w.tag_bind(tag, event, test2, add=True)
694+
695+
self.assertRaises(TypeError, w.tag_unbind, tag)
696+
w.tag_unbind(tag, event)
697+
self.assertEqual(tag_bind(tag, event), '')
698+
self.assertEqual(tag_bind(tag), ())
699+
700+
def _test_tag_bind_rebind(self, w):
701+
tag = 'sel'
702+
event = '<Control-Alt-Key-d>'
703+
w.pack()
704+
tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind
705+
self.assertEqual(tag_bind(tag), ())
706+
self.assertEqual(tag_bind(tag, event), '')
707+
def test1(e): pass
708+
def test2(e): pass
709+
def test3(e): pass
710+
711+
funcid = w.tag_bind(tag, event, test1)
712+
funcid2 = w.tag_bind(tag, event, test2, add=True)
713+
script = tag_bind(tag, event)
714+
self.assertIn(funcid2, script)
715+
self.assertIn(funcid, script)
716+
self.assertCommandExist(funcid)
717+
self.assertCommandExist(funcid2)
718+
719+
funcid3 = w.tag_bind(tag, event, test3)
720+
script = tag_bind(tag, event)
721+
self.assertNotIn(funcid, script)
722+
self.assertNotIn(funcid2, script)
723+
self.assertIn(funcid3, script)
724+
self.assertCommandExist(funcid3)
725+
726+
def test_canvas_tag_bind(self):
727+
c = tkinter.Canvas(self.frame)
728+
self._test_tag_bind(c)
729+
730+
def test_canvas_tag_unbind(self):
731+
c = tkinter.Canvas(self.frame)
732+
self._test_tag_unbind(c)
733+
734+
def test_canvas_tag_bind_rebind(self):
735+
c = tkinter.Canvas(self.frame)
736+
self._test_tag_bind_rebind(c)
737+
738+
def test_text_tag_bind(self):
739+
t = tkinter.Text(self.frame)
740+
self._test_tag_bind(t)
741+
742+
def test_text_tag_unbind(self):
743+
t = tkinter.Text(self.frame)
744+
self._test_tag_unbind(t)
745+
746+
def test_text_tag_bind_rebind(self):
747+
t = tkinter.Text(self.frame)
748+
self._test_tag_bind_rebind(t)
749+
655750
def test_bindtags(self):
656751
f = self.frame
657752
self.assertEqual(self.root.bindtags(), ('.', 'Tk', 'all'))
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Fix the behavior of ``tag_unbind()`` methods of :class:`tkinter.Text` and
2+
:class:`tkinter.Canvas` classes with three arguments. Previously,
3+
``widget.tag_unbind(tag, sequence, funcid)`` destroyed the current binding
4+
for *sequence*, leaving *sequence* unbound, and deleted the *funcid*
5+
command. Now it removes only *funcid* from the binding for *sequence*,
6+
keeping other commands, and deletes the *funcid* command. It leaves
7+
*sequence* unbound only if *funcid* was the last bound command.

0 commit comments

Comments
 (0)