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)
# 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',
(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)
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)
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
--- /dev/null
+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.