diff options
-rw-r--r-- | Lib/test/test_tcl.py | 37 | ||||
-rw-r--r-- | Lib/tkinter/__init__.py | 78 | ||||
-rw-r--r-- | Lib/tkinter/test/test_ttk/test_functions.py | 27 | ||||
-rw-r--r-- | Lib/tkinter/ttk.py | 60 | ||||
-rw-r--r-- | Misc/NEWS | 3 |
5 files changed, 112 insertions, 93 deletions
diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index 9e045da..7485e58 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -7,7 +7,7 @@ from test import support _tkinter = support.import_module('_tkinter') # Make sure tkinter._fix runs to set up the environment -support.import_fresh_module('tkinter') +tkinter = support.import_fresh_module('tkinter') from tkinter import Tcl from _tkinter import TclError @@ -554,6 +554,41 @@ class TclTest(unittest.TestCase): for arg, res in testcases: self.assertEqual(split(arg), res, msg=arg) + def test_splitdict(self): + splitdict = tkinter._splitdict + tcl = self.interp.tk + + arg = '-a {1 2 3} -something foo status {}' + self.assertEqual(splitdict(tcl, arg, False), + {'-a': '1 2 3', '-something': 'foo', 'status': ''}) + self.assertEqual(splitdict(tcl, arg), + {'a': '1 2 3', 'something': 'foo', 'status': ''}) + + arg = ('-a', (1, 2, 3), '-something', 'foo', 'status', '{}') + self.assertEqual(splitdict(tcl, arg, False), + {'-a': (1, 2, 3), '-something': 'foo', 'status': '{}'}) + self.assertEqual(splitdict(tcl, arg), + {'a': (1, 2, 3), 'something': 'foo', 'status': '{}'}) + + self.assertRaises(RuntimeError, splitdict, tcl, '-a b -c ') + self.assertRaises(RuntimeError, splitdict, tcl, ('-a', 'b', '-c')) + + arg = tcl.call('list', + '-a', (1, 2, 3), '-something', 'foo', 'status', ()) + self.assertEqual(splitdict(tcl, arg), + {'a': (1, 2, 3) if self.wantobjects else '1 2 3', + 'something': 'foo', 'status': ''}) + + if tcl_version >= (8, 5): + arg = tcl.call('dict', 'create', + '-a', (1, 2, 3), '-something', 'foo', 'status', ()) + if not self.wantobjects or get_tk_patchlevel() < (8, 5, 5): + # Before 8.5.5 dicts were converted to lists through string + expected = {'a': '1 2 3', 'something': 'foo', 'status': ''} + else: + expected = {'a': (1, 2, 3), 'something': 'foo', 'status': ''} + self.assertEqual(splitdict(tcl, arg), expected) + class BigmemTclTest(unittest.TestCase): diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 4bc7d0f..c9a2c71 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -112,6 +112,29 @@ def _cnfmerge(cnfs): try: _cnfmerge = _tkinter._cnfmerge except AttributeError: pass +def _splitdict(tk, v, cut_minus=True, conv=None): + """Return a properly formatted dict built from Tcl list pairs. + + If cut_minus is True, the supposed '-' prefix will be removed from + keys. If conv is specified, it is used to convert values. + + Tcl list is expected to contain an even number of elements. + """ + t = tk.splitlist(v) + if len(t) % 2: + raise RuntimeError('Tcl list representing a dict is expected ' + 'to contain an even number of elements') + it = iter(t) + dict = {} + for key, value in zip(it, it): + key = str(key) + if cut_minus and key[0] == '-': + key = key[1:] + if conv: + value = conv(value) + dict[key] = value + return dict + class Event: """Container for the properties of an event. @@ -1391,15 +1414,10 @@ class Misc: else: options = self._options(cnf, kw) if not options: - res = self.tk.call('grid', - command, self._w, index) - words = self.tk.splitlist(res) - dict = {} - for i in range(0, len(words), 2): - key = words[i][1:] - value = words[i+1] - dict[key] = self._gridconvvalue(value) - return dict + return _splitdict( + self.tk, + self.tk.call('grid', command, self._w, index), + conv=self._gridconvvalue) res = self.tk.call( ('grid', command, self._w, index) + options) @@ -1959,16 +1977,10 @@ class Pack: def pack_info(self): """Return information about the packing options for this widget.""" - words = self.tk.splitlist( - self.tk.call('pack', 'info', self._w)) - dict = {} - for i in range(0, len(words), 2): - key = words[i][1:] - value = words[i+1] - if str(value)[:1] == '.': - value = self._nametowidget(value) - dict[key] = value - return dict + d = _splitdict(self.tk, self.tk.call('pack', 'info', self._w)) + if 'in' in d: + d['in'] = self.nametowidget(d['in']) + return d info = pack_info propagate = pack_propagate = Misc.pack_propagate slaves = pack_slaves = Misc.pack_slaves @@ -2010,16 +2022,10 @@ class Place: def place_info(self): """Return information about the placing options for this widget.""" - words = self.tk.splitlist( - self.tk.call('place', 'info', self._w)) - dict = {} - for i in range(0, len(words), 2): - key = words[i][1:] - value = words[i+1] - if str(value)[:1] == '.': - value = self._nametowidget(value) - dict[key] = value - return dict + d = _splitdict(self.tk, self.tk.call('place', 'info', self._w)) + if 'in' in d: + d['in'] = self.nametowidget(d['in']) + return d info = place_info slaves = place_slaves = Misc.place_slaves @@ -2059,16 +2065,10 @@ class Grid: def grid_info(self): """Return information about the options for positioning this widget in a grid.""" - words = self.tk.splitlist( - self.tk.call('grid', 'info', self._w)) - dict = {} - for i in range(0, len(words), 2): - key = words[i][1:] - value = words[i+1] - if str(value)[:1] == '.': - value = self._nametowidget(value) - dict[key] = value - return dict + d = _splitdict(self.tk, self.tk.call('grid', 'info', self._w)) + if 'in' in d: + d['in'] = self.nametowidget(d['in']) + return d info = grid_info location = grid_location = Misc.grid_location propagate = grid_propagate = Misc.grid_propagate diff --git a/Lib/tkinter/test/test_ttk/test_functions.py b/Lib/tkinter/test/test_ttk/test_functions.py index 41e6311..c9dcf97 100644 --- a/Lib/tkinter/test/test_ttk/test_functions.py +++ b/Lib/tkinter/test/test_ttk/test_functions.py @@ -324,26 +324,13 @@ class InternalFunctionsTest(unittest.TestCase): "-opt {3 2m}") - def test_dict_from_tcltuple(self): - fakettuple = ('-a', '{1 2 3}', '-something', 'foo') - - self.assertEqual(ttk._dict_from_tcltuple(fakettuple, False), - {'-a': '{1 2 3}', '-something': 'foo'}) - - self.assertEqual(ttk._dict_from_tcltuple(fakettuple), - {'a': '{1 2 3}', 'something': 'foo'}) - - # passing a tuple with a single item should return an empty dict, - # since it tries to break the tuple by pairs. - self.assertFalse(ttk._dict_from_tcltuple(('single', ))) - - sspec = MockStateSpec('a', 'b') - self.assertEqual(ttk._dict_from_tcltuple(('-a', (sspec, 'val'))), - {'a': [('a', 'b', 'val')]}) - - self.assertEqual(ttk._dict_from_tcltuple((MockTclObj('-padding'), - [MockTclObj('1'), 2, MockTclObj('3m')])), - {'padding': [1, 2, '3m']}) + def test_tclobj_to_py(self): + self.assertEqual( + ttk._tclobj_to_py((MockStateSpec('a', 'b'), 'val')), + [('a', 'b', 'val')]) + self.assertEqual( + ttk._tclobj_to_py([MockTclObj('1'), 2, MockTclObj('3m')]), + [1, 2, '3m']) def test_list_from_statespec(self): diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py index c6ca9a5..29d225c 100644 --- a/Lib/tkinter/ttk.py +++ b/Lib/tkinter/ttk.py @@ -26,7 +26,7 @@ __all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label", "tclobjs_to_py", "setup_master"] import tkinter -from tkinter import _flatten, _join, _stringify +from tkinter import _flatten, _join, _stringify, _splitdict # Verify if Tk is new enough to not need the Tile package _REQUIRE_TILE = True if tkinter.TkVersion < 8.5 else False @@ -240,21 +240,6 @@ def _script_from_settings(settings): return '\n'.join(script) -def _dict_from_tcltuple(ttuple, cut_minus=True): - """Break tuple in pairs, format it properly, then build the return - dict. If cut_minus is True, the supposed '-' prefixing options will - be removed. - - ttuple is expected to contain an even number of elements.""" - opt_start = 1 if cut_minus else 0 - - retdict = {} - it = iter(ttuple) - for opt, val in zip(it, it): - retdict[str(opt)[opt_start:]] = val - - return tclobjs_to_py(retdict) - def _list_from_statespec(stuple): """Construct a list from the given statespec tuple according to the accepted statespec accepted by _format_mapdict.""" @@ -314,7 +299,7 @@ def _val_or_dict(tk, options, *args): if len(options) % 2: # option specified without a value, return its value return res - return _dict_from_tcltuple(tk.splitlist(res)) + return _splitdict(tk, res, conv=_tclobj_to_py) def _convert_stringval(value): """Converts a value to, hopefully, a more appropriate Python object.""" @@ -334,20 +319,24 @@ def _to_number(x): x = int(x) return x +def _tclobj_to_py(val): + """Return value converted from Tcl object to Python object.""" + if val and hasattr(val, '__len__') and not isinstance(val, str): + if getattr(val[0], 'typename', None) == 'StateSpec': + val = _list_from_statespec(val) + else: + val = list(map(_convert_stringval, val)) + + elif hasattr(val, 'typename'): # some other (single) Tcl object + val = _convert_stringval(val) + + return val + def tclobjs_to_py(adict): """Returns adict with its values converted from Tcl objects to Python objects.""" for opt, val in adict.items(): - if val and hasattr(val, '__len__') and not isinstance(val, str): - if getattr(val[0], 'typename', None) == 'StateSpec': - val = _list_from_statespec(val) - else: - val = list(map(_convert_stringval, val)) - - elif hasattr(val, 'typename'): # some other (single) Tcl object - val = _convert_stringval(val) - - adict[opt] = val + adict[opt] = _tclobj_to_py(val) return adict @@ -407,8 +396,10 @@ class Style(object): return _list_from_statespec(self.tk.splitlist( self.tk.call(self._name, "map", style, '-%s' % query_opt))) - return _dict_from_tcltuple(self.tk.splitlist( - self.tk.call(self._name, "map", style, *(_format_mapdict(kw))))) + return _splitdict( + self.tk, + self.tk.call(self._name, "map", style, *_format_mapdict(kw)), + conv=_tclobj_to_py) def lookup(self, style, option, state=None, default=None): @@ -1425,13 +1416,16 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): def set(self, item, column=None, value=None): - """With one argument, returns a dictionary of column/value pairs - for the specified item. With two arguments, returns the current - value of the specified column. With three arguments, sets the + """Query or set the value of given item. + + With one argument, return a dictionary of column/value pairs + for the specified item. With two arguments, return the current + value of the specified column. With three arguments, set the value of given column in given item to the specified value.""" res = self.tk.call(self._w, "set", item, column, value) if column is None and value is None: - return _dict_from_tcltuple(self.tk.splitlist(res), False) + return _splitdict(self.tk, res, + cut_minus=False, conv=_tclobj_to_py) else: return res @@ -32,6 +32,9 @@ Core and Builtins Library ------- +- Issue #22226: First letter no longer is stripped from the "status" key in + the result of Treeview.heading(). + - Issue #19524: Fixed resource leak in the HTTP connection when an invalid response is received. Patch by Martin Panter. |