From: Serhiy Storchaka Date: Sat, 6 Sep 2014 19:47:02 +0000 (+0300) Subject: Issue #22226: Added private function _splitdict() in the Tkinter module. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ceaf6827e2c196512184ec9212b356d7ac0d6224;p=python Issue #22226: Added private function _splitdict() in the Tkinter module. First letter no longer is stripped from the "status" key in the result of Treeview.heading(). --- diff --git a/Lib/lib-tk/Tkinter.py b/Lib/lib-tk/Tkinter.py index 76fbc07b49..4de2605b66 100644 --- a/Lib/lib-tk/Tkinter.py +++ b/Lib/lib-tk/Tkinter.py @@ -123,6 +123,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. @@ -1390,15 +1413,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) @@ -1921,16 +1939,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 @@ -1972,16 +1984,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 @@ -2021,16 +2027,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/lib-tk/test/test_ttk/test_functions.py b/Lib/lib-tk/test/test_ttk/test_functions.py index 50c5aeb27e..714d950001 100644 --- a/Lib/lib-tk/test/test_ttk/test_functions.py +++ b/Lib/lib-tk/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/lib-tk/ttk.py b/Lib/lib-tk/ttk.py index 77f1d3a9b5..5ed018a6a9 100644 --- a/Lib/lib-tk/ttk.py +++ b/Lib/lib-tk/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 @@ -242,21 +242,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.""" @@ -316,7 +301,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.""" @@ -336,20 +321,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, basestring): + if getattr(val[0], 'typename', None) == 'StateSpec': + val = _list_from_statespec(val) + else: + val = 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.iteritems(): - if val and hasattr(val, '__len__') and not isinstance(val, basestring): - if getattr(val[0], 'typename', None) == 'StateSpec': - val = _list_from_statespec(val) - else: - val = map(_convert_stringval, val) - - elif hasattr(val, 'typename'): # some other (single) Tcl object - val = _convert_stringval(val) - - adict[opt] = val + for opt, val in adict.items(): + adict[opt] = _tclobj_to_py(val) return adict @@ -409,8 +398,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): @@ -1427,13 +1418,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 diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index c0afba6e55..d627034cf3 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -7,6 +7,9 @@ from subprocess import Popen, PIPE # Skip this test if the _tkinter module wasn't built. _tkinter = test_support.import_module('_tkinter') +# Make sure tkinter._fix runs to set up the environment +tkinter = test_support.import_fresh_module('Tkinter') + from Tkinter import Tcl from _tkinter import TclError @@ -565,6 +568,42 @@ class TclTest(unittest.TestCase): for arg, res in testcases: self.assertEqual(split(arg), res) + 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) + + character_size = 4 if sys.maxunicode > 0xFFFF else 2 class BigmemTclTest(unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS index 91e1db4589..2db7d5a889 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -22,6 +22,9 @@ Core and Builtins Library ------- +- Issue #22226: First letter no longer is stripped from the "status" key in + the result of Treeview.heading(). + - Issue #22051: turtledemo no longer reloads examples to re-run them. Initialization of variables and gui setup should be done in main(), which is called each time a demo is run, but not on import.