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