]> SALOME platform Git repositories - tools/eficas.git/blob - Pmw/Pmw_1_2/lib/PmwBase.py
Salome HOME
Modif V6_4_°
[tools/eficas.git] / Pmw / Pmw_1_2 / lib / PmwBase.py
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
17 import os
18 import string
19 import sys
20 import traceback
21 import types
22 import Tkinter
23
24 # Special values used in index() methods of several megawidgets.
25 END = ['end']
26 SELECT = ['select']
27 DEFAULT = ['default']
28
29 # Constant used to indicate that an option can only be set by a call
30 # to the constructor.
31 INITOPT = ['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.
94 def __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
111 def __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
128 # Get a unique id
129 __counter = 0
130 def __unique():
131   global __counter
132   __counter = __counter + 1
133   return str(__counter)
134
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.
139 __funcBody = (
140     'def %(method)s(this, *args, **kw): return ' +
141     'apply(this.%(forwardFunc)s().%(method)s, args, kw)')
142
143 def forwardmethods(fromClass, toClass, toPart, exclude = ()):
144     # Forward all methods from one class to another.
145
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.
152
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.
157
158     # Example:
159     #     class MyClass:
160     #     ...
161     #         def __init__(self):
162     #             ...
163     #             self.__target = TargetClass()
164     #             ...
165     #         def findtarget(self):
166     #             return self.__target
167     #     forwardmethods(MyClass, TargetClass, '__target', ['dangerous1', 'dangerous2'])
168     #     # ...or...
169     #     forwardmethods(MyClass, TargetClass, MyClass.findtarget, 
170     #             ['dangerous1', 'dangerous2'])
171
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.
175
176
177     # Allow an attribute name (String) or a function to determine the instance
178     if type(toPart) != types.StringType:
179
180         # check that it is something like a function
181         if callable(toPart):
182
183             # If a method is passed, use the function within it
184             if hasattr(toPart, 'im_func'):
185                 toPart = toPart.im_func
186                 
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
192
193         # It's not a valid type
194         else:
195             raise TypeError, 'toPart must be attribute name, function or method'
196
197     # get the full set of candidate methods
198     dict = {}
199     __methodDict(toClass, dict)
200
201     # discard special methods
202     for ex in dict.keys():
203         if ex[:1] == '_' or ex[-1:] == '_':
204             del dict[ex]
205     # discard dangerous methods supplied by the caller
206     for ex in exclude:
207         if dict.has_key(ex):
208             del dict[ex]
209     # discard methods already defined in fromClass
210     for ex in __methods(fromClass):
211         if dict.has_key(ex):
212             del dict[ex]
213
214     for method, func in dict.items():
215         d = {'method': method, 'func': func}
216         if type(toPart) == types.StringType:
217             execString = \
218                 __stringBody % {'method' : method, 'attribute' : toPart}
219         else:
220             execString = \
221                 __funcBody % {'forwardFunc' : forwardName, 'method' : method}
222
223         exec execString in d
224
225         # this creates a method
226         fromClass.__dict__[method] = d[method]
227
228 #=============================================================================
229
230 def setgeometryanddeiconify(window, geom):
231     # To avoid flashes on X and to position the window correctly on NT
232     # (caused by Tk bugs).
233
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()
239         if not redirect:
240             window.overrideredirect(1)
241         window.deiconify()
242         if geom is not None:
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()
247         window.tkraise()
248         if not redirect:
249             window.overrideredirect(0)
250     else:
251         if geom is not None:
252             window.geometry(geom)
253
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.
263
264         #window.tkraise()
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()
269         #window.deiconify()
270
271         window.deiconify()
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.
275             window.tkraise()
276
277 #=============================================================================
278
279 class MegaArchetype:
280     # Megawidget abstract root class.
281
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).
285
286     def __init__(self, parent = None, hullClass = None):
287
288         # Mapping from each megawidget option to a list of information
289         # about the option
290         #   - default value
291         #   - current value
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.
300         #
301         # self._optionInfo = {}
302
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 = {}
311
312         # Mapping from alias names to the names of components or
313         # sub-components.
314         self.__componentAliases = {}
315
316         # Contains information about the keywords provided to the
317         # constructor.  It is a mapping from the keyword to a tuple
318         # containing:
319         #    - value of keyword
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.
328         #
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
333         # constructor.
334         #
335         # self._constructorKeywords = {}
336
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 = ()
342
343         if hullClass is None:
344             self._hull = None
345         else:
346             if parent is None:
347                 parent = Tkinter._default_root
348
349             # Create the hull.
350             self._hull = self.createcomponent('hull',
351                     (), None,
352                     hullClass, (parent,))
353             _hullToMegaWidget[self._hull] = self
354
355             if _useTkOptionDb:
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
362                 _VALUE = _OPT_VALUE
363                 _DEFAULT = _OPT_DEFAULT
364                 for name, info in self._optionInfo.items():
365                     value = info[_VALUE]
366                     if value is _DEFAULT_OPTION_VALUE:
367                         resourceClass = string.upper(name[0]) + name[1:]
368                         value = option_get(name, resourceClass)
369                         if value != '':
370                             try:
371                                 # Convert the string to int/float/tuple, etc
372                                 value = eval(value, {'__builtins__': {}})
373                             except:
374                                 pass
375                             info[_VALUE] = value
376                         else:
377                             info[_VALUE] = info[_DEFAULT]
378
379     def destroy(self):
380         # Clean up optionInfo in case it contains circular references
381         # in the function field, such as self._settitle in class
382         # MegaToplevel.
383
384         self._optionInfo = {}
385         if self._hull is not None:
386             del _hullToMegaWidget[self._hull]
387             self._hull.destroy()
388
389     #======================================================================
390     # Methods used (mainly) during the construction of the megawidget.
391
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.
397
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.
401
402         if not hasattr(self, '_constructorKeywords'):
403             # First time defineoptions has been called.
404             tmp = {}
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
411
412         if not hasattr(self, '_dynamicGroups'):
413             self._dynamicGroups = ()
414         self._dynamicGroups = self._dynamicGroups + tuple(dynamicGroups)
415         self.addoptions(optionDefs)
416
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
421
422         # optimisations:
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
428
429         for name, default, function in optionDefs:
430             if '_' not in name:
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
434                 # if it is not None.
435                 if not optionInfo_has_key(name):
436                     if keywords_has_key(name):
437                         value = keywords[name][0]
438                         optionInfo[name] = [default, value, function]
439                         del keywords[name]
440                     else:
441                         if _useTkOptionDb:
442                             optionInfo[name] = \
443                                     [default, _DEFAULT_OPTION_VALUE, function]
444                         else:
445                             optionInfo[name] = [default, default, function]
446                 elif optionInfo[name][FUNCTION] is None:
447                     optionInfo[name][FUNCTION] = function
448             else:
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]
455
456     def createcomponent(self, componentName, componentAliases,
457             componentGroup, widgetClass, *widgetArgs, **kw):
458         # Create a component (during construction or later).
459
460         if self.__componentInfo.has_key(componentName):
461             raise ValueError, 'Component "%s" already exists' % componentName
462
463         if '_' in componentName:
464             raise ValueError, \
465                     'Component name "%s" must not contain "_"' % componentName
466
467         if hasattr(self, '_constructorKeywords'):
468             keywords = self._constructorKeywords
469         else:
470             keywords = {}
471         for alias, component in componentAliases:
472             # Create aliases to the component and its sub-components.
473             index = string.find(component, '_')
474             if index < 0:
475                 self.__componentAliases[alias] = (component, None)
476             else:
477                 mainComponent = component[:index]
478                 subComponent = component[(index + 1):]
479                 self.__componentAliases[alias] = (mainComponent, subComponent)
480
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*.
484
485             alias = alias + '_'
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]
491                     del keywords[option]
492
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]
500                 del keywords[option]
501             else:
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
507                 # component.
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
513
514         if kw.has_key('pyclass'):
515             widgetClass = kw['pyclass']
516             del kw['pyclass']
517         if widgetClass is None:
518             return 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)
528
529         return widget
530
531     def destroycomponent(self, name):
532         # Remove a megawidget component.
533
534         # This command is for use by megawidget designers to destroy a
535         # megawidget component.
536
537         self.__componentInfo[name][0].destroy()
538         del self.__componentInfo[name]
539
540     def createlabel(self, parent, childCols = 1, childRows = 1):
541
542         labelpos = self['labelpos']
543         labelmargin = self['labelmargin']
544         if labelpos is None:
545             return
546
547         label = self.createcomponent('label',
548                 (), None,
549                 Tkinter.Label, (parent,))
550
551         if labelpos[0] in 'ns':
552             # vertical layout
553             if labelpos[0] == 'n':
554                 row = 0
555                 margin = 1
556             else:
557                 row = childRows + 3
558                 margin = row - 1
559             label.grid(column=2, row=row, columnspan=childCols, sticky=labelpos)
560             parent.grid_rowconfigure(margin, minsize=labelmargin)
561         else:
562             # horizontal layout
563             if labelpos[0] == 'w':
564                 col = 0
565                 margin = 1
566             else:
567                 col = childCols + 3
568                 margin = col - 1
569             label.grid(column=col, row=2, rowspan=childRows, sticky=labelpos)
570             parent.grid_columnconfigure(margin, minsize=labelmargin)
571
572     def initialiseoptions(self, dummy = None):
573         self._initialiseoptions_counter = self._initialiseoptions_counter - 1
574         if self._initialiseoptions_counter == 0:
575             unusedOptions = []
576             keywords = self._constructorKeywords
577             for name in keywords.keys():
578                 used = keywords[name][1]
579                 if not used:
580                     # This keyword argument has not been used.  If it
581                     # does not refer to a dynamic group, mark it as
582                     # unused.
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 "'
589                 else:
590                     text = 'Unknown options "'
591                 raise KeyError, text + string.join(unusedOptions, ', ') + \
592                         '" for ' + self.__class__.__name__
593
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:
599                     func()
600
601     #======================================================================
602     # Method used to configure the megawidget.
603
604     def configure(self, option=None, **kw):
605         # Query or configure the megawidget options.
606         #
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.
616         #
617         # If *option* is None, return all megawidget configuration
618         # options and settings.  Options are returned as standard 5
619         # element tuples
620         #
621         # If *option* is a string, return the 5 element tuple for the
622         # given configuration option.
623
624         # First, deal with the option queries.
625         if len(kw) == 0:
626             # This configure call is querying the values of one or all options.
627             # Return 5-tuples:
628             #     (optionName, resourceName, resourceClass, default, value)
629             if option is None:
630                 rtn = {}
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])
635                 return rtn
636             else:
637                 config = self._optionInfo[option]
638                 resourceClass = string.upper(option[0]) + option[1:]
639                 return (option, option, resourceClass, config[_OPT_DEFAULT],
640                         config[_OPT_VALUE])
641
642         # optimisations:
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
649         VALUE = _OPT_VALUE
650         FUNCTION = _OPT_FUNCTION
651
652         # This will contain a list of options in *kw* which
653         # are known to this megawidget.
654         directOptions = []
655
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.
662         indirectOptions = {}
663         indirectOptions_has_key = indirectOptions.has_key
664
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:
670                     raise KeyError, \
671                             'Cannot configure initialisation option "' \
672                             + option + '" for ' + self.__class__.__name__
673                 optionInfo[option][VALUE] = value
674                 directOptions.append(option)
675             else:
676                 index = string.find(option, '_')
677                 if index >= 0:
678                     # This option may be of the form <component>_<option>.
679                     component = option[:index]
680                     componentOption = option[(index + 1):]
681
682                     # Expand component alias
683                     if componentAliases_has_key(component):
684                         component, subComponent = componentAliases[component]
685                         if subComponent is not None:
686                             componentOption = subComponent + '_' \
687                                     + componentOption
688
689                         # Expand option string to write on error
690                         option = component + '_' + componentOption
691
692                     if componentInfo_has_key(component):
693                         # Configure the named component
694                         componentConfigFuncs = [componentInfo[component][1]]
695                     else:
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])
702
703                         if len(componentConfigFuncs) == 0 and \
704                                 component not in self._dynamicGroups:
705                             raise KeyError, 'Unknown option "' + option + \
706                                     '" for ' + self.__class__.__name__
707
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] \
715                                 = value
716                 else:
717                     raise KeyError, 'Unknown option "' + option + \
718                             '" for ' + self.__class__.__name__
719
720         # Call the configure methods for any components.
721         map(apply, indirectOptions.keys(),
722                 ((),) * len(indirectOptions), indirectOptions.values())
723
724         # Call the configuration callback function for each option.
725         for option in directOptions:
726             info = optionInfo[option]
727             func = info[_OPT_FUNCTION]
728             if func is not None:
729               func()
730
731     def __setitem__(self, key, value):
732         apply(self.configure, (), {key: value})
733
734     #======================================================================
735     # Methods used to query the megawidget.
736
737     def component(self, name):
738         # Return a component widget of the megawidget given the
739         # component's name
740         # This allows the user of a megawidget to access and configure
741         # widget components directly.
742
743         # Find the main component and any subcomponents
744         index = string.find(name, '_')
745         if index < 0:
746             component = name
747             remainingComponents = None
748         else:
749             component = name[:index]
750             remainingComponents = name[(index + 1):]
751
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
758                 else:
759                     remainingComponents = subComponent + '_' \
760                             + remainingComponents
761
762         widget = self.__componentInfo[component][0]
763         if remainingComponents is None:
764             return widget
765         else:
766             return widget.component(remainingComponents)
767
768     def interior(self):
769         return self._hull
770
771     def hulldestroyed(self):
772         return not _hullToMegaWidget.has_key(self._hull)
773
774     def __str__(self):
775         return str(self._hull)
776
777     def cget(self, option):
778         # Get current configuration setting.
779
780         # Return the value of an option, for example myWidget['font']. 
781
782         if self._optionInfo.has_key(option):
783             return self._optionInfo[option][_OPT_VALUE]
784         else:
785             index = string.find(option, '_')
786             if index >= 0:
787                 component = option[:index]
788                 componentOption = option[(index + 1):]
789
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
795
796                     # Expand option string to write on error
797                     option = component + '_' + componentOption
798
799                 if self.__componentInfo.has_key(component):
800                     # Call cget on the component.
801                     componentCget = self.__componentInfo[component][3]
802                     return componentCget(componentOption)
803                 else:
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)
810
811         raise KeyError, 'Unknown option "' + option + \
812                 '" for ' + self.__class__.__name__
813
814     __getitem__ = cget
815
816     def isinitoption(self, option):
817         return self._optionInfo[option][_OPT_FUNCTION] is INITOPT
818
819     def options(self):
820         options = []
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))
826             options.sort()
827         return options
828
829     def components(self):
830         # Return a list of all components.
831
832         # This list includes the 'hull' component and all widget subcomponents
833
834         names = self.__componentInfo.keys()
835         names.sort()
836         return names
837
838     def componentaliases(self):
839         # Return a list of all component aliases.
840
841         componentAliases = self.__componentAliases
842
843         names = componentAliases.keys()
844         names.sort()
845         rtn = []
846         for alias in names:
847             (mainComponent, subComponent) = componentAliases[alias]
848             if subComponent is None:
849                 rtn.append((alias, mainComponent))
850             else:
851                 rtn.append((alias, mainComponent + '_' + subComponent))
852             
853         return rtn
854
855     def componentgroup(self, name):
856         return self.__componentInfo[name][4]
857
858 #=============================================================================
859
860 # The grab functions are mainly called by the activate() and
861 # deactivate() methods.
862 #
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.
866 #
867 # To remove the grab from the window on top of the grab stack, call
868 # popgrab().
869 #
870 # Use releasegrabs() to release the grab and clear the grab stack.
871
872 def pushgrab(grabWindow, globalMode, deactivateFunction):
873     prevFocus = grabWindow.tk.call('focus')
874     grabInfo = {
875         'grabWindow' : grabWindow,
876         'globalMode' : globalMode,
877         'previousFocus' : prevFocus,
878         'deactivateFunction' : deactivateFunction,
879     }
880     _grabStack.append(grabInfo)
881     _grabtop()
882     grabWindow.focus_set()
883
884 def popgrab(window):
885     # Return the grab to the next window in the grab stack, if any.
886
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']()
895                 break
896
897     grabInfo = _grabStack[-1]
898     del _grabStack[-1]
899
900     topWidget = grabInfo['grabWindow']
901     prevFocus = grabInfo['previousFocus']
902     globalMode = grabInfo['globalMode']
903
904     if globalMode != 'nograb':
905         topWidget.grab_release()
906
907     if len(_grabStack) > 0:
908         _grabtop()
909     if prevFocus != '':
910         try:
911             topWidget.tk.call('focus', prevFocus)
912         except Tkinter.TclError:
913             # Previous focus widget has been deleted. Set focus
914             # to root window.
915             Tkinter._default_root.focus_set()
916     else:
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()
921         else:
922             Tkinter._default_root.focus_set()
923
924 def grabstacktopwindow():
925     if len(_grabStack) == 0:
926         return None
927     else:
928         return _grabStack[-1]['grabWindow']
929
930 def releasegrabs():
931     # Release grab and clear the grab stack.
932
933     current = Tkinter._default_root.grab_current()
934     if current is not None:
935         current.grab_release()
936     _grabStack[:] = []
937
938 def _grabtop():
939     grabInfo = _grabStack[-1]
940     topWidget = grabInfo['grabWindow']
941     globalMode = grabInfo['globalMode']
942
943     if globalMode == 'nograb':
944         return
945
946     while 1:
947         try:
948             if globalMode:
949                 topWidget.grab_set_global()
950             else:
951                 topWidget.grab_set()
952             break
953         except Tkinter.TclError:
954             # Another application has grab.  Keep trying until
955             # grab can succeed.
956             topWidget.after(100)
957
958 #=============================================================================
959
960 class MegaToplevel(MegaArchetype):
961
962     def __init__(self, parent = None, **kw):
963         # Define the options for this megawidget.
964         optiondefs = (
965             ('activatecommand',   None,                     None),
966             ('deactivatecommand', None,                     None),
967             ('master',            None,                     None),
968             ('title',             None,                     self._settitle),
969             ('hull_class',        self.__class__.__name__,  None),
970         )
971         self.defineoptions(kw, optiondefs)
972
973         # Initialise the base class (after defining the options).
974         MegaArchetype.__init__(self, parent, Tkinter.Toplevel)
975
976         # Initialise instance.
977
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)
985
986         # Initialise instance variables.
987
988         self._firstShowing = 1
989         # Used by show() to ensure window retains previous position on screen.
990
991         # The IntVar() variable to wait on during a modal dialog.
992         self._wait = None
993
994         self._active = 0
995         self._userDeleteFunc = self.destroy
996         self._userModalDeleteFunc = self.deactivate
997
998         # Check keywords and initialise options.
999         self.initialiseoptions()
1000
1001     def _settitle(self):
1002         title = self['title']
1003         if title is not None:
1004             self.title(title)
1005
1006     def userdeletefunc(self, func=None):
1007         if func:
1008             self._userDeleteFunc = func
1009         else:
1010             return self._userDeleteFunc
1011
1012     def usermodaldeletefunc(self, func=None):
1013         if func:
1014             self._userModalDeleteFunc = func
1015         else:
1016             return self._userModalDeleteFunc
1017
1018     def _userDeleteWindow(self):
1019         if self.active():
1020             self._userModalDeleteFunc()
1021         else:
1022             self._userDeleteFunc()
1023
1024     def destroy(self):
1025         # Allow this to be called more than once.
1026         if _hullToMegaWidget.has_key(self._hull):
1027             self.deactivate()
1028
1029             # Remove circular references, so that object can get cleaned up.
1030             del self._userDeleteFunc
1031             del self._userModalDeleteFunc
1032
1033             MegaArchetype.destroy(self)
1034
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.
1040                 geom = None
1041             else:
1042                 # Position the window at the same place it was last time.
1043                 geom = self._sameposition()
1044             setgeometryanddeiconify(self, geom)
1045
1046         if self._firstShowing:
1047             self._firstShowing = 0
1048         else:
1049             if self.transient() == '':
1050                 self.tkraise()
1051
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)
1062
1063         self.focus()
1064
1065     def _centreonscreen(self):
1066         # Centre the window on the screen.  (Actually halfway across
1067         # and one third down.)
1068
1069         parent = self.winfo_parent()
1070         if type(parent) == types.StringType:
1071             parent = self._hull._nametowidget(parent)
1072
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()
1082
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()
1086         if x < 0:
1087             x = 0
1088         if y < 0:
1089             y = 0
1090         return '+%d+%d' % (x, y)
1091
1092     def _sameposition(self):
1093         # Position the window at the same place it was last time.
1094
1095         geometry = self.geometry()
1096         index = string.find(geometry, '+')
1097         if index >= 0:
1098             return geometry[index:]
1099         else:
1100             return None
1101
1102     def activate(self, globalMode = 0, geometry = 'centerscreenfirst'):
1103         if self._active:
1104             raise ValueError, 'Window is already active'
1105         if self.state() == 'normal':
1106             self.withdraw()
1107
1108         self._active = 1
1109
1110         showbusycursor()
1111
1112         if self._wait is None:
1113             self._wait = Tkinter.IntVar()
1114         self._wait.set(0)
1115
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()
1122             else:
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:
1127                 geom = geometry[5:]
1128             else:
1129                 # Position the window at the same place it was last time.
1130                 geom = self._sameposition()
1131         else:
1132             geom = geometry
1133
1134         self._firstShowing = 0
1135
1136         setgeometryanddeiconify(self, geom)
1137
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)
1149
1150         pushgrab(self._hull, globalMode, self.deactivate)
1151         command = self['activatecommand']
1152         if callable(command):
1153             command()
1154         self.wait_variable(self._wait)
1155
1156         return self._result
1157
1158     def deactivate(self, result=None):
1159         if not self._active:
1160             return
1161         self._active = 0
1162
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.
1167         popgrab(self._hull)
1168
1169         command = self['deactivatecommand']
1170         if callable(command):
1171             command()
1172
1173         self.withdraw()
1174         hidebusycursor(forceFocusRestore = 1)
1175
1176         self._result = result
1177         self._wait.set(1)
1178
1179     def active(self):
1180         return self._active
1181
1182 forwardmethods(MegaToplevel, Tkinter.Toplevel, '_hull')
1183
1184 #=============================================================================
1185
1186 class MegaWidget(MegaArchetype):
1187     def __init__(self, parent = None, **kw):
1188         # Define the options for this megawidget.
1189         optiondefs = (
1190             ('hull_class',       self.__class__.__name__,  None),
1191         )
1192         self.defineoptions(kw, optiondefs)
1193
1194         # Initialise the base class (after defining the options).
1195         MegaArchetype.__init__(self, parent, Tkinter.Frame)
1196
1197         # Check keywords and initialise options.
1198         self.initialiseoptions()
1199
1200 forwardmethods(MegaWidget, Tkinter.Frame, '_hull')
1201
1202 #=============================================================================
1203
1204 # Public functions
1205 #-----------------
1206
1207 _traceTk = 0
1208 def tracetk(root = None, on = 1, withStackTrace = 0, file=None):
1209     global _withStackTrace
1210     global _traceTkFile
1211     global _traceTk
1212
1213     if root is None:
1214         root = Tkinter._default_root
1215
1216     _withStackTrace = withStackTrace
1217     _traceTk = on
1218     if on:
1219         if hasattr(root.tk, '__class__'):
1220             # Tracing already on
1221             return
1222         if file is None:
1223             _traceTkFile = sys.stderr
1224         else:
1225             _traceTkFile = file
1226         tk = _TraceTk(root.tk)
1227     else:
1228         if not hasattr(root.tk, '__class__'):
1229             # Tracing already off
1230             return
1231         tk = root.tk.getTclInterp()
1232     _setTkInterps(root, tk)
1233
1234 def showbusycursor():
1235
1236     _addRootToToplevelBusyInfo()
1237     root = Tkinter._default_root
1238
1239     busyInfo = {
1240         'newBusyWindows' : [],
1241         'previousFocus' : None,
1242         'busyFocus' : None,
1243     }
1244     _busyStack.append(busyInfo)
1245
1246     if _disableKeyboardWhileBusy:
1247         # Remember the focus as it is now, before it is changed.
1248         busyInfo['previousFocus'] = root.tk.call('focus')
1249
1250     if not _havebltbusy(root):
1251         # No busy command, so don't call busy hold on any windows.
1252         return
1253
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'])
1260
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')
1266
1267             if _disableKeyboardWhileBusy:
1268                 # Remember previous focus widget for this toplevel window
1269                 # and set focus to the busy window, which will ignore all
1270                 # keyboard events.
1271                 winInfo['windowFocus'] = \
1272                         window.tk.call('focus', '-lastfor', window._w)
1273                 window.tk.call('focus', winInfo['busyWindow'])
1274                 busyInfo['busyFocus'] = winInfo['busyWindow']
1275
1276     if len(busyInfo['newBusyWindows']) > 0:
1277         if os.name == 'nt':
1278             # NT needs an "update" before it will change the cursor.
1279             window.update()
1280         else:
1281             window.update_idletasks()
1282
1283 def hidebusycursor(forceFocusRestore = 0):
1284
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')
1289
1290     # Pop the busy info off the stack.
1291     busyInfo = _busyStack[-1]
1292     del _busyStack[-1]
1293
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)
1300
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
1304                 # been changed).
1305                 windowFocusNow = window.tk.call('focus', '-lastfor', window._w)
1306                 if windowFocusNow == winInfo['busyWindow']:
1307                     try:
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
1312                         # busy window).
1313                         window.focus_set()
1314
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:
1323                 try:
1324                     root.tk.call('focus', previousFocus)
1325                 except Tkinter.TclError:
1326                     # Previous focus widget has been deleted; forget it.
1327                     pass
1328         else:
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)
1332
1333 def clearbusycursor():
1334     while len(_busyStack) > 0:
1335         hidebusycursor()
1336
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
1344         else:
1345             raise KeyError, 'Unknown busycursor attribute "' + name + '"'
1346
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
1350     # the application.
1351
1352     root = Tkinter._default_root
1353     if root == None:
1354         root = Tkinter.Tk()
1355     if not _toplevelBusyInfo.has_key(root):
1356         _addToplevelBusyInfo(root)
1357
1358 def busycallback(command, updateFunction = None):
1359     if not callable(command):
1360         raise ValueError, \
1361             'cannot register non-command busy callback %s %s' % \
1362                 (repr(command), type(command))
1363     wrapper = _BusyWrapper(command, updateFunction)
1364     return wrapper.callback
1365
1366 _errorReportFile = None
1367 _errorWindow = None
1368
1369 def reporterrorstofile(file = None):
1370     global _errorReportFile
1371     _errorReportFile = file
1372
1373 def displayerror(text):
1374     global _errorWindow
1375
1376     if _errorReportFile is not None:
1377         _errorReportFile.write(text + '\n')
1378     else:
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
1382         # window.
1383         sys.stderr.write(text + '\n')
1384
1385         if _errorWindow is None:
1386             # The error window has not yet been created.
1387             _errorWindow = _ErrorWindow()
1388
1389         _errorWindow.showerror(text)
1390
1391 _root = None
1392 _disableKeyboardWhileBusy = 1
1393
1394 def initialise(
1395         root = None,
1396         size = None,
1397         fontScheme = None,
1398         useTkOptionDb = 0,
1399         noBltBusy = 0,
1400         disableKeyboardWhileBusy = None,
1401 ):
1402     # Remember if show/hidebusycursor should ignore keyboard events.
1403     global _disableKeyboardWhileBusy
1404     if disableKeyboardWhileBusy is not None:
1405         _disableKeyboardWhileBusy = disableKeyboardWhileBusy
1406
1407     # Do not use blt busy command if noBltBusy is set.  Otherwise,
1408     # use blt busy if it is available.
1409     global _haveBltBusy
1410     if noBltBusy:
1411         _haveBltBusy = 0
1412
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
1417
1418     # If we haven't been given a root window, use the default or
1419     # create one.
1420     if root is None:
1421         if Tkinter._default_root is None:
1422             root = Tkinter.Tk()
1423         else:
1424             root = Tkinter._default_root
1425
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.
1430     global _root
1431     if _root is not None and _root != root:
1432         global _busyStack
1433         global _errorWindow
1434         global _grabStack
1435         global _hullToMegaWidget
1436         global _toplevelBusyInfo
1437         _busyStack = []
1438         _errorWindow = None
1439         _grabStack = []
1440         _hullToMegaWidget = {}
1441         _toplevelBusyInfo = {}
1442     _root = root
1443
1444     # Trap Tkinter Toplevel constructors so that a list of Toplevels
1445     # can be maintained.
1446     Tkinter.Toplevel.title = __TkinterToplevelTitle
1447
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
1453
1454     # Modify Tkinter's CallWrapper class to improve the display of
1455     # errors which occur in callbacks.
1456     Tkinter.CallWrapper = __TkinterCallWrapper
1457
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)
1465
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)
1470
1471     return root
1472
1473 def alignlabels(widgets, sticky = None):
1474     if len(widgets) == 0:
1475         return
1476
1477     widgets[0].update_idletasks()
1478
1479     # Determine the size of the maximum length label string.
1480     maxLabelWidth = 0
1481     for iwid in widgets:
1482         labelWidth = iwid.grid_bbox(0, 1)[2]
1483         if labelWidth > maxLabelWidth:
1484             maxLabelWidth = labelWidth
1485
1486     # Adjust the margins for the labels such that the child sites and
1487     # labels line up.
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 #=============================================================================
1493
1494 # Private routines
1495 #-----------------
1496 _callToTkReturned = 1
1497 _recursionCounter = 1
1498
1499 class _TraceTk:
1500     def __init__(self, tclInterp):
1501         self.tclInterp = tclInterp
1502
1503     def getTclInterp(self):
1504         return self.tclInterp
1505
1506     # Calling from python into Tk.
1507     def call(self, *args, **kw):
1508         global _callToTkReturned
1509         global _recursionCounter
1510
1511         _callToTkReturned = 0
1512         if len(args) == 1 and type(args[0]) == types.TupleType:
1513             argStr = str(args[0])
1514         else:
1515             argStr = str(args)
1516         _traceTkFile.write('CALL  TK> %d:%s%s' %
1517                 (_recursionCounter, '  ' * _recursionCounter, argStr))
1518         _recursionCounter = _recursionCounter + 1
1519         try:
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,
1526                             repr(errorString)))
1527             if _withStackTrace:
1528                 _traceTkFile.write('CALL  TK> stack:\n')
1529                 traceback.print_stack()
1530             raise Tkinter.TclError, errorString
1531
1532         _recursionCounter = _recursionCounter - 1
1533         if _callToTkReturned:
1534             _traceTkFile.write('CALL RTN> %d:%s-> %s' %
1535                     (_recursionCounter, '  ' * _recursionCounter, repr(result)))
1536         else:
1537             _callToTkReturned = 1
1538             if result:
1539                 _traceTkFile.write(' -> %s' % repr(result))
1540         _traceTkFile.write('\n')
1541         if _withStackTrace:
1542             _traceTkFile.write('CALL  TK> stack:\n')
1543             traceback.print_stack()
1544
1545         _traceTkFile.flush()
1546         return result
1547
1548     def __getattr__(self, key):
1549         return getattr(self.tclInterp, key)
1550
1551 def _setTkInterps(window, tk):
1552     window.tk = tk
1553     for child in window.children.values():
1554       _setTkInterps(child, tk)
1555
1556 #=============================================================================
1557
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
1562 # toplevel.
1563
1564 # Map from toplevel windows to
1565 #     {'isBusy', 'windowFocus', 'busyWindow',
1566 #         'excludeFromBusy', 'busyCursorName'}
1567 _toplevelBusyInfo = {}
1568
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.
1574
1575 def _addToplevelBusyInfo(window):
1576     if window._w == '.':
1577         busyWindow = '._Busy'
1578     else:
1579         busyWindow = window._w + '._Busy'
1580
1581     _toplevelBusyInfo[window] = {
1582         'isBusy' : 0,
1583         'windowFocus' : None,
1584         'busyWindow' : busyWindow,
1585         'excludeFromBusy' : 0,
1586         'busyCursorName' : None,
1587     }
1588
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
1593     # about it.
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)
1598
1599     return apply(Tkinter.Wm.title, (self,) + args)
1600
1601 _haveBltBusy = None
1602 def _havebltbusy(window):
1603     global _busy_hold, _busy_release, _haveBltBusy
1604     if _haveBltBusy is None:
1605         import PmwBlt
1606         _haveBltBusy = PmwBlt.havebltbusy(window)
1607         _busy_hold = PmwBlt.busy_hold
1608         if os.name == 'nt':
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
1613         else:
1614             _busy_release = PmwBlt.busy_release
1615     return _haveBltBusy
1616
1617 class _BusyWrapper:
1618     def __init__(self, command, updateFunction):
1619         self._command = command
1620         self._updateFunction = updateFunction
1621
1622     def callback(self, *args):
1623         showbusycursor()
1624         rtn = apply(self._command, args)
1625
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()
1630
1631         hidebusycursor()
1632         return rtn
1633
1634 #=============================================================================
1635
1636 def drawarrow(canvas, color, direction, tag, baseOffset = 0.25, edgeOffset = 0.15):
1637     canvas.delete(tag)
1638
1639     bw = (string.atoi(canvas['borderwidth']) + 
1640             string.atoi(canvas['highlightthickness']))
1641     width = string.atoi(canvas['width'])
1642     height = string.atoi(canvas['height'])
1643
1644     if direction in ('up', 'down'):
1645         majorDimension = height
1646         minorDimension = width
1647     else:
1648         majorDimension = width
1649         minorDimension = height
1650
1651     offset = round(baseOffset * majorDimension)
1652     if direction in ('down', 'right'):
1653         base = bw + offset
1654         apex = bw + majorDimension - offset
1655     else:
1656         base = bw + majorDimension - offset
1657         apex = bw + offset
1658
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)
1663     middle = low + half
1664     high = low + 2 * half
1665
1666     if direction in ('up', 'down'):
1667         coords = (low, base, high, base, middle, apex)
1668     else:
1669         coords = (base, low, base, high, apex, middle)
1670     kw = {'fill' : color, 'outline' : color, 'tag' : tag}
1671     apply(canvas.create_polygon, coords, kw)
1672
1673 #=============================================================================
1674
1675 # Modify the Tkinter destroy methods so that it notifies us when a Tk
1676 # toplevel or frame is destroyed.
1677
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 = {}
1681
1682 def __TkinterToplevelDestroy(tkWidget):
1683     if _hullToMegaWidget.has_key(tkWidget):
1684         mega = _hullToMegaWidget[tkWidget]
1685         try:
1686             mega.destroy()
1687         except:
1688             _reporterror(mega.destroy, ())
1689     else:
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)
1699
1700 def __TkinterWidgetDestroy(tkWidget):
1701     if _hullToMegaWidget.has_key(tkWidget):
1702         mega = _hullToMegaWidget[tkWidget]
1703         try:
1704             mega.destroy()
1705         except:
1706             _reporterror(mega.destroy, ())
1707     else:
1708         Tkinter.BaseWidget.destroy(tkWidget)
1709
1710 #=============================================================================
1711
1712 # Add code to Tkinter to improve the display of errors which occur in
1713 # callbacks.
1714
1715 class __TkinterCallWrapper:
1716     def __init__(self, func, subst, widget):
1717         self.func = func
1718         self.subst = subst
1719         self.widget = widget
1720
1721     # Calling back from Tk into python.
1722     def __call__(self, *args):
1723         try:
1724             if self.subst:
1725                 args = apply(self.subst, args)
1726             if _traceTk:
1727                 if not _callToTkReturned:
1728                     _traceTkFile.write('\n')
1729                 if hasattr(self.func, 'im_class'):
1730                     name = self.func.im_class.__name__ + '.' + \
1731                         self.func.__name__
1732                 else:
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)
1740                     else:
1741                         argStr = '(%s Event, %s)' % (eventName, args[0].widget)
1742                 else:
1743                     argStr = str(args)
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
1750         except:
1751             _reporterror(self.func, args)
1752
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',
1767 }
1768
1769 def _reporterror(func, args):
1770     # Fetch current exception values.
1771     exc_type, exc_value, exc_traceback = sys.exc_info()
1772
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)
1780
1781     if type(args) == types.TupleType and len(args) > 0 and \
1782             hasattr(args[0], 'type'):
1783         eventArg = 1
1784     else:
1785         eventArg = 0
1786
1787     # If the argument to the callback is an event, add the event type.
1788     if eventArg:
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)
1793         else:
1794             msg = msg + '  Unknown event type (type num: %d)\n' % eventNum
1795
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)
1802
1803     # If the argument to the callback is an event, add the event contents.
1804     if eventArg:
1805         msg = msg + '\n================================================\n'
1806         msg = msg + '  Event contents:\n'
1807         keys = args[0].__dict__.keys()
1808         keys.sort()
1809         for key in keys:
1810             msg = msg + '    %s: %s\n' % (key, args[0].__dict__[key])
1811
1812     clearbusycursor()
1813     try:
1814         displayerror(msg)
1815     except:
1816         pass
1817
1818 class _ErrorWindow:
1819     def __init__(self):
1820
1821         self._errorQueue = []
1822         self._errorCount = 0
1823         self._open = 0
1824         self._firstShowing = 1
1825
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')
1831
1832         # Create the text widget and scrollbar in a frame
1833         upperframe = Tkinter.Frame(self._top)
1834
1835         scrollbar = Tkinter.Scrollbar(upperframe, orient='vertical')
1836         scrollbar.pack(side = 'right', fill = 'y')
1837
1838         self._text = Tkinter.Text(upperframe, yscrollcommand=scrollbar.set)
1839         self._text.pack(fill = 'both', expand = 1)
1840         scrollbar.configure(command=self._text.yview)
1841
1842         # Create the buttons and label in a frame
1843         lowerframe = Tkinter.Frame(self._top)
1844
1845         ignore = Tkinter.Button(lowerframe,
1846                 text = 'Ignore remaining errors', command = self._hide)
1847         ignore.pack(side='left')
1848
1849         self._nextError = Tkinter.Button(lowerframe,
1850                 text = 'Show next error', command = self._next)
1851         self._nextError.pack(side='left')
1852
1853         self._label = Tkinter.Label(lowerframe, relief='ridge')
1854         self._label.pack(side='left', fill='x', expand=1)
1855
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)
1860
1861     def showerror(self, text):
1862         if self._open:
1863             self._errorQueue.append(text)
1864         else:
1865             self._display(text)
1866             self._open = 1
1867
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
1875             # becomes idle.
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()
1879
1880             pass
1881         else:
1882             if self._firstShowing:
1883                 geom = None
1884             else:
1885                 geometry = self._top.geometry()
1886                 index = string.find(geometry, '+')
1887                 if index >= 0:
1888                     geom = geometry[index:]
1889                 else:
1890                     geom = None
1891             setgeometryanddeiconify(self._top, geom)
1892
1893         if self._firstShowing:
1894             self._firstShowing = 0
1895         else:
1896             self._top.tkraise()
1897
1898         self._top.focus()
1899
1900         self._updateButtons()
1901
1902         # Release any grab, so that buttons in the error window work.
1903         releasegrabs()
1904
1905     def _hide(self):
1906         self._errorCount = self._errorCount + len(self._errorQueue)
1907         self._errorQueue = []
1908         self._top.withdraw()
1909         self._open = 0
1910
1911     def _next(self):
1912         # Display the next error in the queue. 
1913
1914         text = self._errorQueue[0]
1915         del self._errorQueue[0]
1916
1917         self._display(text)
1918         self._updateButtons()
1919
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)
1925
1926     def _updateButtons(self):
1927         numQueued = len(self._errorQueue)
1928         if numQueued > 0:
1929             self._label.configure(text='%d more errors' % numQueued)
1930             self._nextError.configure(state='normal')
1931         else:
1932             self._label.configure(text='No more errors')
1933             self._nextError.configure(state='disabled')