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