1 # Pmw megawidget base classes.
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.
14 # Megawidgets are built by creating a class that inherits from either
15 # the MegaToplevel or MegaWidget class.
24 # Special values used in index() methods of several megawidgets.
29 # Constant used to indicate that an option can only be set by a call
32 _DEFAULT_OPTION_VALUE = ['default_option_value']
35 # Symbolic constants for the indexes into an optionInfo list.
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()
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.
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
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.
86 #=============================================================================
88 # Functions used to forward methods from a class to a component.
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.
94 def __methodDict(cls, dict):
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__)
101 # do bases in reverse order, so first base overrides last base
102 for super in baseList:
103 __methodDict(super, dict)
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:
112 # Return all method names for a class.
114 # Return all method names for a class (attributes are filtered
115 # out). Base classes are searched recursively.
118 __methodDict(cls, dict)
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.
125 'def %(method)s(this, *args, **kw): return ' +
126 'apply(this.%(attribute)s.%(method)s, args, kw)')
132 __counter = __counter + 1
133 return str(__counter)
135 # Function body to resolve a forwarding given the target method name and the
136 # index of the resolution function. The resulting lambda requires only self,
137 # but will forward any other parameters. The target instance is identified
138 # by invoking the resolution function.
140 'def %(method)s(this, *args, **kw): return ' +
141 'apply(this.%(forwardFunc)s().%(method)s, args, kw)')
143 def forwardmethods(fromClass, toClass, toPart, exclude = ()):
144 # Forward all methods from one class to another.
146 # Forwarders will be created in fromClass to forward method
147 # invocations to toClass. The methods to be forwarded are
148 # identified by flattening the interface of toClass, and excluding
149 # methods identified in the exclude list. Methods already defined
150 # in fromClass, or special methods with one or more leading or
151 # trailing underscores will not be forwarded.
153 # For a given object of class fromClass, the corresponding toClass
154 # object is identified using toPart. This can either be a String
155 # denoting an attribute of fromClass objects, or a function taking
156 # a fromClass object and returning a toClass object.
161 # def __init__(self):
163 # self.__target = TargetClass()
165 # def findtarget(self):
166 # return self.__target
167 # forwardmethods(MyClass, TargetClass, '__target', ['dangerous1', 'dangerous2'])
169 # forwardmethods(MyClass, TargetClass, MyClass.findtarget,
170 # ['dangerous1', 'dangerous2'])
172 # In both cases, all TargetClass methods will be forwarded from
173 # MyClass except for dangerous1, dangerous2, special methods like
174 # __str__, and pre-existing methods like findtarget.
177 # Allow an attribute name (String) or a function to determine the instance
178 if type(toPart) != types.StringType:
180 # check that it is something like a function
183 # If a method is passed, use the function within it
184 if hasattr(toPart, 'im_func'):
185 toPart = toPart.im_func
187 # After this is set up, forwarders in this class will use
188 # the forwarding function. The forwarding function name is
189 # guaranteed to be unique, so that it can't be hidden by subclasses
190 forwardName = '__fwdfunc__' + __unique()
191 fromClass.__dict__[forwardName] = toPart
193 # It's not a valid type
195 raise TypeError, 'toPart must be attribute name, function or method'
197 # get the full set of candidate methods
199 __methodDict(toClass, dict)
201 # discard special methods
202 for ex in dict.keys():
203 if ex[:1] == '_' or ex[-1:] == '_':
205 # discard dangerous methods supplied by the caller
209 # discard methods already defined in fromClass
210 for ex in __methods(fromClass):
214 for method, func in dict.items():
215 d = {'method': method, 'func': func}
216 if type(toPart) == types.StringType:
218 __stringBody % {'method' : method, 'attribute' : toPart}
221 __funcBody % {'forwardFunc' : forwardName, 'method' : method}
225 # this creates a method
226 fromClass.__dict__[method] = d[method]
228 #=============================================================================
230 def setgeometryanddeiconify(window, geom):
231 # To avoid flashes on X and to position the window correctly on NT
232 # (caused by Tk bugs).
234 if os.name == 'nt' or \
235 (os.name == 'posix' and sys.platform[:6] == 'cygwin'):
236 # Require overrideredirect trick to stop window frame
237 # appearing momentarily.
238 redirect = window.overrideredirect()
240 window.overrideredirect(1)
243 window.geometry(geom)
244 # Call update_idletasks to ensure NT moves the window to the
245 # correct position it is raised.
246 window.update_idletasks()
249 window.overrideredirect(0)
252 window.geometry(geom)
254 # Problem!? Which way around should the following two calls
255 # go? If deiconify() is called first then I get complaints
256 # from people using the enlightenment or sawfish window
257 # managers that when a dialog is activated it takes about 2
258 # seconds for the contents of the window to appear. But if
259 # tkraise() is called first then I get complaints from people
260 # using the twm window manager that when a dialog is activated
261 # it appears in the top right corner of the screen and also
262 # takes about 2 seconds to appear.
265 # Call update_idletasks to ensure certain window managers (eg:
266 # enlightenment and sawfish) do not cause Tk to delay for
267 # about two seconds before displaying window.
268 #window.update_idletasks()
272 if window.overrideredirect():
273 # The window is not under the control of the window manager
274 # and so we need to raise it ourselves.
277 #=============================================================================
280 # Megawidget abstract root class.
282 # This class provides methods which are inherited by classes
283 # implementing useful bases (this class doesn't provide a
284 # container widget inside which the megawidget can be built).
286 def __init__(self, parent = None, hullClass = None):
288 # Mapping from each megawidget option to a list of information
292 # - function to call when the option is initialised in the
293 # call to initialiseoptions() in the constructor or
294 # modified via configure(). If this is INITOPT, the
295 # option is an initialisation option (an option that can
296 # be set by the call to the constructor but can not be
297 # used with configure).
298 # This mapping is not initialised here, but in the call to
299 # defineoptions() which precedes construction of this base class.
301 # self._optionInfo = {}
303 # Mapping from each component name to a tuple of information
304 # about the component.
305 # - component widget instance
306 # - configure function of widget instance
307 # - the class of the widget (Frame, EntryField, etc)
308 # - cget function of widget instance
309 # - the name of the component group of this component, if any
310 self.__componentInfo = {}
312 # Mapping from alias names to the names of components or
314 self.__componentAliases = {}
316 # Contains information about the keywords provided to the
317 # constructor. It is a mapping from the keyword to a tuple
320 # - a boolean indicating if the keyword has been used.
321 # A keyword is used if, during the construction of a megawidget,
322 # - it is defined in a call to defineoptions() or addoptions(), or
323 # - it references, by name, a component of the megawidget, or
324 # - it references, by group, at least one component
325 # At the end of megawidget construction, a call is made to
326 # initialiseoptions() which reports an error if there are
327 # unused options given to the constructor.
329 # After megawidget construction, the dictionary contains
330 # keywords which refer to a dynamic component group, so that
331 # these components can be created after megawidget
332 # construction and still use the group options given to the
335 # self._constructorKeywords = {}
337 # List of dynamic component groups. If a group is included in
338 # this list, then it not an error if a keyword argument for
339 # the group is given to the constructor or to configure(), but
340 # no components with this group have been created.
341 # self._dynamicGroups = ()
343 if hullClass is None:
347 parent = Tkinter._default_root
350 self._hull = self.createcomponent('hull',
352 hullClass, (parent,))
353 _hullToMegaWidget[self._hull] = self
356 # Now that a widget has been created, query the Tk
357 # option database to get the default values for the
358 # options which have not been set in the call to the
359 # constructor. This assumes that defineoptions() is
360 # called before the __init__().
361 option_get = self.option_get
363 _DEFAULT = _OPT_DEFAULT
364 for name, info in self._optionInfo.items():
366 if value is _DEFAULT_OPTION_VALUE:
367 resourceClass = string.upper(name[0]) + name[1:]
368 value = option_get(name, resourceClass)
371 # Convert the string to int/float/tuple, etc
372 value = eval(value, {'__builtins__': {}})
377 info[_VALUE] = info[_DEFAULT]
380 # Clean up optionInfo in case it contains circular references
381 # in the function field, such as self._settitle in class
384 self._optionInfo = {}
385 if self._hull is not None:
386 del _hullToMegaWidget[self._hull]
389 #======================================================================
390 # Methods used (mainly) during the construction of the megawidget.
392 def defineoptions(self, keywords, optionDefs, dynamicGroups = ()):
393 # Create options, providing the default value and the method
394 # to call when the value is changed. If any option created by
395 # base classes has the same name as one in <optionDefs>, the
396 # base class's value and function will be overriden.
398 # This should be called before the constructor of the base
399 # class, so that default values defined in the derived class
400 # override those in the base class.
402 if not hasattr(self, '_constructorKeywords'):
403 # First time defineoptions has been called.
405 for option, value in keywords.items():
406 tmp[option] = [value, 0]
407 self._constructorKeywords = tmp
408 self._optionInfo = {}
409 self._initialiseoptions_counter = 0
410 self._initialiseoptions_counter = self._initialiseoptions_counter + 1
412 if not hasattr(self, '_dynamicGroups'):
413 self._dynamicGroups = ()
414 self._dynamicGroups = self._dynamicGroups + tuple(dynamicGroups)
415 self.addoptions(optionDefs)
417 def addoptions(self, optionDefs):
418 # Add additional options, providing the default value and the
419 # method to call when the value is changed. See
420 # "defineoptions" for more details
423 optionInfo = self._optionInfo
424 optionInfo_has_key = optionInfo.has_key
425 keywords = self._constructorKeywords
426 keywords_has_key = keywords.has_key
427 FUNCTION = _OPT_FUNCTION
429 for name, default, function in optionDefs:
431 # The option will already exist if it has been defined
432 # in a derived class. In this case, do not override the
433 # default value of the option or the callback function
435 if not optionInfo_has_key(name):
436 if keywords_has_key(name):
437 value = keywords[name][0]
438 optionInfo[name] = [default, value, function]
443 [default, _DEFAULT_OPTION_VALUE, function]
445 optionInfo[name] = [default, default, function]
446 elif optionInfo[name][FUNCTION] is None:
447 optionInfo[name][FUNCTION] = function
449 # This option is of the form "component_option". If this is
450 # not already defined in self._constructorKeywords add it.
451 # This allows a derived class to override the default value
452 # of an option of a component of a base class.
453 if not keywords_has_key(name):
454 keywords[name] = [default, 0]
456 def createcomponent(self, componentName, componentAliases,
457 componentGroup, widgetClass, *widgetArgs, **kw):
458 # Create a component (during construction or later).
460 if self.__componentInfo.has_key(componentName):
461 raise ValueError, 'Component "%s" already exists' % componentName
463 if '_' in componentName:
465 'Component name "%s" must not contain "_"' % componentName
467 if hasattr(self, '_constructorKeywords'):
468 keywords = self._constructorKeywords
471 for alias, component in componentAliases:
472 # Create aliases to the component and its sub-components.
473 index = string.find(component, '_')
475 self.__componentAliases[alias] = (component, None)
477 mainComponent = component[:index]
478 subComponent = component[(index + 1):]
479 self.__componentAliases[alias] = (mainComponent, subComponent)
481 # Remove aliases from the constructor keyword arguments by
482 # replacing any keyword arguments that begin with *alias*
483 # with corresponding keys beginning with *component*.
486 aliasLen = len(alias)
487 for option in keywords.keys():
488 if len(option) > aliasLen and option[:aliasLen] == alias:
489 newkey = component + '_' + option[aliasLen:]
490 keywords[newkey] = keywords[option]
493 componentPrefix = componentName + '_'
494 nameLen = len(componentPrefix)
495 for option in keywords.keys():
496 if len(option) > nameLen and option[:nameLen] == componentPrefix:
497 # The keyword argument refers to this component, so add
498 # this to the options to use when constructing the widget.
499 kw[option[nameLen:]] = keywords[option][0]
502 # Check if this keyword argument refers to the group
503 # of this component. If so, add this to the options
504 # to use when constructing the widget. Mark the
505 # keyword argument as being used, but do not remove it
506 # since it may be required when creating another
508 index = string.find(option, '_')
509 if index >= 0 and componentGroup == option[:index]:
510 rest = option[(index + 1):]
511 kw[rest] = keywords[option][0]
512 keywords[option][1] = 1
514 if kw.has_key('pyclass'):
515 widgetClass = kw['pyclass']
517 if widgetClass is None:
519 if len(widgetArgs) == 1 and type(widgetArgs[0]) == types.TupleType:
520 # Arguments to the constructor can be specified as either
521 # multiple trailing arguments to createcomponent() or as a
522 # single tuple argument.
523 widgetArgs = widgetArgs[0]
524 widget = apply(widgetClass, widgetArgs, kw)
525 componentClass = widget.__class__.__name__
526 self.__componentInfo[componentName] = (widget, widget.configure,
527 componentClass, widget.cget, componentGroup)
531 def destroycomponent(self, name):
532 # Remove a megawidget component.
534 # This command is for use by megawidget designers to destroy a
535 # megawidget component.
537 self.__componentInfo[name][0].destroy()
538 del self.__componentInfo[name]
540 def createlabel(self, parent, childCols = 1, childRows = 1):
542 labelpos = self['labelpos']
543 labelmargin = self['labelmargin']
547 label = self.createcomponent('label',
549 Tkinter.Label, (parent,))
551 if labelpos[0] in 'ns':
553 if labelpos[0] == 'n':
559 label.grid(column=2, row=row, columnspan=childCols, sticky=labelpos)
560 parent.grid_rowconfigure(margin, minsize=labelmargin)
563 if labelpos[0] == 'w':
569 label.grid(column=col, row=2, rowspan=childRows, sticky=labelpos)
570 parent.grid_columnconfigure(margin, minsize=labelmargin)
572 def initialiseoptions(self, dummy = None):
573 self._initialiseoptions_counter = self._initialiseoptions_counter - 1
574 if self._initialiseoptions_counter == 0:
576 keywords = self._constructorKeywords
577 for name in keywords.keys():
578 used = keywords[name][1]
580 # This keyword argument has not been used. If it
581 # does not refer to a dynamic group, mark it as
583 index = string.find(name, '_')
584 if index < 0 or name[:index] not in self._dynamicGroups:
585 unusedOptions.append(name)
586 if len(unusedOptions) > 0:
587 if len(unusedOptions) == 1:
588 text = 'Unknown option "'
590 text = 'Unknown options "'
591 raise KeyError, text + string.join(unusedOptions, ', ') + \
592 '" for ' + self.__class__.__name__
594 # Call the configuration callback function for every option.
595 FUNCTION = _OPT_FUNCTION
596 for info in self._optionInfo.values():
597 func = info[FUNCTION]
598 if func is not None and func is not INITOPT:
601 #======================================================================
602 # Method used to configure the megawidget.
604 def configure(self, option=None, **kw):
605 # Query or configure the megawidget options.
607 # If not empty, *kw* is a dictionary giving new
608 # values for some of the options of this megawidget or its
609 # components. For options defined for this megawidget, set
610 # the value of the option to the new value and call the
611 # configuration callback function, if any. For options of the
612 # form <component>_<option>, where <component> is a component
613 # of this megawidget, call the configure method of the
614 # component giving it the new value of the option. The
615 # <component> part may be an alias or a component group name.
617 # If *option* is None, return all megawidget configuration
618 # options and settings. Options are returned as standard 5
621 # If *option* is a string, return the 5 element tuple for the
622 # given configuration option.
624 # First, deal with the option queries.
626 # This configure call is querying the values of one or all options.
628 # (optionName, resourceName, resourceClass, default, value)
631 for option, config in self._optionInfo.items():
632 resourceClass = string.upper(option[0]) + option[1:]
633 rtn[option] = (option, option, resourceClass,
634 config[_OPT_DEFAULT], config[_OPT_VALUE])
637 config = self._optionInfo[option]
638 resourceClass = string.upper(option[0]) + option[1:]
639 return (option, option, resourceClass, config[_OPT_DEFAULT],
643 optionInfo = self._optionInfo
644 optionInfo_has_key = optionInfo.has_key
645 componentInfo = self.__componentInfo
646 componentInfo_has_key = componentInfo.has_key
647 componentAliases = self.__componentAliases
648 componentAliases_has_key = componentAliases.has_key
650 FUNCTION = _OPT_FUNCTION
652 # This will contain a list of options in *kw* which
653 # are known to this megawidget.
656 # This will contain information about the options in
657 # *kw* of the form <component>_<option>, where
658 # <component> is a component of this megawidget. It is a
659 # dictionary whose keys are the configure method of each
660 # component and whose values are a dictionary of options and
661 # values for the component.
663 indirectOptions_has_key = indirectOptions.has_key
665 for option, value in kw.items():
666 if optionInfo_has_key(option):
667 # This is one of the options of this megawidget.
668 # Make sure it is not an initialisation option.
669 if optionInfo[option][FUNCTION] is INITOPT:
671 'Cannot configure initialisation option "' \
672 + option + '" for ' + self.__class__.__name__
673 optionInfo[option][VALUE] = value
674 directOptions.append(option)
676 index = string.find(option, '_')
678 # This option may be of the form <component>_<option>.
679 component = option[:index]
680 componentOption = option[(index + 1):]
682 # Expand component alias
683 if componentAliases_has_key(component):
684 component, subComponent = componentAliases[component]
685 if subComponent is not None:
686 componentOption = subComponent + '_' \
689 # Expand option string to write on error
690 option = component + '_' + componentOption
692 if componentInfo_has_key(component):
693 # Configure the named component
694 componentConfigFuncs = [componentInfo[component][1]]
696 # Check if this is a group name and configure all
697 # components in the group.
698 componentConfigFuncs = []
699 for info in componentInfo.values():
700 if info[4] == component:
701 componentConfigFuncs.append(info[1])
703 if len(componentConfigFuncs) == 0 and \
704 component not in self._dynamicGroups:
705 raise KeyError, 'Unknown option "' + option + \
706 '" for ' + self.__class__.__name__
708 # Add the configure method(s) (may be more than
709 # one if this is configuring a component group)
710 # and option/value to dictionary.
711 for componentConfigFunc in componentConfigFuncs:
712 if not indirectOptions_has_key(componentConfigFunc):
713 indirectOptions[componentConfigFunc] = {}
714 indirectOptions[componentConfigFunc][componentOption] \
717 raise KeyError, 'Unknown option "' + option + \
718 '" for ' + self.__class__.__name__
720 # Call the configure methods for any components.
721 map(apply, indirectOptions.keys(),
722 ((),) * len(indirectOptions), indirectOptions.values())
724 # Call the configuration callback function for each option.
725 for option in directOptions:
726 info = optionInfo[option]
727 func = info[_OPT_FUNCTION]
731 def __setitem__(self, key, value):
732 apply(self.configure, (), {key: value})
734 #======================================================================
735 # Methods used to query the megawidget.
737 def component(self, name):
738 # Return a component widget of the megawidget given the
740 # This allows the user of a megawidget to access and configure
741 # widget components directly.
743 # Find the main component and any subcomponents
744 index = string.find(name, '_')
747 remainingComponents = None
749 component = name[:index]
750 remainingComponents = name[(index + 1):]
752 # Expand component alias
753 if self.__componentAliases.has_key(component):
754 component, subComponent = self.__componentAliases[component]
755 if subComponent is not None:
756 if remainingComponents is None:
757 remainingComponents = subComponent
759 remainingComponents = subComponent + '_' \
760 + remainingComponents
762 widget = self.__componentInfo[component][0]
763 if remainingComponents is None:
766 return widget.component(remainingComponents)
771 def hulldestroyed(self):
772 return not _hullToMegaWidget.has_key(self._hull)
775 return str(self._hull)
777 def cget(self, option):
778 # Get current configuration setting.
780 # Return the value of an option, for example myWidget['font'].
782 if self._optionInfo.has_key(option):
783 return self._optionInfo[option][_OPT_VALUE]
785 index = string.find(option, '_')
787 component = option[:index]
788 componentOption = option[(index + 1):]
790 # Expand component alias
791 if self.__componentAliases.has_key(component):
792 component, subComponent = self.__componentAliases[component]
793 if subComponent is not None:
794 componentOption = subComponent + '_' + componentOption
796 # Expand option string to write on error
797 option = component + '_' + componentOption
799 if self.__componentInfo.has_key(component):
800 # Call cget on the component.
801 componentCget = self.__componentInfo[component][3]
802 return componentCget(componentOption)
804 # If this is a group name, call cget for one of
805 # the components in the group.
806 for info in self.__componentInfo.values():
807 if info[4] == component:
808 componentCget = info[3]
809 return componentCget(componentOption)
811 raise KeyError, 'Unknown option "' + option + \
812 '" for ' + self.__class__.__name__
816 def isinitoption(self, option):
817 return self._optionInfo[option][_OPT_FUNCTION] is INITOPT
821 if hasattr(self, '_optionInfo'):
822 for option, info in self._optionInfo.items():
823 isinit = info[_OPT_FUNCTION] is INITOPT
824 default = info[_OPT_DEFAULT]
825 options.append((option, default, isinit))
829 def components(self):
830 # Return a list of all components.
832 # This list includes the 'hull' component and all widget subcomponents
834 names = self.__componentInfo.keys()
838 def componentaliases(self):
839 # Return a list of all component aliases.
841 componentAliases = self.__componentAliases
843 names = componentAliases.keys()
847 (mainComponent, subComponent) = componentAliases[alias]
848 if subComponent is None:
849 rtn.append((alias, mainComponent))
851 rtn.append((alias, mainComponent + '_' + subComponent))
855 def componentgroup(self, name):
856 return self.__componentInfo[name][4]
858 #=============================================================================
860 # The grab functions are mainly called by the activate() and
861 # deactivate() methods.
863 # Use pushgrab() to add a new window to the grab stack. This
864 # releases the grab by the window currently on top of the stack (if
865 # there is one) and gives the grab and focus to the new widget.
867 # To remove the grab from the window on top of the grab stack, call
870 # Use releasegrabs() to release the grab and clear the grab stack.
872 def pushgrab(grabWindow, globalMode, deactivateFunction):
873 prevFocus = grabWindow.tk.call('focus')
875 'grabWindow' : grabWindow,
876 'globalMode' : globalMode,
877 'previousFocus' : prevFocus,
878 'deactivateFunction' : deactivateFunction,
880 _grabStack.append(grabInfo)
882 grabWindow.focus_set()
885 # Return the grab to the next window in the grab stack, if any.
887 # If this window is not at the top of the grab stack, then it has
888 # just been deleted by the window manager or deactivated by a
889 # timer. Call the deactivate method for the modal dialog above
890 # this one on the stack.
891 if _grabStack[-1]['grabWindow'] != window:
892 for index in range(len(_grabStack)):
893 if _grabStack[index]['grabWindow'] == window:
894 _grabStack[index + 1]['deactivateFunction']()
897 grabInfo = _grabStack[-1]
900 topWidget = grabInfo['grabWindow']
901 prevFocus = grabInfo['previousFocus']
902 globalMode = grabInfo['globalMode']
904 if globalMode != 'nograb':
905 topWidget.grab_release()
907 if len(_grabStack) > 0:
911 topWidget.tk.call('focus', prevFocus)
912 except Tkinter.TclError:
913 # Previous focus widget has been deleted. Set focus
915 Tkinter._default_root.focus_set()
917 # Make sure that focus does not remain on the released widget.
918 if len(_grabStack) > 0:
919 topWidget = _grabStack[-1]['grabWindow']
920 topWidget.focus_set()
922 Tkinter._default_root.focus_set()
924 def grabstacktopwindow():
925 if len(_grabStack) == 0:
928 return _grabStack[-1]['grabWindow']
931 # Release grab and clear the grab stack.
933 current = Tkinter._default_root.grab_current()
934 if current is not None:
935 current.grab_release()
939 grabInfo = _grabStack[-1]
940 topWidget = grabInfo['grabWindow']
941 globalMode = grabInfo['globalMode']
943 if globalMode == 'nograb':
949 topWidget.grab_set_global()
953 except Tkinter.TclError:
954 # Another application has grab. Keep trying until
958 #=============================================================================
960 class MegaToplevel(MegaArchetype):
962 def __init__(self, parent = None, **kw):
963 # Define the options for this megawidget.
965 ('activatecommand', None, None),
966 ('deactivatecommand', None, None),
967 ('master', None, None),
968 ('title', None, self._settitle),
969 ('hull_class', self.__class__.__name__, None),
971 self.defineoptions(kw, optiondefs)
973 # Initialise the base class (after defining the options).
974 MegaArchetype.__init__(self, parent, Tkinter.Toplevel)
976 # Initialise instance.
978 # Set WM_DELETE_WINDOW protocol, deleting any old callback, so
979 # memory does not leak.
980 if hasattr(self._hull, '_Pmw_WM_DELETE_name'):
981 self._hull.tk.deletecommand(self._hull._Pmw_WM_DELETE_name)
982 self._hull._Pmw_WM_DELETE_name = \
983 self.register(self._userDeleteWindow, needcleanup = 0)
984 self.protocol('WM_DELETE_WINDOW', self._hull._Pmw_WM_DELETE_name)
986 # Initialise instance variables.
988 self._firstShowing = 1
989 # Used by show() to ensure window retains previous position on screen.
991 # The IntVar() variable to wait on during a modal dialog.
995 self._userDeleteFunc = self.destroy
996 self._userModalDeleteFunc = self.deactivate
998 # Check keywords and initialise options.
999 self.initialiseoptions()
1001 def _settitle(self):
1002 title = self['title']
1003 if title is not None:
1006 def userdeletefunc(self, func=None):
1008 self._userDeleteFunc = func
1010 return self._userDeleteFunc
1012 def usermodaldeletefunc(self, func=None):
1014 self._userModalDeleteFunc = func
1016 return self._userModalDeleteFunc
1018 def _userDeleteWindow(self):
1020 self._userModalDeleteFunc()
1022 self._userDeleteFunc()
1025 # Allow this to be called more than once.
1026 if _hullToMegaWidget.has_key(self._hull):
1029 # Remove circular references, so that object can get cleaned up.
1030 del self._userDeleteFunc
1031 del self._userModalDeleteFunc
1033 MegaArchetype.destroy(self)
1035 def show(self, master = None):
1036 if self.state() != 'normal':
1037 if self._firstShowing:
1038 # Just let the window manager determine the window
1039 # position for the first time.
1042 # Position the window at the same place it was last time.
1043 geom = self._sameposition()
1044 setgeometryanddeiconify(self, geom)
1046 if self._firstShowing:
1047 self._firstShowing = 0
1049 if self.transient() == '':
1052 # Do this last, otherwise get flashing on NT:
1053 if master is not None:
1054 if master == 'parent':
1055 parent = self.winfo_parent()
1056 # winfo_parent() should return the parent widget, but the
1057 # the current version of Tkinter returns a string.
1058 if type(parent) == types.StringType:
1059 parent = self._hull._nametowidget(parent)
1060 master = parent.winfo_toplevel()
1061 self.transient(master)
1065 def _centreonscreen(self):
1066 # Centre the window on the screen. (Actually halfway across
1067 # and one third down.)
1069 parent = self.winfo_parent()
1070 if type(parent) == types.StringType:
1071 parent = self._hull._nametowidget(parent)
1073 # Find size of window.
1074 self.update_idletasks()
1075 width = self.winfo_width()
1076 height = self.winfo_height()
1077 if width == 1 and height == 1:
1078 # If the window has not yet been displayed, its size is
1079 # reported as 1x1, so use requested size.
1080 width = self.winfo_reqwidth()
1081 height = self.winfo_reqheight()
1083 # Place in centre of screen:
1084 x = (self.winfo_screenwidth() - width) / 2 - parent.winfo_vrootx()
1085 y = (self.winfo_screenheight() - height) / 3 - parent.winfo_vrooty()
1090 return '+%d+%d' % (x, y)
1092 def _sameposition(self):
1093 # Position the window at the same place it was last time.
1095 geometry = self.geometry()
1096 index = string.find(geometry, '+')
1098 return geometry[index:]
1102 def activate(self, globalMode = 0, geometry = 'centerscreenfirst'):
1104 raise ValueError, 'Window is already active'
1105 if self.state() == 'normal':
1112 if self._wait is None:
1113 self._wait = Tkinter.IntVar()
1116 if geometry == 'centerscreenalways':
1117 geom = self._centreonscreen()
1118 elif geometry == 'centerscreenfirst':
1119 if self._firstShowing:
1120 # Centre the window the first time it is displayed.
1121 geom = self._centreonscreen()
1123 # Position the window at the same place it was last time.
1124 geom = self._sameposition()
1125 elif geometry[:5] == 'first':
1126 if self._firstShowing:
1129 # Position the window at the same place it was last time.
1130 geom = self._sameposition()
1134 self._firstShowing = 0
1136 setgeometryanddeiconify(self, geom)
1138 # Do this last, otherwise get flashing on NT:
1139 master = self['master']
1140 if master is not None:
1141 if master == 'parent':
1142 parent = self.winfo_parent()
1143 # winfo_parent() should return the parent widget, but the
1144 # the current version of Tkinter returns a string.
1145 if type(parent) == types.StringType:
1146 parent = self._hull._nametowidget(parent)
1147 master = parent.winfo_toplevel()
1148 self.transient(master)
1150 pushgrab(self._hull, globalMode, self.deactivate)
1151 command = self['activatecommand']
1152 if callable(command):
1154 self.wait_variable(self._wait)
1158 def deactivate(self, result=None):
1159 if not self._active:
1163 # Restore the focus before withdrawing the window, since
1164 # otherwise the window manager may take the focus away so we
1165 # can't redirect it. Also, return the grab to the next active
1166 # window in the stack, if any.
1169 command = self['deactivatecommand']
1170 if callable(command):
1174 hidebusycursor(forceFocusRestore = 1)
1176 self._result = result
1182 forwardmethods(MegaToplevel, Tkinter.Toplevel, '_hull')
1184 #=============================================================================
1186 class MegaWidget(MegaArchetype):
1187 def __init__(self, parent = None, **kw):
1188 # Define the options for this megawidget.
1190 ('hull_class', self.__class__.__name__, None),
1192 self.defineoptions(kw, optiondefs)
1194 # Initialise the base class (after defining the options).
1195 MegaArchetype.__init__(self, parent, Tkinter.Frame)
1197 # Check keywords and initialise options.
1198 self.initialiseoptions()
1200 forwardmethods(MegaWidget, Tkinter.Frame, '_hull')
1202 #=============================================================================
1208 def tracetk(root = None, on = 1, withStackTrace = 0, file=None):
1209 global _withStackTrace
1214 root = Tkinter._default_root
1216 _withStackTrace = withStackTrace
1219 if hasattr(root.tk, '__class__'):
1220 # Tracing already on
1223 _traceTkFile = sys.stderr
1226 tk = _TraceTk(root.tk)
1228 if not hasattr(root.tk, '__class__'):
1229 # Tracing already off
1231 tk = root.tk.getTclInterp()
1232 _setTkInterps(root, tk)
1234 def showbusycursor():
1236 _addRootToToplevelBusyInfo()
1237 root = Tkinter._default_root
1240 'newBusyWindows' : [],
1241 'previousFocus' : None,
1244 _busyStack.append(busyInfo)
1246 if _disableKeyboardWhileBusy:
1247 # Remember the focus as it is now, before it is changed.
1248 busyInfo['previousFocus'] = root.tk.call('focus')
1250 if not _havebltbusy(root):
1251 # No busy command, so don't call busy hold on any windows.
1254 for (window, winInfo) in _toplevelBusyInfo.items():
1255 if (window.state() != 'withdrawn' and not winInfo['isBusy']
1256 and not winInfo['excludeFromBusy']):
1257 busyInfo['newBusyWindows'].append(window)
1258 winInfo['isBusy'] = 1
1259 _busy_hold(window, winInfo['busyCursorName'])
1261 # Make sure that no events for the busy window get
1262 # through to Tkinter, otherwise it will crash in
1263 # _nametowidget with a 'KeyError: _Busy' if there is
1264 # a binding on the toplevel window.
1265 window.tk.call('bindtags', winInfo['busyWindow'], 'Pmw_Dummy_Tag')
1267 if _disableKeyboardWhileBusy:
1268 # Remember previous focus widget for this toplevel window
1269 # and set focus to the busy window, which will ignore all
1271 winInfo['windowFocus'] = \
1272 window.tk.call('focus', '-lastfor', window._w)
1273 window.tk.call('focus', winInfo['busyWindow'])
1274 busyInfo['busyFocus'] = winInfo['busyWindow']
1276 if len(busyInfo['newBusyWindows']) > 0:
1278 # NT needs an "update" before it will change the cursor.
1281 window.update_idletasks()
1283 def hidebusycursor(forceFocusRestore = 0):
1285 # Remember the focus as it is now, before it is changed.
1286 root = Tkinter._default_root
1287 if _disableKeyboardWhileBusy:
1288 currentFocus = root.tk.call('focus')
1290 # Pop the busy info off the stack.
1291 busyInfo = _busyStack[-1]
1294 for window in busyInfo['newBusyWindows']:
1295 # If this window has not been deleted, release the busy cursor.
1296 if _toplevelBusyInfo.has_key(window):
1297 winInfo = _toplevelBusyInfo[window]
1298 winInfo['isBusy'] = 0
1299 _busy_release(window)
1301 if _disableKeyboardWhileBusy:
1302 # Restore previous focus window for this toplevel window,
1303 # but only if is still set to the busy window (it may have
1305 windowFocusNow = window.tk.call('focus', '-lastfor', window._w)
1306 if windowFocusNow == winInfo['busyWindow']:
1308 window.tk.call('focus', winInfo['windowFocus'])
1309 except Tkinter.TclError:
1310 # Previous focus widget has been deleted. Set focus
1311 # to toplevel window instead (can't leave focus on
1315 if _disableKeyboardWhileBusy:
1316 # Restore the focus, depending on whether the focus had changed
1317 # between the calls to showbusycursor and hidebusycursor.
1318 if forceFocusRestore or busyInfo['busyFocus'] == currentFocus:
1319 # The focus had not changed, so restore it to as it was before
1320 # the call to showbusycursor,
1321 previousFocus = busyInfo['previousFocus']
1322 if previousFocus is not None:
1324 root.tk.call('focus', previousFocus)
1325 except Tkinter.TclError:
1326 # Previous focus widget has been deleted; forget it.
1329 # The focus had changed, so restore it to what it had been
1330 # changed to before the call to hidebusycursor.
1331 root.tk.call('focus', currentFocus)
1333 def clearbusycursor():
1334 while len(_busyStack) > 0:
1337 def setbusycursorattributes(window, **kw):
1338 _addRootToToplevelBusyInfo()
1339 for name, value in kw.items():
1340 if name == 'exclude':
1341 _toplevelBusyInfo[window]['excludeFromBusy'] = value
1342 elif name == 'cursorName':
1343 _toplevelBusyInfo[window]['busyCursorName'] = value
1345 raise KeyError, 'Unknown busycursor attribute "' + name + '"'
1347 def _addRootToToplevelBusyInfo():
1348 # Include the Tk root window in the list of toplevels. This must
1349 # not be called before Tkinter has had a chance to be initialised by
1352 root = Tkinter._default_root
1355 if not _toplevelBusyInfo.has_key(root):
1356 _addToplevelBusyInfo(root)
1358 def busycallback(command, updateFunction = None):
1359 if not callable(command):
1361 'cannot register non-command busy callback %s %s' % \
1362 (repr(command), type(command))
1363 wrapper = _BusyWrapper(command, updateFunction)
1364 return wrapper.callback
1366 _errorReportFile = None
1369 def reporterrorstofile(file = None):
1370 global _errorReportFile
1371 _errorReportFile = file
1373 def displayerror(text):
1376 if _errorReportFile is not None:
1377 _errorReportFile.write(text + '\n')
1379 # Print error on standard error as well as to error window.
1380 # Useful if error window fails to be displayed, for example
1381 # when exception is triggered in a <Destroy> binding for root
1383 sys.stderr.write(text + '\n')
1385 if _errorWindow is None:
1386 # The error window has not yet been created.
1387 _errorWindow = _ErrorWindow()
1389 _errorWindow.showerror(text)
1392 _disableKeyboardWhileBusy = 1
1400 disableKeyboardWhileBusy = None,
1402 # Remember if show/hidebusycursor should ignore keyboard events.
1403 global _disableKeyboardWhileBusy
1404 if disableKeyboardWhileBusy is not None:
1405 _disableKeyboardWhileBusy = disableKeyboardWhileBusy
1407 # Do not use blt busy command if noBltBusy is set. Otherwise,
1408 # use blt busy if it is available.
1413 # Save flag specifying whether the Tk option database should be
1414 # queried when setting megawidget option default values.
1415 global _useTkOptionDb
1416 _useTkOptionDb = useTkOptionDb
1418 # If we haven't been given a root window, use the default or
1421 if Tkinter._default_root is None:
1424 root = Tkinter._default_root
1426 # If this call is initialising a different Tk interpreter than the
1427 # last call, then re-initialise all global variables. Assume the
1428 # last interpreter has been destroyed - ie: Pmw does not (yet)
1429 # support multiple simultaneous interpreters.
1431 if _root is not None and _root != root:
1435 global _hullToMegaWidget
1436 global _toplevelBusyInfo
1440 _hullToMegaWidget = {}
1441 _toplevelBusyInfo = {}
1444 # Trap Tkinter Toplevel constructors so that a list of Toplevels
1445 # can be maintained.
1446 Tkinter.Toplevel.title = __TkinterToplevelTitle
1448 # Trap Tkinter widget destruction so that megawidgets can be
1449 # destroyed when their hull widget is destoyed and the list of
1450 # Toplevels can be pruned.
1451 Tkinter.Toplevel.destroy = __TkinterToplevelDestroy
1452 Tkinter.Widget.destroy = __TkinterWidgetDestroy
1454 # Modify Tkinter's CallWrapper class to improve the display of
1455 # errors which occur in callbacks.
1456 Tkinter.CallWrapper = __TkinterCallWrapper
1458 # Make sure we get to know when the window manager deletes the
1459 # root window. Only do this if the protocol has not yet been set.
1460 # This is required if there is a modal dialog displayed and the
1461 # window manager deletes the root window. Otherwise the
1462 # application will not exit, even though there are no windows.
1463 if root.protocol('WM_DELETE_WINDOW') == '':
1464 root.protocol('WM_DELETE_WINDOW', root.destroy)
1466 # Set the base font size for the application and set the
1467 # Tk option database font resources.
1468 import PmwLogicalFont
1469 PmwLogicalFont._font_initialise(root, size, fontScheme)
1473 def alignlabels(widgets, sticky = None):
1474 if len(widgets) == 0:
1477 widgets[0].update_idletasks()
1479 # Determine the size of the maximum length label string.
1481 for iwid in widgets:
1482 labelWidth = iwid.grid_bbox(0, 1)[2]
1483 if labelWidth > maxLabelWidth:
1484 maxLabelWidth = labelWidth
1486 # Adjust the margins for the labels such that the child sites and
1488 for iwid in widgets:
1489 if sticky is not None:
1490 iwid.component('label').grid(sticky=sticky)
1491 iwid.grid_columnconfigure(0, minsize = maxLabelWidth)
1492 #=============================================================================
1496 _callToTkReturned = 1
1497 _recursionCounter = 1
1500 def __init__(self, tclInterp):
1501 self.tclInterp = tclInterp
1503 def getTclInterp(self):
1504 return self.tclInterp
1506 # Calling from python into Tk.
1507 def call(self, *args, **kw):
1508 global _callToTkReturned
1509 global _recursionCounter
1511 _callToTkReturned = 0
1512 if len(args) == 1 and type(args[0]) == types.TupleType:
1513 argStr = str(args[0])
1516 _traceTkFile.write('CALL TK> %d:%s%s' %
1517 (_recursionCounter, ' ' * _recursionCounter, argStr))
1518 _recursionCounter = _recursionCounter + 1
1520 result = apply(self.tclInterp.call, args, kw)
1521 except Tkinter.TclError, errorString:
1522 _callToTkReturned = 1
1523 _recursionCounter = _recursionCounter - 1
1524 _traceTkFile.write('\nTK ERROR> %d:%s-> %s\n' %
1525 (_recursionCounter, ' ' * _recursionCounter,
1528 _traceTkFile.write('CALL TK> stack:\n')
1529 traceback.print_stack()
1530 raise Tkinter.TclError, errorString
1532 _recursionCounter = _recursionCounter - 1
1533 if _callToTkReturned:
1534 _traceTkFile.write('CALL RTN> %d:%s-> %s' %
1535 (_recursionCounter, ' ' * _recursionCounter, repr(result)))
1537 _callToTkReturned = 1
1539 _traceTkFile.write(' -> %s' % repr(result))
1540 _traceTkFile.write('\n')
1542 _traceTkFile.write('CALL TK> stack:\n')
1543 traceback.print_stack()
1545 _traceTkFile.flush()
1548 def __getattr__(self, key):
1549 return getattr(self.tclInterp, key)
1551 def _setTkInterps(window, tk):
1553 for child in window.children.values():
1554 _setTkInterps(child, tk)
1556 #=============================================================================
1558 # Functions to display a busy cursor. Keep a list of all toplevels
1559 # and display the busy cursor over them. The list will contain the Tk
1560 # root toplevel window as well as all other toplevel windows.
1561 # Also keep a list of the widget which last had focus for each
1564 # Map from toplevel windows to
1565 # {'isBusy', 'windowFocus', 'busyWindow',
1566 # 'excludeFromBusy', 'busyCursorName'}
1567 _toplevelBusyInfo = {}
1569 # Pmw needs to know all toplevel windows, so that it can call blt busy
1570 # on them. This is a hack so we get notified when a Tk topevel is
1571 # created. Ideally, the __init__ 'method' should be overridden, but
1572 # it is a 'read-only special attribute'. Luckily, title() is always
1573 # called from the Tkinter Toplevel constructor.
1575 def _addToplevelBusyInfo(window):
1576 if window._w == '.':
1577 busyWindow = '._Busy'
1579 busyWindow = window._w + '._Busy'
1581 _toplevelBusyInfo[window] = {
1583 'windowFocus' : None,
1584 'busyWindow' : busyWindow,
1585 'excludeFromBusy' : 0,
1586 'busyCursorName' : None,
1589 def __TkinterToplevelTitle(self, *args):
1590 # If this is being called from the constructor, include this
1591 # Toplevel in the list of toplevels and set the initial
1592 # WM_DELETE_WINDOW protocol to destroy() so that we get to know
1594 if not _toplevelBusyInfo.has_key(self):
1595 _addToplevelBusyInfo(self)
1596 self._Pmw_WM_DELETE_name = self.register(self.destroy, None, 0)
1597 self.protocol('WM_DELETE_WINDOW', self._Pmw_WM_DELETE_name)
1599 return apply(Tkinter.Wm.title, (self,) + args)
1602 def _havebltbusy(window):
1603 global _busy_hold, _busy_release, _haveBltBusy
1604 if _haveBltBusy is None:
1606 _haveBltBusy = PmwBlt.havebltbusy(window)
1607 _busy_hold = PmwBlt.busy_hold
1609 # There is a bug in Blt 2.4i on NT where the busy window
1610 # does not follow changes in the children of a window.
1611 # Using forget works around the problem.
1612 _busy_release = PmwBlt.busy_forget
1614 _busy_release = PmwBlt.busy_release
1618 def __init__(self, command, updateFunction):
1619 self._command = command
1620 self._updateFunction = updateFunction
1622 def callback(self, *args):
1624 rtn = apply(self._command, args)
1626 # Call update before hiding the busy windows to clear any
1627 # events that may have occurred over the busy windows.
1628 if callable(self._updateFunction):
1629 self._updateFunction()
1634 #=============================================================================
1636 def drawarrow(canvas, color, direction, tag, baseOffset = 0.25, edgeOffset = 0.15):
1639 bw = (string.atoi(canvas['borderwidth']) +
1640 string.atoi(canvas['highlightthickness']))
1641 width = string.atoi(canvas['width'])
1642 height = string.atoi(canvas['height'])
1644 if direction in ('up', 'down'):
1645 majorDimension = height
1646 minorDimension = width
1648 majorDimension = width
1649 minorDimension = height
1651 offset = round(baseOffset * majorDimension)
1652 if direction in ('down', 'right'):
1654 apex = bw + majorDimension - offset
1656 base = bw + majorDimension - offset
1659 if minorDimension > 3 and minorDimension % 2 == 0:
1660 minorDimension = minorDimension - 1
1661 half = int(minorDimension * (1 - 2 * edgeOffset)) / 2
1662 low = round(bw + edgeOffset * minorDimension)
1664 high = low + 2 * half
1666 if direction in ('up', 'down'):
1667 coords = (low, base, high, base, middle, apex)
1669 coords = (base, low, base, high, apex, middle)
1670 kw = {'fill' : color, 'outline' : color, 'tag' : tag}
1671 apply(canvas.create_polygon, coords, kw)
1673 #=============================================================================
1675 # Modify the Tkinter destroy methods so that it notifies us when a Tk
1676 # toplevel or frame is destroyed.
1678 # A map from the 'hull' component of a megawidget to the megawidget.
1679 # This is used to clean up a megawidget when its hull is destroyed.
1680 _hullToMegaWidget = {}
1682 def __TkinterToplevelDestroy(tkWidget):
1683 if _hullToMegaWidget.has_key(tkWidget):
1684 mega = _hullToMegaWidget[tkWidget]
1688 _reporterror(mega.destroy, ())
1690 # Delete the busy info structure for this toplevel (if the
1691 # window was created before Pmw.initialise() was called, it
1692 # will not have any.
1693 if _toplevelBusyInfo.has_key(tkWidget):
1694 del _toplevelBusyInfo[tkWidget]
1695 if hasattr(tkWidget, '_Pmw_WM_DELETE_name'):
1696 tkWidget.tk.deletecommand(tkWidget._Pmw_WM_DELETE_name)
1697 del tkWidget._Pmw_WM_DELETE_name
1698 Tkinter.BaseWidget.destroy(tkWidget)
1700 def __TkinterWidgetDestroy(tkWidget):
1701 if _hullToMegaWidget.has_key(tkWidget):
1702 mega = _hullToMegaWidget[tkWidget]
1706 _reporterror(mega.destroy, ())
1708 Tkinter.BaseWidget.destroy(tkWidget)
1710 #=============================================================================
1712 # Add code to Tkinter to improve the display of errors which occur in
1715 class __TkinterCallWrapper:
1716 def __init__(self, func, subst, widget):
1719 self.widget = widget
1721 # Calling back from Tk into python.
1722 def __call__(self, *args):
1725 args = apply(self.subst, args)
1727 if not _callToTkReturned:
1728 _traceTkFile.write('\n')
1729 if hasattr(self.func, 'im_class'):
1730 name = self.func.im_class.__name__ + '.' + \
1733 name = self.func.__name__
1734 if len(args) == 1 and hasattr(args[0], 'type'):
1735 # The argument to the callback is an event.
1736 eventName = _eventTypeToName[string.atoi(args[0].type)]
1737 if eventName in ('KeyPress', 'KeyRelease',):
1738 argStr = '(%s %s Event: %s)' % \
1739 (eventName, args[0].keysym, args[0].widget)
1741 argStr = '(%s Event, %s)' % (eventName, args[0].widget)
1744 _traceTkFile.write('CALLBACK> %d:%s%s%s\n' %
1745 (_recursionCounter, ' ' * _recursionCounter, name, argStr))
1746 _traceTkFile.flush()
1747 return apply(self.func, args)
1748 except SystemExit, msg:
1749 raise SystemExit, msg
1751 _reporterror(self.func, args)
1753 _eventTypeToName = {
1754 2 : 'KeyPress', 15 : 'VisibilityNotify', 28 : 'PropertyNotify',
1755 3 : 'KeyRelease', 16 : 'CreateNotify', 29 : 'SelectionClear',
1756 4 : 'ButtonPress', 17 : 'DestroyNotify', 30 : 'SelectionRequest',
1757 5 : 'ButtonRelease', 18 : 'UnmapNotify', 31 : 'SelectionNotify',
1758 6 : 'MotionNotify', 19 : 'MapNotify', 32 : 'ColormapNotify',
1759 7 : 'EnterNotify', 20 : 'MapRequest', 33 : 'ClientMessage',
1760 8 : 'LeaveNotify', 21 : 'ReparentNotify', 34 : 'MappingNotify',
1761 9 : 'FocusIn', 22 : 'ConfigureNotify', 35 : 'VirtualEvents',
1762 10 : 'FocusOut', 23 : 'ConfigureRequest', 36 : 'ActivateNotify',
1763 11 : 'KeymapNotify', 24 : 'GravityNotify', 37 : 'DeactivateNotify',
1764 12 : 'Expose', 25 : 'ResizeRequest', 38 : 'MouseWheelEvent',
1765 13 : 'GraphicsExpose', 26 : 'CirculateNotify',
1766 14 : 'NoExpose', 27 : 'CirculateRequest',
1769 def _reporterror(func, args):
1770 # Fetch current exception values.
1771 exc_type, exc_value, exc_traceback = sys.exc_info()
1773 # Give basic information about the callback exception.
1774 if type(exc_type) == types.ClassType:
1775 # Handle python 1.5 class exceptions.
1776 exc_type = exc_type.__name__
1777 msg = exc_type + ' Exception in Tk callback\n'
1778 msg = msg + ' Function: %s (type: %s)\n' % (repr(func), type(func))
1779 msg = msg + ' Args: %s\n' % str(args)
1781 if type(args) == types.TupleType and len(args) > 0 and \
1782 hasattr(args[0], 'type'):
1787 # If the argument to the callback is an event, add the event type.
1789 eventNum = string.atoi(args[0].type)
1790 if eventNum in _eventTypeToName.keys():
1791 msg = msg + ' Event type: %s (type num: %d)\n' % \
1792 (_eventTypeToName[eventNum], eventNum)
1794 msg = msg + ' Unknown event type (type num: %d)\n' % eventNum
1796 # Add the traceback.
1797 msg = msg + 'Traceback (innermost last):\n'
1798 for tr in traceback.extract_tb(exc_traceback):
1799 msg = msg + ' File "%s", line %s, in %s\n' % (tr[0], tr[1], tr[2])
1800 msg = msg + ' %s\n' % tr[3]
1801 msg = msg + '%s: %s\n' % (exc_type, exc_value)
1803 # If the argument to the callback is an event, add the event contents.
1805 msg = msg + '\n================================================\n'
1806 msg = msg + ' Event contents:\n'
1807 keys = args[0].__dict__.keys()
1810 msg = msg + ' %s: %s\n' % (key, args[0].__dict__[key])
1821 self._errorQueue = []
1822 self._errorCount = 0
1824 self._firstShowing = 1
1826 # Create the toplevel window
1827 self._top = Tkinter.Toplevel()
1828 self._top.protocol('WM_DELETE_WINDOW', self._hide)
1829 self._top.title('Error in background function')
1830 self._top.iconname('Background error')
1832 # Create the text widget and scrollbar in a frame
1833 upperframe = Tkinter.Frame(self._top)
1835 scrollbar = Tkinter.Scrollbar(upperframe, orient='vertical')
1836 scrollbar.pack(side = 'right', fill = 'y')
1838 self._text = Tkinter.Text(upperframe, yscrollcommand=scrollbar.set)
1839 self._text.pack(fill = 'both', expand = 1)
1840 scrollbar.configure(command=self._text.yview)
1842 # Create the buttons and label in a frame
1843 lowerframe = Tkinter.Frame(self._top)
1845 ignore = Tkinter.Button(lowerframe,
1846 text = 'Ignore remaining errors', command = self._hide)
1847 ignore.pack(side='left')
1849 self._nextError = Tkinter.Button(lowerframe,
1850 text = 'Show next error', command = self._next)
1851 self._nextError.pack(side='left')
1853 self._label = Tkinter.Label(lowerframe, relief='ridge')
1854 self._label.pack(side='left', fill='x', expand=1)
1856 # Pack the lower frame first so that it does not disappear
1857 # when the window is resized.
1858 lowerframe.pack(side = 'bottom', fill = 'x')
1859 upperframe.pack(side = 'bottom', fill = 'both', expand = 1)
1861 def showerror(self, text):
1863 self._errorQueue.append(text)
1868 # Display the error window in the same place it was before.
1869 if self._top.state() == 'normal':
1870 # If update_idletasks is not called here, the window may
1871 # be placed partially off the screen. Also, if it is not
1872 # called and many errors are generated quickly in
1873 # succession, the error window may not display errors
1874 # until the last one is generated and the interpreter
1876 # XXX: remove this, since it causes omppython to go into an
1877 # infinite loop if an error occurs in an omp callback.
1878 # self._top.update_idletasks()
1882 if self._firstShowing:
1885 geometry = self._top.geometry()
1886 index = string.find(geometry, '+')
1888 geom = geometry[index:]
1891 setgeometryanddeiconify(self._top, geom)
1893 if self._firstShowing:
1894 self._firstShowing = 0
1900 self._updateButtons()
1902 # Release any grab, so that buttons in the error window work.
1906 self._errorCount = self._errorCount + len(self._errorQueue)
1907 self._errorQueue = []
1908 self._top.withdraw()
1912 # Display the next error in the queue.
1914 text = self._errorQueue[0]
1915 del self._errorQueue[0]
1918 self._updateButtons()
1920 def _display(self, text):
1921 self._errorCount = self._errorCount + 1
1922 text = 'Error: %d\n%s' % (self._errorCount, text)
1923 self._text.delete('1.0', 'end')
1924 self._text.insert('end', text)
1926 def _updateButtons(self):
1927 numQueued = len(self._errorQueue)
1929 self._label.configure(text='%d more errors' % numQueued)
1930 self._nextError.configure(state='normal')
1932 self._label.configure(text='No more errors')
1933 self._nextError.configure(state='disabled')