Commit 87a2803e authored by Serhiy Storchaka's avatar Serhiy Storchaka

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().
parents 1ba003f3 8f0a1d0f
...@@ -7,7 +7,7 @@ from test import support ...@@ -7,7 +7,7 @@ from test import support
_tkinter = support.import_module('_tkinter') _tkinter = support.import_module('_tkinter')
# Make sure tkinter._fix runs to set up the environment # 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 Tcl
from _tkinter import TclError from _tkinter import TclError
...@@ -566,6 +566,41 @@ class TclTest(unittest.TestCase): ...@@ -566,6 +566,41 @@ class TclTest(unittest.TestCase):
for arg, res in testcases: for arg, res in testcases:
self.assertEqual(split(arg), res, msg=arg) 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): class BigmemTclTest(unittest.TestCase):
......
...@@ -112,6 +112,29 @@ def _cnfmerge(cnfs): ...@@ -112,6 +112,29 @@ def _cnfmerge(cnfs):
try: _cnfmerge = _tkinter._cnfmerge try: _cnfmerge = _tkinter._cnfmerge
except AttributeError: pass 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: class Event:
"""Container for the properties of an event. """Container for the properties of an event.
...@@ -1393,15 +1416,10 @@ class Misc: ...@@ -1393,15 +1416,10 @@ class Misc:
else: else:
options = self._options(cnf, kw) options = self._options(cnf, kw)
if not options: if not options:
res = self.tk.call('grid', return _splitdict(
command, self._w, index) self.tk,
words = self.tk.splitlist(res) self.tk.call('grid', command, self._w, index),
dict = {} conv=self._gridconvvalue)
for i in range(0, len(words), 2):
key = words[i][1:]
value = words[i+1]
dict[key] = self._gridconvvalue(value)
return dict
res = self.tk.call( res = self.tk.call(
('grid', command, self._w, index) ('grid', command, self._w, index)
+ options) + options)
...@@ -1961,16 +1979,10 @@ class Pack: ...@@ -1961,16 +1979,10 @@ class Pack:
def pack_info(self): def pack_info(self):
"""Return information about the packing options """Return information about the packing options
for this widget.""" for this widget."""
words = self.tk.splitlist( d = _splitdict(self.tk, self.tk.call('pack', 'info', self._w))
self.tk.call('pack', 'info', self._w)) if 'in' in d:
dict = {} d['in'] = self.nametowidget(d['in'])
for i in range(0, len(words), 2): return d
key = words[i][1:]
value = words[i+1]
if str(value)[:1] == '.':
value = self._nametowidget(value)
dict[key] = value
return dict
info = pack_info info = pack_info
propagate = pack_propagate = Misc.pack_propagate propagate = pack_propagate = Misc.pack_propagate
slaves = pack_slaves = Misc.pack_slaves slaves = pack_slaves = Misc.pack_slaves
...@@ -2012,16 +2024,10 @@ class Place: ...@@ -2012,16 +2024,10 @@ class Place:
def place_info(self): def place_info(self):
"""Return information about the placing options """Return information about the placing options
for this widget.""" for this widget."""
words = self.tk.splitlist( d = _splitdict(self.tk, self.tk.call('place', 'info', self._w))
self.tk.call('place', 'info', self._w)) if 'in' in d:
dict = {} d['in'] = self.nametowidget(d['in'])
for i in range(0, len(words), 2): return d
key = words[i][1:]
value = words[i+1]
if str(value)[:1] == '.':
value = self._nametowidget(value)
dict[key] = value
return dict
info = place_info info = place_info
slaves = place_slaves = Misc.place_slaves slaves = place_slaves = Misc.place_slaves
...@@ -2061,16 +2067,10 @@ class Grid: ...@@ -2061,16 +2067,10 @@ class Grid:
def grid_info(self): def grid_info(self):
"""Return information about the options """Return information about the options
for positioning this widget in a grid.""" for positioning this widget in a grid."""
words = self.tk.splitlist( d = _splitdict(self.tk, self.tk.call('grid', 'info', self._w))
self.tk.call('grid', 'info', self._w)) if 'in' in d:
dict = {} d['in'] = self.nametowidget(d['in'])
for i in range(0, len(words), 2): return d
key = words[i][1:]
value = words[i+1]
if str(value)[:1] == '.':
value = self._nametowidget(value)
dict[key] = value
return dict
info = grid_info info = grid_info
location = grid_location = Misc.grid_location location = grid_location = Misc.grid_location
propagate = grid_propagate = Misc.grid_propagate propagate = grid_propagate = Misc.grid_propagate
......
...@@ -324,26 +324,13 @@ class InternalFunctionsTest(unittest.TestCase): ...@@ -324,26 +324,13 @@ class InternalFunctionsTest(unittest.TestCase):
"-opt {3 2m}") "-opt {3 2m}")
def test_dict_from_tcltuple(self): def test_tclobj_to_py(self):
fakettuple = ('-a', '{1 2 3}', '-something', 'foo') self.assertEqual(
ttk._tclobj_to_py((MockStateSpec('a', 'b'), 'val')),
self.assertEqual(ttk._dict_from_tcltuple(fakettuple, False), [('a', 'b', 'val')])
{'-a': '{1 2 3}', '-something': 'foo'}) self.assertEqual(
ttk._tclobj_to_py([MockTclObj('1'), 2, MockTclObj('3m')]),
self.assertEqual(ttk._dict_from_tcltuple(fakettuple), [1, 2, '3m'])
{'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_list_from_statespec(self): def test_list_from_statespec(self):
......
...@@ -26,7 +26,7 @@ __all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label", ...@@ -26,7 +26,7 @@ __all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label",
"tclobjs_to_py", "setup_master"] "tclobjs_to_py", "setup_master"]
import tkinter 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 # Verify if Tk is new enough to not need the Tile package
_REQUIRE_TILE = True if tkinter.TkVersion < 8.5 else False _REQUIRE_TILE = True if tkinter.TkVersion < 8.5 else False
...@@ -240,21 +240,6 @@ def _script_from_settings(settings): ...@@ -240,21 +240,6 @@ def _script_from_settings(settings):
return '\n'.join(script) 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): def _list_from_statespec(stuple):
"""Construct a list from the given statespec tuple according to the """Construct a list from the given statespec tuple according to the
accepted statespec accepted by _format_mapdict.""" accepted statespec accepted by _format_mapdict."""
...@@ -314,7 +299,7 @@ def _val_or_dict(tk, options, *args): ...@@ -314,7 +299,7 @@ def _val_or_dict(tk, options, *args):
if len(options) % 2: # option specified without a value, return its value if len(options) % 2: # option specified without a value, return its value
return res return res
return _dict_from_tcltuple(tk.splitlist(res)) return _splitdict(tk, res, conv=_tclobj_to_py)
def _convert_stringval(value): def _convert_stringval(value):
"""Converts a value to, hopefully, a more appropriate Python object.""" """Converts a value to, hopefully, a more appropriate Python object."""
...@@ -334,20 +319,24 @@ def _to_number(x): ...@@ -334,20 +319,24 @@ def _to_number(x):
x = int(x) x = int(x)
return 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): def tclobjs_to_py(adict):
"""Returns adict with its values converted from Tcl objects to Python """Returns adict with its values converted from Tcl objects to Python
objects.""" objects."""
for opt, val in adict.items(): for opt, val in adict.items():
if val and hasattr(val, '__len__') and not isinstance(val, str): adict[opt] = _tclobj_to_py(val)
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
return adict return adict
...@@ -407,8 +396,10 @@ class Style(object): ...@@ -407,8 +396,10 @@ class Style(object):
return _list_from_statespec(self.tk.splitlist( return _list_from_statespec(self.tk.splitlist(
self.tk.call(self._name, "map", style, '-%s' % query_opt))) self.tk.call(self._name, "map", style, '-%s' % query_opt)))
return _dict_from_tcltuple(self.tk.splitlist( return _splitdict(
self.tk.call(self._name, "map", style, *(_format_mapdict(kw))))) 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): def lookup(self, style, option, state=None, default=None):
...@@ -1425,13 +1416,16 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): ...@@ -1425,13 +1416,16 @@ class Treeview(Widget, tkinter.XView, tkinter.YView):
def set(self, item, column=None, value=None): def set(self, item, column=None, value=None):
"""With one argument, returns a dictionary of column/value pairs """Query or set the value of given item.
for the specified item. With two arguments, returns the current
value of the specified column. With three arguments, sets the 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.""" value of given column in given item to the specified value."""
res = self.tk.call(self._w, "set", item, column, value) res = self.tk.call(self._w, "set", item, column, value)
if column is None and value is None: 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: else:
return res return res
......
...@@ -132,6 +132,9 @@ Core and Builtins ...@@ -132,6 +132,9 @@ Core and Builtins
Library 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 - Issue #19524: Fixed resource leak in the HTTP connection when an invalid
response is received. Patch by Martin Panter. response is received. Patch by Martin Panter.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment