blob: 43da7269564118e49ba4f367f4d34ca3c8fb620d [file] [log] [blame]
Guilherme Polocda93aa2009-01-28 13:09:03 +00001"""Ttk wrapper.
2
3This module provides classes to allow using Tk themed widget set.
4
5Ttk is based on a revised and enhanced version of
6TIP #48 (http://tip.tcl.tk/48) specified style engine.
7
8Its basic idea is to separate, to the extent possible, the code
9implementing a widget's behavior from the code implementing its
10appearance. Widget class bindings are primarily responsible for
11maintaining the widget state and invoking callbacks, all aspects
12of the widgets appearance lies at Themes.
13"""
14
15__version__ = "0.3.1"
16
17__author__ = "Guilherme Polo <ggpolo@gmail.com>"
18
19__all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label",
20 "Labelframe", "LabelFrame", "Menubutton", "Notebook", "Panedwindow",
21 "PanedWindow", "Progressbar", "Radiobutton", "Scale", "Scrollbar",
22 "Separator", "Sizegrip", "Style", "Treeview",
23 # Extensions
24 "LabeledScale", "OptionMenu",
25 # functions
26 "tclobjs_to_py"]
27
28import Tkinter
29
30_flatten = Tkinter._flatten
31
32# Verify if Tk is new enough to not need Tile checking
33_REQUIRE_TILE = True if Tkinter.TkVersion < 8.5 else False
34
35def _loadttk(loadtk):
36 # This extends the default Tkinter.Tk._loadtk method so we can be
37 # sure that ttk is available for use, or not.
38 def _wrapper(self):
39 loadtk(self)
40
41 if _REQUIRE_TILE:
42 import os
43 tilelib = os.environ.get('TILE_LIBRARY')
44 if tilelib:
45 # append custom tile path to the the list of directories that
46 # Tcl uses when attempting to resolve packages with the package
47 # command
48 self.tk.eval('global auto_path; '
49 'lappend auto_path {%s}' % tilelib)
50 self.tk.eval('package require tile') # TclError may be raised here
51
52 return _wrapper
53
54# Store the original Tkinter.Tk._loadtk before replacing it just in case
55# someone wants to restore it.
56__loadtk__ = Tkinter.Tk._loadtk
57Tkinter.Tk._loadtk = _loadttk(Tkinter.Tk._loadtk)
58
59
60def _format_optdict(optdict, script=False, ignore=None):
61 """Formats optdict to a tuple to pass it to tk.call.
62
63 E.g. (script=False):
64 {'foreground': 'blue', 'padding': [1, 2, 3, 4]} returns:
65 ('-foreground', 'blue', '-padding', '1 2 3 4')"""
66 format = "%s" if not script else "{%s}"
67
68 opts = []
69 for opt, value in optdict.iteritems():
70 if ignore and opt in ignore:
71 continue
72
73 if isinstance(value, (list, tuple)):
74 v = []
75 for val in value:
76 if isinstance(val, basestring):
77 v.append(unicode(val) if val else '{}')
78 else:
79 v.append(str(val))
80
81 # format v according to the script option, but also check for
82 # space in any value in v in order to group them correctly
83 value = format % ' '.join(
84 ('{%s}' if ' ' in val else '%s') % val for val in v)
85
86 if script and value == '':
87 value = '{}' # empty string in Python is equivalent to {} in Tcl
88
89 opts.append(("-%s" % opt, value))
90
91 # Remember: _flatten skips over None
92 return _flatten(opts)
93
94def _format_mapdict(mapdict, script=False):
95 """Formats mapdict to pass it to tk.call.
96
97 E.g. (script=False):
98 {'expand': [('active', 'selected', 'grey'), ('focus', [1, 2, 3, 4])]}
99
100 returns:
101
102 ('-expand', '{active selected} grey focus {1, 2, 3, 4}')"""
103 # if caller passes a Tcl script to tk.call, all the values need to
104 # be grouped into words (arguments to a command in Tcl dialect)
105 format = "%s" if not script else "{%s}"
106
107 opts = []
108 for opt, value in mapdict.iteritems():
109
110 opt_val = []
111 # each value in mapdict is expected to be a sequence, where each item
112 # is another sequence containing a state (or several) and a value
113 for statespec in value:
114 state, val = statespec[:-1], statespec[-1]
115
116 if len(state) > 1: # group multiple states
117 state = "{%s}" % ' '.join(state)
118 else: # single state
119 # if it is empty (something that evaluates to False), then
120 # format it to Tcl code to denote the "normal" state
121 state = state[0] or '{}'
122
123 if isinstance(val, (list, tuple)): # val needs to be grouped
124 val = "{%s}" % ' '.join(map(str, val))
125
126 opt_val.append("%s %s" % (state, val))
127
128 opts.append(("-%s" % opt, format % ' '.join(opt_val)))
129
130 return _flatten(opts)
131
132def _format_elemcreate(etype, script=False, *args, **kw):
133 """Formats args and kw according to the given element factory etype."""
134 spec = None
135 opts = ()
136 if etype in ("image", "vsapi"):
137 if etype == "image": # define an element based on an image
138 # first arg should be the default image name
139 iname = args[0]
140 # next args, if any, are statespec/value pairs which is almost
141 # a mapdict, but we just need the value
142 imagespec = _format_mapdict({None: args[1:]})[1]
143 spec = "%s %s" % (iname, imagespec)
144
145 else:
146 # define an element whose visual appearance is drawn using the
147 # Microsoft Visual Styles API which is responsible for the
148 # themed styles on Windows XP and Vista.
149 # Availability: Tk 8.6, Windows XP and Vista.
150 class_name, part_id = args[:2]
151 statemap = _format_mapdict({None: args[2:]})[1]
152 spec = "%s %s %s" % (class_name, part_id, statemap)
153
154 opts = _format_optdict(kw, script)
155
156 elif etype == "from": # clone an element
157 # it expects a themename and optionally an element to clone from,
158 # otherwise it will clone {} (empty element)
159 spec = args[0] # theme name
160 if len(args) > 1: # elementfrom specified
161 opts = (args[1], )
162
163 if script:
164 spec = '{%s}' % spec
165 opts = ' '.join(map(str, opts))
166
167 return spec, opts
168
169def _format_layoutlist(layout, indent=0, indent_size=2):
170 """Formats a layout list so we can pass the result to ttk::style
171 layout and ttk::style settings. Note that the layout doesn't has to
172 be a list necessarily.
173
174 E.g.:
175 [("Menubutton.background", None),
176 ("Menubutton.button", {"children":
177 [("Menubutton.focus", {"children":
178 [("Menubutton.padding", {"children":
179 [("Menubutton.label", {"side": "left", "expand": 1})]
180 })]
181 })]
182 }),
183 ("Menubutton.indicator", {"side": "right"})
184 ]
185
186 returns:
187
188 Menubutton.background
189 Menubutton.button -children {
190 Menubutton.focus -children {
191 Menubutton.padding -children {
192 Menubutton.label -side left -expand 1
193 }
194 }
195 }
196 Menubutton.indicator -side right"""
197 script = []
198
199 for layout_elem in layout:
200 elem, opts = layout_elem
201 opts = opts or {}
202 fopts = ' '.join(map(str, _format_optdict(opts, True, "children")))
203 head = "%s%s%s" % (' ' * indent, elem, (" %s" % fopts) if fopts else '')
204
205 if "children" in opts:
206 script.append(head + " -children {")
207 indent += indent_size
208 newscript, indent = _format_layoutlist(opts['children'], indent,
209 indent_size)
210 script.append(newscript)
211 indent -= indent_size
212 script.append('%s}' % (' ' * indent))
213 else:
214 script.append(head)
215
216 return '\n'.join(script), indent
217
218def _script_from_settings(settings):
219 """Returns an appropriate script, based on settings, according to
220 theme_settings definition to be used by theme_settings and
221 theme_create."""
222 script = []
223 # a script will be generated according to settings passed, which
224 # will then be evaluated by Tcl
225 for name, opts in settings.iteritems():
226 # will format specific keys according to Tcl code
227 if opts.get('configure'): # format 'configure'
228 s = ' '.join(map(unicode, _format_optdict(opts['configure'], True)))
229 script.append("ttk::style configure %s %s;" % (name, s))
230
231 if opts.get('map'): # format 'map'
232 s = ' '.join(map(unicode, _format_mapdict(opts['map'], True)))
233 script.append("ttk::style map %s %s;" % (name, s))
234
235 if 'layout' in opts: # format 'layout' which may be empty
236 if not opts['layout']:
237 s = 'null' # could be any other word, but this one makes sense
238 else:
239 s, _ = _format_layoutlist(opts['layout'])
240 script.append("ttk::style layout %s {\n%s\n}" % (name, s))
241
242 if opts.get('element create'): # format 'element create'
243 eopts = opts['element create']
244 etype = eopts[0]
245
246 # find where args end, and where kwargs start
247 argc = 1 # etype was the first one
248 while argc < len(eopts) and not hasattr(eopts[argc], 'iteritems'):
249 argc += 1
250
251 elemargs = eopts[1:argc]
252 elemkw = eopts[argc] if argc < len(eopts) and eopts[argc] else {}
253 spec, opts = _format_elemcreate(etype, True, *elemargs, **elemkw)
254
255 script.append("ttk::style element create %s %s %s %s" % (
256 name, etype, spec, opts))
257
258 return '\n'.join(script)
259
260def _dict_from_tcltuple(ttuple, cut_minus=True):
261 """Break tuple in pairs, format it properly, then build the return
262 dict. If cut_minus is True, the supposed '-' prefixing options will
263 be removed.
264
265 ttuple is expected to contain an even number of elements."""
266 opt_start = 1 if cut_minus else 0
267
268 retdict = {}
269 it = iter(ttuple)
270 for opt, val in zip(it, it):
271 retdict[str(opt)[opt_start:]] = val
272
273 return tclobjs_to_py(retdict)
274
275def _list_from_statespec(stuple):
276 """Construct a list from the given statespec tuple according to the
277 accepted statespec accepted by _format_mapdict."""
278 nval = []
279 for val in stuple:
280 typename = getattr(val, 'typename', None)
281 if typename is None:
282 nval.append(val)
283 else: # this is a Tcl object
284 val = str(val)
285 if typename == 'StateSpec':
286 val = val.split()
287 nval.append(val)
288
289 it = iter(nval)
290 return [_flatten(spec) for spec in zip(it, it)]
291
292def _list_from_layouttuple(ltuple):
293 """Construct a list from the tuple returned by ttk::layout, this is
294 somewhat the reverse of _format_layoutlist."""
295 res = []
296
297 indx = 0
298 while indx < len(ltuple):
299 name = ltuple[indx]
300 opts = {}
301 res.append((name, opts))
302 indx += 1
303
304 while indx < len(ltuple): # grab name's options
305 opt, val = ltuple[indx:indx + 2]
306 if not opt.startswith('-'): # found next name
307 break
308
309 opt = opt[1:] # remove the '-' from the option
310 indx += 2
311
312 if opt == 'children':
313 val = _list_from_layouttuple(val)
314
315 opts[opt] = val
316
317 return res
318
319def _val_or_dict(options, func, *args):
320 """Format options then call func with args and options and return
321 the appropriate result.
322
323 If no option is specified, a dict is returned. If a option is
324 specified with the None value, the value for that option is returned.
325 Otherwise, the function just sets the passed options and the caller
326 shouldn't be expecting a return value anyway."""
327 options = _format_optdict(options)
328 res = func(*(args + options))
329
330 if len(options) % 2: # option specified without a value, return its value
331 return res
332
333 return _dict_from_tcltuple(res)
334
335def _convert_stringval(value):
336 """Converts a value to, hopefully, a more appropriate Python object."""
337 value = unicode(value)
338 try:
339 value = int(value)
340 except (ValueError, TypeError):
341 pass
342
343 return value
344
345def tclobjs_to_py(adict):
346 """Returns adict with its values converted from Tcl objects to Python
347 objects."""
348 for opt, val in adict.iteritems():
349 if val and hasattr(val, '__len__') and not isinstance(val, basestring):
350 if getattr(val[0], 'typename', None) == 'StateSpec':
351 val = _list_from_statespec(val)
352 else:
353 val = map(_convert_stringval, val)
354
355 elif hasattr(val, 'typename'): # some other (single) Tcl object
356 val = _convert_stringval(val)
357
358 adict[opt] = val
359
360 return adict
361
362
363class Style(object):
364 """Manipulate style database."""
365
366 _name = "ttk::style"
367
368 def __init__(self, master=None):
369 if master is None:
370 if Tkinter._support_default_root:
371 master = Tkinter._default_root or Tkinter.Tk()
372 else:
373 raise RuntimeError("No master specified and Tkinter is "
374 "configured to not support default master")
375
376 self.master = master
377 self.tk = self.master.tk
378
379
380 def configure(self, style, query_opt=None, **kw):
381 """Query or sets the default value of the specified option(s) in
382 style.
383
384 Each key in kw is an option and each value is either a string or
385 a sequence identifying the value for that option."""
386 if query_opt is not None:
387 kw[query_opt] = None
388 return _val_or_dict(kw, self.tk.call, self._name, "configure", style)
389
390
391 def map(self, style, query_opt=None, **kw):
392 """Query or sets dynamic values of the specified option(s) in
393 style.
394
395 Each key in kw is an option and each value should be a list or a
396 tuple (usually) containing statespecs grouped in tuples, or list,
397 or something else of your preference. A statespec is compound of
398 one or more states and then a value."""
399 if query_opt is not None:
400 return _list_from_statespec(
401 self.tk.call(self._name, "map", style, '-%s' % query_opt))
402
403 return _dict_from_tcltuple(
404 self.tk.call(self._name, "map", style, *(_format_mapdict(kw))))
405
406
407 def lookup(self, style, option, state=None, default=None):
408 """Returns the value specified for option in style.
409
410 If state is specified it is expected to be a sequence of one
411 or more states. If the default argument is set, it is used as
412 a fallback value in case no specification for option is found."""
413 state = ' '.join(state) if state else ''
414
415 return self.tk.call(self._name, "lookup", style, '-%s' % option,
416 state, default)
417
418
419 def layout(self, style, layoutspec=None):
420 """Define the widget layout for given style. If layoutspec is
421 omitted, return the layout specification for given style.
422
423 layoutspec is expected to be a list or an object different than
424 None that evaluates to False if you want to "turn off" that style.
425 If it is a list (or tuple, or something else), each item should be
426 a tuple where the first item is the layout name and the second item
427 should have the format described below:
428
429 LAYOUTS
430
431 A layout can contain the value None, if takes no options, or
432 a dict of options specifying how to arrange the element.
433 The layout mechanism uses a simplified version of the pack
434 geometry manager: given an initial cavity, each element is
435 allocated a parcel. Valid options/values are:
436
437 side: whichside
438 Specifies which side of the cavity to place the
439 element; one of top, right, bottom or left. If
440 omitted, the element occupies the entire cavity.
441
442 sticky: nswe
443 Specifies where the element is placed inside its
444 allocated parcel.
445
446 children: [sublayout... ]
447 Specifies a list of elements to place inside the
448 element. Each element is a tuple (or other sequence)
449 where the first item is the layout name, and the other
450 is a LAYOUT."""
451 lspec = None
452 if layoutspec:
453 lspec = _format_layoutlist(layoutspec)[0]
454 elif layoutspec is not None: # will disable the layout ({}, '', etc)
455 lspec = "null" # could be any other word, but this may make sense
456 # when calling layout(style) later
457
458 return _list_from_layouttuple(
459 self.tk.call(self._name, "layout", style, lspec))
460
461
462 def element_create(self, elementname, etype, *args, **kw):
463 """Create a new element in the current theme of given etype."""
464 spec, opts = _format_elemcreate(etype, False, *args, **kw)
465 self.tk.call(self._name, "element", "create", elementname, etype,
466 spec, *opts)
467
468
469 def element_names(self):
470 """Returns the list of elements defined in the current theme."""
471 return self.tk.call(self._name, "element", "names")
472
473
474 def element_options(self, elementname):
475 """Return the list of elementname's options."""
476 return self.tk.call(self._name, "element", "options", elementname)
477
478
479 def theme_create(self, themename, parent=None, settings=None):
480 """Creates a new theme.
481
482 It is an error if themename already exists. If parent is
483 specified, the new theme will inherit styles, elements and
484 layouts from the specified parent theme. If settings are present,
485 they are expected to have the same syntax used for theme_settings."""
486 script = _script_from_settings(settings) if settings else ''
487
488 if parent:
489 self.tk.call(self._name, "theme", "create", themename,
490 "-parent", parent, "-settings", script)
491 else:
492 self.tk.call(self._name, "theme", "create", themename,
493 "-settings", script)
494
495
496 def theme_settings(self, themename, settings):
497 """Temporarily sets the current theme to themename, apply specified
498 settings and then restore the previous theme.
499
500 Each key in settings is a style and each value may contain the
501 keys 'configure', 'map', 'layout' and 'element create' and they
502 are expected to have the same format as specified by the methods
503 configure, map, layout and element_create respectively."""
504 script = _script_from_settings(settings)
505 self.tk.call(self._name, "theme", "settings", themename, script)
506
507
508 def theme_names(self):
509 """Returns a list of all known themes."""
510 return self.tk.call(self._name, "theme", "names")
511
512
513 def theme_use(self, themename=None):
514 """If themename is None, returns the theme in use, otherwise, set
515 the current theme to themename, refreshes all widgets and emits
516 a <<ThemeChanged>> event."""
517 if themename is None:
518 # Starting on Tk 8.6, checking this global is no longer needed
519 # since it allows doing self.tk.call(self._name, "theme", "use")
520 return self.tk.eval("return $ttk::currentTheme")
521
522 # using "ttk::setTheme" instead of "ttk::style theme use" causes
523 # the variable currentTheme to be updated, also, ttk::setTheme calls
524 # "ttk::style theme use" in order to change theme.
525 self.tk.call("ttk::setTheme", themename)
526
527
528class Widget(Tkinter.Widget):
529 """Base class for Tk themed widgets."""
530
531 def __init__(self, master, widgetname, kw=None):
532 """Constructs a Ttk Widget with the parent master.
533
534 STANDARD OPTIONS
535
536 class, cursor, takefocus, style
537
538 SCROLLABLE WIDGET OPTIONS
539
540 xscrollcommand, yscrollcommand
541
542 LABEL WIDGET OPTIONS
543
544 text, textvariable, underline, image, compound, width
545
546 WIDGET STATES
547
548 active, disabled, focus, pressed, selected, background,
549 readonly, alternate, invalid
550 """
551 Tkinter.Widget.__init__(self, master, widgetname, kw=kw)
552
553
554 def identify(self, x, y):
555 """Returns the name of the element at position x, y, or the empty
556 string if the point does not lie within any element.
557
558 x and y are pixel coordinates relative to the widget."""
559 return self.tk.call(self._w, "identify", x, y)
560
561
562 def instate(self, statespec, callback=None, *args, **kw):
563 """Test the widget's state.
564
565 If callback is not specified, returns True if the widget state
566 matches statespec and False otherwise. If callback is specified,
567 then it will be invoked with *args, **kw if the widget state
568 matches statespec. statespec is expected to be a sequence."""
569 ret = self.tk.call(self._w, "instate", ' '.join(statespec))
570 if ret and callback:
571 return callback(*args, **kw)
572
573 return bool(ret)
574
575
576 def state(self, statespec=None):
577 """Modify or inquire widget state.
578
579 Widget state is returned if statespec is None, otherwise it is
580 set according to the statespec flags and then a new state spec
581 is returned indicating which flags were changed. statespec is
582 expected to be a sequence."""
583 if statespec is not None:
584 statespec = ' '.join(statespec)
585
586 return self.tk.splitlist(str(self.tk.call(self._w, "state", statespec)))
587
588
589class Button(Widget):
590 """Ttk Button widget, displays a textual label and/or image, and
591 evaluates a command when pressed."""
592
593 def __init__(self, master=None, **kw):
594 """Construct a Ttk Button widget with the parent master.
595
596 STANDARD OPTIONS
597
598 class, compound, cursor, image, state, style, takefocus,
599 text, textvariable, underline, width
600
601 WIDGET-SPECIFIC OPTIONS
602
603 command, default, width
604 """
605 Widget.__init__(self, master, "ttk::button", kw)
606
607
608 def invoke(self):
609 """Invokes the command associated with the button."""
610 return self.tk.call(self._w, "invoke")
611
612
613class Checkbutton(Widget):
614 """Ttk Checkbutton widget which is either in on- or off-state."""
615
616 def __init__(self, master=None, **kw):
617 """Construct a Ttk Checkbutton widget with the parent master.
618
619 STANDARD OPTIONS
620
621 class, compound, cursor, image, state, style, takefocus,
622 text, textvariable, underline, width
623
624 WIDGET-SPECIFIC OPTIONS
625
626 command, offvalue, onvalue, variable
627 """
628 Widget.__init__(self, master, "ttk::checkbutton", kw)
629
630
631 def invoke(self):
632 """Toggles between the selected and deselected states and
633 invokes the associated command. If the widget is currently
634 selected, sets the option variable to the offvalue option
635 and deselects the widget; otherwise, sets the option variable
636 to the option onvalue.
637
638 Returns the result of the associated command."""
639 return self.tk.call(self._w, "invoke")
640
641
642class Entry(Widget, Tkinter.Entry):
643 """Ttk Entry widget displays a one-line text string and allows that
644 string to be edited by the user."""
645
646 def __init__(self, master=None, widget=None, **kw):
647 """Constructs a Ttk Entry widget with the parent master.
648
649 STANDARD OPTIONS
650
651 class, cursor, style, takefocus, xscrollcommand
652
653 WIDGET-SPECIFIC OPTIONS
654
655 exportselection, invalidcommand, justify, show, state,
656 textvariable, validate, validatecommand, width
657
658 VALIDATION MODES
659
660 none, key, focus, focusin, focusout, all
661 """
662 Widget.__init__(self, master, widget or "ttk::entry", kw)
663
664
665 def bbox(self, index):
666 """Return a tuple of (x, y, width, height) which describes the
667 bounding box of the character given by index."""
668 return self.tk.call(self._w, "bbox", index)
669
670
671 def identify(self, x, y):
672 """Returns the name of the element at position x, y, or the
673 empty string if the coordinates are outside the window."""
674 return self.tk.call(self._w, "identify", x, y)
675
676
677 def validate(self):
678 """Force revalidation, independent of the conditions specified
679 by the validate option. Returns False if validation fails, True
680 if it succeeds. Sets or clears the invalid state accordingly."""
681 return bool(self.tk.call(self._w, "validate"))
682
683
684class Combobox(Entry):
685 """Ttk Combobox widget combines a text field with a pop-down list of
686 values."""
687
688 def __init__(self, master=None, **kw):
689 """Construct a Ttk Combobox widget with the parent master.
690
691 STANDARD OPTIONS
692
693 class, cursor, style, takefocus
694
695 WIDGET-SPECIFIC OPTIONS
696
697 exportselection, justify, height, postcommand, state,
698 textvariable, values, width
699 """
700 # The "values" option may need special formatting, so leave to
701 # _format_optdict the responsability to format it
702 if "values" in kw:
703 kw["values"] = _format_optdict({'v': kw["values"]})[1]
704
705 Entry.__init__(self, master, "ttk::combobox", **kw)
706
707
708 def __setitem__(self, item, value):
709 if item == "values":
710 value = _format_optdict({item: value})[1]
711
712 Entry.__setitem__(self, item, value)
713
714
715 def configure(self, cnf=None, **kw):
716 """Custom Combobox configure, created to properly format the values
717 option."""
718 if "values" in kw:
719 kw["values"] = _format_optdict({'v': kw["values"]})[1]
720
721 return Entry.configure(self, cnf, **kw)
722
723
724 def current(self, newindex=None):
725 """If newindex is supplied, sets the combobox value to the
726 element at position newindex in the list of values. Otherwise,
727 returns the index of the current value in the list of values
728 or -1 if the current value does not appear in the list."""
729 return self.tk.call(self._w, "current", newindex)
730
731
732 def set(self, value):
733 """Sets the value of the combobox to value."""
734 self.tk.call(self._w, "set", value)
735
736
737class Frame(Widget):
738 """Ttk Frame widget is a container, used to group other widgets
739 together."""
740
741 def __init__(self, master=None, **kw):
742 """Construct a Ttk Frame with parent master.
743
744 STANDARD OPTIONS
745
746 class, cursor, style, takefocus
747
748 WIDGET-SPECIFIC OPTIONS
749
750 borderwidth, relief, padding, width, height
751 """
752 Widget.__init__(self, master, "ttk::frame", kw)
753
754
755class Label(Widget):
756 """Ttk Label widget displays a textual label and/or image."""
757
758 def __init__(self, master=None, **kw):
759 """Construct a Ttk Label with parent master.
760
761 STANDARD OPTIONS
762
763 class, compound, cursor, image, style, takefocus, text,
764 textvariable, underline, width
765
766 WIDGET-SPECIFIC OPTIONS
767
768 anchor, background, font, foreground, justify, padding,
769 relief, text, wraplength
770 """
771 Widget.__init__(self, master, "ttk::label", kw)
772
773
774class Labelframe(Widget):
775 """Ttk Labelframe widget is a container used to group other widgets
776 together. It has an optional label, which may be a plain text string
777 or another widget."""
778
779 def __init__(self, master=None, **kw):
780 """Construct a Ttk Labelframe with parent master.
781
782 STANDARD OPTIONS
783
784 class, cursor, style, takefocus
785
786 WIDGET-SPECIFIC OPTIONS
787 labelanchor, text, underline, padding, labelwidget, width,
788 height
789 """
790 Widget.__init__(self, master, "ttk::labelframe", kw)
791
792LabelFrame = Labelframe # Tkinter name compatibility
793
794
795class Menubutton(Widget):
796 """Ttk Menubutton widget displays a textual label and/or image, and
797 displays a menu when pressed."""
798
799 def __init__(self, master=None, **kw):
800 """Construct a Ttk Menubutton with parent master.
801
802 STANDARD OPTIONS
803
804 class, compound, cursor, image, state, style, takefocus,
805 text, textvariable, underline, width
806
807 WIDGET-SPECIFIC OPTIONS
808
809 direction, menu
810 """
811 Widget.__init__(self, master, "ttk::menubutton", kw)
812
813
814class Notebook(Widget):
815 """Ttk Notebook widget manages a collection of windows and displays
816 a single one at a time. Each child window is associated with a tab,
817 which the user may select to change the currently-displayed window."""
818
819 def __init__(self, master=None, **kw):
820 """Construct a Ttk Notebook with parent master.
821
822 STANDARD OPTIONS
823
824 class, cursor, style, takefocus
825
826 WIDGET-SPECIFIC OPTIONS
827
828 height, padding, width
829
830 TAB OPTIONS
831
832 state, sticky, padding, text, image, compound, underline
833
834 TAB IDENTIFIERS (tab_id)
835
836 The tab_id argument found in several methods may take any of
837 the following forms:
838
839 * An integer between zero and the number of tabs
840 * The name of a child window
841 * A positional specification of the form "@x,y", which
842 defines the tab
843 * The string "current", which identifies the
844 currently-selected tab
845 * The string "end", which returns the number of tabs (only
846 valid for method index)
847 """
848 Widget.__init__(self, master, "ttk::notebook", kw)
849
850
851 def add(self, child, **kw):
852 """Adds a new tab to the notebook.
853
854 If window is currently managed by the notebook but hidden, it is
855 restored to its previous position."""
856 self.tk.call(self._w, "add", child, *(_format_optdict(kw)))
857
858
859 def forget(self, tab_id):
860 """Removes the tab specified by tab_id, unmaps and unmanages the
861 associated window."""
862 self.tk.call(self._w, "forget", tab_id)
863
864
865 def hide(self, tab_id):
866 """Hides the tab specified by tab_id.
867
868 The tab will not be displayed, but the associated window remains
869 managed by the notebook and its configuration remembered. Hidden
870 tabs may be restored with the add command."""
871 self.tk.call(self._w, "hide", tab_id)
872
873
874 def identify(self, x, y):
875 """Returns the name of the tab element at position x, y, or the
876 empty string if none."""
877 return self.tk.call(self._w, "identify", x, y)
878
879
880 def index(self, tab_id):
881 """Returns the numeric index of the tab specified by tab_id, or
882 the total number of tabs if tab_id is the string "end"."""
883 return self.tk.call(self._w, "index", tab_id)
884
885
886 def insert(self, pos, child, **kw):
887 """Inserts a pane at the specified position.
888
889 pos is either the string end, an integer index, or the name of
890 a managed child. If child is already managed by the notebook,
891 moves it to the specified position."""
892 self.tk.call(self._w, "insert", pos, child, *(_format_optdict(kw)))
893
894
895 def select(self, tab_id=None):
896 """Selects the specified tab.
897
898 The associated child window will be displayed, and the
899 previously-selected window (if different) is unmapped. If tab_id
900 is omitted, returns the widget name of the currently selected
901 pane."""
902 return self.tk.call(self._w, "select", tab_id)
903
904
905 def tab(self, tab_id, option=None, **kw):
906 """Query or modify the options of the specific tab_id.
907
908 If kw is not given, returns a dict of the tab option values. If option
909 is specified, returns the value of that option. Otherwise, sets the
910 options to the corresponding values."""
911 if option is not None:
912 kw[option] = None
913 return _val_or_dict(kw, self.tk.call, self._w, "tab", tab_id)
914
915
916 def tabs(self):
917 """Returns a list of windows managed by the notebook."""
918 return self.tk.call(self._w, "tabs") or ()
919
920
921 def enable_traversal(self):
922 """Enable keyboard traversal for a toplevel window containing
923 this notebook.
924
925 This will extend the bindings for the toplevel window containing
926 this notebook as follows:
927
928 Control-Tab: selects the tab following the currently selected
929 one
930
931 Shift-Control-Tab: selects the tab preceding the currently
932 selected one
933
934 Alt-K: where K is the mnemonic (underlined) character of any
935 tab, will select that tab.
936
937 Multiple notebooks in a single toplevel may be enabled for
938 traversal, including nested notebooks. However, notebook traversal
939 only works properly if all panes are direct children of the
940 notebook."""
941 # The only, and good, difference I see is about mnemonics, which works
942 # after calling this method. Control-Tab and Shift-Control-Tab always
943 # works (here at least).
944 self.tk.call("ttk::notebook::enableTraversal", self._w)
945
946
947class Panedwindow(Widget, Tkinter.PanedWindow):
948 """Ttk Panedwindow widget displays a number of subwindows, stacked
949 either vertically or horizontally."""
950
951 def __init__(self, master=None, **kw):
952 """Construct a Ttk Panedwindow with parent master.
953
954 STANDARD OPTIONS
955
956 class, cursor, style, takefocus
957
958 WIDGET-SPECIFIC OPTIONS
959
960 orient, width, height
961
962 PANE OPTIONS
963
964 weight
965 """
966 Widget.__init__(self, master, "ttk::panedwindow", kw)
967
968
969 forget = Tkinter.PanedWindow.forget # overrides Pack.forget
970
971
972 def insert(self, pos, child, **kw):
973 """Inserts a pane at the specified positions.
974
975 pos is either the string end, and integer index, or the name
976 of a child. If child is already managed by the paned window,
977 moves it to the specified position."""
978 self.tk.call(self._w, "insert", pos, child, *(_format_optdict(kw)))
979
980
981 def pane(self, pane, option=None, **kw):
982 """Query or modify the options of the specified pane.
983
984 pane is either an integer index or the name of a managed subwindow.
985 If kw is not given, returns a dict of the pane option values. If
986 option is specified then the value for that option is returned.
987 Otherwise, sets the options to the correspoding values."""
988 if option is not None:
989 kw[option] = None
990 return _val_or_dict(kw, self.tk.call, self._w, "pane", pane)
991
992
993 def sashpos(self, index, newpos=None):
994 """If newpos is specified, sets the position of sash number index.
995
996 May adjust the positions of adjacent sashes to ensure that
997 positions are monotonically increasing. Sash positions are further
998 constrained to be between 0 and the total size of the widget.
999
1000 Returns the new position of sash number index."""
1001 return self.tk.call(self._w, "sashpos", index, newpos)
1002
1003PanedWindow = Panedwindow # Tkinter name compatibility
1004
1005
1006class Progressbar(Widget):
1007 """Ttk Progressbar widget shows the status of a long-running
1008 operation. They can operate in two modes: determinate mode shows the
1009 amount completed relative to the total amount of work to be done, and
1010 indeterminate mode provides an animated display to let the user know
1011 that something is happening."""
1012
1013 def __init__(self, master=None, **kw):
1014 """Construct a Ttk Progressbar with parent master.
1015
1016 STANDARD OPTIONS
1017
1018 class, cursor, style, takefocus
1019
1020 WIDGET-SPECIFIC OPTIONS
1021
1022 orient, length, mode, maximum, value, variable, phase
1023 """
1024 Widget.__init__(self, master, "ttk::progressbar", kw)
1025
1026
1027 def start(self, interval=None):
1028 """Begin autoincrement mode: schedules a recurring timer event
1029 that calls method step every interval milliseconds.
1030
1031 interval defaults to 50 milliseconds (20 steps/second) if ommited."""
1032 self.tk.call(self._w, "start", interval)
1033
1034
1035 def step(self, amount=None):
1036 """Increments the value option by amount.
1037
1038 amount defaults to 1.0 if omitted."""
1039 self.tk.call(self._w, "step", amount)
1040
1041
1042 def stop(self):
1043 """Stop autoincrement mode: cancels any recurring timer event
1044 initiated by start."""
1045 self.tk.call(self._w, "stop")
1046
1047
1048class Radiobutton(Widget):
1049 """Ttk Radiobutton widgets are used in groups to show or change a
1050 set of mutually-exclusive options."""
1051
1052 def __init__(self, master=None, **kw):
1053 """Construct a Ttk Radiobutton with parent master.
1054
1055 STANDARD OPTIONS
1056
1057 class, compound, cursor, image, state, style, takefocus,
1058 text, textvariable, underline, width
1059
1060 WIDGET-SPECIFIC OPTIONS
1061
1062 command, value, variable
1063 """
1064 Widget.__init__(self, master, "ttk::radiobutton", kw)
1065
1066
1067 def invoke(self):
1068 """Sets the option variable to the option value, selects the
1069 widget, and invokes the associated command.
1070
1071 Returns the result of the command, or an empty string if
1072 no command is specified."""
1073 return self.tk.call(self._w, "invoke")
1074
1075
1076class Scale(Widget, Tkinter.Scale):
1077 """Ttk Scale widget is typically used to control the numeric value of
1078 a linked variable that varies uniformly over some range."""
1079
1080 def __init__(self, master=None, **kw):
1081 """Construct a Ttk Scale with parent master.
1082
1083 STANDARD OPTIONS
1084
1085 class, cursor, style, takefocus
1086
1087 WIDGET-SPECIFIC OPTIONS
1088
1089 command, from, length, orient, to, value, variable
1090 """
1091 Widget.__init__(self, master, "ttk::scale", kw)
1092
1093
1094 def configure(self, cnf=None, **kw):
1095 """Modify or query scale options.
1096
1097 Setting a value for any of the "from", "from_" or "to" options
1098 generates a <<RangeChanged>> event."""
1099 if cnf:
1100 kw.update(cnf)
1101 Widget.configure(self, **kw)
1102 if any(['from' in kw, 'from_' in kw, 'to' in kw]):
1103 self.event_generate('<<RangeChanged>>')
1104
1105
1106 def get(self, x=None, y=None):
1107 """Get the current value of the value option, or the value
1108 corresponding to the coordinates x, y if they are specified.
1109
1110 x and y are pixel coordinates relative to the scale widget
1111 origin."""
1112 return self.tk.call(self._w, 'get', x, y)
1113
1114
1115class Scrollbar(Widget, Tkinter.Scrollbar):
1116 """Ttk Scrollbar controls the viewport of a scrollable widget."""
1117
1118 def __init__(self, master=None, **kw):
1119 """Construct a Ttk Scrollbar with parent master.
1120
1121 STANDARD OPTIONS
1122
1123 class, cursor, style, takefocus
1124
1125 WIDGET-SPECIFIC OPTIONS
1126
1127 command, orient
1128 """
1129 Widget.__init__(self, master, "ttk::scrollbar", kw)
1130
1131
1132class Separator(Widget):
1133 """Ttk Separator widget displays a horizontal or vertical separator
1134 bar."""
1135
1136 def __init__(self, master=None, **kw):
1137 """Construct a Ttk Separator with parent master.
1138
1139 STANDARD OPTIONS
1140
1141 class, cursor, style, takefocus
1142
1143 WIDGET-SPECIFIC OPTIONS
1144
1145 orient
1146 """
1147 Widget.__init__(self, master, "ttk::separator", kw)
1148
1149
1150class Sizegrip(Widget):
1151 """Ttk Sizegrip allows the user to resize the containing toplevel
1152 window by pressing and dragging the grip."""
1153
1154 def __init__(self, master=None, **kw):
1155 """Construct a Ttk Sizegrip with parent master.
1156
1157 STANDARD OPTIONS
1158
1159 class, cursor, state, style, takefocus
1160 """
1161 Widget.__init__(self, master, "ttk::sizegrip", kw)
1162
1163
1164class Treeview(Widget):
1165 """Ttk Treeview widget displays a hierarchical collection of items.
1166
1167 Each item has a textual label, an optional image, and an optional list
1168 of data values. The data values are displayed in successive columns
1169 after the tree label."""
1170
1171 def __init__(self, master=None, **kw):
1172 """Construct a Ttk Treeview with parent master.
1173
1174 STANDARD OPTIONS
1175
1176 class, cursor, style, takefocus, xscrollcommand,
1177 yscrollcommand
1178
1179 WIDGET-SPECIFIC OPTIONS
1180
1181 columns, displaycolumns, height, padding, selectmode, show
1182
1183 ITEM OPTIONS
1184
1185 text, image, values, open, tags
1186
1187 TAG OPTIONS
1188
1189 foreground, background, font, image
1190 """
1191 Widget.__init__(self, master, "ttk::treeview", kw)
1192
1193
1194 def bbox(self, item, column=None):
1195 """Returns the bounding box (relative to the treeview widget's
1196 window) of the specified item in the form x y width height.
1197
1198 If column is specified, returns the bounding box of that cell.
1199 If the item is not visible (i.e., if it is a descendant of a
1200 closed item or is scrolled offscreen), returns an empty string."""
1201 return self.tk.call(self._w, "bbox", item, column)
1202
1203
1204 def get_children(self, item=None):
1205 """Returns a tuple of children belonging to item.
1206
1207 If item is not specified, returns root children."""
1208 return self.tk.call(self._w, "children", item or '') or ()
1209
1210
1211 def set_children(self, item, *newchildren):
1212 """Replaces item's child with newchildren.
1213
1214 Children present in item that are not present in newchildren
1215 are detached from tree. No items in newchildren may be an
1216 ancestor of item."""
1217 self.tk.call(self._w, "children", item, newchildren)
1218
1219
1220 def column(self, column, option=None, **kw):
1221 """Query or modify the options for the specified column.
1222
1223 If kw is not given, returns a dict of the column option values. If
1224 option is specified then the value for that option is returned.
1225 Otherwise, sets the options to the corresponding values."""
1226 if option is not None:
1227 kw[option] = None
1228 return _val_or_dict(kw, self.tk.call, self._w, "column", column)
1229
1230
1231 def delete(self, *items):
1232 """Delete all specified items and all their descendants. The root
1233 item may not be deleted."""
1234 self.tk.call(self._w, "delete", items)
1235
1236
1237 def detach(self, *items):
1238 """Unlinks all of the specified items from the tree.
1239
1240 The items and all of their descendants are still present, and may
1241 be reinserted at another point in the tree, but will not be
1242 displayed. The root item may not be detached."""
1243 self.tk.call(self._w, "detach", items)
1244
1245
1246 def exists(self, item):
1247 """Returns True if the specified item is present in the three,
1248 False otherwise."""
1249 return bool(self.tk.call(self._w, "exists", item))
1250
1251
1252 def focus(self, item=None):
1253 """If item is specified, sets the focus item to item. Otherwise,
1254 returns the current focus item, or '' if there is none."""
1255 return self.tk.call(self._w, "focus", item)
1256
1257
1258 def heading(self, column, option=None, **kw):
1259 """Query or modify the heading options for the specified column.
1260
1261 If kw is not given, returns a dict of the heading option values. If
1262 option is specified then the value for that option is returned.
1263 Otherwise, sets the options to the corresponding values.
1264
1265 Valid options/values are:
1266 text: text
1267 The text to display in the column heading
1268 image: image_name
1269 Specifies an image to display to the right of the column
1270 heading
1271 anchor: anchor
1272 Specifies how the heading text should be aligned. One of
1273 the standard Tk anchor values
1274 command: callback
1275 A callback to be invoked when the heading label is
1276 pressed.
1277
1278 To configure the tree column heading, call this with column = "#0" """
1279 cmd = kw.get('command')
1280 if cmd and not isinstance(cmd, basestring):
1281 # callback not registered yet, do it now
1282 kw['command'] = self.master.register(cmd, self._substitute)
1283
1284 if option is not None:
1285 kw[option] = None
1286
1287 return _val_or_dict(kw, self.tk.call, self._w, 'heading', column)
1288
1289
1290 def identify(self, component, x, y):
1291 """Returns a description of the specified component under the
1292 point given by x and y, or the empty string if no such component
1293 is present at that position."""
1294 return self.tk.call(self._w, "identify", component, x, y)
1295
1296
1297 def identify_row(self, y):
1298 """Returns the item ID of the item at position y."""
1299 return self.identify("row", 0, y)
1300
1301
1302 def identify_column(self, x):
1303 """Returns the data column identifier of the cell at position x.
1304
1305 The tree column has ID #0."""
1306 return self.identify("column", x, 0)
1307
1308
1309 def identify_region(self, x, y):
1310 """Returns one of:
1311
1312 heading: Tree heading area.
1313 separator: Space between two columns headings;
1314 tree: The tree area.
1315 cell: A data cell.
1316
1317 * Availability: Tk 8.6"""
1318 return self.identify("region", x, y)
1319
1320
1321 def identify_element(self, x, y):
1322 """Returns the element at position x, y.
1323
1324 * Availability: Tk 8.6"""
1325 return self.identify("element", x, y)
1326
1327
1328 def index(self, item):
1329 """Returns the integer index of item within its parent's list
1330 of children."""
1331 return self.tk.call(self._w, "index", item)
1332
1333
1334 def insert(self, parent, index, iid=None, **kw):
1335 """Creates a new item and return the item identifier of the newly
1336 created item.
1337
1338 parent is the item ID of the parent item, or the empty string
1339 to create a new top-level item. index is an integer, or the value
1340 end, specifying where in the list of parent's children to insert
1341 the new item. If index is less than or equal to zero, the new node
1342 is inserted at the beginning, if index is greater than or equal to
1343 the current number of children, it is inserted at the end. If iid
1344 is specified, it is used as the item identifier, iid must not
1345 already exist in the tree. Otherwise, a new unique identifier
1346 is generated."""
1347 opts = _format_optdict(kw)
1348 if iid:
1349 res = self.tk.call(self._w, "insert", parent, index,
1350 "-id", iid, *opts)
1351 else:
1352 res = self.tk.call(self._w, "insert", parent, index, *opts)
1353
1354 return res
1355
1356
1357 def item(self, item, option=None, **kw):
1358 """Query or modify the options for the specified item.
1359
1360 If no options are given, a dict with options/values for the item
1361 is returned. If option is specified then the value for that option
1362 is returned. Otherwise, sets the options to the corresponding
1363 values as given by kw."""
1364 if option is not None:
1365 kw[option] = None
1366 return _val_or_dict(kw, self.tk.call, self._w, "item", item)
1367
1368
1369 def move(self, item, parent, index):
1370 """Moves item to position index in parent's list of children.
1371
1372 It is illegal to move an item under one of its descendants. If
1373 index is less than or equal to zero, item is moved to the
1374 beginning, if greater than or equal to the number of children,
1375 it is moved to the end. If item was detached it is reattached."""
1376 self.tk.call(self._w, "move", item, parent, index)
1377
1378 reattach = move # A sensible method name for reattaching detached items
1379
1380
1381 def next(self, item):
1382 """Returns the identifier of item's next sibling, or '' if item
1383 is the last child of its parent."""
1384 return self.tk.call(self._w, "next", item)
1385
1386
1387 def parent(self, item):
1388 """Returns the ID of the parent of item, or '' if item is at the
1389 top level of the hierarchy."""
1390 return self.tk.call(self._w, "parent", item)
1391
1392
1393 def prev(self, item):
1394 """Returns the identifier of item's previous sibling, or '' if
1395 item is the first child of its parent."""
1396 return self.tk.call(self._w, "prev", item)
1397
1398
1399 def see(self, item):
1400 """Ensure that item is visible.
1401
1402 Sets all of item's ancestors open option to True, and scrolls
1403 the widget if necessary so that item is within the visible
1404 portion of the tree."""
1405 self.tk.call(self._w, "see", item)
1406
1407
1408 def selection(self, selop=None, items=None):
1409 """If selop is not specified, returns selected items."""
1410 return self.tk.call(self._w, "selection", selop, items)
1411
1412
1413 def selection_set(self, items):
1414 """items becomes the new selection."""
1415 self.selection("set", items)
1416
1417
1418 def selection_add(self, items):
1419 """Add items to the selection."""
1420 self.selection("add", items)
1421
1422
1423 def selection_remove(self, items):
1424 """Remove items from the selection."""
1425 self.selection("remove", items)
1426
1427
1428 def selection_toggle(self, items):
1429 """Toggle the selection state of each item in items."""
1430 self.selection("toggle", items)
1431
1432
1433 def set(self, item, column=None, value=None):
1434 """With one argument, returns a dictionary of column/value pairs
1435 for the specified item. With two arguments, returns the current
1436 value of the specified column. With three arguments, sets the
1437 value of given column in given item to the specified value."""
1438 res = self.tk.call(self._w, "set", item, column, value)
1439 if column is None and value is None:
1440 return _dict_from_tcltuple(res, False)
1441 else:
1442 return res
1443
1444
1445 def tag_bind(self, tagname, sequence=None, callback=None):
1446 """Bind a callback for the given event sequence to the tag tagname.
1447 When an event is delivered to an item, the callbacks for each
1448 of the item's tags option are called."""
1449 self._bind((self._w, "tag", "bind", tagname), sequence, callback, add=0)
1450
1451
1452 def tag_configure(self, tagname, option=None, **kw):
1453 """Query or modify the options for the specified tagname.
1454
1455 If kw is not given, returns a dict of the option settings for tagname.
1456 If option is specified, returns the value for that option for the
1457 specified tagname. Otherwise, sets the options to the corresponding
1458 values for the given tagname."""
1459 if option is not None:
1460 kw[option] = None
1461 return _val_or_dict(kw, self.tk.call, self._w, "tag", "configure",
1462 tagname)
1463
1464
1465 def tag_has(self, tagname, item=None):
1466 """If item is specified, returns 1 or 0 depending on whether the
1467 specified item has the given tagname. Otherwise, returns a list of
1468 all items which have the specified tag.
1469
1470 * Availability: Tk 8.6"""
1471 return self.tk.call(self._w, "tag", "has", tagname, item)
1472
1473
1474 def xview(self, *args):
1475 """Query or modify horizontal position of the treeview."""
1476 return self.tk.call(self._w, "xview", *args)
1477
1478
1479 def yview(self, *args):
1480 """Query or modify vertical position of the treeview."""
1481 return self.tk.call(self._w, "yview", *args)
1482
1483
1484# Extensions
1485
1486class LabeledScale(Frame, object):
1487 """A Ttk Scale widget with a Ttk Label widget indicating its
1488 current value.
1489
1490 The Ttk Scale can be accessed through instance.scale, and Ttk Label
1491 can be accessed through instance.label"""
1492
1493 def __init__(self, master=None, variable=None, from_=0, to=10, **kw):
1494 """Construct an horizontal LabeledScale with parent master, a
1495 variable to be associated with the Ttk Scale widget and its range.
1496 If variable is not specified, a Tkinter.IntVar is created.
1497
1498 WIDGET-SPECIFIC OPTIONS
1499
1500 compound: 'top' or 'bottom'
1501 Specifies how to display the label relative to the scale.
1502 Defaults to 'top'.
1503 """
1504 self._label_top = kw.pop('compound', 'top') == 'top'
1505
1506 Frame.__init__(self, master, **kw)
1507 self._variable = variable or Tkinter.IntVar(master)
1508 self._variable.set(from_)
1509 self._last_valid = from_
1510
1511 self.label = Label(self)
1512 self.scale = Scale(self, variable=self._variable, from_=from_, to=to)
1513 self.scale.bind('<<RangeChanged>>', self._adjust)
1514
1515 # position scale and label according to the compound option
1516 scale_side = 'bottom' if self._label_top else 'top'
1517 label_side = 'top' if scale_side == 'bottom' else 'bottom'
1518 self.scale.pack(side=scale_side, fill='x')
1519 tmp = Label(self).pack(side=label_side) # place holder
1520 self.label.place(anchor='n' if label_side == 'top' else 's')
1521
1522 # update the label as scale or variable changes
1523 self.__tracecb = self._variable.trace_variable('w', self._adjust)
1524 self.bind('<Configure>', self._adjust)
1525 self.bind('<Map>', self._adjust)
1526
1527
1528 def destroy(self):
1529 """Destroy this widget and possibly its associated variable."""
1530 try:
1531 self._variable.trace_vdelete('w', self.__tracecb)
1532 except AttributeError:
1533 # widget has been destroyed already
1534 pass
1535 else:
1536 del self._variable
1537 Frame.destroy(self)
1538
1539
1540 def _adjust(self, *args):
1541 """Adjust the label position according to the scale."""
1542 def adjust_label():
1543 self.update_idletasks() # "force" scale redraw
1544
1545 x, y = self.scale.coords()
1546 if self._label_top:
1547 y = self.scale.winfo_y() - self.label.winfo_reqheight()
1548 else:
1549 y = self.scale.winfo_reqheight() + self.label.winfo_reqheight()
1550
1551 self.label.place_configure(x=x, y=y)
1552
1553 from_, to = self.scale['from'], self.scale['to']
1554 if to < from_:
1555 from_, to = to, from_
1556 newval = self._variable.get()
1557 if not from_ <= newval <= to:
1558 # value outside range, set value back to the last valid one
1559 self.value = self._last_valid
1560 return
1561
1562 self._last_valid = newval
1563 self.label['text'] = newval
1564 self.after_idle(adjust_label)
1565
1566
1567 def _get_value(self):
1568 """Return current scale value."""
1569 return self._variable.get()
1570
1571
1572 def _set_value(self, val):
1573 """Set new scale value."""
1574 self._variable.set(val)
1575
1576
1577 value = property(_get_value, _set_value)
1578
1579
1580class OptionMenu(Menubutton):
1581 """Themed OptionMenu, based after Tkinter's OptionMenu, which allows
1582 the user to select a value from a menu."""
1583
1584 def __init__(self, master, variable, default=None, *values, **kwargs):
1585 """Construct a themed OptionMenu widget with master as the parent,
1586 the resource textvariable set to variable, the initially selected
1587 value specified by the default parameter, the menu values given by
1588 *values and additional keywords.
1589
1590 WIDGET-SPECIFIC OPTIONS
1591
1592 style: stylename
1593 Menubutton style.
1594 direction: 'above', 'below', 'left', 'right', or 'flush'
1595 Menubutton direction.
1596 command: callback
1597 A callback that will be invoked after selecting an item.
1598 """
1599 kw = {'textvariable': variable, 'style': kwargs.pop('style', None),
1600 'direction': kwargs.pop('direction', None)}
1601 Menubutton.__init__(self, master, **kw)
1602 self['menu'] = Tkinter.Menu(self, tearoff=False)
1603
1604 self._variable = variable
1605 self._callback = kwargs.pop('command', None)
1606 if kwargs:
1607 raise Tkinter.TclError('unknown option -%s' % (
1608 kwargs.iterkeys().next()))
1609
1610 self.set_menu(default, *values)
1611
1612
1613 def __getitem__(self, item):
1614 if item == 'menu':
1615 return self.nametowidget(Menubutton.__getitem__(self, item))
1616
1617 return Menubutton.__getitem__(self, item)
1618
1619
1620 def set_menu(self, default=None, *values):
1621 """Build a new menu of radiobuttons with *values and optionally
1622 a default value."""
1623 menu = self['menu']
1624 menu.delete(0, 'end')
1625 for val in values:
1626 menu.add_radiobutton(label=val,
1627 command=Tkinter._setit(self._variable, val, self._callback))
1628
1629 if default:
1630 self._variable.set(default)
1631
1632
1633 def destroy(self):
1634 """Destroy this widget and its associated variable."""
1635 del self._variable
1636 Menubutton.destroy(self)