summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/tkinter/test/test_ttk/test_functions.py7
-rw-r--r--Lib/tkinter/test/test_ttk/test_style.py90
-rw-r--r--Lib/tkinter/ttk.py38
-rw-r--r--Misc/NEWS.d/next/Library/2020-11-15-17-02-00.bpo-42328.bqpPlR.rst4
4 files changed, 111 insertions, 28 deletions
diff --git a/Lib/tkinter/test/test_ttk/test_functions.py b/Lib/tkinter/test/test_ttk/test_functions.py
index f8e69a9..5c23d6f 100644
--- a/Lib/tkinter/test/test_ttk/test_functions.py
+++ b/Lib/tkinter/test/test_ttk/test_functions.py
@@ -137,6 +137,9 @@ class InternalFunctionsTest(unittest.TestCase):
result = ttk._format_mapdict(opts)
self.assertEqual(result, ('-üñíćódè', 'á vãl'))
+ self.assertEqual(ttk._format_mapdict({'opt': [('value',)]}),
+ ('-opt', '{} value'))
+
# empty states
valid = {'opt': [('', '', 'hi')]}
self.assertEqual(ttk._format_mapdict(valid), ('-opt', '{ } hi'))
@@ -159,10 +162,6 @@ class InternalFunctionsTest(unittest.TestCase):
opts = {'a': None}
self.assertRaises(TypeError, ttk._format_mapdict, opts)
- # items in the value must have size >= 2
- self.assertRaises(IndexError, ttk._format_mapdict,
- {'a': [('invalid', )]})
-
def test_format_elemcreate(self):
self.assertTrue(ttk._format_elemcreate(None), (None, ()))
diff --git a/Lib/tkinter/test/test_ttk/test_style.py b/Lib/tkinter/test/test_ttk/test_style.py
index 3537536..54e9133 100644
--- a/Lib/tkinter/test/test_ttk/test_style.py
+++ b/Lib/tkinter/test/test_ttk/test_style.py
@@ -1,11 +1,22 @@
import unittest
import tkinter
from tkinter import ttk
+from test import support
from test.support import requires, run_unittest
from tkinter.test.support import AbstractTkTest
requires('gui')
+CLASS_NAMES = [
+ '.', 'ComboboxPopdownFrame', 'Heading',
+ 'Horizontal.TProgressbar', 'Horizontal.TScale', 'Item', 'Sash',
+ 'TButton', 'TCheckbutton', 'TCombobox', 'TEntry',
+ 'TLabelframe', 'TLabelframe.Label', 'TMenubutton',
+ 'TNotebook', 'TNotebook.Tab', 'Toolbutton', 'TProgressbar',
+ 'TRadiobutton', 'Treeview', 'TScale', 'TScrollbar', 'TSpinbox',
+ 'Vertical.TProgressbar', 'Vertical.TScale'
+]
+
class StyleTest(AbstractTkTest, unittest.TestCase):
def setUp(self):
@@ -23,11 +34,36 @@ class StyleTest(AbstractTkTest, unittest.TestCase):
def test_map(self):
style = self.style
- style.map('TButton', background=[('active', 'background', 'blue')])
- self.assertEqual(style.map('TButton', 'background'),
- [('active', 'background', 'blue')] if self.wantobjects else
- [('active background', 'blue')])
- self.assertIsInstance(style.map('TButton'), dict)
+
+ # Single state
+ for states in ['active'], [('active',)]:
+ with self.subTest(states=states):
+ style.map('TButton', background=[(*states, 'white')])
+ expected = [('active', 'white')]
+ self.assertEqual(style.map('TButton', 'background'), expected)
+ m = style.map('TButton')
+ self.assertIsInstance(m, dict)
+ self.assertEqual(m['background'], expected)
+
+ # Multiple states
+ for states in ['pressed', '!disabled'], ['pressed !disabled'], [('pressed', '!disabled')]:
+ with self.subTest(states=states):
+ style.map('TButton', background=[(*states, 'black')])
+ expected = [('pressed', '!disabled', 'black')]
+ self.assertEqual(style.map('TButton', 'background'), expected)
+ m = style.map('TButton')
+ self.assertIsInstance(m, dict)
+ self.assertEqual(m['background'], expected)
+
+ # Default state
+ for states in [], [''], [()]:
+ with self.subTest(states=states):
+ style.map('TButton', background=[(*states, 'grey')])
+ expected = [('grey',)]
+ self.assertEqual(style.map('TButton', 'background'), expected)
+ m = style.map('TButton')
+ self.assertIsInstance(m, dict)
+ self.assertEqual(m['background'], expected)
def test_lookup(self):
@@ -86,6 +122,50 @@ class StyleTest(AbstractTkTest, unittest.TestCase):
self.style.theme_use(curr_theme)
+ def test_configure_custom_copy(self):
+ style = self.style
+
+ curr_theme = self.style.theme_use()
+ self.addCleanup(self.style.theme_use, curr_theme)
+ for theme in self.style.theme_names():
+ self.style.theme_use(theme)
+ for name in CLASS_NAMES:
+ default = style.configure(name)
+ if not default:
+ continue
+ with self.subTest(theme=theme, name=name):
+ if support.verbose >= 2:
+ print('configure', theme, name, default)
+ newname = f'C.{name}'
+ self.assertEqual(style.configure(newname), None)
+ style.configure(newname, **default)
+ self.assertEqual(style.configure(newname), default)
+ for key, value in default.items():
+ self.assertEqual(style.configure(newname, key), value)
+
+
+ def test_map_custom_copy(self):
+ style = self.style
+
+ curr_theme = self.style.theme_use()
+ self.addCleanup(self.style.theme_use, curr_theme)
+ for theme in self.style.theme_names():
+ self.style.theme_use(theme)
+ for name in CLASS_NAMES:
+ default = style.map(name)
+ if not default:
+ continue
+ with self.subTest(theme=theme, name=name):
+ if support.verbose >= 2:
+ print('map', theme, name, default)
+ newname = f'C.{name}'
+ self.assertEqual(style.map(newname), {})
+ style.map(newname, **default)
+ self.assertEqual(style.map(newname), default)
+ for key, value in default.items():
+ self.assertEqual(style.map(newname, key), value)
+
+
tests_gui = (StyleTest, )
if __name__ == "__main__":
diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py
index c7c71cd..968fd54 100644
--- a/Lib/tkinter/ttk.py
+++ b/Lib/tkinter/ttk.py
@@ -81,8 +81,6 @@ def _mapdict_values(items):
# ['active selected', 'grey', 'focus', [1, 2, 3, 4]]
opt_val = []
for *state, val in items:
- # hacks for backward compatibility
- state[0] # raise IndexError if empty
if len(state) == 1:
# if it is empty (something that evaluates to False), then
# format it to Tcl code to denote the "normal" state
@@ -243,19 +241,22 @@ def _script_from_settings(settings):
def _list_from_statespec(stuple):
"""Construct a list from the given statespec tuple according to the
accepted statespec accepted by _format_mapdict."""
- nval = []
- for val in stuple:
- typename = getattr(val, 'typename', None)
- if typename is None:
- nval.append(val)
- else: # this is a Tcl object
+ if isinstance(stuple, str):
+ return stuple
+ result = []
+ it = iter(stuple)
+ for state, val in zip(it, it):
+ if hasattr(state, 'typename'): # this is a Tcl object
+ state = str(state).split()
+ elif isinstance(state, str):
+ state = state.split()
+ elif not isinstance(state, (tuple, list)):
+ state = (state,)
+ if hasattr(val, 'typename'):
val = str(val)
- if typename == 'StateSpec':
- val = val.split()
- nval.append(val)
+ result.append((*state, val))
- it = iter(nval)
- return [_flatten(spec) for spec in zip(it, it)]
+ return result
def _list_from_layouttuple(tk, ltuple):
"""Construct a list from the tuple returned by ttk::layout, this is
@@ -395,13 +396,12 @@ class Style(object):
or something else of your preference. A statespec is compound of
one or more states and then a value."""
if query_opt is not None:
- return _list_from_statespec(self.tk.splitlist(
- self.tk.call(self._name, "map", style, '-%s' % query_opt)))
+ result = self.tk.call(self._name, "map", style, '-%s' % query_opt)
+ return _list_from_statespec(self.tk.splitlist(result))
- return _splitdict(
- self.tk,
- self.tk.call(self._name, "map", style, *_format_mapdict(kw)),
- conv=_tclobj_to_py)
+ result = self.tk.call(self._name, "map", style, *_format_mapdict(kw))
+ return {k: _list_from_statespec(self.tk.splitlist(v))
+ for k, v in _splitdict(self.tk, result).items()}
def lookup(self, style, option, state=None, default=None):
diff --git a/Misc/NEWS.d/next/Library/2020-11-15-17-02-00.bpo-42328.bqpPlR.rst b/Misc/NEWS.d/next/Library/2020-11-15-17-02-00.bpo-42328.bqpPlR.rst
new file mode 100644
index 0000000..7e6a176
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-11-15-17-02-00.bpo-42328.bqpPlR.rst
@@ -0,0 +1,4 @@
+Fixed :meth:`tkinter.ttk.Style.map`. The function accepts now the
+representation of the default state as empty sequence (as returned by
+``Style.map()``). The structure of the result is now the same on all platform
+and does not depend on the value of ``wantobjects``.