summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorGuilherme Polo <ggpolo@gmail.com>2009-01-28 14:41:10 (GMT)
committerGuilherme Polo <ggpolo@gmail.com>2009-01-28 14:41:10 (GMT)
commit5f2384857468552554bbf9414d1389cf62142ae1 (patch)
tree6e1e8dc45e86daa3a2df741e55f30a37214a0242 /Lib
parent9844a1ffc3645a431ea9198e52214615e7752de7 (diff)
downloadcpython-5f2384857468552554bbf9414d1389cf62142ae1.zip
cpython-5f2384857468552554bbf9414d1389cf62142ae1.tar.gz
cpython-5f2384857468552554bbf9414d1389cf62142ae1.tar.bz2
Merged revisions 69050 via svnmerge from
svn+ssh://pythondev/python/trunk ........ r69050 | guilherme.polo | 2009-01-28 11:09:03 -0200 (Wed, 28 Jan 2009) | 2 lines Added the ttk module. See issue #2983: Ttk support for Tkinter. ........
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_tk_guionly.py14
-rw-r--r--Lib/test/test_tk_textonly.py8
-rw-r--r--Lib/tkinter/test/README14
-rw-r--r--Lib/tkinter/test/__init__.py0
-rw-r--r--Lib/tkinter/test/runtktests.py68
-rw-r--r--Lib/tkinter/test/support.py25
-rw-r--r--Lib/tkinter/test/test_ttk/__init__.py0
-rw-r--r--Lib/tkinter/test/test_ttk/test_extensions.py266
-rw-r--r--Lib/tkinter/test/test_ttk/test_functions.py423
-rw-r--r--Lib/tkinter/test/test_ttk/test_style.py99
-rw-r--r--Lib/tkinter/test/test_ttk/test_widgets.py1115
-rw-r--r--Lib/tkinter/ttk.py1636
12 files changed, 3668 insertions, 0 deletions
diff --git a/Lib/test/test_tk_guionly.py b/Lib/test/test_tk_guionly.py
new file mode 100644
index 0000000..d3eb5a1c
--- /dev/null
+++ b/Lib/test/test_tk_guionly.py
@@ -0,0 +1,14 @@
+from test import support
+from tkinter.test import runtktests
+
+def test_main(enable_gui=False):
+ if enable_gui:
+ if support.use_resources is None:
+ support.use_resources = ['gui']
+ elif 'gui' not in support.use_resources:
+ support.use_resources.append('gui')
+
+ support.run_unittest(*runtktests.get_tests(text=False))
+
+if __name__ == '__main__':
+ test_main(enable_gui=True)
diff --git a/Lib/test/test_tk_textonly.py b/Lib/test/test_tk_textonly.py
new file mode 100644
index 0000000..ad13244
--- /dev/null
+++ b/Lib/test/test_tk_textonly.py
@@ -0,0 +1,8 @@
+from test import support
+from tkinter.test import runtktests
+
+def test_main():
+ support.run_unittest(*runtktests.get_tests(gui=False))
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/tkinter/test/README b/Lib/tkinter/test/README
new file mode 100644
index 0000000..79cd16c
--- /dev/null
+++ b/Lib/tkinter/test/README
@@ -0,0 +1,14 @@
+Writing new tests
+=================
+
+Precaution
+----------
+
+ New tests should always use only one Tk window at once, like all the
+ current tests do. This means that you have to destroy the current window
+ before creating another one, and clean up after the test. The motivation
+ behind this is that some tests may depend on having its window focused
+ while it is running to work properly, and it may be hard to force focus
+ on your window across platforms (right now only test_traversal at
+ test_ttk.test_widgets.NotebookTest depends on this).
+
diff --git a/Lib/tkinter/test/__init__.py b/Lib/tkinter/test/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Lib/tkinter/test/__init__.py
diff --git a/Lib/tkinter/test/runtktests.py b/Lib/tkinter/test/runtktests.py
new file mode 100644
index 0000000..3d3868f
--- /dev/null
+++ b/Lib/tkinter/test/runtktests.py
@@ -0,0 +1,68 @@
+"""
+Use this module to get and run all tk tests.
+
+tkinter tests should live in a package inside the directory where this file
+lives, like test_tkinter.
+Extensions also should live in packages following the same rule as above.
+"""
+
+import os
+import sys
+import unittest
+import test.support
+
+this_dir_path = os.path.abspath(os.path.dirname(__file__))
+
+def is_package(path):
+ for name in os.listdir(path):
+ if name in ('__init__.py', '__init__.pyc', '__init.pyo'):
+ return True
+ return False
+
+def get_tests_modules(basepath=this_dir_path, gui=True):
+ """This will import and yield modules whose names start with test_
+ and are inside packages found in the path starting at basepath."""
+ py_ext = '.py'
+
+ for dirpath, dirnames, filenames in os.walk(basepath):
+ for dirname in list(dirnames):
+ if dirname[0] == '.':
+ dirnames.remove(dirname)
+
+ if is_package(dirpath) and filenames:
+ pkg_name = dirpath[len(basepath) + len(os.sep):].replace('/', '.')
+ filenames = filter(
+ lambda x: x.startswith('test_') and x.endswith(py_ext),
+ filenames)
+
+ for name in filenames:
+ try:
+ yield __import__(
+ "%s.%s.%s" % (
+ "tkinter.test",
+ pkg_name,
+ name[:-len(py_ext)]),
+ fromlist=['']
+ )
+ except test.support.ResourceDenied:
+ if gui:
+ raise
+
+def get_tests(text=True, gui=True):
+ """Yield all the tests in the modules found by get_tests_modules.
+
+ If nogui is True, only tests that do not require a GUI will be
+ returned."""
+ attrs = []
+ if text:
+ attrs.append('tests_nogui')
+ if gui:
+ attrs.append('tests_gui')
+ for module in get_tests_modules(gui=gui):
+ for attr in attrs:
+ for test in getattr(module, attr, ()):
+ yield test
+
+if __name__ == "__main__":
+ test.support.use_resources = ['gui']
+ test.support.run_unittest(*get_tests())
diff --git a/Lib/tkinter/test/support.py b/Lib/tkinter/test/support.py
new file mode 100644
index 0000000..67264df
--- /dev/null
+++ b/Lib/tkinter/test/support.py
@@ -0,0 +1,25 @@
+import tkinter
+
+def get_tk_root():
+ try:
+ root = tkinter._default_root
+ except AttributeError:
+ # it is possible to disable default root in Tkinter, although
+ # I haven't seen people doing it (but apparently someone did it
+ # here).
+ root = None
+
+ if root is None:
+ # create a new master only if there isn't one already
+ root = tkinter.Tk()
+
+ return root
+
+
+def simulate_mouse_click(widget, x, y):
+ """Generate proper events to click at the x, y position (tries to act
+ like an X server)."""
+ widget.event_generate('<Enter>', x=0, y=0)
+ widget.event_generate('<Motion>', x=x, y=y)
+ widget.event_generate('<ButtonPress-1>', x=x, y=y)
+ widget.event_generate('<ButtonRelease-1>', x=x, y=y)
diff --git a/Lib/tkinter/test/test_ttk/__init__.py b/Lib/tkinter/test/test_ttk/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Lib/tkinter/test/test_ttk/__init__.py
diff --git a/Lib/tkinter/test/test_ttk/test_extensions.py b/Lib/tkinter/test/test_ttk/test_extensions.py
new file mode 100644
index 0000000..a127545
--- /dev/null
+++ b/Lib/tkinter/test/test_ttk/test_extensions.py
@@ -0,0 +1,266 @@
+import sys
+import unittest
+import tkinter
+from tkinter import ttk
+from test.support import requires, run_unittest
+
+import tkinter.test.support as support
+
+requires('gui')
+
+class LabeledScaleTest(unittest.TestCase):
+
+ def test_widget_destroy(self):
+ # automatically created variable
+ x = ttk.LabeledScale()
+ var = x._variable._name
+ x.destroy()
+ self.failUnlessRaises(tkinter.TclError, x.tk.globalgetvar, var)
+
+ # manually created variable
+ myvar = tkinter.DoubleVar()
+ name = myvar._name
+ x = ttk.LabeledScale(variable=myvar)
+ x.destroy()
+ self.failUnlessEqual(x.tk.globalgetvar(name), myvar.get())
+ del myvar
+ self.failUnlessRaises(tkinter.TclError, x.tk.globalgetvar, name)
+
+ # checking that the tracing callback is properly removed
+ myvar = tkinter.IntVar()
+ # LabeledScale will start tracing myvar
+ x = ttk.LabeledScale(variable=myvar)
+ x.destroy()
+ # Unless the tracing callback was removed, creating a new
+ # LabeledScale with the same var will cause an error now. This
+ # happens because the variable will be set to (possibly) a new
+ # value which causes the tracing callback to be called and then
+ # it tries calling instance attributes not yet defined.
+ ttk.LabeledScale(variable=myvar)
+ if hasattr(sys, 'last_type'):
+ self.failIf(sys.last_type == tkinter.TclError)
+
+
+ def test_initialization(self):
+ # master passing
+ x = ttk.LabeledScale()
+ self.failUnlessEqual(x.master, tkinter._default_root)
+ x.destroy()
+ master = tkinter.Frame()
+ x = ttk.LabeledScale(master)
+ self.failUnlessEqual(x.master, master)
+ x.destroy()
+
+ # variable initialization/passing
+ passed_expected = ((2.5, 2), ('0', 0), (0, 0), (10, 10),
+ (-1, -1), (sys.maxsize + 1, sys.maxsize + 1))
+ for pair in passed_expected:
+ x = ttk.LabeledScale(from_=pair[0])
+ self.failUnlessEqual(x.value, pair[1])
+ x.destroy()
+ x = ttk.LabeledScale(from_='2.5')
+ self.failUnlessRaises(ValueError, x._variable.get)
+ x.destroy()
+ x = ttk.LabeledScale(from_=None)
+ self.failUnlessRaises(ValueError, x._variable.get)
+ x.destroy()
+ # variable should have its default value set to the from_ value
+ myvar = tkinter.DoubleVar(value=20)
+ x = ttk.LabeledScale(variable=myvar)
+ self.failUnlessEqual(x.value, 0)
+ x.destroy()
+ # check that it is really using a DoubleVar
+ x = ttk.LabeledScale(variable=myvar, from_=0.5)
+ self.failUnlessEqual(x.value, 0.5)
+ self.failUnlessEqual(x._variable._name, myvar._name)
+ x.destroy()
+
+ # widget positionment
+ def check_positions(scale, scale_pos, label, label_pos):
+ self.failUnlessEqual(scale.pack_info()['side'], scale_pos)
+ self.failUnlessEqual(label.place_info()['anchor'], label_pos)
+ x = ttk.LabeledScale(compound='top')
+ check_positions(x.scale, 'bottom', x.label, 'n')
+ x.destroy()
+ x = ttk.LabeledScale(compound='bottom')
+ check_positions(x.scale, 'top', x.label, 's')
+ x.destroy()
+ x = ttk.LabeledScale(compound='unknown') # invert default positions
+ check_positions(x.scale, 'top', x.label, 's')
+ x.destroy()
+ x = ttk.LabeledScale() # take default positions
+ check_positions(x.scale, 'bottom', x.label, 'n')
+ x.destroy()
+
+ # extra, and invalid, kwargs
+ self.failUnlessRaises(tkinter.TclError, ttk.LabeledScale, a='b')
+
+
+ def test_horizontal_range(self):
+ lscale = ttk.LabeledScale(from_=0, to=10)
+ lscale.pack()
+ lscale.wait_visibility()
+ lscale.update()
+
+ linfo_1 = lscale.label.place_info()
+ prev_xcoord = lscale.scale.coords()[0]
+ self.failUnlessEqual(prev_xcoord, int(linfo_1['x']))
+ # change range to: from -5 to 5. This should change the x coord of
+ # the scale widget, since 0 is at the middle of the new
+ # range.
+ lscale.scale.configure(from_=-5, to=5)
+ # The following update is needed since the test doesn't use mainloop,
+ # at the same time this shouldn't affect test outcome
+ lscale.update()
+ curr_xcoord = lscale.scale.coords()[0]
+ self.failUnless(prev_xcoord != curr_xcoord)
+ # the label widget should have been repositioned too
+ linfo_2 = lscale.label.place_info()
+ self.failUnlessEqual(lscale.label['text'], 0)
+ self.failUnlessEqual(curr_xcoord, int(linfo_2['x']))
+ # change the range back
+ lscale.scale.configure(from_=0, to=10)
+ self.failUnless(prev_xcoord != curr_xcoord)
+ self.failUnlessEqual(prev_xcoord, int(linfo_1['x']))
+
+ lscale.destroy()
+
+
+ def test_variable_change(self):
+ x = ttk.LabeledScale()
+ x.pack()
+ x.wait_visibility()
+ x.update()
+
+ curr_xcoord = x.scale.coords()[0]
+ newval = x.value + 1
+ x.value = newval
+ # The following update is needed since the test doesn't use mainloop,
+ # at the same time this shouldn't affect test outcome
+ x.update()
+ self.failUnlessEqual(x.label['text'], newval)
+ self.failUnless(x.scale.coords()[0] > curr_xcoord)
+ self.failUnlessEqual(x.scale.coords()[0],
+ int(x.label.place_info()['x']))
+
+ # value outside range
+ x.value = x.scale['to'] + 1 # no changes shouldn't happen
+ x.update()
+ self.failUnlessEqual(x.label['text'], newval)
+ self.failUnlessEqual(x.scale.coords()[0],
+ int(x.label.place_info()['x']))
+
+ x.destroy()
+
+
+ def test_resize(self):
+ x = ttk.LabeledScale()
+ x.pack(expand=True, fill='both')
+ x.wait_visibility()
+ x.update()
+
+ width, height = x.master.winfo_width(), x.master.winfo_height()
+ width, height = width * 2, height * 2
+
+ x.value = 3
+ x.update()
+ x.master.wm_geometry("%dx%d" % (width, height))
+ self.failUnlessEqual(int(x.label.place_info()['x']),
+ x.scale.coords()[0])
+
+ x.master.wm_geometry("%dx%d" % (width, height))
+ x.destroy()
+
+
+class OptionMenuTest(unittest.TestCase):
+
+ def setUp(self):
+ self.root = support.get_tk_root()
+ self.textvar = tkinter.StringVar(self.root)
+
+ def tearDown(self):
+ del self.textvar
+ self.root.destroy()
+
+
+ def test_widget_destroy(self):
+ var = tkinter.StringVar()
+ optmenu = ttk.OptionMenu(None, var)
+ name = var._name
+ optmenu.update_idletasks()
+ optmenu.destroy()
+ self.failUnlessEqual(optmenu.tk.globalgetvar(name), var.get())
+ del var
+ self.failUnlessRaises(tkinter.TclError, optmenu.tk.globalgetvar, name)
+
+
+ def test_initialization(self):
+ self.failUnlessRaises(tkinter.TclError,
+ ttk.OptionMenu, None, self.textvar, invalid='thing')
+
+ optmenu = ttk.OptionMenu(None, self.textvar, 'b', 'a', 'b')
+ self.failUnlessEqual(optmenu._variable.get(), 'b')
+
+ self.failUnless(optmenu['menu'])
+ self.failUnless(optmenu['textvariable'])
+
+ optmenu.destroy()
+
+
+ def test_menu(self):
+ items = ('a', 'b', 'c')
+ default = 'a'
+ optmenu = ttk.OptionMenu(None, self.textvar, default, *items)
+ found_default = False
+ for i in range(len(items)):
+ value = optmenu['menu'].entrycget(i, 'value')
+ self.failUnlessEqual(value, items[i])
+ if value == default:
+ found_default = True
+ self.failUnless(found_default)
+ optmenu.destroy()
+
+ # default shouldn't be in menu if it is not part of values
+ default = 'd'
+ optmenu = ttk.OptionMenu(None, self.textvar, default, *items)
+ curr = None
+ i = 0
+ while True:
+ last, curr = curr, optmenu['menu'].entryconfigure(i, 'value')
+ if last == curr:
+ # no more menu entries
+ break
+ self.failIf(curr == default)
+ i += 1
+ self.failUnlessEqual(i, len(items))
+
+ # check that variable is updated correctly
+ optmenu.pack()
+ optmenu.wait_visibility()
+ optmenu['menu'].invoke(0)
+ self.failUnlessEqual(optmenu._variable.get(), items[0])
+
+ # changing to an invalid index shouldn't change the variable
+ self.failUnlessRaises(tkinter.TclError, optmenu['menu'].invoke, -1)
+ self.failUnlessEqual(optmenu._variable.get(), items[0])
+
+ optmenu.destroy()
+
+ # specifying a callback
+ success = []
+ def cb_test(item):
+ self.failUnlessEqual(item, items[1])
+ success.append(True)
+ optmenu = ttk.OptionMenu(None, self.textvar, 'a', command=cb_test,
+ *items)
+ optmenu['menu'].invoke(1)
+ if not success:
+ self.fail("Menu callback not invoked")
+
+ optmenu.destroy()
+
+
+tests_gui = (LabeledScaleTest, OptionMenuTest)
+
+if __name__ == "__main__":
+ run_unittest(*tests_gui)
diff --git a/Lib/tkinter/test/test_ttk/test_functions.py b/Lib/tkinter/test/test_ttk/test_functions.py
new file mode 100644
index 0000000..c9f8d5d
--- /dev/null
+++ b/Lib/tkinter/test/test_ttk/test_functions.py
@@ -0,0 +1,423 @@
+# -*- encoding: utf-8 -*-
+import unittest
+from tkinter import ttk
+
+class MockTclObj(object):
+ typename = 'test'
+
+ def __init__(self, val):
+ self.val = val
+
+ def __str__(self):
+ return str(self.val)
+
+
+class MockStateSpec(object):
+ typename = 'StateSpec'
+
+ def __init__(self, *args):
+ self.val = args
+
+ def __str__(self):
+ return ' '.join(self.val)
+
+
+class InternalFunctionsTest(unittest.TestCase):
+
+ def test_format_optdict(self):
+ def check_against(fmt_opts, result):
+ for i in range(0, len(fmt_opts), 2):
+ self.failUnlessEqual(result.pop(fmt_opts[i]), fmt_opts[i + 1])
+ if result:
+ self.fail("result still got elements: %s" % result)
+
+ # passing an empty dict should return an empty object (tuple here)
+ self.failIf(ttk._format_optdict({}))
+
+ # check list formatting
+ check_against(
+ ttk._format_optdict({'fg': 'blue', 'padding': [1, 2, 3, 4]}),
+ {'-fg': 'blue', '-padding': '1 2 3 4'})
+
+ # check tuple formatting (same as list)
+ check_against(
+ ttk._format_optdict({'test': (1, 2, '', 0)}),
+ {'-test': '1 2 {} 0'})
+
+ # check untouched values
+ check_against(
+ ttk._format_optdict({'test': {'left': 'as is'}}),
+ {'-test': {'left': 'as is'}})
+
+ # check script formatting and untouched value(s)
+ check_against(
+ ttk._format_optdict(
+ {'test': [1, -1, '', '2m', 0], 'nochange1': 3,
+ 'nochange2': 'abc def'}, script=True),
+ {'-test': '{1 -1 {} 2m 0}', '-nochange1': 3,
+ '-nochange2': 'abc def' })
+
+ opts = {'αβγ': True, 'á': False}
+ orig_opts = opts.copy()
+ # check if giving unicode keys is fine
+ check_against(ttk._format_optdict(opts), {'-αβγ': True, '-á': False})
+ # opts should remain unchanged
+ self.failUnlessEqual(opts, orig_opts)
+
+ # passing values with spaces inside a tuple/list
+ check_against(
+ ttk._format_optdict(
+ {'option': ('one two', 'three')}),
+ {'-option': '{one two} three'})
+
+ # ignore an option
+ amount_opts = len(ttk._format_optdict(opts, ignore=('á'))) / 2
+ self.failUnlessEqual(amount_opts, len(opts) - 1)
+
+ # ignore non-existing options
+ amount_opts = len(ttk._format_optdict(opts, ignore=('á', 'b'))) / 2
+ self.failUnlessEqual(amount_opts, len(opts) - 1)
+
+ # ignore every option
+ self.failIf(ttk._format_optdict(opts, ignore=list(opts.keys())))
+
+
+ def test_format_mapdict(self):
+ opts = {'a': [('b', 'c', 'val'), ('d', 'otherval'), ('', 'single')]}
+ result = ttk._format_mapdict(opts)
+ self.failUnlessEqual(len(result), len(list(opts.keys())) * 2)
+ self.failUnlessEqual(result, ('-a', '{b c} val d otherval {} single'))
+ self.failUnlessEqual(ttk._format_mapdict(opts, script=True),
+ ('-a', '{{b c} val d otherval {} single}'))
+
+ self.failUnlessEqual(ttk._format_mapdict({2: []}), ('-2', ''))
+
+ opts = {'üñíćódè': [('á', 'vãl')]}
+ result = ttk._format_mapdict(opts)
+ self.failUnlessEqual(result, ('-üñíćódè', 'á vãl'))
+
+ # empty states
+ valid = {'opt': [('', '', 'hi')]}
+ self.failUnlessEqual(ttk._format_mapdict(valid), ('-opt', '{ } hi'))
+
+ # when passing multiple states, they all must be strings
+ invalid = {'opt': [(1, 2, 'valid val')]}
+ self.failUnlessRaises(TypeError, ttk._format_mapdict, invalid)
+ invalid = {'opt': [([1], '2', 'valid val')]}
+ self.failUnlessRaises(TypeError, ttk._format_mapdict, invalid)
+ # but when passing a single state, it can be anything
+ valid = {'opt': [[1, 'value']]}
+ self.failUnlessEqual(ttk._format_mapdict(valid), ('-opt', '1 value'))
+ # special attention to single states which evalute to False
+ for stateval in (None, 0, False, '', set()): # just some samples
+ valid = {'opt': [(stateval, 'value')]}
+ self.failUnlessEqual(ttk._format_mapdict(valid),
+ ('-opt', '{} value'))
+
+ # values must be iterable
+ opts = {'a': None}
+ self.failUnlessRaises(TypeError, ttk._format_mapdict, opts)
+
+ # items in the value must have size >= 2
+ self.failUnlessRaises(IndexError, ttk._format_mapdict,
+ {'a': [('invalid', )]})
+
+
+ def test_format_elemcreate(self):
+ self.failUnless(ttk._format_elemcreate(None), (None, ()))
+
+ ## Testing type = image
+ # image type expects at least an image name, so this should raise
+ # IndexError since it tries to access the index 0 of an empty tuple
+ self.failUnlessRaises(IndexError, ttk._format_elemcreate, 'image')
+
+ # don't format returned values as a tcl script
+ # minimum acceptable for image type
+ self.failUnlessEqual(ttk._format_elemcreate('image', False, 'test'),
+ ("test ", ()))
+ # specifiyng a state spec
+ self.failUnlessEqual(ttk._format_elemcreate('image', False, 'test',
+ ('', 'a')), ("test {} a", ()))
+ # state spec with multiple states
+ self.failUnlessEqual(ttk._format_elemcreate('image', False, 'test',
+ ('a', 'b', 'c')), ("test {a b} c", ()))
+ # state spec and options
+ self.failUnlessEqual(ttk._format_elemcreate('image', False, 'test',
+ ('a', 'b'), a='x', b='y'), ("test a b", ("-a", "x", "-b", "y")))
+ # format returned values as a tcl script
+ # state spec with multiple states and an option with a multivalue
+ self.failUnlessEqual(ttk._format_elemcreate('image', True, 'test',
+ ('a', 'b', 'c', 'd'), x=[2, 3]), ("{test {a b c} d}", "-x {2 3}"))
+
+ ## Testing type = vsapi
+ # vsapi type expects at least a class name and a part_id, so this
+ # should raise an ValueError since it tries to get two elements from
+ # an empty tuple
+ self.failUnlessRaises(ValueError, ttk._format_elemcreate, 'vsapi')
+
+ # don't format returned values as a tcl script
+ # minimum acceptable for vsapi
+ self.failUnlessEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b'),
+ ("a b ", ()))
+ # now with a state spec with multiple states
+ self.failUnlessEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b',
+ ('a', 'b', 'c')), ("a b {a b} c", ()))
+ # state spec and option
+ self.failUnlessEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b',
+ ('a', 'b'), opt='x'), ("a b a b", ("-opt", "x")))
+ # format returned values as a tcl script
+ # state spec with a multivalue and an option
+ self.failUnlessEqual(ttk._format_elemcreate('vsapi', True, 'a', 'b',
+ ('a', 'b', [1, 2]), opt='x'), ("{a b {a b} {1 2}}", "-opt x"))
+
+ # Testing type = from
+ # from type expects at least a type name
+ self.failUnlessRaises(IndexError, ttk._format_elemcreate, 'from')
+
+ self.failUnlessEqual(ttk._format_elemcreate('from', False, 'a'),
+ ('a', ()))
+ self.failUnlessEqual(ttk._format_elemcreate('from', False, 'a', 'b'),
+ ('a', ('b', )))
+ self.failUnlessEqual(ttk._format_elemcreate('from', True, 'a', 'b'),
+ ('{a}', 'b'))
+
+
+ def test_format_layoutlist(self):
+ def sample(indent=0, indent_size=2):
+ return ttk._format_layoutlist(
+ [('a', {'other': [1, 2, 3], 'children':
+ [('b', {'children':
+ [('c', {'children':
+ [('d', {'nice': 'opt'})], 'something': (1, 2)
+ })]
+ })]
+ })], indent=indent, indent_size=indent_size)[0]
+
+ def sample_expected(indent=0, indent_size=2):
+ spaces = lambda amount=0: ' ' * (amount + indent)
+ return (
+ "%sa -other {1 2 3} -children {\n"
+ "%sb -children {\n"
+ "%sc -something {1 2} -children {\n"
+ "%sd -nice opt\n"
+ "%s}\n"
+ "%s}\n"
+ "%s}" % (spaces(), spaces(indent_size),
+ spaces(2 * indent_size), spaces(3 * indent_size),
+ spaces(2 * indent_size), spaces(indent_size), spaces()))
+
+ # empty layout
+ self.failUnlessEqual(ttk._format_layoutlist([])[0], '')
+
+ # _format_layoutlist always expects the second item (in every item)
+ # to act like a dict (except when the value evalutes to False).
+ self.failUnlessRaises(AttributeError,
+ ttk._format_layoutlist, [('a', 'b')])
+
+ smallest = ttk._format_layoutlist([('a', None)], indent=0)
+ self.failUnlessEqual(smallest,
+ ttk._format_layoutlist([('a', '')], indent=0))
+ self.failUnlessEqual(smallest[0], 'a')
+
+ # testing indentation levels
+ self.failUnlessEqual(sample(), sample_expected())
+ for i in range(4):
+ self.failUnlessEqual(sample(i), sample_expected(i))
+ self.failUnlessEqual(sample(i, i), sample_expected(i, i))
+
+ # invalid layout format, different kind of exceptions will be
+ # raised by internal functions
+
+ # plain wrong format
+ self.failUnlessRaises(ValueError, ttk._format_layoutlist,
+ ['bad', 'format'])
+ # will try to use iteritems in the 'bad' string
+ self.failUnlessRaises(AttributeError, ttk._format_layoutlist,
+ [('name', 'bad')])
+ # bad children formatting
+ self.failUnlessRaises(ValueError, ttk._format_layoutlist,
+ [('name', {'children': {'a': None}})])
+
+
+ def test_script_from_settings(self):
+ # empty options
+ self.failIf(ttk._script_from_settings({'name':
+ {'configure': None, 'map': None, 'element create': None}}))
+
+ # empty layout
+ self.failUnlessEqual(
+ ttk._script_from_settings({'name': {'layout': None}}),
+ "ttk::style layout name {\nnull\n}")
+
+ configdict = {'αβγ': True, 'á': False}
+ self.failUnless(
+ ttk._script_from_settings({'name': {'configure': configdict}}))
+
+ mapdict = {'üñíćódè': [('á', 'vãl')]}
+ self.failUnless(
+ ttk._script_from_settings({'name': {'map': mapdict}}))
+
+ # invalid image element
+ self.failUnlessRaises(IndexError,
+ ttk._script_from_settings, {'name': {'element create': ['image']}})
+
+ # minimal valid image
+ self.failUnless(ttk._script_from_settings({'name':
+ {'element create': ['image', 'name']}}))
+
+ image = {'thing': {'element create':
+ ['image', 'name', ('state1', 'state2', 'val')]}}
+ self.failUnlessEqual(ttk._script_from_settings(image),
+ "ttk::style element create thing image {name {state1 state2} val} ")
+
+ image['thing']['element create'].append({'opt': 30})
+ self.failUnlessEqual(ttk._script_from_settings(image),
+ "ttk::style element create thing image {name {state1 state2} val} "
+ "-opt 30")
+
+ image['thing']['element create'][-1]['opt'] = [MockTclObj(3),
+ MockTclObj('2m')]
+ self.failUnlessEqual(ttk._script_from_settings(image),
+ "ttk::style element create thing image {name {state1 state2} val} "
+ "-opt {3 2m}")
+
+
+ def test_dict_from_tcltuple(self):
+ fakettuple = ('-a', '{1 2 3}', '-something', 'foo')
+
+ self.failUnlessEqual(ttk._dict_from_tcltuple(fakettuple, False),
+ {'-a': '{1 2 3}', '-something': 'foo'})
+
+ self.failUnlessEqual(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.failIf(ttk._dict_from_tcltuple(('single', )))
+
+ sspec = MockStateSpec('a', 'b')
+ self.failUnlessEqual(ttk._dict_from_tcltuple(('-a', (sspec, 'val'))),
+ {'a': [('a', 'b', 'val')]})
+
+ self.failUnlessEqual(ttk._dict_from_tcltuple((MockTclObj('-padding'),
+ [MockTclObj('1'), 2, MockTclObj('3m')])),
+ {'padding': [1, 2, '3m']})
+
+
+ def test_list_from_statespec(self):
+ def test_it(sspec, value, res_value, states):
+ self.failUnlessEqual(ttk._list_from_statespec(
+ (sspec, value)), [states + (res_value, )])
+
+ states_even = tuple('state%d' % i for i in range(6))
+ statespec = MockStateSpec(*states_even)
+ test_it(statespec, 'val', 'val', states_even)
+ test_it(statespec, MockTclObj('val'), 'val', states_even)
+
+ states_odd = tuple('state%d' % i for i in range(5))
+ statespec = MockStateSpec(*states_odd)
+ test_it(statespec, 'val', 'val', states_odd)
+
+ test_it(('a', 'b', 'c'), MockTclObj('val'), 'val', ('a', 'b', 'c'))
+
+
+ def test_list_from_layouttuple(self):
+ # empty layout tuple
+ self.failIf(ttk._list_from_layouttuple(()))
+
+ # shortest layout tuple
+ self.failUnlessEqual(ttk._list_from_layouttuple(('name', )),
+ [('name', {})])
+
+ # not so interesting ltuple
+ sample_ltuple = ('name', '-option', 'value')
+ self.failUnlessEqual(ttk._list_from_layouttuple(sample_ltuple),
+ [('name', {'option': 'value'})])
+
+ # empty children
+ self.failUnlessEqual(ttk._list_from_layouttuple(
+ ('something', '-children', ())),
+ [('something', {'children': []})]
+ )
+
+ # more interesting ltuple
+ ltuple = (
+ 'name', '-option', 'niceone', '-children', (
+ ('otherone', '-children', (
+ ('child', )), '-otheropt', 'othervalue'
+ )
+ )
+ )
+ self.failUnlessEqual(ttk._list_from_layouttuple(ltuple),
+ [('name', {'option': 'niceone', 'children':
+ [('otherone', {'otheropt': 'othervalue', 'children':
+ [('child', {})]
+ })]
+ })]
+ )
+
+ # bad tuples
+ self.failUnlessRaises(ValueError, ttk._list_from_layouttuple,
+ ('name', 'no_minus'))
+ self.failUnlessRaises(ValueError, ttk._list_from_layouttuple,
+ ('name', 'no_minus', 'value'))
+ self.failUnlessRaises(ValueError, ttk._list_from_layouttuple,
+ ('something', '-children')) # no children
+ self.failUnlessRaises(ValueError, ttk._list_from_layouttuple,
+ ('something', '-children', 'value')) # invalid children
+
+
+ def test_val_or_dict(self):
+ def func(opt, val=None):
+ if val is None:
+ return "test val"
+ return (opt, val)
+
+ options = {'test': None}
+ self.failUnlessEqual(ttk._val_or_dict(options, func), "test val")
+
+ options = {'test': 3}
+ self.failUnlessEqual(ttk._val_or_dict(options, func), options)
+
+
+ def test_convert_stringval(self):
+ tests = (
+ (0, 0), ('09', 9), ('a', 'a'), ('áÚ', 'áÚ'), ([], '[]'),
+ (None, 'None')
+ )
+ for orig, expected in tests:
+ self.failUnlessEqual(ttk._convert_stringval(orig), expected)
+
+
+class TclObjsToPyTest(unittest.TestCase):
+
+ def test_unicode(self):
+ adict = {'opt': 'välúè'}
+ self.failUnlessEqual(ttk.tclobjs_to_py(adict), {'opt': 'välúè'})
+
+ adict['opt'] = MockTclObj(adict['opt'])
+ self.failUnlessEqual(ttk.tclobjs_to_py(adict), {'opt': 'välúè'})
+
+ def test_multivalues(self):
+ adict = {'opt': [1, 2, 3, 4]}
+ self.failUnlessEqual(ttk.tclobjs_to_py(adict), {'opt': [1, 2, 3, 4]})
+
+ adict['opt'] = [1, 'xm', 3]
+ self.failUnlessEqual(ttk.tclobjs_to_py(adict), {'opt': [1, 'xm', 3]})
+
+ adict['opt'] = (MockStateSpec('a', 'b'), 'válũè')
+ self.failUnlessEqual(ttk.tclobjs_to_py(adict),
+ {'opt': [('a', 'b', 'válũè')]})
+
+ self.failUnlessEqual(ttk.tclobjs_to_py({'x': ['y z']}),
+ {'x': ['y z']})
+
+ def test_nosplit(self):
+ self.failUnlessEqual(ttk.tclobjs_to_py({'text': 'some text'}),
+ {'text': 'some text'})
+
+tests_nogui = (InternalFunctionsTest, TclObjsToPyTest)
+
+if __name__ == "__main__":
+ from test.support import run_unittest
+ run_unittest(*tests_nogui)
diff --git a/Lib/tkinter/test/test_ttk/test_style.py b/Lib/tkinter/test/test_ttk/test_style.py
new file mode 100644
index 0000000..823b38d
--- /dev/null
+++ b/Lib/tkinter/test/test_ttk/test_style.py
@@ -0,0 +1,99 @@
+import unittest
+import tkinter
+from tkinter import ttk
+from test.support import requires, run_unittest
+
+import tkinter.test.support as support
+
+requires('gui')
+
+class StyleTest(unittest.TestCase):
+
+ def setUp(self):
+ self.root = support.get_tk_root()
+ self.style = ttk.Style(self.root)
+
+ def tearDown(self):
+ # As tests have shown, these tests are likely to deliver
+ # <<ThemeChanged>> events after the root is destroyed, so
+ # lets let them happen now.
+ self.root.update_idletasks()
+ self.root.destroy()
+
+
+ def test_configure(self):
+ style = self.style
+ style.configure('TButton', background='yellow')
+ self.failUnlessEqual(style.configure('TButton', 'background'),
+ 'yellow')
+ self.failUnless(isinstance(style.configure('TButton'), dict))
+
+
+ def test_map(self):
+ style = self.style
+ style.map('TButton', background=[('active', 'background', 'blue')])
+ self.failUnlessEqual(style.map('TButton', 'background'),
+ [('active', 'background', 'blue')])
+ self.failUnless(isinstance(style.map('TButton'), dict))
+
+
+ def test_lookup(self):
+ style = self.style
+ style.configure('TButton', background='yellow')
+ style.map('TButton', background=[('active', 'background', 'blue')])
+
+ self.failUnlessEqual(style.lookup('TButton', 'background'), 'yellow')
+ self.failUnlessEqual(style.lookup('TButton', 'background',
+ ['active', 'background']), 'blue')
+ self.failUnlessEqual(style.lookup('TButton', 'optionnotdefined',
+ default='iknewit'), 'iknewit')
+
+
+ def test_layout(self):
+ style = self.style
+ self.failUnlessRaises(tkinter.TclError, style.layout, 'NotALayout')
+ tv_style = style.layout('Treeview')
+
+ # "erase" Treeview layout
+ style.layout('Treeview', '')
+ self.failUnlessEqual(style.layout('Treeview'),
+ [('null', {'sticky': 'nswe'})]
+ )
+
+ # restore layout
+ style.layout('Treeview', tv_style)
+ self.failUnlessEqual(style.layout('Treeview'), tv_style)
+
+ # should return a list
+ self.failUnless(isinstance(style.layout('TButton'), list))
+
+ # correct layout, but "option" doesn't exist as option
+ self.failUnlessRaises(tkinter.TclError, style.layout, 'Treeview',
+ [('name', {'option': 'inexistant'})])
+
+
+ def test_theme_use(self):
+ self.failUnlessRaises(tkinter.TclError, self.style.theme_use,
+ 'nonexistingname')
+
+ curr_theme = self.style.theme_use()
+ new_theme = None
+ for theme in self.style.theme_names():
+ if theme != curr_theme:
+ new_theme = theme
+ self.style.theme_use(theme)
+ break
+ else:
+ # just one theme available, can't go on with tests
+ return
+
+ self.failIf(curr_theme == new_theme)
+ self.failIf(new_theme != self.style.theme_use())
+
+ self.style.theme_use(curr_theme)
+
+
+tests_gui = (StyleTest, )
+
+if __name__ == "__main__":
+ run_unittest(*tests_gui)
diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py
new file mode 100644
index 0000000..e92d2fa
--- /dev/null
+++ b/Lib/tkinter/test/test_ttk/test_widgets.py
@@ -0,0 +1,1115 @@
+import unittest
+import tkinter
+from tkinter import ttk
+from test.support import requires, run_unittest
+
+import tkinter.test.support as support
+from tkinter.test.test_ttk.test_functions import MockTclObj, MockStateSpec
+
+requires('gui')
+
+class WidgetTest(unittest.TestCase):
+ """Tests methods available in every ttk widget."""
+
+ def setUp(self):
+ self.widget = ttk.Button()
+ self.widget.pack()
+ self.widget.wait_visibility()
+
+ def tearDown(self):
+ self.widget.destroy()
+
+
+ def test_identify(self):
+ self.widget.update_idletasks()
+ self.failUnlessEqual(self.widget.identify(5, 5), "label")
+ self.failUnlessEqual(self.widget.identify(-1, -1), "")
+
+ self.failUnlessRaises(tkinter.TclError, self.widget.identify, None, 5)
+ self.failUnlessRaises(tkinter.TclError, self.widget.identify, 5, None)
+ self.failUnlessRaises(tkinter.TclError, self.widget.identify, 5, '')
+
+
+ def test_widget_state(self):
+ # XXX not sure about the portability of all these tests
+ self.failUnlessEqual(self.widget.state(), ())
+ self.failUnlessEqual(self.widget.instate(['!disabled']), True)
+
+ # changing from !disabled to disabled
+ self.failUnlessEqual(self.widget.state(['disabled']), ('!disabled', ))
+ # no state change
+ self.failUnlessEqual(self.widget.state(['disabled']), ())
+ # change back to !disable but also active
+ self.failUnlessEqual(self.widget.state(['!disabled', 'active']),
+ ('!active', 'disabled'))
+ # no state changes, again
+ self.failUnlessEqual(self.widget.state(['!disabled', 'active']), ())
+ self.failUnlessEqual(self.widget.state(['active', '!disabled']), ())
+
+ def test_cb(arg1, **kw):
+ return arg1, kw
+ self.failUnlessEqual(self.widget.instate(['!disabled'],
+ test_cb, "hi", **{"msg": "there"}),
+ ('hi', {'msg': 'there'}))
+
+ # attempt to set invalid statespec
+ currstate = self.widget.state()
+ self.failUnlessRaises(tkinter.TclError, self.widget.instate,
+ ['badstate'])
+ self.failUnlessRaises(tkinter.TclError, self.widget.instate,
+ ['disabled', 'badstate'])
+ # verify that widget didn't change its state
+ self.failUnlessEqual(currstate, self.widget.state())
+
+ # ensuring that passing None as state doesn't modify current state
+ self.widget.state(['active', '!disabled'])
+ self.failUnlessEqual(self.widget.state(), ('active', ))
+
+
+class ButtonTest(unittest.TestCase):
+
+ def test_invoke(self):
+ success = []
+ btn = ttk.Button(command=lambda: success.append(1))
+ btn.invoke()
+ self.failUnless(success)
+
+
+class CheckbuttonTest(unittest.TestCase):
+
+ def test_invoke(self):
+ success = []
+ def cb_test():
+ success.append(1)
+ return "cb test called"
+
+ cbtn = ttk.Checkbutton(command=cb_test)
+ # the variable automatically created by ttk.Checkbutton is actually
+ # undefined till we invoke the Checkbutton
+ self.failUnlessEqual(cbtn.state(), ('alternate', ))
+ self.failUnlessRaises(tkinter.TclError, cbtn.tk.globalgetvar,
+ cbtn['variable'])
+
+ res = cbtn.invoke()
+ self.failUnlessEqual(res, "cb test called")
+ self.failUnlessEqual(cbtn['onvalue'],
+ cbtn.tk.globalgetvar(cbtn['variable']))
+ self.failUnless(success)
+
+ cbtn['command'] = ''
+ res = cbtn.invoke()
+ self.failUnlessEqual(res, '')
+ self.failIf(len(success) > 1)
+ self.failUnlessEqual(cbtn['offvalue'],
+ cbtn.tk.globalgetvar(cbtn['variable']))
+
+
+class ComboboxTest(unittest.TestCase):
+
+ def setUp(self):
+ self.combo = ttk.Combobox()
+
+ def tearDown(self):
+ self.combo.destroy()
+
+ def _show_drop_down_listbox(self):
+ width = self.combo.winfo_width()
+ self.combo.event_generate('<ButtonPress-1>', x=width - 5, y=5)
+ self.combo.event_generate('<ButtonRelease-1>', x=width - 5, y=5)
+ self.combo.update_idletasks()
+
+
+ def test_virtual_event(self):
+ success = []
+
+ self.combo['values'] = [1]
+ self.combo.bind('<<ComboboxSelected>>',
+ lambda evt: success.append(True))
+ self.combo.pack()
+ self.combo.wait_visibility()
+
+ height = self.combo.winfo_height()
+ self._show_drop_down_listbox()
+ self.combo.update()
+ self.combo.event_generate('<Return>')
+ self.combo.update()
+
+ self.failUnless(success)
+
+
+ def test_postcommand(self):
+ success = []
+
+ self.combo['postcommand'] = lambda: success.append(True)
+ self.combo.pack()
+ self.combo.wait_visibility()
+
+ self._show_drop_down_listbox()
+ self.failUnless(success)
+
+ # testing postcommand removal
+ self.combo['postcommand'] = ''
+ self._show_drop_down_listbox()
+ self.failUnlessEqual(len(success), 1)
+
+
+ def test_values(self):
+ def check_get_current(getval, currval):
+ self.failUnlessEqual(self.combo.get(), getval)
+ self.failUnlessEqual(self.combo.current(), currval)
+
+ check_get_current('', -1)
+
+ self.combo['values'] = ['a', 1, 'c']
+
+ self.combo.set('c')
+ check_get_current('c', 2)
+
+ self.combo.current(0)
+ check_get_current('a', 0)
+
+ self.combo.set('d')
+ check_get_current('d', -1)
+
+ # testing values with empty string
+ self.combo.set('')
+ self.combo['values'] = (1, 2, '', 3)
+ check_get_current('', 2)
+
+ # testing values with empty string set through configure
+ self.combo.configure(values=[1, '', 2])
+ self.failUnlessEqual(self.combo['values'], ('1', '', '2'))
+
+ # out of range
+ self.failUnlessRaises(tkinter.TclError, self.combo.current,
+ len(self.combo['values']))
+ # it expects an integer (or something that can be converted to int)
+ self.failUnlessRaises(tkinter.TclError, self.combo.current, '')
+
+ # testing creating combobox with empty string in values
+ combo2 = ttk.Combobox(values=[1, 2, ''])
+ self.failUnlessEqual(combo2['values'], ('1', '2', ''))
+ combo2.destroy()
+
+
+class EntryTest(unittest.TestCase):
+
+ def setUp(self):
+ self.entry = ttk.Entry()
+
+ def tearDown(self):
+ self.entry.destroy()
+
+
+ def test_bbox(self):
+ self.failUnlessEqual(len(self.entry.bbox(0)), 4)
+ for item in self.entry.bbox(0):
+ self.failUnless(isinstance(item, int))
+
+ self.failUnlessRaises(tkinter.TclError, self.entry.bbox, 'noindex')
+ self.failUnlessRaises(tkinter.TclError, self.entry.bbox, None)
+
+
+ def test_identify(self):
+ self.entry.pack()
+ self.entry.wait_visibility()
+ self.entry.update_idletasks()
+
+ self.failUnlessEqual(self.entry.identify(5, 5), "textarea")
+ self.failUnlessEqual(self.entry.identify(-1, -1), "")
+
+ self.failUnlessRaises(tkinter.TclError, self.entry.identify, None, 5)
+ self.failUnlessRaises(tkinter.TclError, self.entry.identify, 5, None)
+ self.failUnlessRaises(tkinter.TclError, self.entry.identify, 5, '')
+
+
+ def test_validation_options(self):
+ success = []
+ test_invalid = lambda: success.append(True)
+
+ self.entry['validate'] = 'none'
+ self.entry['validatecommand'] = lambda: False
+
+ self.entry['invalidcommand'] = test_invalid
+ self.entry.validate()
+ self.failUnless(success)
+
+ self.entry['invalidcommand'] = ''
+ self.entry.validate()
+ self.failUnlessEqual(len(success), 1)
+
+ self.entry['invalidcommand'] = test_invalid
+ self.entry['validatecommand'] = lambda: True
+ self.entry.validate()
+ self.failUnlessEqual(len(success), 1)
+
+ self.entry['validatecommand'] = ''
+ self.entry.validate()
+ self.failUnlessEqual(len(success), 1)
+
+ self.entry['validatecommand'] = True
+ self.failUnlessRaises(tkinter.TclError, self.entry.validate)
+
+
+ def test_validation(self):
+ validation = []
+ def validate(to_insert):
+ if not 'a' <= to_insert.lower() <= 'z':
+ validation.append(False)
+ return False
+ validation.append(True)
+ return True
+
+ self.entry['validate'] = 'key'
+ self.entry['validatecommand'] = self.entry.register(validate), '%S'
+
+ self.entry.insert('end', 1)
+ self.entry.insert('end', 'a')
+ self.failUnlessEqual(validation, [False, True])
+ self.failUnlessEqual(self.entry.get(), 'a')
+
+
+ def test_revalidation(self):
+ def validate(content):
+ for letter in content:
+ if not 'a' <= letter.lower() <= 'z':
+ return False
+ return True
+
+ self.entry['validatecommand'] = self.entry.register(validate), '%P'
+
+ self.entry.insert('end', 'avocado')
+ self.failUnlessEqual(self.entry.validate(), True)
+ self.failUnlessEqual(self.entry.state(), ())
+
+ self.entry.delete(0, 'end')
+ self.failUnlessEqual(self.entry.get(), '')
+
+ self.entry.insert('end', 'a1b')
+ self.failUnlessEqual(self.entry.validate(), False)
+ self.failUnlessEqual(self.entry.state(), ('invalid', ))
+
+ self.entry.delete(1)
+ self.failUnlessEqual(self.entry.validate(), True)
+ self.failUnlessEqual(self.entry.state(), ())
+
+
+class PanedwindowTest(unittest.TestCase):
+
+ def setUp(self):
+ self.paned = ttk.Panedwindow()
+
+ def tearDown(self):
+ self.paned.destroy()
+
+
+ def test_add(self):
+ # attempt to add a child that is not a direct child of the paned window
+ label = ttk.Label(self.paned)
+ child = ttk.Label(label)
+ self.failUnlessRaises(tkinter.TclError, self.paned.add, child)
+ label.destroy()
+ child.destroy()
+ # another attempt
+ label = ttk.Label()
+ child = ttk.Label(label)
+ self.failUnlessRaises(tkinter.TclError, self.paned.add, child)
+ child.destroy()
+ label.destroy()
+
+ good_child = ttk.Label()
+ self.paned.add(good_child)
+ # re-adding a child is not accepted
+ self.failUnlessRaises(tkinter.TclError, self.paned.add, good_child)
+
+ other_child = ttk.Label(self.paned)
+ self.paned.add(other_child)
+ self.failUnlessEqual(self.paned.pane(0), self.paned.pane(1))
+ self.failUnlessRaises(tkinter.TclError, self.paned.pane, 2)
+ good_child.destroy()
+ other_child.destroy()
+ self.failUnlessRaises(tkinter.TclError, self.paned.pane, 0)
+
+
+ def test_forget(self):
+ self.failUnlessRaises(tkinter.TclError, self.paned.forget, None)
+ self.failUnlessRaises(tkinter.TclError, self.paned.forget, 0)
+
+ self.paned.add(ttk.Label())
+ self.paned.forget(0)
+ self.failUnlessRaises(tkinter.TclError, self.paned.forget, 0)
+
+
+ def test_insert(self):
+ self.failUnlessRaises(tkinter.TclError, self.paned.insert, None, 0)
+ self.failUnlessRaises(tkinter.TclError, self.paned.insert, 0, None)
+ self.failUnlessRaises(tkinter.TclError, self.paned.insert, 0, 0)
+
+ child = ttk.Label()
+ child2 = ttk.Label()
+ child3 = ttk.Label()
+
+ self.failUnlessRaises(tkinter.TclError, self.paned.insert, 0, child)
+
+ self.paned.insert('end', child2)
+ self.paned.insert(0, child)
+ self.failUnlessEqual(self.paned.panes(), (str(child), str(child2)))
+
+ self.paned.insert(0, child2)
+ self.failUnlessEqual(self.paned.panes(), (str(child2), str(child)))
+
+ self.paned.insert('end', child3)
+ self.failUnlessEqual(self.paned.panes(),
+ (str(child2), str(child), str(child3)))
+
+ # reinserting a child should move it to its current position
+ panes = self.paned.panes()
+ self.paned.insert('end', child3)
+ self.failUnlessEqual(panes, self.paned.panes())
+
+ # moving child3 to child2 position should result in child2 ending up
+ # in previous child position and child ending up in previous child3
+ # position
+ self.paned.insert(child2, child3)
+ self.failUnlessEqual(self.paned.panes(),
+ (str(child3), str(child2), str(child)))
+
+
+ def test_pane(self):
+ self.failUnlessRaises(tkinter.TclError, self.paned.pane, 0)
+
+ child = ttk.Label()
+ self.paned.add(child)
+ self.failUnless(isinstance(self.paned.pane(0), dict))
+ self.failUnlessEqual(self.paned.pane(0, weight=None), 0)
+ # newer form for querying a single option
+ self.failUnlessEqual(self.paned.pane(0, 'weight'), 0)
+ self.failUnlessEqual(self.paned.pane(0), self.paned.pane(str(child)))
+
+ self.failUnlessRaises(tkinter.TclError, self.paned.pane, 0,
+ badoption='somevalue')
+
+
+ def test_sashpos(self):
+ self.failUnlessRaises(tkinter.TclError, self.paned.sashpos, None)
+ self.failUnlessRaises(tkinter.TclError, self.paned.sashpos, '')
+ self.failUnlessRaises(tkinter.TclError, self.paned.sashpos, 0)
+
+ child = ttk.Label(self.paned, text='a')
+ self.paned.add(child, weight=1)
+ self.failUnlessRaises(tkinter.TclError, self.paned.sashpos, 0)
+ child2 = ttk.Label(self.paned, text='b')
+ self.paned.add(child2)
+ self.failUnlessRaises(tkinter.TclError, self.paned.sashpos, 1)
+
+ self.paned.pack(expand=True, fill='both')
+ self.paned.wait_visibility()
+
+ curr_pos = self.paned.sashpos(0)
+ self.paned.sashpos(0, 1000)
+ self.failUnless(curr_pos != self.paned.sashpos(0))
+ self.failUnless(isinstance(self.paned.sashpos(0), int))
+
+
+class RadiobuttonTest(unittest.TestCase):
+
+ def test_invoke(self):
+ success = []
+ def cb_test():
+ success.append(1)
+ return "cb test called"
+
+ myvar = tkinter.IntVar()
+ cbtn = ttk.Radiobutton(command=cb_test, variable=myvar, value=0)
+ cbtn2 = ttk.Radiobutton(command=cb_test, variable=myvar, value=1)
+
+ res = cbtn.invoke()
+ self.failUnlessEqual(res, "cb test called")
+ self.failUnlessEqual(cbtn['value'], myvar.get())
+ self.failUnlessEqual(myvar.get(),
+ cbtn.tk.globalgetvar(cbtn['variable']))
+ self.failUnless(success)
+
+ cbtn2['command'] = ''
+ res = cbtn2.invoke()
+ self.failUnlessEqual(res, '')
+ self.failIf(len(success) > 1)
+ self.failUnlessEqual(cbtn2['value'], myvar.get())
+ self.failUnlessEqual(myvar.get(),
+ cbtn.tk.globalgetvar(cbtn['variable']))
+
+ self.failUnlessEqual(str(cbtn['variable']), str(cbtn2['variable']))
+
+
+
+class ScaleTest(unittest.TestCase):
+
+ def setUp(self):
+ self.scale = ttk.Scale()
+ self.scale.pack()
+ self.scale.update()
+
+ def tearDown(self):
+ self.scale.destroy()
+
+
+ def test_custom_event(self):
+ failure = [1, 1, 1] # will need to be empty
+
+ funcid = self.scale.bind('<<RangeChanged>>', lambda evt: failure.pop())
+
+ self.scale['from'] = 10
+ self.scale['from_'] = 10
+ self.scale['to'] = 3
+
+ self.failIf(failure)
+
+ failure = [1, 1, 1]
+ self.scale.configure(from_=2, to=5)
+ self.scale.configure(from_=0, to=-2)
+ self.scale.configure(to=10)
+
+ self.failIf(failure)
+
+
+ def test_get(self):
+ scale_width = self.scale.winfo_width()
+ self.failUnlessEqual(self.scale.get(scale_width, 0), self.scale['to'])
+
+ self.failUnlessEqual(self.scale.get(0, 0), self.scale['from'])
+ self.failUnlessEqual(self.scale.get(), self.scale['value'])
+ self.scale['value'] = 30
+ self.failUnlessEqual(self.scale.get(), self.scale['value'])
+
+ self.failUnlessRaises(tkinter.TclError, self.scale.get, '', 0)
+ self.failUnlessRaises(tkinter.TclError, self.scale.get, 0, '')
+
+
+ def test_set(self):
+ # set restricts the max/min values according to the current range
+ max = self.scale['to']
+ new_max = max + 10
+ self.scale.set(new_max)
+ self.failUnlessEqual(self.scale.get(), max)
+ min = self.scale['from']
+ self.scale.set(min - 1)
+ self.failUnlessEqual(self.scale.get(), min)
+
+ # changing directly the variable doesn't impose this limitation tho
+ var = tkinter.DoubleVar()
+ self.scale['variable'] = var
+ var.set(max + 5)
+ self.failUnlessEqual(self.scale.get(), var.get())
+ self.failUnlessEqual(self.scale.get(), max + 5)
+ del var
+
+ # the same happens with the value option
+ self.scale['value'] = max + 10
+ self.failUnlessEqual(self.scale.get(), max + 10)
+ self.failUnlessEqual(self.scale.get(), self.scale['value'])
+
+ # nevertheless, note that the max/min values we can get specifying
+ # x, y coords are the ones according to the current range
+ self.failUnlessEqual(self.scale.get(0, 0), min)
+ self.failUnlessEqual(self.scale.get(self.scale.winfo_width(), 0), max)
+
+ self.failUnlessRaises(tkinter.TclError, self.scale.set, None)
+
+
+class NotebookTest(unittest.TestCase):
+
+ def setUp(self):
+ self.nb = ttk.Notebook()
+ self.child1 = ttk.Label()
+ self.child2 = ttk.Label()
+ self.nb.add(self.child1, text='a')
+ self.nb.add(self.child2, text='b')
+
+ def tearDown(self):
+ self.child1.destroy()
+ self.child2.destroy()
+ self.nb.destroy()
+
+
+ def test_tab_identifiers(self):
+ self.nb.forget(0)
+ self.nb.hide(self.child2)
+ self.failUnlessRaises(tkinter.TclError, self.nb.tab, self.child1)
+ self.failUnlessEqual(self.nb.index('end'), 1)
+ self.nb.add(self.child2)
+ self.failUnlessEqual(self.nb.index('end'), 1)
+ self.nb.select(self.child2)
+
+ self.failUnless(self.nb.tab('current'))
+ self.nb.add(self.child1, text='a')
+
+ self.nb.pack()
+ self.nb.wait_visibility()
+ self.failUnlessEqual(self.nb.tab('@5,5'), self.nb.tab('current'))
+
+ for i in range(5, 100, 5):
+ if self.nb.tab('@%d, 5' % i, text=None) == 'a':
+ break
+ else:
+ self.fail("Tab with text 'a' not found")
+
+
+ def test_add_and_hidden(self):
+ self.failUnlessRaises(tkinter.TclError, self.nb.hide, -1)
+ self.failUnlessRaises(tkinter.TclError, self.nb.hide, 'hi')
+ self.failUnlessRaises(tkinter.TclError, self.nb.hide, None)
+ self.failUnlessRaises(tkinter.TclError, self.nb.add, None)
+ self.failUnlessRaises(tkinter.TclError, self.nb.add, ttk.Label(),
+ unknown='option')
+
+ tabs = self.nb.tabs()
+ self.nb.hide(self.child1)
+ self.nb.add(self.child1)
+ self.failUnlessEqual(self.nb.tabs(), tabs)
+
+ child = ttk.Label()
+ self.nb.add(child, text='c')
+ tabs = self.nb.tabs()
+
+ curr = self.nb.index('current')
+ # verify that the tab gets readded at its previous position
+ child2_index = self.nb.index(self.child2)
+ self.nb.hide(self.child2)
+ self.nb.add(self.child2)
+ self.failUnlessEqual(self.nb.tabs(), tabs)
+ self.failUnlessEqual(self.nb.index(self.child2), child2_index)
+ self.failUnless(str(self.child2) == self.nb.tabs()[child2_index])
+ # but the tab next to it (not hidden) is the one selected now
+ self.failUnlessEqual(self.nb.index('current'), curr + 1)
+
+
+ def test_forget(self):
+ self.failUnlessRaises(tkinter.TclError, self.nb.forget, -1)
+ self.failUnlessRaises(tkinter.TclError, self.nb.forget, 'hi')
+ self.failUnlessRaises(tkinter.TclError, self.nb.forget, None)
+
+ tabs = self.nb.tabs()
+ child1_index = self.nb.index(self.child1)
+ self.nb.forget(self.child1)
+ self.failIf(str(self.child1) in self.nb.tabs())
+ self.failUnlessEqual(len(tabs) - 1, len(self.nb.tabs()))
+
+ self.nb.add(self.child1)
+ self.failUnlessEqual(self.nb.index(self.child1), 1)
+ self.failIf(child1_index == self.nb.index(self.child1))
+
+
+ def test_index(self):
+ self.failUnlessRaises(tkinter.TclError, self.nb.index, -1)
+ self.failUnlessRaises(tkinter.TclError, self.nb.index, None)
+
+ self.failUnless(isinstance(self.nb.index('end'), int))
+ self.failUnlessEqual(self.nb.index(self.child1), 0)
+ self.failUnlessEqual(self.nb.index(self.child2), 1)
+ self.failUnlessEqual(self.nb.index('end'), 2)
+
+
+ def test_insert(self):
+ # moving tabs
+ tabs = self.nb.tabs()
+ self.nb.insert(1, tabs[0])
+ self.failUnlessEqual(self.nb.tabs(), (tabs[1], tabs[0]))
+ self.nb.insert(self.child1, self.child2)
+ self.failUnlessEqual(self.nb.tabs(), tabs)
+ self.nb.insert('end', self.child1)
+ self.failUnlessEqual(self.nb.tabs(), (tabs[1], tabs[0]))
+ self.nb.insert('end', 0)
+ self.failUnlessEqual(self.nb.tabs(), tabs)
+ # bad moves
+ self.failUnlessRaises(tkinter.TclError, self.nb.insert, 2, tabs[0])
+ self.failUnlessRaises(tkinter.TclError, self.nb.insert, -1, tabs[0])
+
+ # new tab
+ child3 = ttk.Label()
+ self.nb.insert(1, child3)
+ self.failUnlessEqual(self.nb.tabs(), (tabs[0], str(child3), tabs[1]))
+ self.nb.forget(child3)
+ self.failUnlessEqual(self.nb.tabs(), tabs)
+ self.nb.insert(self.child1, child3)
+ self.failUnlessEqual(self.nb.tabs(), (str(child3), ) + tabs)
+ self.nb.forget(child3)
+ self.failUnlessRaises(tkinter.TclError, self.nb.insert, 2, child3)
+ self.failUnlessRaises(tkinter.TclError, self.nb.insert, -1, child3)
+
+ # bad inserts
+ self.failUnlessRaises(tkinter.TclError, self.nb.insert, 'end', None)
+ self.failUnlessRaises(tkinter.TclError, self.nb.insert, None, 0)
+ self.failUnlessRaises(tkinter.TclError, self.nb.insert, None, None)
+
+
+ def test_select(self):
+ self.nb.pack()
+ self.nb.wait_visibility()
+
+ success = []
+ tab_changed = []
+
+ self.child1.bind('<Unmap>', lambda evt: success.append(True))
+ self.nb.bind('<<NotebookTabChanged>>',
+ lambda evt: tab_changed.append(True))
+
+ self.failUnlessEqual(self.nb.select(), str(self.child1))
+ self.nb.select(self.child2)
+ self.failUnless(success)
+ self.failUnlessEqual(self.nb.select(), str(self.child2))
+
+ self.nb.update()
+ self.failUnless(tab_changed)
+
+
+ def test_tab(self):
+ self.failUnlessRaises(tkinter.TclError, self.nb.tab, -1)
+ self.failUnlessRaises(tkinter.TclError, self.nb.tab, 'notab')
+ self.failUnlessRaises(tkinter.TclError, self.nb.tab, None)
+
+ self.failUnless(isinstance(self.nb.tab(self.child1), dict))
+ self.failUnlessEqual(self.nb.tab(self.child1, text=None), 'a')
+ # newer form for querying a single option
+ self.failUnlessEqual(self.nb.tab(self.child1, 'text'), 'a')
+ self.nb.tab(self.child1, text='abc')
+ self.failUnlessEqual(self.nb.tab(self.child1, text=None), 'abc')
+ self.failUnlessEqual(self.nb.tab(self.child1, 'text'), 'abc')
+
+
+ def test_tabs(self):
+ self.failUnlessEqual(len(self.nb.tabs()), 2)
+
+ self.nb.forget(self.child1)
+ self.nb.forget(self.child2)
+
+ self.failUnlessEqual(self.nb.tabs(), ())
+
+
+ def test_traversal(self):
+ self.nb.pack()
+ self.nb.wait_visibility()
+
+ self.nb.select(0)
+
+ support.simulate_mouse_click(self.nb, 5, 5)
+ self.nb.event_generate('<Control-Tab>')
+ self.failUnlessEqual(self.nb.select(), str(self.child2))
+ self.nb.event_generate('<Shift-Control-Tab>')
+ self.failUnlessEqual(self.nb.select(), str(self.child1))
+ self.nb.event_generate('<Shift-Control-Tab>')
+ self.failUnlessEqual(self.nb.select(), str(self.child2))
+
+ self.nb.tab(self.child1, text='a', underline=0)
+ self.nb.enable_traversal()
+ self.nb.event_generate('<Alt-a>')
+ self.failUnlessEqual(self.nb.select(), str(self.child1))
+
+
+class TreeviewTest(unittest.TestCase):
+
+ def setUp(self):
+ self.tv = ttk.Treeview()
+
+ def tearDown(self):
+ self.tv.destroy()
+
+
+ def test_bbox(self):
+ self.tv.pack()
+ self.failUnlessEqual(self.tv.bbox(''), '')
+ self.tv.wait_visibility()
+ self.tv.update()
+
+ item_id = self.tv.insert('', 'end')
+ children = self.tv.get_children()
+ self.failUnless(children)
+
+ bbox = self.tv.bbox(children[0])
+ self.failUnlessEqual(len(bbox), 4)
+ self.failUnless(isinstance(bbox, tuple))
+ for item in bbox:
+ if not isinstance(item, int):
+ self.fail("Invalid bounding box: %s" % bbox)
+ break
+
+ # compare width in bboxes
+ self.tv['columns'] = ['test']
+ self.tv.column('test', width=50)
+ bbox_column0 = self.tv.bbox(children[0], 0)
+ root_width = self.tv.column('#0', width=None)
+ self.failUnlessEqual(bbox_column0[0], bbox[0] + root_width)
+
+ # verify that bbox of a closed item is the empty string
+ child1 = self.tv.insert(item_id, 'end')
+ self.failUnlessEqual(self.tv.bbox(child1), '')
+
+
+ def test_children(self):
+ # no children yet, should get an empty tuple
+ self.failUnlessEqual(self.tv.get_children(), ())
+
+ item_id = self.tv.insert('', 'end')
+ self.failUnless(isinstance(self.tv.get_children(), tuple))
+ self.failUnlessEqual(self.tv.get_children()[0], item_id)
+
+ # add item_id and child3 as children of child2
+ child2 = self.tv.insert('', 'end')
+ child3 = self.tv.insert('', 'end')
+ self.tv.set_children(child2, item_id, child3)
+ self.failUnlessEqual(self.tv.get_children(child2), (item_id, child3))
+
+ # child3 has child2 as parent, thus trying to set child2 as a children
+ # of child3 should result in an error
+ self.failUnlessRaises(tkinter.TclError,
+ self.tv.set_children, child3, child2)
+
+ # remove child2 children
+ self.tv.set_children(child2)
+ self.failUnlessEqual(self.tv.get_children(child2), ())
+
+ # remove root's children
+ self.tv.set_children('')
+ self.failUnlessEqual(self.tv.get_children(), ())
+
+
+ def test_column(self):
+ # return a dict with all options/values
+ self.failUnless(isinstance(self.tv.column('#0'), dict))
+ # return a single value of the given option
+ self.failUnless(isinstance(self.tv.column('#0', width=None), int))
+ # set a new value for an option
+ self.tv.column('#0', width=10)
+ # testing new way to get option value
+ self.failUnlessEqual(self.tv.column('#0', 'width'), 10)
+ self.failUnlessEqual(self.tv.column('#0', width=None), 10)
+ # check read-only option
+ self.failUnlessRaises(tkinter.TclError, self.tv.column, '#0', id='X')
+
+ self.failUnlessRaises(tkinter.TclError, self.tv.column, 'invalid')
+ invalid_kws = [
+ {'unknown_option': 'some value'}, {'stretch': 'wrong'},
+ {'anchor': 'wrong'}, {'width': 'wrong'}, {'minwidth': 'wrong'}
+ ]
+ for kw in invalid_kws:
+ self.failUnlessRaises(tkinter.TclError, self.tv.column, '#0',
+ **kw)
+
+
+ def test_delete(self):
+ self.failUnlessRaises(tkinter.TclError, self.tv.delete, '#0')
+
+ item_id = self.tv.insert('', 'end')
+ item2 = self.tv.insert(item_id, 'end')
+ self.failUnlessEqual(self.tv.get_children(), (item_id, ))
+ self.failUnlessEqual(self.tv.get_children(item_id), (item2, ))
+
+ self.tv.delete(item_id)
+ self.failIf(self.tv.get_children())
+
+ # reattach should fail
+ self.failUnlessRaises(tkinter.TclError,
+ self.tv.reattach, item_id, '', 'end')
+
+ # test multiple item delete
+ item1 = self.tv.insert('', 'end')
+ item2 = self.tv.insert('', 'end')
+ self.failUnlessEqual(self.tv.get_children(), (item1, item2))
+
+ self.tv.delete(item1, item2)
+ self.failIf(self.tv.get_children())
+
+
+ def test_detach_reattach(self):
+ item_id = self.tv.insert('', 'end')
+ item2 = self.tv.insert(item_id, 'end')
+
+ # calling detach without items is valid, although it does nothing
+ prev = self.tv.get_children()
+ self.tv.detach() # this should do nothing
+ self.failUnlessEqual(prev, self.tv.get_children())
+
+ self.failUnlessEqual(self.tv.get_children(), (item_id, ))
+ self.failUnlessEqual(self.tv.get_children(item_id), (item2, ))
+
+ # detach item with children
+ self.tv.detach(item_id)
+ self.failIf(self.tv.get_children())
+
+ # reattach item with children
+ self.tv.reattach(item_id, '', 'end')
+ self.failUnlessEqual(self.tv.get_children(), (item_id, ))
+ self.failUnlessEqual(self.tv.get_children(item_id), (item2, ))
+
+ # move a children to the root
+ self.tv.move(item2, '', 'end')
+ self.failUnlessEqual(self.tv.get_children(), (item_id, item2))
+ self.failUnlessEqual(self.tv.get_children(item_id), ())
+
+ # bad values
+ self.failUnlessRaises(tkinter.TclError,
+ self.tv.reattach, 'nonexistant', '', 'end')
+ self.failUnlessRaises(tkinter.TclError,
+ self.tv.detach, 'nonexistant')
+ self.failUnlessRaises(tkinter.TclError,
+ self.tv.reattach, item2, 'otherparent', 'end')
+ self.failUnlessRaises(tkinter.TclError,
+ self.tv.reattach, item2, '', 'invalid')
+
+ # multiple detach
+ self.tv.detach(item_id, item2)
+ self.failUnlessEqual(self.tv.get_children(), ())
+ self.failUnlessEqual(self.tv.get_children(item_id), ())
+
+
+ def test_exists(self):
+ self.failUnlessEqual(self.tv.exists('something'), False)
+ self.failUnlessEqual(self.tv.exists(''), True)
+ self.failUnlessEqual(self.tv.exists({}), False)
+
+ # the following will make a tk.call equivalent to
+ # tk.call(treeview, "exists") which should result in an error
+ # in the tcl interpreter since tk requires an item.
+ self.failUnlessRaises(tkinter.TclError, self.tv.exists, None)
+
+
+ def test_focus(self):
+ # nothing is focused right now
+ self.failUnlessEqual(self.tv.focus(), '')
+
+ item1 = self.tv.insert('', 'end')
+ self.tv.focus(item1)
+ self.failUnlessEqual(self.tv.focus(), item1)
+
+ self.tv.delete(item1)
+ self.failUnlessEqual(self.tv.focus(), '')
+
+ # try focusing inexistant item
+ self.failUnlessRaises(tkinter.TclError, self.tv.focus, 'hi')
+
+
+ def test_heading(self):
+ # check a dict is returned
+ self.failUnless(isinstance(self.tv.heading('#0'), dict))
+
+ # check a value is returned
+ self.tv.heading('#0', text='hi')
+ self.failUnlessEqual(self.tv.heading('#0', 'text'), 'hi')
+ self.failUnlessEqual(self.tv.heading('#0', text=None), 'hi')
+
+ # invalid option
+ self.failUnlessRaises(tkinter.TclError, self.tv.heading, '#0',
+ background=None)
+ # invalid value
+ self.failUnlessRaises(tkinter.TclError, self.tv.heading, '#0',
+ anchor=1)
+
+
+ def test_heading_callback(self):
+ def simulate_heading_click(x, y):
+ support.simulate_mouse_click(self.tv, x, y)
+ self.tv.update_idletasks()
+
+ success = [] # no success for now
+
+ self.tv.pack()
+ self.tv.wait_visibility()
+ self.tv.heading('#0', command=lambda: success.append(True))
+ self.tv.column('#0', width=100)
+ self.tv.update()
+
+ # assuming that the coords (5, 5) fall into heading #0
+ simulate_heading_click(5, 5)
+ if not success:
+ self.fail("The command associated to the treeview heading wasn't "
+ "invoked.")
+
+ success = []
+ commands = self.tv.master._tclCommands
+ self.tv.heading('#0', command=str(self.tv.heading('#0', command=None)))
+ self.failUnlessEqual(commands, self.tv.master._tclCommands)
+ simulate_heading_click(5, 5)
+ if not success:
+ self.fail("The command associated to the treeview heading wasn't "
+ "invoked.")
+
+ # XXX The following raises an error in a tcl interpreter, but not in
+ # Python
+ #self.tv.heading('#0', command='I dont exist')
+ #simulate_heading_click(5, 5)
+
+
+ def test_index(self):
+ # item 'what' doesn't exist
+ self.failUnlessRaises(tkinter.TclError, self.tv.index, 'what')
+
+ self.failUnlessEqual(self.tv.index(''), 0)
+
+ item1 = self.tv.insert('', 'end')
+ item2 = self.tv.insert('', 'end')
+ c1 = self.tv.insert(item1, 'end')
+ c2 = self.tv.insert(item1, 'end')
+ self.failUnlessEqual(self.tv.index(item1), 0)
+ self.failUnlessEqual(self.tv.index(c1), 0)
+ self.failUnlessEqual(self.tv.index(c2), 1)
+ self.failUnlessEqual(self.tv.index(item2), 1)
+
+ self.tv.move(item2, '', 0)
+ self.failUnlessEqual(self.tv.index(item2), 0)
+ self.failUnlessEqual(self.tv.index(item1), 1)
+
+ # check that index still works even after its parent and siblings
+ # have been detached
+ self.tv.detach(item1)
+ self.failUnlessEqual(self.tv.index(c2), 1)
+ self.tv.detach(c1)
+ self.failUnlessEqual(self.tv.index(c2), 0)
+
+ # but it fails after item has been deleted
+ self.tv.delete(item1)
+ self.failUnlessRaises(tkinter.TclError, self.tv.index, c2)
+
+
+ def test_insert_item(self):
+ # parent 'none' doesn't exist
+ self.failUnlessRaises(tkinter.TclError, self.tv.insert, 'none', 'end')
+
+ # open values
+ self.failUnlessRaises(tkinter.TclError, self.tv.insert, '', 'end',
+ open='')
+ self.failUnlessRaises(tkinter.TclError, self.tv.insert, '', 'end',
+ open='please')
+ self.failIf(self.tv.delete(self.tv.insert('', 'end', open=True)))
+ self.failIf(self.tv.delete(self.tv.insert('', 'end', open=False)))
+
+ # invalid index
+ self.failUnlessRaises(tkinter.TclError, self.tv.insert, '', 'middle')
+
+ # trying to duplicate item id is invalid
+ itemid = self.tv.insert('', 'end', 'first-item')
+ self.failUnlessEqual(itemid, 'first-item')
+ self.failUnlessRaises(tkinter.TclError, self.tv.insert, '', 'end',
+ 'first-item')
+ self.failUnlessRaises(tkinter.TclError, self.tv.insert, '', 'end',
+ MockTclObj('first-item'))
+
+ # unicode values
+ value = '\xe1ba'
+ item = self.tv.insert('', 'end', values=(value, ))
+ self.failUnlessEqual(self.tv.item(item, 'values'), (value, ))
+ self.failUnlessEqual(self.tv.item(item, values=None), (value, ))
+
+ self.tv.item(item, values=list(self.tv.item(item, values=None)))
+ self.failUnlessEqual(self.tv.item(item, values=None), (value, ))
+
+ self.failUnless(isinstance(self.tv.item(item), dict))
+
+ # erase item values
+ self.tv.item(item, values='')
+ self.failIf(self.tv.item(item, values=None))
+
+ # item tags
+ item = self.tv.insert('', 'end', tags=[1, 2, value])
+ self.failUnlessEqual(self.tv.item(item, tags=None), ('1', '2', value))
+ self.tv.item(item, tags=[])
+ self.failIf(self.tv.item(item, tags=None))
+ self.tv.item(item, tags=(1, 2))
+ self.failUnlessEqual(self.tv.item(item, tags=None), ('1', '2'))
+
+ # values with spaces
+ item = self.tv.insert('', 'end', values=('a b c',
+ '%s %s' % (value, value)))
+ self.failUnlessEqual(self.tv.item(item, values=None),
+ ('a b c', '%s %s' % (value, value)))
+
+ # text
+ self.failUnlessEqual(self.tv.item(
+ self.tv.insert('', 'end', text="Label here"), text=None),
+ "Label here")
+ self.failUnlessEqual(self.tv.item(
+ self.tv.insert('', 'end', text=value), text=None),
+ value)
+
+
+ def test_set(self):
+ self.tv['columns'] = ['A', 'B']
+ item = self.tv.insert('', 'end', values=['a', 'b'])
+ self.failUnlessEqual(self.tv.set(item), {'A': 'a', 'B': 'b'})
+
+ self.tv.set(item, 'B', 'a')
+ self.failUnlessEqual(self.tv.item(item, values=None), ('a', 'a'))
+
+ self.tv['columns'] = ['B']
+ self.failUnlessEqual(self.tv.set(item), {'B': 'a'})
+
+ self.tv.set(item, 'B', 'b')
+ self.failUnlessEqual(self.tv.set(item, column='B'), 'b')
+ self.failUnlessEqual(self.tv.item(item, values=None), ('b', 'a'))
+
+ self.tv.set(item, 'B', 123)
+ self.failUnlessEqual(self.tv.set(item, 'B'), 123)
+ self.failUnlessEqual(self.tv.item(item, values=None), (123, 'a'))
+ self.failUnlessEqual(self.tv.set(item), {'B': 123})
+
+ # inexistant column
+ self.failUnlessRaises(tkinter.TclError, self.tv.set, item, 'A')
+ self.failUnlessRaises(tkinter.TclError, self.tv.set, item, 'A', 'b')
+
+ # inexistant item
+ self.failUnlessRaises(tkinter.TclError, self.tv.set, 'notme')
+
+
+ def test_tag_bind(self):
+ events = []
+ item1 = self.tv.insert('', 'end', tags=['call'])
+ item2 = self.tv.insert('', 'end', tags=['call'])
+ self.tv.tag_bind('call', '<ButtonPress-1>',
+ lambda evt: events.append(1))
+ self.tv.tag_bind('call', '<ButtonRelease-1>',
+ lambda evt: events.append(2))
+
+ self.tv.pack()
+ self.tv.wait_visibility()
+ self.tv.update()
+
+ pos_y = set()
+ found = set()
+ for i in range(0, 100, 10):
+ if len(found) == 2: # item1 and item2 already found
+ break
+ item_id = self.tv.identify_row(i)
+ if item_id and item_id not in found:
+ pos_y.add(i)
+ found.add(item_id)
+
+ self.failUnlessEqual(len(pos_y), 2) # item1 and item2 y pos
+ for y in pos_y:
+ support.simulate_mouse_click(self.tv, 0, y)
+
+ # by now there should be 4 things in the events list, since each
+ # item had a bind for two events that were simulated above
+ self.failUnlessEqual(len(events), 4)
+ for evt in zip(events[::2], events[1::2]):
+ self.failUnlessEqual(evt, (1, 2))
+
+
+ def test_tag_configure(self):
+ # Just testing parameter passing for now
+ self.failUnlessRaises(TypeError, self.tv.tag_configure)
+ self.failUnlessRaises(tkinter.TclError, self.tv.tag_configure,
+ 'test', sky='blue')
+ self.tv.tag_configure('test', foreground='blue')
+ self.failUnlessEqual(self.tv.tag_configure('test', 'foreground'),
+ 'blue')
+ self.failUnlessEqual(self.tv.tag_configure('test', foreground=None),
+ 'blue')
+ self.failUnless(isinstance(self.tv.tag_configure('test'), dict))
+
+
+tests_gui = (
+ WidgetTest, ButtonTest, CheckbuttonTest, RadiobuttonTest,
+ ComboboxTest, EntryTest, PanedwindowTest, ScaleTest, NotebookTest,
+ TreeviewTest
+ )
+
+if __name__ == "__main__":
+ run_unittest(*tests_gui)
diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py
new file mode 100644
index 0000000..82420aa
--- /dev/null
+++ b/Lib/tkinter/ttk.py
@@ -0,0 +1,1636 @@
+"""Ttk wrapper.
+
+This module provides classes to allow using Tk themed widget set.
+
+Ttk is based on a revised and enhanced version of
+TIP #48 (http://tip.tcl.tk/48) specified style engine.
+
+Its basic idea is to separate, to the extent possible, the code
+implementing a widget's behavior from the code implementing its
+appearance. Widget class bindings are primarily responsible for
+maintaining the widget state and invoking callbacks, all aspects
+of the widgets appearance lies at Themes.
+"""
+
+__version__ = "0.3.1"
+
+__author__ = "Guilherme Polo <ggpolo@gmail.com>"
+
+__all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label",
+ "Labelframe", "LabelFrame", "Menubutton", "Notebook", "Panedwindow",
+ "PanedWindow", "Progressbar", "Radiobutton", "Scale", "Scrollbar",
+ "Separator", "Sizegrip", "Style", "Treeview",
+ # Extensions
+ "LabeledScale", "OptionMenu",
+ # functions
+ "tclobjs_to_py"]
+
+import tkinter
+
+_flatten = tkinter._flatten
+
+# Verify if Tk is new enough to not need Tile checking
+_REQUIRE_TILE = True if tkinter.TkVersion < 8.5 else False
+
+def _loadttk(loadtk):
+ # This extends the default tkinter.Tk._loadtk method so we can be
+ # sure that ttk is available for use, or not.
+ def _wrapper(self):
+ loadtk(self)
+
+ if _REQUIRE_TILE:
+ import os
+ tilelib = os.environ.get('TILE_LIBRARY')
+ if tilelib:
+ # append custom tile path to the the list of directories that
+ # Tcl uses when attempting to resolve packages with the package
+ # command
+ self.tk.eval('global auto_path; '
+ 'lappend auto_path {%s}' % tilelib)
+ self.tk.eval('package require tile') # TclError may be raised here
+
+ return _wrapper
+
+# Store the original tkinter.Tk._loadtk before replacing it just in case
+# someone wants to restore it.
+__loadtk__ = tkinter.Tk._loadtk
+tkinter.Tk._loadtk = _loadttk(tkinter.Tk._loadtk)
+
+
+def _format_optdict(optdict, script=False, ignore=None):
+ """Formats optdict to a tuple to pass it to tk.call.
+
+ E.g. (script=False):
+ {'foreground': 'blue', 'padding': [1, 2, 3, 4]} returns:
+ ('-foreground', 'blue', '-padding', '1 2 3 4')"""
+ format = "%s" if not script else "{%s}"
+
+ opts = []
+ for opt, value in optdict.items():
+ if ignore and opt in ignore:
+ continue
+
+ if isinstance(value, (list, tuple)):
+ v = []
+ for val in value:
+ if isinstance(val, str):
+ v.append(str(val) if val else '{}')
+ else:
+ v.append(str(val))
+
+ # format v according to the script option, but also check for
+ # space in any value in v in order to group them correctly
+ value = format % ' '.join(
+ ('{%s}' if ' ' in val else '%s') % val for val in v)
+
+ if script and value == '':
+ value = '{}' # empty string in Python is equivalent to {} in Tcl
+
+ opts.append(("-%s" % opt, value))
+
+ # Remember: _flatten skips over None
+ return _flatten(opts)
+
+def _format_mapdict(mapdict, script=False):
+ """Formats mapdict to pass it to tk.call.
+
+ E.g. (script=False):
+ {'expand': [('active', 'selected', 'grey'), ('focus', [1, 2, 3, 4])]}
+
+ returns:
+
+ ('-expand', '{active selected} grey focus {1, 2, 3, 4}')"""
+ # if caller passes a Tcl script to tk.call, all the values need to
+ # be grouped into words (arguments to a command in Tcl dialect)
+ format = "%s" if not script else "{%s}"
+
+ opts = []
+ for opt, value in mapdict.items():
+
+ opt_val = []
+ # each value in mapdict is expected to be a sequence, where each item
+ # is another sequence containing a state (or several) and a value
+ for statespec in value:
+ state, val = statespec[:-1], statespec[-1]
+
+ if len(state) > 1: # group multiple states
+ state = "{%s}" % ' '.join(state)
+ else: # single state
+ # if it is empty (something that evaluates to False), then
+ # format it to Tcl code to denote the "normal" state
+ state = state[0] or '{}'
+
+ if isinstance(val, (list, tuple)): # val needs to be grouped
+ val = "{%s}" % ' '.join(map(str, val))
+
+ opt_val.append("%s %s" % (state, val))
+
+ opts.append(("-%s" % opt, format % ' '.join(opt_val)))
+
+ return _flatten(opts)
+
+def _format_elemcreate(etype, script=False, *args, **kw):
+ """Formats args and kw according to the given element factory etype."""
+ spec = None
+ opts = ()
+ if etype in ("image", "vsapi"):
+ if etype == "image": # define an element based on an image
+ # first arg should be the default image name
+ iname = args[0]
+ # next args, if any, are statespec/value pairs which is almost
+ # a mapdict, but we just need the value
+ imagespec = _format_mapdict({None: args[1:]})[1]
+ spec = "%s %s" % (iname, imagespec)
+
+ else:
+ # define an element whose visual appearance is drawn using the
+ # Microsoft Visual Styles API which is responsible for the
+ # themed styles on Windows XP and Vista.
+ # Availability: Tk 8.6, Windows XP and Vista.
+ class_name, part_id = args[:2]
+ statemap = _format_mapdict({None: args[2:]})[1]
+ spec = "%s %s %s" % (class_name, part_id, statemap)
+
+ opts = _format_optdict(kw, script)
+
+ elif etype == "from": # clone an element
+ # it expects a themename and optionally an element to clone from,
+ # otherwise it will clone {} (empty element)
+ spec = args[0] # theme name
+ if len(args) > 1: # elementfrom specified
+ opts = (args[1], )
+
+ if script:
+ spec = '{%s}' % spec
+ opts = ' '.join(map(str, opts))
+
+ return spec, opts
+
+def _format_layoutlist(layout, indent=0, indent_size=2):
+ """Formats a layout list so we can pass the result to ttk::style
+ layout and ttk::style settings. Note that the layout doesn't has to
+ be a list necessarily.
+
+ E.g.:
+ [("Menubutton.background", None),
+ ("Menubutton.button", {"children":
+ [("Menubutton.focus", {"children":
+ [("Menubutton.padding", {"children":
+ [("Menubutton.label", {"side": "left", "expand": 1})]
+ })]
+ })]
+ }),
+ ("Menubutton.indicator", {"side": "right"})
+ ]
+
+ returns:
+
+ Menubutton.background
+ Menubutton.button -children {
+ Menubutton.focus -children {
+ Menubutton.padding -children {
+ Menubutton.label -side left -expand 1
+ }
+ }
+ }
+ Menubutton.indicator -side right"""
+ script = []
+
+ for layout_elem in layout:
+ elem, opts = layout_elem
+ opts = opts or {}
+ fopts = ' '.join(map(str, _format_optdict(opts, True, "children")))
+ head = "%s%s%s" % (' ' * indent, elem, (" %s" % fopts) if fopts else '')
+
+ if "children" in opts:
+ script.append(head + " -children {")
+ indent += indent_size
+ newscript, indent = _format_layoutlist(opts['children'], indent,
+ indent_size)
+ script.append(newscript)
+ indent -= indent_size
+ script.append('%s}' % (' ' * indent))
+ else:
+ script.append(head)
+
+ return '\n'.join(script), indent
+
+def _script_from_settings(settings):
+ """Returns an appropriate script, based on settings, according to
+ theme_settings definition to be used by theme_settings and
+ theme_create."""
+ script = []
+ # a script will be generated according to settings passed, which
+ # will then be evaluated by Tcl
+ for name, opts in settings.items():
+ # will format specific keys according to Tcl code
+ if opts.get('configure'): # format 'configure'
+ s = ' '.join(map(str, _format_optdict(opts['configure'], True)))
+ script.append("ttk::style configure %s %s;" % (name, s))
+
+ if opts.get('map'): # format 'map'
+ s = ' '.join(map(str, _format_mapdict(opts['map'], True)))
+ script.append("ttk::style map %s %s;" % (name, s))
+
+ if 'layout' in opts: # format 'layout' which may be empty
+ if not opts['layout']:
+ s = 'null' # could be any other word, but this one makes sense
+ else:
+ s, _ = _format_layoutlist(opts['layout'])
+ script.append("ttk::style layout %s {\n%s\n}" % (name, s))
+
+ if opts.get('element create'): # format 'element create'
+ eopts = opts['element create']
+ etype = eopts[0]
+
+ # find where args end, and where kwargs start
+ argc = 1 # etype was the first one
+ while argc < len(eopts) and not hasattr(eopts[argc], 'items'):
+ argc += 1
+
+ elemargs = eopts[1:argc]
+ elemkw = eopts[argc] if argc < len(eopts) and eopts[argc] else {}
+ spec, opts = _format_elemcreate(etype, True, *elemargs, **elemkw)
+
+ script.append("ttk::style element create %s %s %s %s" % (
+ name, etype, spec, opts))
+
+ 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."""
+ nval = []
+ for val in stuple:
+ typename = getattr(val, 'typename', None)
+ if typename is None:
+ nval.append(val)
+ else: # this is a Tcl object
+ val = str(val)
+ if typename == 'StateSpec':
+ val = val.split()
+ nval.append(val)
+
+ it = iter(nval)
+ return [_flatten(spec) for spec in zip(it, it)]
+
+def _list_from_layouttuple(ltuple):
+ """Construct a list from the tuple returned by ttk::layout, this is
+ somewhat the reverse of _format_layoutlist."""
+ res = []
+
+ indx = 0
+ while indx < len(ltuple):
+ name = ltuple[indx]
+ opts = {}
+ res.append((name, opts))
+ indx += 1
+
+ while indx < len(ltuple): # grab name's options
+ opt, val = ltuple[indx:indx + 2]
+ if not opt.startswith('-'): # found next name
+ break
+
+ opt = opt[1:] # remove the '-' from the option
+ indx += 2
+
+ if opt == 'children':
+ val = _list_from_layouttuple(val)
+
+ opts[opt] = val
+
+ return res
+
+def _val_or_dict(options, func, *args):
+ """Format options then call func with args and options and return
+ the appropriate result.
+
+ If no option is specified, a dict is returned. If a option is
+ specified with the None value, the value for that option is returned.
+ Otherwise, the function just sets the passed options and the caller
+ shouldn't be expecting a return value anyway."""
+ options = _format_optdict(options)
+ res = func(*(args + options))
+
+ if len(options) % 2: # option specified without a value, return its value
+ return res
+
+ return _dict_from_tcltuple(res)
+
+def _convert_stringval(value):
+ """Converts a value to, hopefully, a more appropriate Python object."""
+ value = str(value)
+ try:
+ value = int(value)
+ except (ValueError, TypeError):
+ pass
+
+ return value
+
+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
+
+ return adict
+
+
+class Style(object):
+ """Manipulate style database."""
+
+ _name = "ttk::style"
+
+ def __init__(self, master=None):
+ if master is None:
+ if tkinter._support_default_root:
+ master = tkinter._default_root or tkinter.Tk()
+ else:
+ raise RuntimeError("No master specified and tkinter is "
+ "configured to not support default master")
+
+ self.master = master
+ self.tk = self.master.tk
+
+
+ def configure(self, style, query_opt=None, **kw):
+ """Query or sets the default value of the specified option(s) in
+ style.
+
+ Each key in kw is an option and each value is either a string or
+ a sequence identifying the value for that option."""
+ if query_opt is not None:
+ kw[query_opt] = None
+ return _val_or_dict(kw, self.tk.call, self._name, "configure", style)
+
+
+ def map(self, style, query_opt=None, **kw):
+ """Query or sets dynamic values of the specified option(s) in
+ style.
+
+ Each key in kw is an option and each value should be a list or a
+ tuple (usually) containing statespecs grouped in tuples, or list,
+ 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.call(self._name, "map", style, '-%s' % query_opt))
+
+ return _dict_from_tcltuple(
+ self.tk.call(self._name, "map", style, *(_format_mapdict(kw))))
+
+
+ def lookup(self, style, option, state=None, default=None):
+ """Returns the value specified for option in style.
+
+ If state is specified it is expected to be a sequence of one
+ or more states. If the default argument is set, it is used as
+ a fallback value in case no specification for option is found."""
+ state = ' '.join(state) if state else ''
+
+ return self.tk.call(self._name, "lookup", style, '-%s' % option,
+ state, default)
+
+
+ def layout(self, style, layoutspec=None):
+ """Define the widget layout for given style. If layoutspec is
+ omitted, return the layout specification for given style.
+
+ layoutspec is expected to be a list or an object different than
+ None that evaluates to False if you want to "turn off" that style.
+ If it is a list (or tuple, or something else), each item should be
+ a tuple where the first item is the layout name and the second item
+ should have the format described below:
+
+ LAYOUTS
+
+ A layout can contain the value None, if takes no options, or
+ a dict of options specifying how to arrange the element.
+ The layout mechanism uses a simplified version of the pack
+ geometry manager: given an initial cavity, each element is
+ allocated a parcel. Valid options/values are:
+
+ side: whichside
+ Specifies which side of the cavity to place the
+ element; one of top, right, bottom or left. If
+ omitted, the element occupies the entire cavity.
+
+ sticky: nswe
+ Specifies where the element is placed inside its
+ allocated parcel.
+
+ children: [sublayout... ]
+ Specifies a list of elements to place inside the
+ element. Each element is a tuple (or other sequence)
+ where the first item is the layout name, and the other
+ is a LAYOUT."""
+ lspec = None
+ if layoutspec:
+ lspec = _format_layoutlist(layoutspec)[0]
+ elif layoutspec is not None: # will disable the layout ({}, '', etc)
+ lspec = "null" # could be any other word, but this may make sense
+ # when calling layout(style) later
+
+ return _list_from_layouttuple(
+ self.tk.call(self._name, "layout", style, lspec))
+
+
+ def element_create(self, elementname, etype, *args, **kw):
+ """Create a new element in the current theme of given etype."""
+ spec, opts = _format_elemcreate(etype, False, *args, **kw)
+ self.tk.call(self._name, "element", "create", elementname, etype,
+ spec, *opts)
+
+
+ def element_names(self):
+ """Returns the list of elements defined in the current theme."""
+ return self.tk.call(self._name, "element", "names")
+
+
+ def element_options(self, elementname):
+ """Return the list of elementname's options."""
+ return self.tk.call(self._name, "element", "options", elementname)
+
+
+ def theme_create(self, themename, parent=None, settings=None):
+ """Creates a new theme.
+
+ It is an error if themename already exists. If parent is
+ specified, the new theme will inherit styles, elements and
+ layouts from the specified parent theme. If settings are present,
+ they are expected to have the same syntax used for theme_settings."""
+ script = _script_from_settings(settings) if settings else ''
+
+ if parent:
+ self.tk.call(self._name, "theme", "create", themename,
+ "-parent", parent, "-settings", script)
+ else:
+ self.tk.call(self._name, "theme", "create", themename,
+ "-settings", script)
+
+
+ def theme_settings(self, themename, settings):
+ """Temporarily sets the current theme to themename, apply specified
+ settings and then restore the previous theme.
+
+ Each key in settings is a style and each value may contain the
+ keys 'configure', 'map', 'layout' and 'element create' and they
+ are expected to have the same format as specified by the methods
+ configure, map, layout and element_create respectively."""
+ script = _script_from_settings(settings)
+ self.tk.call(self._name, "theme", "settings", themename, script)
+
+
+ def theme_names(self):
+ """Returns a list of all known themes."""
+ return self.tk.call(self._name, "theme", "names")
+
+
+ def theme_use(self, themename=None):
+ """If themename is None, returns the theme in use, otherwise, set
+ the current theme to themename, refreshes all widgets and emits
+ a <<ThemeChanged>> event."""
+ if themename is None:
+ # Starting on Tk 8.6, checking this global is no longer needed
+ # since it allows doing self.tk.call(self._name, "theme", "use")
+ return self.tk.eval("return $ttk::currentTheme")
+
+ # using "ttk::setTheme" instead of "ttk::style theme use" causes
+ # the variable currentTheme to be updated, also, ttk::setTheme calls
+ # "ttk::style theme use" in order to change theme.
+ self.tk.call("ttk::setTheme", themename)
+
+
+class Widget(tkinter.Widget):
+ """Base class for Tk themed widgets."""
+
+ def __init__(self, master, widgetname, kw=None):
+ """Constructs a Ttk Widget with the parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, takefocus, style
+
+ SCROLLABLE WIDGET OPTIONS
+
+ xscrollcommand, yscrollcommand
+
+ LABEL WIDGET OPTIONS
+
+ text, textvariable, underline, image, compound, width
+
+ WIDGET STATES
+
+ active, disabled, focus, pressed, selected, background,
+ readonly, alternate, invalid
+ """
+ tkinter.Widget.__init__(self, master, widgetname, kw=kw)
+
+
+ def identify(self, x, y):
+ """Returns the name of the element at position x, y, or the empty
+ string if the point does not lie within any element.
+
+ x and y are pixel coordinates relative to the widget."""
+ return self.tk.call(self._w, "identify", x, y)
+
+
+ def instate(self, statespec, callback=None, *args, **kw):
+ """Test the widget's state.
+
+ If callback is not specified, returns True if the widget state
+ matches statespec and False otherwise. If callback is specified,
+ then it will be invoked with *args, **kw if the widget state
+ matches statespec. statespec is expected to be a sequence."""
+ ret = self.tk.call(self._w, "instate", ' '.join(statespec))
+ if ret and callback:
+ return callback(*args, **kw)
+
+ return bool(ret)
+
+
+ def state(self, statespec=None):
+ """Modify or inquire widget state.
+
+ Widget state is returned if statespec is None, otherwise it is
+ set according to the statespec flags and then a new state spec
+ is returned indicating which flags were changed. statespec is
+ expected to be a sequence."""
+ if statespec is not None:
+ statespec = ' '.join(statespec)
+
+ return self.tk.splitlist(str(self.tk.call(self._w, "state", statespec)))
+
+
+class Button(Widget):
+ """Ttk Button widget, displays a textual label and/or image, and
+ evaluates a command when pressed."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Button widget with the parent master.
+
+ STANDARD OPTIONS
+
+ class, compound, cursor, image, state, style, takefocus,
+ text, textvariable, underline, width
+
+ WIDGET-SPECIFIC OPTIONS
+
+ command, default, width
+ """
+ Widget.__init__(self, master, "ttk::button", kw)
+
+
+ def invoke(self):
+ """Invokes the command associated with the button."""
+ return self.tk.call(self._w, "invoke")
+
+
+class Checkbutton(Widget):
+ """Ttk Checkbutton widget which is either in on- or off-state."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Checkbutton widget with the parent master.
+
+ STANDARD OPTIONS
+
+ class, compound, cursor, image, state, style, takefocus,
+ text, textvariable, underline, width
+
+ WIDGET-SPECIFIC OPTIONS
+
+ command, offvalue, onvalue, variable
+ """
+ Widget.__init__(self, master, "ttk::checkbutton", kw)
+
+
+ def invoke(self):
+ """Toggles between the selected and deselected states and
+ invokes the associated command. If the widget is currently
+ selected, sets the option variable to the offvalue option
+ and deselects the widget; otherwise, sets the option variable
+ to the option onvalue.
+
+ Returns the result of the associated command."""
+ return self.tk.call(self._w, "invoke")
+
+
+class Entry(Widget, tkinter.Entry):
+ """Ttk Entry widget displays a one-line text string and allows that
+ string to be edited by the user."""
+
+ def __init__(self, master=None, widget=None, **kw):
+ """Constructs a Ttk Entry widget with the parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, style, takefocus, xscrollcommand
+
+ WIDGET-SPECIFIC OPTIONS
+
+ exportselection, invalidcommand, justify, show, state,
+ textvariable, validate, validatecommand, width
+
+ VALIDATION MODES
+
+ none, key, focus, focusin, focusout, all
+ """
+ Widget.__init__(self, master, widget or "ttk::entry", kw)
+
+
+ def bbox(self, index):
+ """Return a tuple of (x, y, width, height) which describes the
+ bounding box of the character given by index."""
+ return self.tk.call(self._w, "bbox", index)
+
+
+ def identify(self, x, y):
+ """Returns the name of the element at position x, y, or the
+ empty string if the coordinates are outside the window."""
+ return self.tk.call(self._w, "identify", x, y)
+
+
+ def validate(self):
+ """Force revalidation, independent of the conditions specified
+ by the validate option. Returns False if validation fails, True
+ if it succeeds. Sets or clears the invalid state accordingly."""
+ return bool(self.tk.call(self._w, "validate"))
+
+
+class Combobox(Entry):
+ """Ttk Combobox widget combines a text field with a pop-down list of
+ values."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Combobox widget with the parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, style, takefocus
+
+ WIDGET-SPECIFIC OPTIONS
+
+ exportselection, justify, height, postcommand, state,
+ textvariable, values, width
+ """
+ # The "values" option may need special formatting, so leave to
+ # _format_optdict the responsability to format it
+ if "values" in kw:
+ kw["values"] = _format_optdict({'v': kw["values"]})[1]
+
+ Entry.__init__(self, master, "ttk::combobox", **kw)
+
+
+ def __setitem__(self, item, value):
+ if item == "values":
+ value = _format_optdict({item: value})[1]
+
+ Entry.__setitem__(self, item, value)
+
+
+ def configure(self, cnf=None, **kw):
+ """Custom Combobox configure, created to properly format the values
+ option."""
+ if "values" in kw:
+ kw["values"] = _format_optdict({'v': kw["values"]})[1]
+
+ return Entry.configure(self, cnf, **kw)
+
+
+ def current(self, newindex=None):
+ """If newindex is supplied, sets the combobox value to the
+ element at position newindex in the list of values. Otherwise,
+ returns the index of the current value in the list of values
+ or -1 if the current value does not appear in the list."""
+ return self.tk.call(self._w, "current", newindex)
+
+
+ def set(self, value):
+ """Sets the value of the combobox to value."""
+ self.tk.call(self._w, "set", value)
+
+
+class Frame(Widget):
+ """Ttk Frame widget is a container, used to group other widgets
+ together."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Frame with parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, style, takefocus
+
+ WIDGET-SPECIFIC OPTIONS
+
+ borderwidth, relief, padding, width, height
+ """
+ Widget.__init__(self, master, "ttk::frame", kw)
+
+
+class Label(Widget):
+ """Ttk Label widget displays a textual label and/or image."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Label with parent master.
+
+ STANDARD OPTIONS
+
+ class, compound, cursor, image, style, takefocus, text,
+ textvariable, underline, width
+
+ WIDGET-SPECIFIC OPTIONS
+
+ anchor, background, font, foreground, justify, padding,
+ relief, text, wraplength
+ """
+ Widget.__init__(self, master, "ttk::label", kw)
+
+
+class Labelframe(Widget):
+ """Ttk Labelframe widget is a container used to group other widgets
+ together. It has an optional label, which may be a plain text string
+ or another widget."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Labelframe with parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, style, takefocus
+
+ WIDGET-SPECIFIC OPTIONS
+ labelanchor, text, underline, padding, labelwidget, width,
+ height
+ """
+ Widget.__init__(self, master, "ttk::labelframe", kw)
+
+LabelFrame = Labelframe # tkinter name compatibility
+
+
+class Menubutton(Widget):
+ """Ttk Menubutton widget displays a textual label and/or image, and
+ displays a menu when pressed."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Menubutton with parent master.
+
+ STANDARD OPTIONS
+
+ class, compound, cursor, image, state, style, takefocus,
+ text, textvariable, underline, width
+
+ WIDGET-SPECIFIC OPTIONS
+
+ direction, menu
+ """
+ Widget.__init__(self, master, "ttk::menubutton", kw)
+
+
+class Notebook(Widget):
+ """Ttk Notebook widget manages a collection of windows and displays
+ a single one at a time. Each child window is associated with a tab,
+ which the user may select to change the currently-displayed window."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Notebook with parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, style, takefocus
+
+ WIDGET-SPECIFIC OPTIONS
+
+ height, padding, width
+
+ TAB OPTIONS
+
+ state, sticky, padding, text, image, compound, underline
+
+ TAB IDENTIFIERS (tab_id)
+
+ The tab_id argument found in several methods may take any of
+ the following forms:
+
+ * An integer between zero and the number of tabs
+ * The name of a child window
+ * A positional specification of the form "@x,y", which
+ defines the tab
+ * The string "current", which identifies the
+ currently-selected tab
+ * The string "end", which returns the number of tabs (only
+ valid for method index)
+ """
+ Widget.__init__(self, master, "ttk::notebook", kw)
+
+
+ def add(self, child, **kw):
+ """Adds a new tab to the notebook.
+
+ If window is currently managed by the notebook but hidden, it is
+ restored to its previous position."""
+ self.tk.call(self._w, "add", child, *(_format_optdict(kw)))
+
+
+ def forget(self, tab_id):
+ """Removes the tab specified by tab_id, unmaps and unmanages the
+ associated window."""
+ self.tk.call(self._w, "forget", tab_id)
+
+
+ def hide(self, tab_id):
+ """Hides the tab specified by tab_id.
+
+ The tab will not be displayed, but the associated window remains
+ managed by the notebook and its configuration remembered. Hidden
+ tabs may be restored with the add command."""
+ self.tk.call(self._w, "hide", tab_id)
+
+
+ def identify(self, x, y):
+ """Returns the name of the tab element at position x, y, or the
+ empty string if none."""
+ return self.tk.call(self._w, "identify", x, y)
+
+
+ def index(self, tab_id):
+ """Returns the numeric index of the tab specified by tab_id, or
+ the total number of tabs if tab_id is the string "end"."""
+ return self.tk.call(self._w, "index", tab_id)
+
+
+ def insert(self, pos, child, **kw):
+ """Inserts a pane at the specified position.
+
+ pos is either the string end, an integer index, or the name of
+ a managed child. If child is already managed by the notebook,
+ moves it to the specified position."""
+ self.tk.call(self._w, "insert", pos, child, *(_format_optdict(kw)))
+
+
+ def select(self, tab_id=None):
+ """Selects the specified tab.
+
+ The associated child window will be displayed, and the
+ previously-selected window (if different) is unmapped. If tab_id
+ is omitted, returns the widget name of the currently selected
+ pane."""
+ return self.tk.call(self._w, "select", tab_id)
+
+
+ def tab(self, tab_id, option=None, **kw):
+ """Query or modify the options of the specific tab_id.
+
+ If kw is not given, returns a dict of the tab option values. If option
+ is specified, returns the value of that option. Otherwise, sets the
+ options to the corresponding values."""
+ if option is not None:
+ kw[option] = None
+ return _val_or_dict(kw, self.tk.call, self._w, "tab", tab_id)
+
+
+ def tabs(self):
+ """Returns a list of windows managed by the notebook."""
+ return self.tk.call(self._w, "tabs") or ()
+
+
+ def enable_traversal(self):
+ """Enable keyboard traversal for a toplevel window containing
+ this notebook.
+
+ This will extend the bindings for the toplevel window containing
+ this notebook as follows:
+
+ Control-Tab: selects the tab following the currently selected
+ one
+
+ Shift-Control-Tab: selects the tab preceding the currently
+ selected one
+
+ Alt-K: where K is the mnemonic (underlined) character of any
+ tab, will select that tab.
+
+ Multiple notebooks in a single toplevel may be enabled for
+ traversal, including nested notebooks. However, notebook traversal
+ only works properly if all panes are direct children of the
+ notebook."""
+ # The only, and good, difference I see is about mnemonics, which works
+ # after calling this method. Control-Tab and Shift-Control-Tab always
+ # works (here at least).
+ self.tk.call("ttk::notebook::enableTraversal", self._w)
+
+
+class Panedwindow(Widget, tkinter.PanedWindow):
+ """Ttk Panedwindow widget displays a number of subwindows, stacked
+ either vertically or horizontally."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Panedwindow with parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, style, takefocus
+
+ WIDGET-SPECIFIC OPTIONS
+
+ orient, width, height
+
+ PANE OPTIONS
+
+ weight
+ """
+ Widget.__init__(self, master, "ttk::panedwindow", kw)
+
+
+ forget = tkinter.PanedWindow.forget # overrides Pack.forget
+
+
+ def insert(self, pos, child, **kw):
+ """Inserts a pane at the specified positions.
+
+ pos is either the string end, and integer index, or the name
+ of a child. If child is already managed by the paned window,
+ moves it to the specified position."""
+ self.tk.call(self._w, "insert", pos, child, *(_format_optdict(kw)))
+
+
+ def pane(self, pane, option=None, **kw):
+ """Query or modify the options of the specified pane.
+
+ pane is either an integer index or the name of a managed subwindow.
+ If kw is not given, returns a dict of the pane option values. If
+ option is specified then the value for that option is returned.
+ Otherwise, sets the options to the correspoding values."""
+ if option is not None:
+ kw[option] = None
+ return _val_or_dict(kw, self.tk.call, self._w, "pane", pane)
+
+
+ def sashpos(self, index, newpos=None):
+ """If newpos is specified, sets the position of sash number index.
+
+ May adjust the positions of adjacent sashes to ensure that
+ positions are monotonically increasing. Sash positions are further
+ constrained to be between 0 and the total size of the widget.
+
+ Returns the new position of sash number index."""
+ return self.tk.call(self._w, "sashpos", index, newpos)
+
+PanedWindow = Panedwindow # tkinter name compatibility
+
+
+class Progressbar(Widget):
+ """Ttk Progressbar widget shows the status of a long-running
+ operation. They can operate in two modes: determinate mode shows the
+ amount completed relative to the total amount of work to be done, and
+ indeterminate mode provides an animated display to let the user know
+ that something is happening."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Progressbar with parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, style, takefocus
+
+ WIDGET-SPECIFIC OPTIONS
+
+ orient, length, mode, maximum, value, variable, phase
+ """
+ Widget.__init__(self, master, "ttk::progressbar", kw)
+
+
+ def start(self, interval=None):
+ """Begin autoincrement mode: schedules a recurring timer event
+ that calls method step every interval milliseconds.
+
+ interval defaults to 50 milliseconds (20 steps/second) if ommited."""
+ self.tk.call(self._w, "start", interval)
+
+
+ def step(self, amount=None):
+ """Increments the value option by amount.
+
+ amount defaults to 1.0 if omitted."""
+ self.tk.call(self._w, "step", amount)
+
+
+ def stop(self):
+ """Stop autoincrement mode: cancels any recurring timer event
+ initiated by start."""
+ self.tk.call(self._w, "stop")
+
+
+class Radiobutton(Widget):
+ """Ttk Radiobutton widgets are used in groups to show or change a
+ set of mutually-exclusive options."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Radiobutton with parent master.
+
+ STANDARD OPTIONS
+
+ class, compound, cursor, image, state, style, takefocus,
+ text, textvariable, underline, width
+
+ WIDGET-SPECIFIC OPTIONS
+
+ command, value, variable
+ """
+ Widget.__init__(self, master, "ttk::radiobutton", kw)
+
+
+ def invoke(self):
+ """Sets the option variable to the option value, selects the
+ widget, and invokes the associated command.
+
+ Returns the result of the command, or an empty string if
+ no command is specified."""
+ return self.tk.call(self._w, "invoke")
+
+
+class Scale(Widget, tkinter.Scale):
+ """Ttk Scale widget is typically used to control the numeric value of
+ a linked variable that varies uniformly over some range."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Scale with parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, style, takefocus
+
+ WIDGET-SPECIFIC OPTIONS
+
+ command, from, length, orient, to, value, variable
+ """
+ Widget.__init__(self, master, "ttk::scale", kw)
+
+
+ def configure(self, cnf=None, **kw):
+ """Modify or query scale options.
+
+ Setting a value for any of the "from", "from_" or "to" options
+ generates a <<RangeChanged>> event."""
+ if cnf:
+ kw.update(cnf)
+ Widget.configure(self, **kw)
+ if any(['from' in kw, 'from_' in kw, 'to' in kw]):
+ self.event_generate('<<RangeChanged>>')
+
+
+ def get(self, x=None, y=None):
+ """Get the current value of the value option, or the value
+ corresponding to the coordinates x, y if they are specified.
+
+ x and y are pixel coordinates relative to the scale widget
+ origin."""
+ return self.tk.call(self._w, 'get', x, y)
+
+
+class Scrollbar(Widget, tkinter.Scrollbar):
+ """Ttk Scrollbar controls the viewport of a scrollable widget."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Scrollbar with parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, style, takefocus
+
+ WIDGET-SPECIFIC OPTIONS
+
+ command, orient
+ """
+ Widget.__init__(self, master, "ttk::scrollbar", kw)
+
+
+class Separator(Widget):
+ """Ttk Separator widget displays a horizontal or vertical separator
+ bar."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Separator with parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, style, takefocus
+
+ WIDGET-SPECIFIC OPTIONS
+
+ orient
+ """
+ Widget.__init__(self, master, "ttk::separator", kw)
+
+
+class Sizegrip(Widget):
+ """Ttk Sizegrip allows the user to resize the containing toplevel
+ window by pressing and dragging the grip."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Sizegrip with parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, state, style, takefocus
+ """
+ Widget.__init__(self, master, "ttk::sizegrip", kw)
+
+
+class Treeview(Widget):
+ """Ttk Treeview widget displays a hierarchical collection of items.
+
+ Each item has a textual label, an optional image, and an optional list
+ of data values. The data values are displayed in successive columns
+ after the tree label."""
+
+ def __init__(self, master=None, **kw):
+ """Construct a Ttk Treeview with parent master.
+
+ STANDARD OPTIONS
+
+ class, cursor, style, takefocus, xscrollcommand,
+ yscrollcommand
+
+ WIDGET-SPECIFIC OPTIONS
+
+ columns, displaycolumns, height, padding, selectmode, show
+
+ ITEM OPTIONS
+
+ text, image, values, open, tags
+
+ TAG OPTIONS
+
+ foreground, background, font, image
+ """
+ Widget.__init__(self, master, "ttk::treeview", kw)
+
+
+ def bbox(self, item, column=None):
+ """Returns the bounding box (relative to the treeview widget's
+ window) of the specified item in the form x y width height.
+
+ If column is specified, returns the bounding box of that cell.
+ If the item is not visible (i.e., if it is a descendant of a
+ closed item or is scrolled offscreen), returns an empty string."""
+ return self.tk.call(self._w, "bbox", item, column)
+
+
+ def get_children(self, item=None):
+ """Returns a tuple of children belonging to item.
+
+ If item is not specified, returns root children."""
+ return self.tk.call(self._w, "children", item or '') or ()
+
+
+ def set_children(self, item, *newchildren):
+ """Replaces item's child with newchildren.
+
+ Children present in item that are not present in newchildren
+ are detached from tree. No items in newchildren may be an
+ ancestor of item."""
+ self.tk.call(self._w, "children", item, newchildren)
+
+
+ def column(self, column, option=None, **kw):
+ """Query or modify the options for the specified column.
+
+ If kw is not given, returns a dict of the column option values. If
+ option is specified then the value for that option is returned.
+ Otherwise, sets the options to the corresponding values."""
+ if option is not None:
+ kw[option] = None
+ return _val_or_dict(kw, self.tk.call, self._w, "column", column)
+
+
+ def delete(self, *items):
+ """Delete all specified items and all their descendants. The root
+ item may not be deleted."""
+ self.tk.call(self._w, "delete", items)
+
+
+ def detach(self, *items):
+ """Unlinks all of the specified items from the tree.
+
+ The items and all of their descendants are still present, and may
+ be reinserted at another point in the tree, but will not be
+ displayed. The root item may not be detached."""
+ self.tk.call(self._w, "detach", items)
+
+
+ def exists(self, item):
+ """Returns True if the specified item is present in the three,
+ False otherwise."""
+ return bool(self.tk.call(self._w, "exists", item))
+
+
+ def focus(self, item=None):
+ """If item is specified, sets the focus item to item. Otherwise,
+ returns the current focus item, or '' if there is none."""
+ return self.tk.call(self._w, "focus", item)
+
+
+ def heading(self, column, option=None, **kw):
+ """Query or modify the heading options for the specified column.
+
+ If kw is not given, returns a dict of the heading option values. If
+ option is specified then the value for that option is returned.
+ Otherwise, sets the options to the corresponding values.
+
+ Valid options/values are:
+ text: text
+ The text to display in the column heading
+ image: image_name
+ Specifies an image to display to the right of the column
+ heading
+ anchor: anchor
+ Specifies how the heading text should be aligned. One of
+ the standard Tk anchor values
+ command: callback
+ A callback to be invoked when the heading label is
+ pressed.
+
+ To configure the tree column heading, call this with column = "#0" """
+ cmd = kw.get('command')
+ if cmd and not isinstance(cmd, str):
+ # callback not registered yet, do it now
+ kw['command'] = self.master.register(cmd, self._substitute)
+
+ if option is not None:
+ kw[option] = None
+
+ return _val_or_dict(kw, self.tk.call, self._w, 'heading', column)
+
+
+ def identify(self, component, x, y):
+ """Returns a description of the specified component under the
+ point given by x and y, or the empty string if no such component
+ is present at that position."""
+ return self.tk.call(self._w, "identify", component, x, y)
+
+
+ def identify_row(self, y):
+ """Returns the item ID of the item at position y."""
+ return self.identify("row", 0, y)
+
+
+ def identify_column(self, x):
+ """Returns the data column identifier of the cell at position x.
+
+ The tree column has ID #0."""
+ return self.identify("column", x, 0)
+
+
+ def identify_region(self, x, y):
+ """Returns one of:
+
+ heading: Tree heading area.
+ separator: Space between two columns headings;
+ tree: The tree area.
+ cell: A data cell.
+
+ * Availability: Tk 8.6"""
+ return self.identify("region", x, y)
+
+
+ def identify_element(self, x, y):
+ """Returns the element at position x, y.
+
+ * Availability: Tk 8.6"""
+ return self.identify("element", x, y)
+
+
+ def index(self, item):
+ """Returns the integer index of item within its parent's list
+ of children."""
+ return self.tk.call(self._w, "index", item)
+
+
+ def insert(self, parent, index, iid=None, **kw):
+ """Creates a new item and return the item identifier of the newly
+ created item.
+
+ parent is the item ID of the parent item, or the empty string
+ to create a new top-level item. index is an integer, or the value
+ end, specifying where in the list of parent's children to insert
+ the new item. If index is less than or equal to zero, the new node
+ is inserted at the beginning, if index is greater than or equal to
+ the current number of children, it is inserted at the end. If iid
+ is specified, it is used as the item identifier, iid must not
+ already exist in the tree. Otherwise, a new unique identifier
+ is generated."""
+ opts = _format_optdict(kw)
+ if iid:
+ res = self.tk.call(self._w, "insert", parent, index,
+ "-id", iid, *opts)
+ else:
+ res = self.tk.call(self._w, "insert", parent, index, *opts)
+
+ return res
+
+
+ def item(self, item, option=None, **kw):
+ """Query or modify the options for the specified item.
+
+ If no options are given, a dict with options/values for the item
+ is returned. If option is specified then the value for that option
+ is returned. Otherwise, sets the options to the corresponding
+ values as given by kw."""
+ if option is not None:
+ kw[option] = None
+ return _val_or_dict(kw, self.tk.call, self._w, "item", item)
+
+
+ def move(self, item, parent, index):
+ """Moves item to position index in parent's list of children.
+
+ It is illegal to move an item under one of its descendants. If
+ index is less than or equal to zero, item is moved to the
+ beginning, if greater than or equal to the number of children,
+ it is moved to the end. If item was detached it is reattached."""
+ self.tk.call(self._w, "move", item, parent, index)
+
+ reattach = move # A sensible method name for reattaching detached items
+
+
+ def next(self, item):
+ """Returns the identifier of item's next sibling, or '' if item
+ is the last child of its parent."""
+ return self.tk.call(self._w, "next", item)
+
+
+ def parent(self, item):
+ """Returns the ID of the parent of item, or '' if item is at the
+ top level of the hierarchy."""
+ return self.tk.call(self._w, "parent", item)
+
+
+ def prev(self, item):
+ """Returns the identifier of item's previous sibling, or '' if
+ item is the first child of its parent."""
+ return self.tk.call(self._w, "prev", item)
+
+
+ def see(self, item):
+ """Ensure that item is visible.
+
+ Sets all of item's ancestors open option to True, and scrolls
+ the widget if necessary so that item is within the visible
+ portion of the tree."""
+ self.tk.call(self._w, "see", item)
+
+
+ def selection(self, selop=None, items=None):
+ """If selop is not specified, returns selected items."""
+ return self.tk.call(self._w, "selection", selop, items)
+
+
+ def selection_set(self, items):
+ """items becomes the new selection."""
+ self.selection("set", items)
+
+
+ def selection_add(self, items):
+ """Add items to the selection."""
+ self.selection("add", items)
+
+
+ def selection_remove(self, items):
+ """Remove items from the selection."""
+ self.selection("remove", items)
+
+
+ def selection_toggle(self, items):
+ """Toggle the selection state of each item in items."""
+ self.selection("toggle", items)
+
+
+ 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
+ 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(res, False)
+ else:
+ return res
+
+
+ def tag_bind(self, tagname, sequence=None, callback=None):
+ """Bind a callback for the given event sequence to the tag tagname.
+ When an event is delivered to an item, the callbacks for each
+ of the item's tags option are called."""
+ self._bind((self._w, "tag", "bind", tagname), sequence, callback, add=0)
+
+
+ def tag_configure(self, tagname, option=None, **kw):
+ """Query or modify the options for the specified tagname.
+
+ If kw is not given, returns a dict of the option settings for tagname.
+ If option is specified, returns the value for that option for the
+ specified tagname. Otherwise, sets the options to the corresponding
+ values for the given tagname."""
+ if option is not None:
+ kw[option] = None
+ return _val_or_dict(kw, self.tk.call, self._w, "tag", "configure",
+ tagname)
+
+
+ def tag_has(self, tagname, item=None):
+ """If item is specified, returns 1 or 0 depending on whether the
+ specified item has the given tagname. Otherwise, returns a list of
+ all items which have the specified tag.
+
+ * Availability: Tk 8.6"""
+ return self.tk.call(self._w, "tag", "has", tagname, item)
+
+
+ def xview(self, *args):
+ """Query or modify horizontal position of the treeview."""
+ return self.tk.call(self._w, "xview", *args)
+
+
+ def yview(self, *args):
+ """Query or modify vertical position of the treeview."""
+ return self.tk.call(self._w, "yview", *args)
+
+
+# Extensions
+
+class LabeledScale(Frame):
+ """A Ttk Scale widget with a Ttk Label widget indicating its
+ current value.
+
+ The Ttk Scale can be accessed through instance.scale, and Ttk Label
+ can be accessed through instance.label"""
+
+ def __init__(self, master=None, variable=None, from_=0, to=10, **kw):
+ """Construct an horizontal LabeledScale with parent master, a
+ variable to be associated with the Ttk Scale widget and its range.
+ If variable is not specified, a tkinter.IntVar is created.
+
+ WIDGET-SPECIFIC OPTIONS
+
+ compound: 'top' or 'bottom'
+ Specifies how to display the label relative to the scale.
+ Defaults to 'top'.
+ """
+ self._label_top = kw.pop('compound', 'top') == 'top'
+
+ Frame.__init__(self, master, **kw)
+ self._variable = variable or tkinter.IntVar(master)
+ self._variable.set(from_)
+ self._last_valid = from_
+
+ self.label = Label(self)
+ self.scale = Scale(self, variable=self._variable, from_=from_, to=to)
+ self.scale.bind('<<RangeChanged>>', self._adjust)
+
+ # position scale and label according to the compound option
+ scale_side = 'bottom' if self._label_top else 'top'
+ label_side = 'top' if scale_side == 'bottom' else 'bottom'
+ self.scale.pack(side=scale_side, fill='x')
+ tmp = Label(self).pack(side=label_side) # place holder
+ self.label.place(anchor='n' if label_side == 'top' else 's')
+
+ # update the label as scale or variable changes
+ self.__tracecb = self._variable.trace_variable('w', self._adjust)
+ self.bind('<Configure>', self._adjust)
+ self.bind('<Map>', self._adjust)
+
+
+ def destroy(self):
+ """Destroy this widget and possibly its associated variable."""
+ try:
+ self._variable.trace_vdelete('w', self.__tracecb)
+ except AttributeError:
+ # widget has been destroyed already
+ pass
+ else:
+ del self._variable
+ Frame.destroy(self)
+
+
+ def _adjust(self, *args):
+ """Adjust the label position according to the scale."""
+ def adjust_label():
+ self.update_idletasks() # "force" scale redraw
+
+ x, y = self.scale.coords()
+ if self._label_top:
+ y = self.scale.winfo_y() - self.label.winfo_reqheight()
+ else:
+ y = self.scale.winfo_reqheight() + self.label.winfo_reqheight()
+
+ self.label.place_configure(x=x, y=y)
+
+ from_, to = self.scale['from'], self.scale['to']
+ if to < from_:
+ from_, to = to, from_
+ newval = self._variable.get()
+ if not from_ <= newval <= to:
+ # value outside range, set value back to the last valid one
+ self.value = self._last_valid
+ return
+
+ self._last_valid = newval
+ self.label['text'] = newval
+ self.after_idle(adjust_label)
+
+
+ def _get_value(self):
+ """Return current scale value."""
+ return self._variable.get()
+
+
+ def _set_value(self, val):
+ """Set new scale value."""
+ self._variable.set(val)
+
+
+ value = property(_get_value, _set_value)
+
+
+class OptionMenu(Menubutton):
+ """Themed OptionMenu, based after tkinter's OptionMenu, which allows
+ the user to select a value from a menu."""
+
+ def __init__(self, master, variable, default=None, *values, **kwargs):
+ """Construct a themed OptionMenu widget with master as the parent,
+ the resource textvariable set to variable, the initially selected
+ value specified by the default parameter, the menu values given by
+ *values and additional keywords.
+
+ WIDGET-SPECIFIC OPTIONS
+
+ style: stylename
+ Menubutton style.
+ direction: 'above', 'below', 'left', 'right', or 'flush'
+ Menubutton direction.
+ command: callback
+ A callback that will be invoked after selecting an item.
+ """
+ kw = {'textvariable': variable, 'style': kwargs.pop('style', None),
+ 'direction': kwargs.pop('direction', None)}
+ Menubutton.__init__(self, master, **kw)
+ self['menu'] = tkinter.Menu(self, tearoff=False)
+
+ self._variable = variable
+ self._callback = kwargs.pop('command', None)
+ if kwargs:
+ raise tkinter.TclError('unknown option -%s' % (
+ next(iter(kwargs.keys()))))
+
+ self.set_menu(default, *values)
+
+
+ def __getitem__(self, item):
+ if item == 'menu':
+ return self.nametowidget(Menubutton.__getitem__(self, item))
+
+ return Menubutton.__getitem__(self, item)
+
+
+ def set_menu(self, default=None, *values):
+ """Build a new menu of radiobuttons with *values and optionally
+ a default value."""
+ menu = self['menu']
+ menu.delete(0, 'end')
+ for val in values:
+ menu.add_radiobutton(label=val,
+ command=tkinter._setit(self._variable, val, self._callback))
+
+ if default:
+ self._variable.set(default)
+
+
+ def destroy(self):
+ """Destroy this widget and its associated variable."""
+ del self._variable
+ Menubutton.destroy(self)