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
21 changes: 20 additions & 1 deletion Lib/test/test_tkinter/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,27 @@ class Button2(tkinter.Button):
self.assertNotEqual(str(f), str(f2))
b = tkinter.Button(f2)
b2 = Button2(f2)
for name in str(b).split('.') + str(b2).split('.'):
for w in (t, f, f2, b, b2):
# The full path name starts with a dot, the name of the root.
self.assertTrue(str(w).startswith('.'), msg=repr(str(w)))
name = w.winfo_name()
# A generated name is not empty and contains no dot, which would
# be interpreted as a path name component separator.
self.assertTrue(name, msg=repr(name))
self.assertNotIn('.', name, msg=repr(name))
# A generated name can be used not only as a window name, but also
# as a canvas or text tag, an option database pattern or a Tcl list
# element, so it must avoid characters that are special there.
# It is marked so as not to look like a user-chosen name.
self.assertFalse(name.isidentifier(), msg=repr(name))
# A capital letter starts a class name in an option pattern.
self.assertFalse(name[0].isupper(), msg=repr(name))
# "!&|^()" are operators in canvas tag expressions (gh-143070),
# "*" separates words in an option pattern, and whitespace and
# "{}[]\\"$;" are special in Tcl lists and scripts.
self.assertNotRegex(name, r'[][!&|^()*\s{}"\\$;]', msg=repr(name))
# "-", "@" and "~" are special only as the first character.
self.assertNotIn(name[0], '-@~', msg=repr(name))
b3 = tkinter.Button(f2)
b4 = Button2(f2)
self.assertEqual(len({str(b), str(b2), str(b3), str(b4)}), 4)
Expand Down
8 changes: 8 additions & 0 deletions Lib/test/test_tkinter/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,9 @@ def test_image(self):
# An embedded image occupies a single index position.
self.assertEqual(text.index('end - 1 char'), '1.3')

# The image name can be used as an index; it is matched as a whole.
self.assertEqual(text.index(name), '1.1')

# Either a name or an image is required, and the index must be valid.
self.assertRaises(TclError, text.image_create, '1.0')
self.assertRaises(TclError, text.image_create, 'invalid',
Expand All @@ -405,6 +408,11 @@ def test_window(self):
(str(button),))
self.assertEqual(text.window_cget('1.1', 'window'), str(button))

# The window can be addressed by its name where an index is expected;
# the name is matched as a whole rather than parsed (gh-143070).
self.assertEqual(text.index(str(button)), '1.1')
self.assertEqual(text.window_cget(str(button), 'window'), str(button))

text.window_configure('1.1', padx=5)
self.assertEqual(text.window_cget('1.1', 'padx'), 5)

Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_tkinter/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,12 @@ def test_find(self):
for result in (c.find_all(), c.find_withtag(r1)):
self.assertIsInstance(result, tuple)

# An automatically generated widget name can be used as a tag
# (gh-143070).
w = tkinter.Frame(c)
r4 = c.create_window(0, 0, window=w, tags=str(w))
self.assertEqual(c.find_withtag(str(w)), (r4,))

self.assertRaises(TclError, c.find_closest, 'spam', 0)
self.assertRaises(TclError, c.find_enclosed, 0, 0, 'spam', 0)
self.assertRaises(TclError, c.find_overlapping, 0, 0, 'spam', 0)
Expand Down
10 changes: 7 additions & 3 deletions Lib/tkinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2949,16 +2949,20 @@ def _setup(self, master, cnf):
del cnf['name']
if not name:
name = self.__class__.__name__.lower()
# Generated names are marked with a leading "+", which a user is
# unlikely to use, so they do not clash with explicit names.
# "+" is also one of the few symbols with no special meaning in
# canvas and text tag expressions, so the name can be used as a tag.
if name[-1].isdigit():
name += "!" # Avoid duplication when calculating names below
name += "+" # Avoid duplication when calculating names below
if master._last_child_ids is None:
master._last_child_ids = {}
count = master._last_child_ids.get(name, 0) + 1
master._last_child_ids[name] = count
if count == 1:
name = '!%s' % (name,)
name = '+%s' % (name,)
else:
name = '!%s%d' % (name, count)
name = '+%s%d' % (name, count)
self._name = name
if master._w=='.':
self._w = '.' + name
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Automatically generated :mod:`tkinter` widget names now start with ``"+"``
instead of ``"!"``, so that they can be used as tags in the
:class:`!tkinter.Canvas` and :class:`!tkinter.Text` widgets.
Loading