1# Pmw megawidget base classes. 2 3# This module provides a foundation for building megawidgets. It 4# contains the MegaArchetype class which manages component widgets and 5# configuration options. Also provided are the MegaToplevel and 6# MegaWidget classes, derived from the MegaArchetype class. The 7# MegaToplevel class contains a Tkinter Toplevel widget to act as the 8# container of the megawidget. This is used as the base class of all 9# megawidgets that are contained in their own top level window, such 10# as a Dialog window. The MegaWidget class contains a Tkinter Frame 11# to act as the container of the megawidget. This is used as the base 12# class of all other megawidgets, such as a ComboBox or ButtonBox. 13# 14# Megawidgets are built by creating a class that inherits from either 15# the MegaToplevel or MegaWidget class. 16 17import os 18import string 19import sys 20import traceback 21import types 22import Tkinter 23 24# Special values used in index() methods of several megawidgets. 25END = ['end'] 26SELECT = ['select'] 27DEFAULT = ['default'] 28 29# Constant used to indicate that an option can only be set by a call 30# to the constructor. 31INITOPT = ['initopt'] 32_DEFAULT_OPTION_VALUE = ['default_option_value'] 33_useTkOptionDb = 0 34 35# Symbolic constants for the indexes into an optionInfo list. 36_OPT_DEFAULT = 0 37_OPT_VALUE = 1 38_OPT_FUNCTION = 2 39 40# Stacks 41 42_busyStack = [] 43 # Stack which tracks nested calls to show/hidebusycursor (called 44 # either directly or from activate()/deactivate()). Each element 45 # is a dictionary containing: 46 # 'newBusyWindows' : List of windows which had busy_hold called 47 # on them during a call to showbusycursor(). 48 # The corresponding call to hidebusycursor() 49 # will call busy_release on these windows. 50 # 'busyFocus' : The blt _Busy window which showbusycursor() 51 # set the focus to. 52 # 'previousFocus' : The focus as it was when showbusycursor() 53 # was called. The corresponding call to 54 # hidebusycursor() will restore this focus if 55 # the focus has not been changed from busyFocus. 56 57_grabStack = [] 58 # Stack of grabbed windows. It tracks calls to push/popgrab() 59 # (called either directly or from activate()/deactivate()). The 60 # window on the top of the stack is the window currently with the 61 # grab. Each element is a dictionary containing: 62 # 'grabWindow' : The window grabbed by pushgrab(). The 63 # corresponding call to popgrab() will release 64 # the grab on this window and restore the grab 65 # on the next window in the stack (if there is one). 66 # 'globalMode' : True if the grabWindow was grabbed with a 67 # global grab, false if the grab was local 68 # and 'nograb' if no grab was performed. 69 # 'previousFocus' : The focus as it was when pushgrab() 70 # was called. The corresponding call to 71 # popgrab() will restore this focus. 72 # 'deactivateFunction' : 73 # The function to call (usually grabWindow.deactivate) if 74 # popgrab() is called (usually from a deactivate() method) 75 # on a window which is not at the top of the stack (that is, 76 # does not have the grab or focus). For example, if a modal 77 # dialog is deleted by the window manager or deactivated by 78 # a timer. In this case, all dialogs above and including 79 # this one are deactivated, starting at the top of the 80 # stack. 81 82 # Note that when dealing with focus windows, the name of the Tk 83 # widget is used, since it may be the '_Busy' window, which has no 84 # python instance associated with it. 85 86#============================================================================= 87 88# Functions used to forward methods from a class to a component. 89 90# Fill in a flattened method resolution dictionary for a class (attributes are 91# filtered out). Flattening honours the MI method resolution rules 92# (depth-first search of bases in order). The dictionary has method names 93# for keys and functions for values. 94def __methodDict(cls, dict): 95 96 # the strategy is to traverse the class in the _reverse_ of the normal 97 # order, and overwrite any duplicates. 98 baseList = list(cls.__bases__) 99 baseList.reverse() 100 101 # do bases in reverse order, so first base overrides last base 102 for super in baseList: 103 __methodDict(super, dict) 104 105 # do my methods last to override base classes 106 for key, value in cls.__dict__.items(): 107 # ignore class attributes 108 if type(value) == types.FunctionType: 109 dict[key] = value 110 111def __methods(cls): 112 # Return all method names for a class. 113 114 # Return all method names for a class (attributes are filtered 115 # out). Base classes are searched recursively. 116 117 dict = {} 118 __methodDict(cls, dict) 119 return dict.keys() 120 121# Function body to resolve a forwarding given the target method name and the 122# attribute name. The resulting lambda requires only self, but will forward 123# any other parameters. 124__stringBody = ( 125 'def %(method)s(this, *args, **kw): return ' + 126 #'apply(this.%(attribute)s.%(method)s, args, kw)') 127 'this.%(attribute)s.%(method)s(*args, **kw)') 128 129# Get a unique id 130__counter = 0 131def __unique(): 132 global __counter 133 __counter = __counter + 1 134 return str(__counter) 135 136# Function body to resolve a forwarding given the target method name and the 137# index of the resolution function. The resulting lambda requires only self, 138# but will forward any other parameters. The target instance is identified 139# by invoking the resolution function. 140__funcBody = ( 141 'def %(method)s(this, *args, **kw): return ' + 142 #'apply(this.%(forwardFunc)s().%(method)s, args, kw)') 143 'this.%(forwardFunc)s().%(method)s(*args, **kw)'); 144 145def forwardmethods(fromClass, toClass, toPart, exclude = ()): 146 # Forward all methods from one class to another. 147 148 # Forwarders will be created in fromClass to forward method 149 # invocations to toClass. The methods to be forwarded are 150 # identified by flattening the interface of toClass, and excluding 151 # methods identified in the exclude list. Methods already defined 152 # in fromClass, or special methods with one or more leading or 153 # trailing underscores will not be forwarded. 154 155 # For a given object of class fromClass, the corresponding toClass 156 # object is identified using toPart. This can either be a String 157 # denoting an attribute of fromClass objects, or a function taking 158 # a fromClass object and returning a toClass object. 159 160 # Example: 161 # class MyClass: 162 # ... 163 # def __init__(self): 164 # ... 165 # self.__target = TargetClass() 166 # ... 167 # def findtarget(self): 168 # return self.__target 169 # forwardmethods(MyClass, TargetClass, '__target', ['dangerous1', 'dangerous2']) 170 # # ...or... 171 # forwardmethods(MyClass, TargetClass, MyClass.findtarget, 172 # ['dangerous1', 'dangerous2']) 173 174 # In both cases, all TargetClass methods will be forwarded from 175 # MyClass except for dangerous1, dangerous2, special methods like 176 # __str__, and pre-existing methods like findtarget. 177 178 179 # Allow an attribute name (String) or a function to determine the instance 180 if type(toPart) != types.StringType: 181 182 # check that it is something like a function 183 if callable(toPart): 184 185 # If a method is passed, use the function within it 186 if hasattr(toPart, 'im_func'): 187 toPart = toPart.im_func 188 189 # After this is set up, forwarders in this class will use 190 # the forwarding function. The forwarding function name is 191 # guaranteed to be unique, so that it can't be hidden by subclasses 192 forwardName = '__fwdfunc__' + __unique() 193 fromClass.__dict__[forwardName] = toPart 194 195 # It's not a valid type 196 else: 197 raise TypeError, 'toPart must be attribute name, function or method' 198 199 # get the full set of candidate methods 200 dict = {} 201 __methodDict(toClass, dict) 202 203 # discard special methods 204 for ex in dict.keys(): 205 if ex[:1] == '_' or ex[-1:] == '_': 206 del dict[ex] 207 # discard dangerous methods supplied by the caller 208 for ex in exclude: 209 if dict.has_key(ex): 210 del dict[ex] 211 # discard methods already defined in fromClass 212 for ex in __methods(fromClass): 213 if dict.has_key(ex): 214 del dict[ex] 215 216 for method, func in dict.items(): 217 d = {'method': method, 'func': func} 218 if type(toPart) == types.StringType: 219 execString = \ 220 __stringBody % {'method' : method, 'attribute' : toPart} 221 else: 222 execString = \ 223 __funcBody % {'forwardFunc' : forwardName, 'method' : method} 224 225 exec execString in d 226 227 # this creates a method 228 fromClass.__dict__[method] = d[method] 229 230#============================================================================= 231 232def setgeometryanddeiconify(window, geom): 233 # To avoid flashes on X and to position the window correctly on NT 234 # (caused by Tk bugs). 235 236 if os.name == 'nt' or \ 237 (os.name == 'posix' and sys.platform[:6] == 'cygwin'): 238 # Require overrideredirect trick to stop window frame 239 # appearing momentarily. 240 redirect = window.overrideredirect() 241 if not redirect: 242 window.overrideredirect(1) 243 window.deiconify() 244 if geom is not None: 245 window.geometry(geom) 246 # Call update_idletasks to ensure NT moves the window to the 247 # correct position it is raised. 248 window.update_idletasks() 249 window.tkraise() 250 if not redirect: 251 window.overrideredirect(0) 252 else: 253 if geom is not None: 254 window.geometry(geom) 255 256 # Problem!? Which way around should the following two calls 257 # go? If deiconify() is called first then I get complaints 258 # from people using the enlightenment or sawfish window 259 # managers that when a dialog is activated it takes about 2 260 # seconds for the contents of the window to appear. But if 261 # tkraise() is called first then I get complaints from people 262 # using the twm window manager that when a dialog is activated 263 # it appears in the top right corner of the screen and also 264 # takes about 2 seconds to appear. 265 266 #window.tkraise() 267 # Call update_idletasks to ensure certain window managers (eg: 268 # enlightenment and sawfish) do not cause Tk to delay for 269 # about two seconds before displaying window. 270 #window.update_idletasks() 271 #window.deiconify() 272 273 window.deiconify() 274 if window.overrideredirect(): 275 # The window is not under the control of the window manager 276 # and so we need to raise it ourselves. 277 window.tkraise() 278 279#============================================================================= 280 281class MegaArchetype: 282 # Megawidget abstract root class. 283 284 # This class provides methods which are inherited by classes 285 # implementing useful bases (this class doesn't provide a 286 # container widget inside which the megawidget can be built). 287 288 def __init__(self, parent = None, hullClass = None): 289 290 # Mapping from each megawidget option to a list of information 291 # about the option 292 # - default value 293 # - current value 294 # - function to call when the option is initialised in the 295 # call to initialiseoptions() in the constructor or 296 # modified via configure(). If this is INITOPT, the 297 # option is an initialisation option (an option that can 298 # be set by the call to the constructor but can not be 299 # used with configure). 300 # This mapping is not initialised here, but in the call to 301 # defineoptions() which precedes construction of this base class. 302 # 303 # self._optionInfo = {} 304 305 # Mapping from each component name to a tuple of information 306 # about the component. 307 # - component widget instance 308 # - configure function of widget instance 309 # - the class of the widget (Frame, EntryField, etc) 310 # - cget function of widget instance 311 # - the name of the component group of this component, if any 312 self.__componentInfo = {} 313 314 # Mapping from alias names to the names of components or 315 # sub-components. 316 self.__componentAliases = {} 317 318 # Contains information about the keywords provided to the 319 # constructor. It is a mapping from the keyword to a tuple 320 # containing: 321 # - value of keyword 322 # - a boolean indicating if the keyword has been used. 323 # A keyword is used if, during the construction of a megawidget, 324 # - it is defined in a call to defineoptions() or addoptions(), or 325 # - it references, by name, a component of the megawidget, or 326 # - it references, by group, at least one component 327 # At the end of megawidget construction, a call is made to 328 # initialiseoptions() which reports an error if there are 329 # unused options given to the constructor. 330 # 331 # After megawidget construction, the dictionary contains 332 # keywords which refer to a dynamic component group, so that 333 # these components can be created after megawidget 334 # construction and still use the group options given to the 335 # constructor. 336 # 337 # self._constructorKeywords = {} 338 339 # List of dynamic component groups. If a group is included in 340 # this list, then it not an error if a keyword argument for 341 # the group is given to the constructor or to configure(), but 342 # no components with this group have been created. 343 # self._dynamicGroups = () 344 345 if hullClass is None: 346 self._hull = None 347 else: 348 if parent is None: 349 parent = Tkinter._default_root 350 351 # Create the hull. 352 self._hull = self.createcomponent('hull', 353 (), None, 354 hullClass, (parent,)) 355 _hullToMegaWidget[self._hull] = self 356 357 if _useTkOptionDb: 358 # Now that a widget has been created, query the Tk 359 # option database to get the default values for the 360 # options which have not been set in the call to the 361 # constructor. This assumes that defineoptions() is 362 # called before the __init__(). 363 option_get = self.option_get 364 _VALUE = _OPT_VALUE 365 _DEFAULT = _OPT_DEFAULT 366 for name, info in self._optionInfo.items(): 367 value = info[_VALUE] 368 if value is _DEFAULT_OPTION_VALUE: 369 resourceClass = string.upper(name[0]) + name[1:] 370 value = option_get(name, resourceClass) 371 if value != '': 372 try: 373 # Convert the string to int/float/tuple, etc 374 value = eval(value, {'__builtins__': {}}) 375 except: 376 pass 377 info[_VALUE] = value 378 else: 379 info[_VALUE] = info[_DEFAULT] 380 381 def destroy(self): 382 # Clean up optionInfo in case it contains circular references 383 # in the function field, such as self._settitle in class 384 # MegaToplevel. 385 386 self._optionInfo = {} 387 if self._hull is not None: 388 del _hullToMegaWidget[self._hull] 389 self._hull.destroy() 390 391 #====================================================================== 392 # Methods used (mainly) during the construction of the megawidget. 393 394 def defineoptions(self, keywords, optionDefs, dynamicGroups = ()): 395 # Create options, providing the default value and the method 396 # to call when the value is changed. If any option created by 397 # base classes has the same name as one in <optionDefs>, the 398 # base class's value and function will be overriden. 399 400 # This should be called before the constructor of the base 401 # class, so that default values defined in the derived class 402 # override those in the base class. 403 404 if not hasattr(self, '_constructorKeywords'): 405 # First time defineoptions has been called. 406 tmp = {} 407 for option, value in keywords.items(): 408 tmp[option] = [value, 0] 409 self._constructorKeywords = tmp 410 self._optionInfo = {} 411 self._initialiseoptions_counter = 0 412 self._initialiseoptions_counter = self._initialiseoptions_counter + 1 413 414 if not hasattr(self, '_dynamicGroups'): 415 self._dynamicGroups = () 416 self._dynamicGroups = self._dynamicGroups + tuple(dynamicGroups) 417 self.addoptions(optionDefs) 418 419 def addoptions(self, optionDefs): 420 # Add additional options, providing the default value and the 421 # method to call when the value is changed. See 422 # "defineoptions" for more details 423 424 # optimisations: 425 optionInfo = self._optionInfo 426 optionInfo_has_key = optionInfo.has_key 427 keywords = self._constructorKeywords 428 keywords_has_key = keywords.has_key 429 FUNCTION = _OPT_FUNCTION 430 431 for name, default, function in optionDefs: 432 if '_' not in name: 433 # The option will already exist if it has been defined 434 # in a derived class. In this case, do not override the 435 # default value of the option or the callback function 436 # if it is not None. 437 if not optionInfo_has_key(name): 438 if keywords_has_key(name): 439 value = keywords[name][0] 440 optionInfo[name] = [default, value, function] 441 del keywords[name] 442 else: 443 if _useTkOptionDb: 444 optionInfo[name] = \ 445 [default, _DEFAULT_OPTION_VALUE, function] 446 else: 447 optionInfo[name] = [default, default, function] 448 elif optionInfo[name][FUNCTION] is None: 449 optionInfo[name][FUNCTION] = function 450 else: 451 # This option is of the form "component_option". If this is 452 # not already defined in self._constructorKeywords add it. 453 # This allows a derived class to override the default value 454 # of an option of a component of a base class. 455 if not keywords_has_key(name): 456 keywords[name] = [default, 0] 457 458 def createcomponent(self, componentName, componentAliases, 459 componentGroup, widgetClass, *widgetArgs, **kw): 460 # Create a component (during construction or later). 461 462 if self.__componentInfo.has_key(componentName): 463 raise ValueError, 'Component "%s" already exists' % componentName 464 465 if '_' in componentName: 466 raise ValueError, \ 467 'Component name "%s" must not contain "_"' % componentName 468 469 if hasattr(self, '_constructorKeywords'): 470 keywords = self._constructorKeywords 471 else: 472 keywords = {} 473 for alias, component in componentAliases: 474 # Create aliases to the component and its sub-components. 475 index = string.find(component, '_') 476 if index < 0: 477 self.__componentAliases[alias] = (component, None) 478 else: 479 mainComponent = component[:index] 480 subComponent = component[(index + 1):] 481 self.__componentAliases[alias] = (mainComponent, subComponent) 482 483 # Remove aliases from the constructor keyword arguments by 484 # replacing any keyword arguments that begin with *alias* 485 # with corresponding keys beginning with *component*. 486 487 alias = alias + '_' 488 aliasLen = len(alias) 489 for option in keywords.keys(): 490 if len(option) > aliasLen and option[:aliasLen] == alias: 491 newkey = component + '_' + option[aliasLen:] 492 keywords[newkey] = keywords[option] 493 del keywords[option] 494 495 componentPrefix = componentName + '_' 496 nameLen = len(componentPrefix) 497 for option in keywords.keys(): 498 if len(option) > nameLen and option[:nameLen] == componentPrefix: 499 # The keyword argument refers to this component, so add 500 # this to the options to use when constructing the widget. 501 kw[option[nameLen:]] = keywords[option][0] 502 del keywords[option] 503 else: 504 # Check if this keyword argument refers to the group 505 # of this component. If so, add this to the options 506 # to use when constructing the widget. Mark the 507 # keyword argument as being used, but do not remove it 508 # since it may be required when creating another 509 # component. 510 index = string.find(option, '_') 511 if index >= 0 and componentGroup == option[:index]: 512 rest = option[(index + 1):] 513 kw[rest] = keywords[option][0] 514 keywords[option][1] = 1 515 516 if kw.has_key('pyclass'): 517 widgetClass = kw['pyclass'] 518 del kw['pyclass'] 519 if widgetClass is None: 520 return None 521 if len(widgetArgs) == 1 and type(widgetArgs[0]) == types.TupleType: 522 # Arguments to the constructor can be specified as either 523 # multiple trailing arguments to createcomponent() or as a 524 # single tuple argument. 525 widgetArgs = widgetArgs[0] 526 widget = apply(widgetClass, widgetArgs, kw) 527 componentClass = widget.__class__.__name__ 528 self.__componentInfo[componentName] = (widget, widget.configure, 529 componentClass, widget.cget, componentGroup) 530 531 return widget 532 533 def destroycomponent(self, name): 534 # Remove a megawidget component. 535 536 # This command is for use by megawidget designers to destroy a 537 # megawidget component. 538 539 self.__componentInfo[name][0].destroy() 540 del self.__componentInfo[name] 541 542 def createlabel(self, parent, childCols = 1, childRows = 1): 543 544 labelpos = self['labelpos'] 545 labelmargin = self['labelmargin'] 546 if labelpos is None: 547 return 548 549 label = self.createcomponent('label', 550 (), None, 551 Tkinter.Label, (parent,)) 552 553 if labelpos[0] in 'ns': 554 # vertical layout 555 if labelpos[0] == 'n': 556 row = 0 557 margin = 1 558 else: 559 row = childRows + 3 560 margin = row - 1 561 label.grid(column=2, row=row, columnspan=childCols, sticky=labelpos) 562 parent.grid_rowconfigure(margin, minsize=labelmargin) 563 else: 564 # horizontal layout 565 if labelpos[0] == 'w': 566 col = 0 567 margin = 1 568 else: 569 col = childCols + 3 570 margin = col - 1 571 label.grid(column=col, row=2, rowspan=childRows, sticky=labelpos) 572 parent.grid_columnconfigure(margin, minsize=labelmargin) 573 574 def initialiseoptions(self, dummy = None): 575 self._initialiseoptions_counter = self._initialiseoptions_counter - 1 576 if self._initialiseoptions_counter == 0: 577 unusedOptions = [] 578 keywords = self._constructorKeywords 579 for name in keywords.keys(): 580 used = keywords[name][1] 581 if not used: 582 # This keyword argument has not been used. If it 583 # does not refer to a dynamic group, mark it as 584 # unused. 585 index = string.find(name, '_') 586 if index < 0 or name[:index] not in self._dynamicGroups: 587 unusedOptions.append(name) 588 if len(unusedOptions) > 0: 589 if len(unusedOptions) == 1: 590 text = 'Unknown option "' 591 else: 592 text = 'Unknown options "' 593 raise KeyError, text + string.join(unusedOptions, ', ') + \ 594 '" for ' + self.__class__.__name__ 595 596 # Call the configuration callback function for every option. 597 FUNCTION = _OPT_FUNCTION 598 for info in self._optionInfo.values(): 599 func = info[FUNCTION] 600 if func is not None and func is not INITOPT: 601 func() 602 603 #====================================================================== 604 # Method used to configure the megawidget. 605 606 def configure(self, option=None, **kw): 607 # Query or configure the megawidget options. 608 # 609 # If not empty, *kw* is a dictionary giving new 610 # values for some of the options of this megawidget or its 611 # components. For options defined for this megawidget, set 612 # the value of the option to the new value and call the 613 # configuration callback function, if any. For options of the 614 # form <component>_<option>, where <component> is a component 615 # of this megawidget, call the configure method of the 616 # component giving it the new value of the option. The 617 # <component> part may be an alias or a component group name. 618 # 619 # If *option* is None, return all megawidget configuration 620 # options and settings. Options are returned as standard 5 621 # element tuples 622 # 623 # If *option* is a string, return the 5 element tuple for the 624 # given configuration option. 625 626 # First, deal with the option queries. 627 if len(kw) == 0: 628 # This configure call is querying the values of one or all options. 629 # Return 5-tuples: 630 # (optionName, resourceName, resourceClass, default, value) 631 if option is None: 632 rtn = {} 633 for option, config in self._optionInfo.items(): 634 resourceClass = string.upper(option[0]) + option[1:] 635 rtn[option] = (option, option, resourceClass, 636 config[_OPT_DEFAULT], config[_OPT_VALUE]) 637 return rtn 638 else: 639 config = self._optionInfo[option] 640 resourceClass = string.upper(option[0]) + option[1:] 641 return (option, option, resourceClass, config[_OPT_DEFAULT], 642 config[_OPT_VALUE]) 643 644 # optimisations: 645 optionInfo = self._optionInfo 646 optionInfo_has_key = optionInfo.has_key 647 componentInfo = self.__componentInfo 648 componentInfo_has_key = componentInfo.has_key 649 componentAliases = self.__componentAliases 650 componentAliases_has_key = componentAliases.has_key 651 VALUE = _OPT_VALUE 652 FUNCTION = _OPT_FUNCTION 653 654 # This will contain a list of options in *kw* which 655 # are known to this megawidget. 656 directOptions = [] 657 658 # This will contain information about the options in 659 # *kw* of the form <component>_<option>, where 660 # <component> is a component of this megawidget. It is a 661 # dictionary whose keys are the configure method of each 662 # component and whose values are a dictionary of options and 663 # values for the component. 664 indirectOptions = {} 665 indirectOptions_has_key = indirectOptions.has_key 666 667 for option, value in kw.items(): 668 if optionInfo_has_key(option): 669 # This is one of the options of this megawidget. 670 # Make sure it is not an initialisation option. 671 if optionInfo[option][FUNCTION] is INITOPT: 672 raise KeyError, \ 673 'Cannot configure initialisation option "' \ 674 + option + '" for ' + self.__class__.__name__ 675 optionInfo[option][VALUE] = value 676 directOptions.append(option) 677 else: 678 index = string.find(option, '_') 679 if index >= 0: 680 # This option may be of the form <component>_<option>. 681 component = option[:index] 682 componentOption = option[(index + 1):] 683 684 # Expand component alias 685 if componentAliases_has_key(component): 686 component, subComponent = componentAliases[component] 687 if subComponent is not None: 688 componentOption = subComponent + '_' \ 689 + componentOption 690 691 # Expand option string to write on error 692 option = component + '_' + componentOption 693 694 if componentInfo_has_key(component): 695 # Configure the named component 696 componentConfigFuncs = [componentInfo[component][1]] 697 else: 698 # Check if this is a group name and configure all 699 # components in the group. 700 componentConfigFuncs = [] 701 for info in componentInfo.values(): 702 if info[4] == component: 703 componentConfigFuncs.append(info[1]) 704 705 if len(componentConfigFuncs) == 0 and \ 706 component not in self._dynamicGroups: 707 raise KeyError, 'Unknown option "' + option + \ 708 '" for ' + self.__class__.__name__ 709 710 # Add the configure method(s) (may be more than 711 # one if this is configuring a component group) 712 # and option/value to dictionary. 713 for componentConfigFunc in componentConfigFuncs: 714 if not indirectOptions_has_key(componentConfigFunc): 715 indirectOptions[componentConfigFunc] = {} 716 indirectOptions[componentConfigFunc][componentOption] \ 717 = value 718 else: 719 raise KeyError, 'Unknown option "' + option + \ 720 '" for ' + self.__class__.__name__ 721 722 # Call the configure methods for any components. 723 #apply has been deprecated since python 2.3 724 #map(apply, indirectOptions.keys(), 725 # ((),) * len(indirectOptions), indirectOptions.values()) 726 for func in indirectOptions.keys(): 727 func( **indirectOptions[func]) 728 729 # Call the configuration callback function for each option. 730 for option in directOptions: 731 info = optionInfo[option] 732 func = info[_OPT_FUNCTION] 733 if func is not None: 734 func() 735 736 def __setitem__(self, key, value): 737 #apply has been deprecated since Python 2.3 738 #apply(self.configure, (), {key: value}) 739 self.configure(*(), **{key: value}) 740 741 #====================================================================== 742 # Methods used to query the megawidget. 743 744 def component(self, name): 745 # Return a component widget of the megawidget given the 746 # component's name 747 # This allows the user of a megawidget to access and configure 748 # widget components directly. 749 750 # Find the main component and any subcomponents 751 index = string.find(name, '_') 752 if index < 0: 753 component = name 754 remainingComponents = None 755 else: 756 component = name[:index] 757 remainingComponents = name[(index + 1):] 758 759 # Expand component alias 760 if self.__componentAliases.has_key(component): 761 component, subComponent = self.__componentAliases[component] 762 if subComponent is not None: 763 if remainingComponents is None: 764 remainingComponents = subComponent 765 else: 766 remainingComponents = subComponent + '_' \ 767 + remainingComponents 768 769 widget = self.__componentInfo[component][0] 770 if remainingComponents is None: 771 return widget 772 else: 773 return widget.component(remainingComponents) 774 775 def interior(self): 776 return self._hull 777 778 def hulldestroyed(self): 779 return not _hullToMegaWidget.has_key(self._hull) 780 781 def __str__(self): 782 return str(self._hull) 783 784 def cget(self, option): 785 # Get current configuration setting. 786 787 # Return the value of an option, for example myWidget['font']. 788 if self._optionInfo.has_key(option): 789 return self._optionInfo[option][_OPT_VALUE] 790 else: 791 index = string.find(option, '_') 792 if index >= 0: 793 component = option[:index] 794 componentOption = option[(index + 1):] 795 796 # Expand component alias 797 if self.__componentAliases.has_key(component): 798 component, subComponent = self.__componentAliases[component] 799 if subComponent is not None: 800 componentOption = subComponent + '_' + componentOption 801 802 # Expand option string to write on error 803 option = component + '_' + componentOption 804 805 if self.__componentInfo.has_key(component): 806 # Call cget on the component. 807 componentCget = self.__componentInfo[component][3] 808 return componentCget(componentOption) 809 else: 810 # If this is a group name, call cget for one of 811 # the components in the group. 812 for info in self.__componentInfo.values(): 813 if info[4] == component: 814 componentCget = info[3] 815 return componentCget(componentOption) 816 817 raise KeyError, 'Unknown option "' + option + \ 818 '" for ' + self.__class__.__name__ 819 820 __getitem__ = cget 821 822 def isinitoption(self, option): 823 return self._optionInfo[option][_OPT_FUNCTION] is INITOPT 824 825 def options(self): 826 options = [] 827 if hasattr(self, '_optionInfo'): 828 for option, info in self._optionInfo.items(): 829 isinit = info[_OPT_FUNCTION] is INITOPT 830 default = info[_OPT_DEFAULT] 831 options.append((option, default, isinit)) 832 options.sort() 833 return options 834 835 def components(self): 836 # Return a list of all components. 837 838 # This list includes the 'hull' component and all widget subcomponents 839 840 names = self.__componentInfo.keys() 841 names.sort() 842 return names 843 844 def componentaliases(self): 845 # Return a list of all component aliases. 846 847 componentAliases = self.__componentAliases 848 849 names = componentAliases.keys() 850 names.sort() 851 rtn = [] 852 for alias in names: 853 (mainComponent, subComponent) = componentAliases[alias] 854 if subComponent is None: 855 rtn.append((alias, mainComponent)) 856 else: 857 rtn.append((alias, mainComponent + '_' + subComponent)) 858 859 return rtn 860 861 def componentgroup(self, name): 862 return self.__componentInfo[name][4] 863 864#============================================================================= 865 866# The grab functions are mainly called by the activate() and 867# deactivate() methods. 868# 869# Use pushgrab() to add a new window to the grab stack. This 870# releases the grab by the window currently on top of the stack (if 871# there is one) and gives the grab and focus to the new widget. 872# 873# To remove the grab from the window on top of the grab stack, call 874# popgrab(). 875# 876# Use releasegrabs() to release the grab and clear the grab stack. 877 878def pushgrab(grabWindow, globalMode, deactivateFunction): 879 prevFocus = grabWindow.tk.call('focus') 880 grabInfo = { 881 'grabWindow' : grabWindow, 882 'globalMode' : globalMode, 883 'previousFocus' : prevFocus, 884 'deactivateFunction' : deactivateFunction, 885 } 886 _grabStack.append(grabInfo) 887 _grabtop() 888 grabWindow.focus_set() 889 890def popgrab(window): 891 # Return the grab to the next window in the grab stack, if any. 892 893 # If this window is not at the top of the grab stack, then it has 894 # just been deleted by the window manager or deactivated by a 895 # timer. Call the deactivate method for the modal dialog above 896 # this one on the stack. 897 if _grabStack[-1]['grabWindow'] != window: 898 for index in range(len(_grabStack)): 899 if _grabStack[index]['grabWindow'] == window: 900 _grabStack[index + 1]['deactivateFunction']() 901 break 902 903 grabInfo = _grabStack[-1] 904 del _grabStack[-1] 905 906 topWidget = grabInfo['grabWindow'] 907 prevFocus = grabInfo['previousFocus'] 908 globalMode = grabInfo['globalMode'] 909 910 if globalMode != 'nograb': 911 topWidget.grab_release() 912 913 if len(_grabStack) > 0: 914 _grabtop() 915 if prevFocus != '': 916 try: 917 topWidget.tk.call('focus', prevFocus) 918 except Tkinter.TclError: 919 # Previous focus widget has been deleted. Set focus 920 # to root window. 921 Tkinter._default_root.focus_set() 922 else: 923 # Make sure that focus does not remain on the released widget. 924 if len(_grabStack) > 0: 925 topWidget = _grabStack[-1]['grabWindow'] 926 topWidget.focus_set() 927 else: 928 Tkinter._default_root.focus_set() 929 930def grabstacktopwindow(): 931 if len(_grabStack) == 0: 932 return None 933 else: 934 return _grabStack[-1]['grabWindow'] 935 936def releasegrabs(): 937 # Release grab and clear the grab stack. 938 939 current = Tkinter._default_root.grab_current() 940 if current is not None: 941 current.grab_release() 942 _grabStack[:] = [] 943 944def _grabtop(): 945 grabInfo = _grabStack[-1] 946 topWidget = grabInfo['grabWindow'] 947 globalMode = grabInfo['globalMode'] 948 949 if globalMode == 'nograb': 950 return 951 952 while 1: 953 try: 954 if globalMode: 955 topWidget.grab_set_global() 956 else: 957 topWidget.grab_set() 958 break 959 except Tkinter.TclError: 960 # Another application has grab. Keep trying until 961 # grab can succeed. 962 topWidget.after(100) 963 964#============================================================================= 965 966class MegaToplevel(MegaArchetype): 967 968 def __init__(self, parent = None, **kw): 969 # Define the options for this megawidget. 970 optiondefs = ( 971 ('activatecommand', None, None), 972 ('deactivatecommand', None, None), 973 ('master', None, None), 974 ('title', None, self._settitle), 975 ('hull_class', self.__class__.__name__, None), 976 ) 977 self.defineoptions(kw, optiondefs) 978 979 # Initialise the base class (after defining the options). 980 MegaArchetype.__init__(self, parent, Tkinter.Toplevel) 981 982 # Initialise instance. 983 984 # Set WM_DELETE_WINDOW protocol, deleting any old callback, so 985 # memory does not leak. 986 if hasattr(self._hull, '_Pmw_WM_DELETE_name'): 987 self._hull.tk.deletecommand(self._hull._Pmw_WM_DELETE_name) 988 self._hull._Pmw_WM_DELETE_name = \ 989 self.register(self._userDeleteWindow, needcleanup = 0) 990 self.protocol('WM_DELETE_WINDOW', self._hull._Pmw_WM_DELETE_name) 991 992 # Initialise instance variables. 993 994 self._firstShowing = 1 995 # Used by show() to ensure window retains previous position on screen. 996 997 # The IntVar() variable to wait on during a modal dialog. 998 self._wait = None 999 1000 self._active = 0 1001 self._userDeleteFunc = self.destroy 1002 self._userModalDeleteFunc = self.deactivate 1003 1004 # Check keywords and initialise options. 1005 self.initialiseoptions() 1006 1007 def _settitle(self): 1008 title = self['title'] 1009 if title is not None: 1010 self.title(title) 1011 1012 def userdeletefunc(self, func=None): 1013 if func: 1014 self._userDeleteFunc = func 1015 else: 1016 return self._userDeleteFunc 1017 1018 def usermodaldeletefunc(self, func=None): 1019 if func: 1020 self._userModalDeleteFunc = func 1021 else: 1022 return self._userModalDeleteFunc 1023 1024 def _userDeleteWindow(self): 1025 if self.active(): 1026 self._userModalDeleteFunc() 1027 else: 1028 self._userDeleteFunc() 1029 1030 def destroy(self): 1031 # Allow this to be called more than once. 1032 if _hullToMegaWidget.has_key(self._hull): 1033 self.deactivate() 1034 1035 # Remove circular references, so that object can get cleaned up. 1036 del self._userDeleteFunc 1037 del self._userModalDeleteFunc 1038 1039 MegaArchetype.destroy(self) 1040 1041 def show(self, master = None): 1042 if self.state() != 'normal': 1043 if self._firstShowing: 1044 # Just let the window manager determine the window 1045 # position for the first time. 1046 geom = None 1047 else: 1048 # Position the window at the same place it was last time. 1049 geom = self._sameposition() 1050 setgeometryanddeiconify(self, geom) 1051 1052 if self._firstShowing: 1053 self._firstShowing = 0 1054 else: 1055 if self.transient() == '': 1056 self.tkraise() 1057 1058 # Do this last, otherwise get flashing on NT: 1059 if master is not None: 1060 if master == 'parent': 1061 parent = self.winfo_parent() 1062 # winfo_parent() should return the parent widget, but the 1063 # the current version of Tkinter returns a string. 1064 if type(parent) == types.StringType: 1065 parent = self._hull._nametowidget(parent) 1066 master = parent.winfo_toplevel() 1067 self.transient(master) 1068 1069 self.focus() 1070 1071 def _centreonscreen(self): 1072 # Centre the window on the screen. (Actually halfway across 1073 # and one third down.) 1074 1075 parent = self.winfo_parent() 1076 if type(parent) == types.StringType: 1077 parent = self._hull._nametowidget(parent) 1078 1079 # Find size of window. 1080 self.update_idletasks() 1081 width = self.winfo_width() 1082 height = self.winfo_height() 1083 if width == 1 and height == 1: 1084 # If the window has not yet been displayed, its size is 1085 # reported as 1x1, so use requested size. 1086 width = self.winfo_reqwidth() 1087 height = self.winfo_reqheight() 1088 1089 # Place in centre of screen: 1090 x = (self.winfo_screenwidth() - width) / 2 - parent.winfo_vrootx() 1091 y = (self.winfo_screenheight() - height) / 3 - parent.winfo_vrooty() 1092 if x < 0: 1093 x = 0 1094 if y < 0: 1095 y = 0 1096 return '+%d+%d' % (x, y) 1097 1098 def _sameposition(self): 1099 # Position the window at the same place it was last time. 1100 1101 geometry = self.geometry() 1102 index = string.find(geometry, '+') 1103 if index >= 0: 1104 return geometry[index:] 1105 else: 1106 return None 1107 1108 def activate(self, globalMode = 0, geometry = 'centerscreenfirst'): 1109 if self._active: 1110 raise ValueError, 'Window is already active' 1111 if self.state() == 'normal': 1112 self.withdraw() 1113 1114 self._active = 1 1115 1116 showbusycursor() 1117 1118 if self._wait is None: 1119 self._wait = Tkinter.IntVar() 1120 self._wait.set(0) 1121 1122 if geometry == 'centerscreenalways': 1123 geom = self._centreonscreen() 1124 elif geometry == 'centerscreenfirst': 1125 if self._firstShowing: 1126 # Centre the window the first time it is displayed. 1127 geom = self._centreonscreen() 1128 else: 1129 # Position the window at the same place it was last time. 1130 geom = self._sameposition() 1131 elif geometry[:5] == 'first': 1132 if self._firstShowing: 1133 geom = geometry[5:] 1134 else: 1135 # Position the window at the same place it was last time. 1136 geom = self._sameposition() 1137 else: 1138 geom = geometry 1139 1140 self._firstShowing = 0 1141 1142 setgeometryanddeiconify(self, geom) 1143 1144 # Do this last, otherwise get flashing on NT: 1145 master = self['master'] 1146 if master is not None: 1147 if master == 'parent': 1148 parent = self.winfo_parent() 1149 # winfo_parent() should return the parent widget, but the 1150 # the current version of Tkinter returns a string. 1151 if type(parent) == types.StringType: 1152 parent = self._hull._nametowidget(parent) 1153 master = parent.winfo_toplevel() 1154 self.transient(master) 1155 1156 pushgrab(self._hull, globalMode, self.deactivate) 1157 command = self['activatecommand'] 1158 if callable(command): 1159 command() 1160 self.wait_variable(self._wait) 1161 1162 return self._result 1163 1164 def deactivate(self, result=None): 1165 if not self._active: 1166 return 1167 self._active = 0 1168 1169 # Restore the focus before withdrawing the window, since 1170 # otherwise the window manager may take the focus away so we 1171 # can't redirect it. Also, return the grab to the next active 1172 # window in the stack, if any. 1173 popgrab(self._hull) 1174 1175 command = self['deactivatecommand'] 1176 if callable(command): 1177 command() 1178 1179 self.withdraw() 1180 hidebusycursor(forceFocusRestore = 1) 1181 1182 self._result = result 1183 self._wait.set(1) 1184 1185 def active(self): 1186 return self._active 1187 1188forwardmethods(MegaToplevel, Tkinter.Toplevel, '_hull') 1189 1190#============================================================================= 1191 1192class MegaWidget(MegaArchetype): 1193 def __init__(self, parent = None, **kw): 1194 # Define the options for this megawidget. 1195 optiondefs = ( 1196 ('hull_class', self.__class__.__name__, None), 1197 ) 1198 self.defineoptions(kw, optiondefs) 1199 1200 # Initialise the base class (after defining the options). 1201 MegaArchetype.__init__(self, parent, Tkinter.Frame) 1202 1203 # Check keywords and initialise options. 1204 self.initialiseoptions() 1205 1206forwardmethods(MegaWidget, Tkinter.Frame, '_hull') 1207 1208#============================================================================= 1209 1210# Public functions 1211#----------------- 1212 1213_traceTk = 0 1214def tracetk(root = None, on = 1, withStackTrace = 0, file=None): 1215 global _withStackTrace 1216 global _traceTkFile 1217 global _traceTk 1218 1219 if root is None: 1220 root = Tkinter._default_root 1221 1222 _withStackTrace = withStackTrace 1223 _traceTk = on 1224 if on: 1225 if hasattr(root.tk, '__class__'): 1226 # Tracing already on 1227 return 1228 if file is None: 1229 _traceTkFile = sys.stderr 1230 else: 1231 _traceTkFile = file 1232 tk = _TraceTk(root.tk) 1233 else: 1234 if not hasattr(root.tk, '__class__'): 1235 # Tracing already off 1236 return 1237 tk = root.tk.getTclInterp() 1238 _setTkInterps(root, tk) 1239 1240def showbusycursor(): 1241 1242 _addRootToToplevelBusyInfo() 1243 root = Tkinter._default_root 1244 1245 busyInfo = { 1246 'newBusyWindows' : [], 1247 'previousFocus' : None, 1248 'busyFocus' : None, 1249 } 1250 _busyStack.append(busyInfo) 1251 1252 if _disableKeyboardWhileBusy: 1253 # Remember the focus as it is now, before it is changed. 1254 busyInfo['previousFocus'] = root.tk.call('focus') 1255 1256 if not _havebltbusy(root): 1257 # No busy command, so don't call busy hold on any windows. 1258 return 1259 1260 for (window, winInfo) in _toplevelBusyInfo.items(): 1261 if (window.state() != 'withdrawn' and not winInfo['isBusy'] 1262 and not winInfo['excludeFromBusy']): 1263 busyInfo['newBusyWindows'].append(window) 1264 winInfo['isBusy'] = 1 1265 _busy_hold(window, winInfo['busyCursorName']) 1266 1267 # Make sure that no events for the busy window get 1268 # through to Tkinter, otherwise it will crash in 1269 # _nametowidget with a 'KeyError: _Busy' if there is 1270 # a binding on the toplevel window. 1271 window.tk.call('bindtags', winInfo['busyWindow'], 'Pmw_Dummy_Tag') 1272 1273 if _disableKeyboardWhileBusy: 1274 # Remember previous focus widget for this toplevel window 1275 # and set focus to the busy window, which will ignore all 1276 # keyboard events. 1277 winInfo['windowFocus'] = \ 1278 window.tk.call('focus', '-lastfor', window._w) 1279 window.tk.call('focus', winInfo['busyWindow']) 1280 busyInfo['busyFocus'] = winInfo['busyWindow'] 1281 1282 if len(busyInfo['newBusyWindows']) > 0: 1283 if os.name == 'nt': 1284 # NT needs an "update" before it will change the cursor. 1285 window.update() 1286 else: 1287 window.update_idletasks() 1288 1289def hidebusycursor(forceFocusRestore = 0): 1290 1291 # Remember the focus as it is now, before it is changed. 1292 root = Tkinter._default_root 1293 if _disableKeyboardWhileBusy: 1294 currentFocus = root.tk.call('focus') 1295 1296 # Pop the busy info off the stack. 1297 busyInfo = _busyStack[-1] 1298 del _busyStack[-1] 1299 1300 for window in busyInfo['newBusyWindows']: 1301 # If this window has not been deleted, release the busy cursor. 1302 if _toplevelBusyInfo.has_key(window): 1303 winInfo = _toplevelBusyInfo[window] 1304 winInfo['isBusy'] = 0 1305 _busy_release(window) 1306 1307 if _disableKeyboardWhileBusy: 1308 # Restore previous focus window for this toplevel window, 1309 # but only if is still set to the busy window (it may have 1310 # been changed). 1311 windowFocusNow = window.tk.call('focus', '-lastfor', window._w) 1312 if windowFocusNow == winInfo['busyWindow']: 1313 try: 1314 window.tk.call('focus', winInfo['windowFocus']) 1315 except Tkinter.TclError: 1316 # Previous focus widget has been deleted. Set focus 1317 # to toplevel window instead (can't leave focus on 1318 # busy window). 1319 window.focus_set() 1320 1321 if _disableKeyboardWhileBusy: 1322 # Restore the focus, depending on whether the focus had changed 1323 # between the calls to showbusycursor and hidebusycursor. 1324 if forceFocusRestore or busyInfo['busyFocus'] == currentFocus: 1325 # The focus had not changed, so restore it to as it was before 1326 # the call to showbusycursor, 1327 previousFocus = busyInfo['previousFocus'] 1328 if previousFocus is not None: 1329 try: 1330 root.tk.call('focus', previousFocus) 1331 except Tkinter.TclError: 1332 # Previous focus widget has been deleted; forget it. 1333 pass 1334 else: 1335 # The focus had changed, so restore it to what it had been 1336 # changed to before the call to hidebusycursor. 1337 root.tk.call('focus', currentFocus) 1338 1339def clearbusycursor(): 1340 while len(_busyStack) > 0: 1341 hidebusycursor() 1342 1343def setbusycursorattributes(window, **kw): 1344 _addRootToToplevelBusyInfo() 1345 for name, value in kw.items(): 1346 if name == 'exclude': 1347 _toplevelBusyInfo[window]['excludeFromBusy'] = value 1348 elif name == 'cursorName': 1349 _toplevelBusyInfo[window]['busyCursorName'] = value 1350 else: 1351 raise KeyError, 'Unknown busycursor attribute "' + name + '"' 1352 1353def _addRootToToplevelBusyInfo(): 1354 # Include the Tk root window in the list of toplevels. This must 1355 # not be called before Tkinter has had a chance to be initialised by 1356 # the application. 1357 1358 root = Tkinter._default_root 1359 if root == None: 1360 root = Tkinter.Tk() 1361 if not _toplevelBusyInfo.has_key(root): 1362 _addToplevelBusyInfo(root) 1363 1364def busycallback(command, updateFunction = None): 1365 if not callable(command): 1366 raise ValueError, \ 1367 'cannot register non-command busy callback %s %s' % \ 1368 (repr(command), type(command)) 1369 wrapper = _BusyWrapper(command, updateFunction) 1370 return wrapper.callback 1371 1372_errorReportFile = None 1373_errorWindow = None 1374 1375def reporterrorstofile(file = None): 1376 global _errorReportFile 1377 _errorReportFile = file 1378 1379def displayerror(text): 1380 global _errorWindow 1381 1382 if _errorReportFile is not None: 1383 _errorReportFile.write(text + '\n') 1384 else: 1385 # Print error on standard error as well as to error window. 1386 # Useful if error window fails to be displayed, for example 1387 # when exception is triggered in a <Destroy> binding for root 1388 # window. 1389 sys.stderr.write(text + '\n') 1390 1391 if _errorWindow is None: 1392 # The error window has not yet been created. 1393 _errorWindow = _ErrorWindow() 1394 1395 _errorWindow.showerror(text) 1396 1397_root = None 1398_disableKeyboardWhileBusy = 1 1399 1400def initialise( 1401 root = None, 1402 size = None, 1403 fontScheme = None, 1404 useTkOptionDb = 0, 1405 noBltBusy = 0, 1406 disableKeyboardWhileBusy = None, 1407): 1408 # Remember if show/hidebusycursor should ignore keyboard events. 1409 global _disableKeyboardWhileBusy 1410 if disableKeyboardWhileBusy is not None: 1411 _disableKeyboardWhileBusy = disableKeyboardWhileBusy 1412 1413 # Do not use blt busy command if noBltBusy is set. Otherwise, 1414 # use blt busy if it is available. 1415 global _haveBltBusy 1416 if noBltBusy: 1417 _haveBltBusy = 0 1418 1419 # Save flag specifying whether the Tk option database should be 1420 # queried when setting megawidget option default values. 1421 global _useTkOptionDb 1422 _useTkOptionDb = useTkOptionDb 1423 1424 # If we haven't been given a root window, use the default or 1425 # create one. 1426 if root is None: 1427 if Tkinter._default_root is None: 1428 root = Tkinter.Tk() 1429 else: 1430 root = Tkinter._default_root 1431 1432 # If this call is initialising a different Tk interpreter than the 1433 # last call, then re-initialise all global variables. Assume the 1434 # last interpreter has been destroyed - ie: Pmw does not (yet) 1435 # support multiple simultaneous interpreters. 1436 global _root 1437 if _root is not None and _root != root: 1438 global _busyStack 1439 global _errorWindow 1440 global _grabStack 1441 global _hullToMegaWidget 1442 global _toplevelBusyInfo 1443 _busyStack = [] 1444 _errorWindow = None 1445 _grabStack = [] 1446 _hullToMegaWidget = {} 1447 _toplevelBusyInfo = {} 1448 _root = root 1449 1450 # Trap Tkinter Toplevel constructors so that a list of Toplevels 1451 # can be maintained. 1452 Tkinter.Toplevel.title = __TkinterToplevelTitle 1453 1454 # Trap Tkinter widget destruction so that megawidgets can be 1455 # destroyed when their hull widget is destoyed and the list of 1456 # Toplevels can be pruned. 1457 Tkinter.Toplevel.destroy = __TkinterToplevelDestroy 1458 Tkinter.Widget.destroy = __TkinterWidgetDestroy 1459 1460 # Modify Tkinter's CallWrapper class to improve the display of 1461 # errors which occur in callbacks. 1462 Tkinter.CallWrapper = __TkinterCallWrapper 1463 1464 # Make sure we get to know when the window manager deletes the 1465 # root window. Only do this if the protocol has not yet been set. 1466 # This is required if there is a modal dialog displayed and the 1467 # window manager deletes the root window. Otherwise the 1468 # application will not exit, even though there are no windows. 1469 if root.protocol('WM_DELETE_WINDOW') == '': 1470 root.protocol('WM_DELETE_WINDOW', root.destroy) 1471 1472 # Set the base font size for the application and set the 1473 # Tk option database font resources. 1474 import PmwLogicalFont 1475 PmwLogicalFont._font_initialise(root, size, fontScheme) 1476 1477 return root 1478 1479def alignlabels(widgets, sticky = None): 1480 if len(widgets) == 0: 1481 return 1482 1483 widgets[0].update_idletasks() 1484 1485 # Determine the size of the maximum length label string. 1486 maxLabelWidth = 0 1487 for iwid in widgets: 1488 labelWidth = iwid.grid_bbox(0, 1)[2] 1489 if labelWidth > maxLabelWidth: 1490 maxLabelWidth = labelWidth 1491 1492 # Adjust the margins for the labels such that the child sites and 1493 # labels line up. 1494 for iwid in widgets: 1495 if sticky is not None: 1496 iwid.component('label').grid(sticky=sticky) 1497 iwid.grid_columnconfigure(0, minsize = maxLabelWidth) 1498#============================================================================= 1499 1500# Private routines 1501#----------------- 1502_callToTkReturned = 1 1503_recursionCounter = 1 1504 1505class _TraceTk: 1506 def __init__(self, tclInterp): 1507 self.tclInterp = tclInterp 1508 1509 def getTclInterp(self): 1510 return self.tclInterp 1511 1512 # Calling from python into Tk. 1513 def call(self, *args, **kw): 1514 global _callToTkReturned 1515 global _recursionCounter 1516 1517 _callToTkReturned = 0 1518 if len(args) == 1 and type(args[0]) == types.TupleType: 1519 argStr = str(args[0]) 1520 else: 1521 argStr = str(args) 1522 _traceTkFile.write('CALL TK> %d:%s%s' % 1523 (_recursionCounter, ' ' * _recursionCounter, argStr)) 1524 _recursionCounter = _recursionCounter + 1 1525 try: 1526 result = apply(self.tclInterp.call, args, kw) 1527 except Tkinter.TclError, errorString: 1528 _callToTkReturned = 1 1529 _recursionCounter = _recursionCounter - 1 1530 _traceTkFile.write('\nTK ERROR> %d:%s-> %s\n' % 1531 (_recursionCounter, ' ' * _recursionCounter, 1532 repr(errorString))) 1533 if _withStackTrace: 1534 _traceTkFile.write('CALL TK> stack:\n') 1535 traceback.print_stack() 1536 raise Tkinter.TclError, errorString 1537 1538 _recursionCounter = _recursionCounter - 1 1539 if _callToTkReturned: 1540 _traceTkFile.write('CALL RTN> %d:%s-> %s' % 1541 (_recursionCounter, ' ' * _recursionCounter, repr(result))) 1542 else: 1543 _callToTkReturned = 1 1544 if result: 1545 _traceTkFile.write(' -> %s' % repr(result)) 1546 _traceTkFile.write('\n') 1547 if _withStackTrace: 1548 _traceTkFile.write('CALL TK> stack:\n') 1549 traceback.print_stack() 1550 1551 _traceTkFile.flush() 1552 return result 1553 1554 def __getattr__(self, key): 1555 return getattr(self.tclInterp, key) 1556 1557def _setTkInterps(window, tk): 1558 window.tk = tk 1559 for child in window.children.values(): 1560 _setTkInterps(child, tk) 1561 1562#============================================================================= 1563 1564# Functions to display a busy cursor. Keep a list of all toplevels 1565# and display the busy cursor over them. The list will contain the Tk 1566# root toplevel window as well as all other toplevel windows. 1567# Also keep a list of the widget which last had focus for each 1568# toplevel. 1569 1570# Map from toplevel windows to 1571# {'isBusy', 'windowFocus', 'busyWindow', 1572# 'excludeFromBusy', 'busyCursorName'} 1573_toplevelBusyInfo = {} 1574 1575# Pmw needs to know all toplevel windows, so that it can call blt busy 1576# on them. This is a hack so we get notified when a Tk topevel is 1577# created. Ideally, the __init__ 'method' should be overridden, but 1578# it is a 'read-only special attribute'. Luckily, title() is always 1579# called from the Tkinter Toplevel constructor. 1580 1581def _addToplevelBusyInfo(window): 1582 if window._w == '.': 1583 busyWindow = '._Busy' 1584 else: 1585 busyWindow = window._w + '._Busy' 1586 1587 _toplevelBusyInfo[window] = { 1588 'isBusy' : 0, 1589 'windowFocus' : None, 1590 'busyWindow' : busyWindow, 1591 'excludeFromBusy' : 0, 1592 'busyCursorName' : None, 1593 } 1594 1595def __TkinterToplevelTitle(self, *args): 1596 # If this is being called from the constructor, include this 1597 # Toplevel in the list of toplevels and set the initial 1598 # WM_DELETE_WINDOW protocol to destroy() so that we get to know 1599 # about it. 1600 if not _toplevelBusyInfo.has_key(self): 1601 _addToplevelBusyInfo(self) 1602 self._Pmw_WM_DELETE_name = self.register(self.destroy, None, 0) 1603 self.protocol('WM_DELETE_WINDOW', self._Pmw_WM_DELETE_name) 1604 1605 return apply(Tkinter.Wm.title, (self,) + args) 1606 1607_haveBltBusy = None 1608def _havebltbusy(window): 1609 global _busy_hold, _busy_release, _haveBltBusy 1610 if _haveBltBusy is None: 1611 import PmwBlt 1612 _haveBltBusy = PmwBlt.havebltbusy(window) 1613 _busy_hold = PmwBlt.busy_hold 1614 if os.name == 'nt': 1615 # There is a bug in Blt 2.4i on NT where the busy window 1616 # does not follow changes in the children of a window. 1617 # Using forget works around the problem. 1618 _busy_release = PmwBlt.busy_forget 1619 else: 1620 _busy_release = PmwBlt.busy_release 1621 return _haveBltBusy 1622 1623class _BusyWrapper: 1624 def __init__(self, command, updateFunction): 1625 self._command = command 1626 self._updateFunction = updateFunction 1627 1628 def callback(self, *args): 1629 showbusycursor() 1630 rtn = apply(self._command, args) 1631 1632 # Call update before hiding the busy windows to clear any 1633 # events that may have occurred over the busy windows. 1634 if callable(self._updateFunction): 1635 self._updateFunction() 1636 1637 hidebusycursor() 1638 return rtn 1639 1640#============================================================================= 1641 1642def drawarrow(canvas, color, direction, tag, baseOffset = 0.25, edgeOffset = 0.15): 1643 canvas.delete(tag) 1644 1645 bw = (string.atoi(canvas['borderwidth']) + 1646 string.atoi(canvas['highlightthickness'])) 1647 width = string.atoi(canvas['width']) 1648 height = string.atoi(canvas['height']) 1649 1650 if direction in ('up', 'down'): 1651 majorDimension = height 1652 minorDimension = width 1653 else: 1654 majorDimension = width 1655 minorDimension = height 1656 1657 offset = round(baseOffset * majorDimension) 1658 if direction in ('down', 'right'): 1659 base = bw + offset 1660 apex = bw + majorDimension - offset 1661 else: 1662 base = bw + majorDimension - offset 1663 apex = bw + offset 1664 1665 if minorDimension > 3 and minorDimension % 2 == 0: 1666 minorDimension = minorDimension - 1 1667 half = int(minorDimension * (1 - 2 * edgeOffset)) / 2 1668 low = round(bw + edgeOffset * minorDimension) 1669 middle = low + half 1670 high = low + 2 * half 1671 1672 if direction in ('up', 'down'): 1673 coords = (low, base, high, base, middle, apex) 1674 else: 1675 coords = (base, low, base, high, apex, middle) 1676 kw = {'fill' : color, 'outline' : color, 'tag' : tag} 1677 apply(canvas.create_polygon, coords, kw) 1678 1679#============================================================================= 1680 1681# Modify the Tkinter destroy methods so that it notifies us when a Tk 1682# toplevel or frame is destroyed. 1683 1684# A map from the 'hull' component of a megawidget to the megawidget. 1685# This is used to clean up a megawidget when its hull is destroyed. 1686_hullToMegaWidget = {} 1687 1688def __TkinterToplevelDestroy(tkWidget): 1689 if _hullToMegaWidget.has_key(tkWidget): 1690 mega = _hullToMegaWidget[tkWidget] 1691 try: 1692 mega.destroy() 1693 except: 1694 _reporterror(mega.destroy, ()) 1695 else: 1696 # Delete the busy info structure for this toplevel (if the 1697 # window was created before Pmw.initialise() was called, it 1698 # will not have any. 1699 if _toplevelBusyInfo.has_key(tkWidget): 1700 del _toplevelBusyInfo[tkWidget] 1701 if hasattr(tkWidget, '_Pmw_WM_DELETE_name'): 1702 tkWidget.tk.deletecommand(tkWidget._Pmw_WM_DELETE_name) 1703 del tkWidget._Pmw_WM_DELETE_name 1704 Tkinter.BaseWidget.destroy(tkWidget) 1705 1706def __TkinterWidgetDestroy(tkWidget): 1707 if _hullToMegaWidget.has_key(tkWidget): 1708 mega = _hullToMegaWidget[tkWidget] 1709 try: 1710 mega.destroy() 1711 except: 1712 _reporterror(mega.destroy, ()) 1713 else: 1714 Tkinter.BaseWidget.destroy(tkWidget) 1715 1716#============================================================================= 1717 1718# Add code to Tkinter to improve the display of errors which occur in 1719# callbacks. 1720 1721class __TkinterCallWrapper: 1722 def __init__(self, func, subst, widget): 1723 self.func = func 1724 self.subst = subst 1725 self.widget = widget 1726 1727 # Calling back from Tk into python. 1728 def __call__(self, *args): 1729 try: 1730 if self.subst: 1731 args = apply(self.subst, args) 1732 if _traceTk: 1733 if not _callToTkReturned: 1734 _traceTkFile.write('\n') 1735 if hasattr(self.func, 'im_class'): 1736 name = self.func.im_class.__name__ + '.' + \ 1737 self.func.__name__ 1738 else: 1739 name = self.func.__name__ 1740 if len(args) == 1 and hasattr(args[0], 'type'): 1741 # The argument to the callback is an event. 1742 eventName = _eventTypeToName[string.atoi(args[0].type)] 1743 if eventName in ('KeyPress', 'KeyRelease',): 1744 argStr = '(%s %s Event: %s)' % \ 1745 (eventName, args[0].keysym, args[0].widget) 1746 else: 1747 argStr = '(%s Event, %s)' % (eventName, args[0].widget) 1748 else: 1749 argStr = str(args) 1750 _traceTkFile.write('CALLBACK> %d:%s%s%s\n' % 1751 (_recursionCounter, ' ' * _recursionCounter, name, argStr)) 1752 _traceTkFile.flush() 1753 return apply(self.func, args) 1754 except SystemExit, msg: 1755 raise SystemExit, msg 1756 except: 1757 _reporterror(self.func, args) 1758 1759_eventTypeToName = { 1760 2 : 'KeyPress', 15 : 'VisibilityNotify', 28 : 'PropertyNotify', 1761 3 : 'KeyRelease', 16 : 'CreateNotify', 29 : 'SelectionClear', 1762 4 : 'ButtonPress', 17 : 'DestroyNotify', 30 : 'SelectionRequest', 1763 5 : 'ButtonRelease', 18 : 'UnmapNotify', 31 : 'SelectionNotify', 1764 6 : 'MotionNotify', 19 : 'MapNotify', 32 : 'ColormapNotify', 1765 7 : 'EnterNotify', 20 : 'MapRequest', 33 : 'ClientMessage', 1766 8 : 'LeaveNotify', 21 : 'ReparentNotify', 34 : 'MappingNotify', 1767 9 : 'FocusIn', 22 : 'ConfigureNotify', 35 : 'VirtualEvents', 1768 10 : 'FocusOut', 23 : 'ConfigureRequest', 36 : 'ActivateNotify', 1769 11 : 'KeymapNotify', 24 : 'GravityNotify', 37 : 'DeactivateNotify', 1770 12 : 'Expose', 25 : 'ResizeRequest', 38 : 'MouseWheelEvent', 1771 13 : 'GraphicsExpose', 26 : 'CirculateNotify', 1772 14 : 'NoExpose', 27 : 'CirculateRequest', 1773} 1774 1775def _reporterror(func, args): 1776 # Fetch current exception values. 1777 exc_type, exc_value, exc_traceback = sys.exc_info() 1778 1779 # Give basic information about the callback exception. 1780 if type(exc_type) == types.ClassType: 1781 # Handle python 1.5 class exceptions. 1782 exc_type = exc_type.__name__ 1783 msg = str(exc_type) + ' Exception in Tk callback\n' 1784 msg = msg + ' Function: %s (type: %s)\n' % (repr(func), type(func)) 1785 msg = msg + ' Args: %s\n' % str(args) 1786 1787 if type(args) == types.TupleType and len(args) > 0 and \ 1788 hasattr(args[0], 'type'): 1789 eventArg = 1 1790 else: 1791 eventArg = 0 1792 1793 # If the argument to the callback is an event, add the event type. 1794 if eventArg: 1795 eventNum = string.atoi(args[0].type) 1796 if eventNum in _eventTypeToName.keys(): 1797 msg = msg + ' Event type: %s (type num: %d)\n' % \ 1798 (_eventTypeToName[eventNum], eventNum) 1799 else: 1800 msg = msg + ' Unknown event type (type num: %d)\n' % eventNum 1801 1802 # Add the traceback. 1803 msg = msg + 'Traceback (innermost last):\n' 1804 for tr in traceback.extract_tb(exc_traceback): 1805 msg = msg + ' File "%s", line %s, in %s\n' % (tr[0], tr[1], tr[2]) 1806 msg = msg + ' %s\n' % tr[3] 1807 msg = msg + '%s: %s\n' % (exc_type, exc_value) 1808 1809 # If the argument to the callback is an event, add the event contents. 1810 if eventArg: 1811 msg = msg + '\n================================================\n' 1812 msg = msg + ' Event contents:\n' 1813 keys = args[0].__dict__.keys() 1814 keys.sort() 1815 for key in keys: 1816 msg = msg + ' %s: %s\n' % (key, args[0].__dict__[key]) 1817 1818 clearbusycursor() 1819 try: 1820 displayerror(msg) 1821 except: 1822 pass 1823 1824class _ErrorWindow: 1825 def __init__(self): 1826 1827 self._errorQueue = [] 1828 self._errorCount = 0 1829 self._open = 0 1830 self._firstShowing = 1 1831 1832 # Create the toplevel window 1833 self._top = Tkinter.Toplevel() 1834 self._top.protocol('WM_DELETE_WINDOW', self._hide) 1835 self._top.title('Error in background function') 1836 self._top.iconname('Background error') 1837 1838 # Create the text widget and scrollbar in a frame 1839 upperframe = Tkinter.Frame(self._top) 1840 1841 scrollbar = Tkinter.Scrollbar(upperframe, orient='vertical') 1842 scrollbar.pack(side = 'right', fill = 'y') 1843 1844 self._text = Tkinter.Text(upperframe, yscrollcommand=scrollbar.set) 1845 self._text.pack(fill = 'both', expand = 1) 1846 scrollbar.configure(command=self._text.yview) 1847 1848 # Create the buttons and label in a frame 1849 lowerframe = Tkinter.Frame(self._top) 1850 1851 ignore = Tkinter.Button(lowerframe, 1852 text = 'Ignore remaining errors', command = self._hide) 1853 ignore.pack(side='left') 1854 1855 self._nextError = Tkinter.Button(lowerframe, 1856 text = 'Show next error', command = self._next) 1857 self._nextError.pack(side='left') 1858 1859 self._label = Tkinter.Label(lowerframe, relief='ridge') 1860 self._label.pack(side='left', fill='x', expand=1) 1861 1862 # Pack the lower frame first so that it does not disappear 1863 # when the window is resized. 1864 lowerframe.pack(side = 'bottom', fill = 'x') 1865 upperframe.pack(side = 'bottom', fill = 'both', expand = 1) 1866 1867 def showerror(self, text): 1868 if self._open: 1869 self._errorQueue.append(text) 1870 else: 1871 self._display(text) 1872 self._open = 1 1873 1874 # Display the error window in the same place it was before. 1875 if self._top.state() == 'normal': 1876 # If update_idletasks is not called here, the window may 1877 # be placed partially off the screen. Also, if it is not 1878 # called and many errors are generated quickly in 1879 # succession, the error window may not display errors 1880 # until the last one is generated and the interpreter 1881 # becomes idle. 1882 # XXX: remove this, since it causes omppython to go into an 1883 # infinite loop if an error occurs in an omp callback. 1884 # self._top.update_idletasks() 1885 1886 pass 1887 else: 1888 if self._firstShowing: 1889 geom = None 1890 else: 1891 geometry = self._top.geometry() 1892 index = string.find(geometry, '+') 1893 if index >= 0: 1894 geom = geometry[index:] 1895 else: 1896 geom = None 1897 setgeometryanddeiconify(self._top, geom) 1898 1899 if self._firstShowing: 1900 self._firstShowing = 0 1901 else: 1902 self._top.tkraise() 1903 1904 self._top.focus() 1905 1906 self._updateButtons() 1907 1908 # Release any grab, so that buttons in the error window work. 1909 releasegrabs() 1910 1911 def _hide(self): 1912 self._errorCount = self._errorCount + len(self._errorQueue) 1913 self._errorQueue = [] 1914 self._top.withdraw() 1915 self._open = 0 1916 1917 def _next(self): 1918 # Display the next error in the queue. 1919 1920 text = self._errorQueue[0] 1921 del self._errorQueue[0] 1922 1923 self._display(text) 1924 self._updateButtons() 1925 1926 def _display(self, text): 1927 self._errorCount = self._errorCount + 1 1928 text = 'Error: %d\n%s' % (self._errorCount, text) 1929 self._text.delete('1.0', 'end') 1930 self._text.insert('end', text) 1931 1932 def _updateButtons(self): 1933 numQueued = len(self._errorQueue) 1934 if numQueued > 0: 1935 self._label.configure(text='%d more errors' % numQueued) 1936 self._nextError.configure(state='normal') 1937 else: 1938 self._label.configure(text='No more errors') 1939 self._nextError.configure(state='disabled') 1940