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