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