Skip to content

Commit c39cfe0

Browse files
author
guilherme.polo
committed
Added the ttk module. See issue #2983: Ttk support for Tkinter.
git-svn-id: http://svn.python.org/projects/python/trunk@69050 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent 7efb1e8 commit c39cfe0

14 files changed

+5089
-1
lines changed

Doc/library/tk.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ Graphical User Interfaces with Tk
1212

1313
Tk/Tcl has long been an integral part of Python. It provides a robust and
1414
platform independent windowing toolkit, that is available to Python programmers
15-
using the :mod:`Tkinter` module, and its extension, the :mod:`Tix` module.
15+
using the :mod:`Tkinter` module, and its extensions, the :mod:`Tix` and
16+
the :mod:`ttk` modules.
1617

1718
The :mod:`Tkinter` module is a thin object-oriented layer on top of Tcl/Tk. To
1819
use :mod:`Tkinter`, you don't need to write Tcl code, but you will need to
@@ -32,6 +33,7 @@ alternatives, see the :ref:`other-gui-packages` section.
3233
.. toctree::
3334

3435
tkinter.rst
36+
ttk.rst
3537
tix.rst
3638
scrolledtext.rst
3739
turtle.rst

Doc/library/ttk.rst

+1,399
Large diffs are not rendered by default.

Lib/lib-tk/test/README

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Writing new tests
2+
=================
3+
4+
Precaution
5+
----------
6+
7+
New tests should always use only one Tk window at once, like all the
8+
current tests do. This means that you have to destroy the current window
9+
before creating another one, and clean up after the test. The motivation
10+
behind this is that some tests may depend on having its window focused
11+
while it is running to work properly, and it may be hard to force focus
12+
on your window across platforms (right now only test_traversal at
13+
test_ttk.test_widgets.NotebookTest depends on this).
14+

Lib/lib-tk/test/runtktests.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""
2+
Use this module to get and run all tk tests.
3+
4+
Tkinter tests should live in a package inside the directory where this file
5+
lives, like test_tkinter.
6+
Extensions also should live in packages following the same rule as above.
7+
"""
8+
9+
import os
10+
import sys
11+
import unittest
12+
import test.test_support
13+
14+
this_dir_path = os.path.abspath(os.path.dirname(__file__))
15+
16+
def is_package(path):
17+
for name in os.listdir(path):
18+
if name in ('__init__.py', '__init__.pyc', '__init.pyo'):
19+
return True
20+
return False
21+
22+
def get_tests_modules(basepath=this_dir_path, gui=True):
23+
"""This will import and yield modules whose names start with test_
24+
and are inside packages found in the path starting at basepath."""
25+
py_ext = '.py'
26+
27+
for dirpath, dirnames, filenames in os.walk(basepath):
28+
for dirname in list(dirnames):
29+
if dirname[0] == '.':
30+
dirnames.remove(dirname)
31+
32+
if is_package(dirpath) and filenames:
33+
pkg_name = dirpath[len(basepath) + len(os.sep):].replace('/', '.')
34+
filenames = filter(
35+
lambda x: x.startswith('test_') and x.endswith(py_ext),
36+
filenames)
37+
38+
for name in filenames:
39+
try:
40+
yield __import__(
41+
"%s.%s" % (pkg_name, name[:-len(py_ext)]),
42+
fromlist=['']
43+
)
44+
except test.test_support.ResourceDenied:
45+
if gui:
46+
raise
47+
48+
def get_tests(text=True, gui=True):
49+
"""Yield all the tests in the modules found by get_tests_modules.
50+
51+
If nogui is True, only tests that do not require a GUI will be
52+
returned."""
53+
attrs = []
54+
if text:
55+
attrs.append('tests_nogui')
56+
if gui:
57+
attrs.append('tests_gui')
58+
for module in get_tests_modules(gui=gui):
59+
for attr in attrs:
60+
for test in getattr(module, attr, ()):
61+
yield test
62+
63+
if __name__ == "__main__":
64+
test.test_support.use_resources = ['gui']
65+
test.test_support.run_unittest(*get_tests())

Lib/lib-tk/test/test_ttk/__init__.py

Whitespace-only changes.

Lib/lib-tk/test/test_ttk/support.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Tkinter
2+
3+
def get_tk_root():
4+
try:
5+
root = Tkinter._default_root
6+
except AttributeError:
7+
# it is possible to disable default root in Tkinter, although
8+
# I haven't seen people doing it (but apparently someone did it
9+
# here).
10+
root = None
11+
12+
if root is None:
13+
# create a new master only if there isn't one already
14+
root = Tkinter.Tk()
15+
16+
return root
17+
18+
19+
def simulate_mouse_click(widget, x, y):
20+
"""Generate proper events to click at the x, y position (tries to act
21+
like an X server)."""
22+
widget.event_generate('<Enter>', x=0, y=0)
23+
widget.event_generate('<Motion>', x=x, y=y)
24+
widget.event_generate('<ButtonPress-1>', x=x, y=y)
25+
widget.event_generate('<ButtonRelease-1>', x=x, y=y)
+266
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import sys
2+
import unittest
3+
import Tkinter
4+
import ttk
5+
from test.test_support import requires, run_unittest
6+
7+
import support
8+
9+
requires('gui')
10+
11+
class LabeledScaleTest(unittest.TestCase):
12+
13+
def test_widget_destroy(self):
14+
# automatically created variable
15+
x = ttk.LabeledScale()
16+
var = x._variable._name
17+
x.destroy()
18+
self.failUnlessRaises(Tkinter.TclError, x.tk.globalgetvar, var)
19+
20+
# manually created variable
21+
myvar = Tkinter.DoubleVar()
22+
name = myvar._name
23+
x = ttk.LabeledScale(variable=myvar)
24+
x.destroy()
25+
self.failUnlessEqual(x.tk.globalgetvar(name), myvar.get())
26+
del myvar
27+
self.failUnlessRaises(Tkinter.TclError, x.tk.globalgetvar, name)
28+
29+
# checking that the tracing callback is properly removed
30+
myvar = Tkinter.IntVar()
31+
# LabeledScale will start tracing myvar
32+
x = ttk.LabeledScale(variable=myvar)
33+
x.destroy()
34+
# Unless the tracing callback was removed, creating a new
35+
# LabeledScale with the same var will cause an error now. This
36+
# happens because the variable will be set to (possibly) a new
37+
# value which causes the tracing callback to be called and then
38+
# it tries calling instance attributes not yet defined.
39+
ttk.LabeledScale(variable=myvar)
40+
if hasattr(sys, 'last_type'):
41+
self.failIf(sys.last_type == Tkinter.TclError)
42+
43+
44+
def test_initialization(self):
45+
# master passing
46+
x = ttk.LabeledScale()
47+
self.failUnlessEqual(x.master, Tkinter._default_root)
48+
x.destroy()
49+
master = Tkinter.Frame()
50+
x = ttk.LabeledScale(master)
51+
self.failUnlessEqual(x.master, master)
52+
x.destroy()
53+
54+
# variable initialization/passing
55+
passed_expected = ((2.5, 2), ('0', 0), (0, 0), (10, 10),
56+
(-1, -1), (sys.maxint + 1, sys.maxint + 1))
57+
for pair in passed_expected:
58+
x = ttk.LabeledScale(from_=pair[0])
59+
self.failUnlessEqual(x.value, pair[1])
60+
x.destroy()
61+
x = ttk.LabeledScale(from_='2.5')
62+
self.failUnlessRaises(ValueError, x._variable.get)
63+
x.destroy()
64+
x = ttk.LabeledScale(from_=None)
65+
self.failUnlessRaises(ValueError, x._variable.get)
66+
x.destroy()
67+
# variable should have its default value set to the from_ value
68+
myvar = Tkinter.DoubleVar(value=20)
69+
x = ttk.LabeledScale(variable=myvar)
70+
self.failUnlessEqual(x.value, 0)
71+
x.destroy()
72+
# check that it is really using a DoubleVar
73+
x = ttk.LabeledScale(variable=myvar, from_=0.5)
74+
self.failUnlessEqual(x.value, 0.5)
75+
self.failUnlessEqual(x._variable._name, myvar._name)
76+
x.destroy()
77+
78+
# widget positionment
79+
def check_positions(scale, scale_pos, label, label_pos):
80+
self.failUnlessEqual(scale.pack_info()['side'], scale_pos)
81+
self.failUnlessEqual(label.place_info()['anchor'], label_pos)
82+
x = ttk.LabeledScale(compound='top')
83+
check_positions(x.scale, 'bottom', x.label, 'n')
84+
x.destroy()
85+
x = ttk.LabeledScale(compound='bottom')
86+
check_positions(x.scale, 'top', x.label, 's')
87+
x.destroy()
88+
x = ttk.LabeledScale(compound='unknown') # invert default positions
89+
check_positions(x.scale, 'top', x.label, 's')
90+
x.destroy()
91+
x = ttk.LabeledScale() # take default positions
92+
check_positions(x.scale, 'bottom', x.label, 'n')
93+
x.destroy()
94+
95+
# extra, and invalid, kwargs
96+
self.failUnlessRaises(Tkinter.TclError, ttk.LabeledScale, a='b')
97+
98+
99+
def test_horizontal_range(self):
100+
lscale = ttk.LabeledScale(from_=0, to=10)
101+
lscale.pack()
102+
lscale.wait_visibility()
103+
lscale.update()
104+
105+
linfo_1 = lscale.label.place_info()
106+
prev_xcoord = lscale.scale.coords()[0]
107+
self.failUnlessEqual(prev_xcoord, int(linfo_1['x']))
108+
# change range to: from -5 to 5. This should change the x coord of
109+
# the scale widget, since 0 is at the middle of the new
110+
# range.
111+
lscale.scale.configure(from_=-5, to=5)
112+
# The following update is needed since the test doesn't use mainloop,
113+
# at the same time this shouldn't affect test outcome
114+
lscale.update()
115+
curr_xcoord = lscale.scale.coords()[0]
116+
self.failUnless(prev_xcoord != curr_xcoord)
117+
# the label widget should have been repositioned too
118+
linfo_2 = lscale.label.place_info()
119+
self.failUnlessEqual(lscale.label['text'], 0)
120+
self.failUnlessEqual(curr_xcoord, int(linfo_2['x']))
121+
# change the range back
122+
lscale.scale.configure(from_=0, to=10)
123+
self.failUnless(prev_xcoord != curr_xcoord)
124+
self.failUnlessEqual(prev_xcoord, int(linfo_1['x']))
125+
126+
lscale.destroy()
127+
128+
129+
def test_variable_change(self):
130+
x = ttk.LabeledScale()
131+
x.pack()
132+
x.wait_visibility()
133+
x.update()
134+
135+
curr_xcoord = x.scale.coords()[0]
136+
newval = x.value + 1
137+
x.value = newval
138+
# The following update is needed since the test doesn't use mainloop,
139+
# at the same time this shouldn't affect test outcome
140+
x.update()
141+
self.failUnlessEqual(x.label['text'], newval)
142+
self.failUnless(x.scale.coords()[0] > curr_xcoord)
143+
self.failUnlessEqual(x.scale.coords()[0],
144+
int(x.label.place_info()['x']))
145+
146+
# value outside range
147+
x.value = x.scale['to'] + 1 # no changes shouldn't happen
148+
x.update()
149+
self.failUnlessEqual(x.label['text'], newval)
150+
self.failUnlessEqual(x.scale.coords()[0],
151+
int(x.label.place_info()['x']))
152+
153+
x.destroy()
154+
155+
156+
def test_resize(self):
157+
x = ttk.LabeledScale()
158+
x.pack(expand=True, fill='both')
159+
x.wait_visibility()
160+
x.update()
161+
162+
width, height = x.master.winfo_width(), x.master.winfo_height()
163+
width, height = width * 2, height * 2
164+
165+
x.value = 3
166+
x.update()
167+
x.master.wm_geometry("%dx%d" % (width, height))
168+
self.failUnlessEqual(int(x.label.place_info()['x']),
169+
x.scale.coords()[0])
170+
171+
x.master.wm_geometry("%dx%d" % (width, height))
172+
x.destroy()
173+
174+
175+
class OptionMenuTest(unittest.TestCase):
176+
177+
def setUp(self):
178+
self.root = support.get_tk_root()
179+
self.textvar = Tkinter.StringVar(self.root)
180+
181+
def tearDown(self):
182+
del self.textvar
183+
self.root.destroy()
184+
185+
186+
def test_widget_destroy(self):
187+
var = Tkinter.StringVar()
188+
optmenu = ttk.OptionMenu(None, var)
189+
name = var._name
190+
optmenu.update_idletasks()
191+
optmenu.destroy()
192+
self.failUnlessEqual(optmenu.tk.globalgetvar(name), var.get())
193+
del var
194+
self.failUnlessRaises(Tkinter.TclError, optmenu.tk.globalgetvar, name)
195+
196+
197+
def test_initialization(self):
198+
self.failUnlessRaises(Tkinter.TclError,
199+
ttk.OptionMenu, None, self.textvar, invalid='thing')
200+
201+
optmenu = ttk.OptionMenu(None, self.textvar, 'b', 'a', 'b')
202+
self.failUnlessEqual(optmenu._variable.get(), 'b')
203+
204+
self.failUnless(optmenu['menu'])
205+
self.failUnless(optmenu['textvariable'])
206+
207+
optmenu.destroy()
208+
209+
210+
def test_menu(self):
211+
items = ('a', 'b', 'c')
212+
default = 'a'
213+
optmenu = ttk.OptionMenu(None, self.textvar, default, *items)
214+
found_default = False
215+
for i in range(len(items)):
216+
value = optmenu['menu'].entrycget(i, 'value')
217+
self.failUnlessEqual(value, items[i])
218+
if value == default:
219+
found_default = True
220+
self.failUnless(found_default)
221+
optmenu.destroy()
222+
223+
# default shouldn't be in menu if it is not part of values
224+
default = 'd'
225+
optmenu = ttk.OptionMenu(None, self.textvar, default, *items)
226+
curr = None
227+
i = 0
228+
while True:
229+
last, curr = curr, optmenu['menu'].entryconfigure(i, 'value')
230+
if last == curr:
231+
# no more menu entries
232+
break
233+
self.failIf(curr == default)
234+
i += 1
235+
self.failUnlessEqual(i, len(items))
236+
237+
# check that variable is updated correctly
238+
optmenu.pack()
239+
optmenu.wait_visibility()
240+
optmenu['menu'].invoke(0)
241+
self.failUnlessEqual(optmenu._variable.get(), items[0])
242+
243+
# changing to an invalid index shouldn't change the variable
244+
self.failUnlessRaises(Tkinter.TclError, optmenu['menu'].invoke, -1)
245+
self.failUnlessEqual(optmenu._variable.get(), items[0])
246+
247+
optmenu.destroy()
248+
249+
# specifying a callback
250+
success = []
251+
def cb_test(item):
252+
self.failUnlessEqual(item, items[1])
253+
success.append(True)
254+
optmenu = ttk.OptionMenu(None, self.textvar, 'a', command=cb_test,
255+
*items)
256+
optmenu['menu'].invoke(1)
257+
if not success:
258+
self.fail("Menu callback not invoked")
259+
260+
optmenu.destroy()
261+
262+
263+
tests_gui = (LabeledScaleTest, OptionMenuTest)
264+
265+
if __name__ == "__main__":
266+
run_unittest(*tests_gui)

0 commit comments

Comments
 (0)