# Pmw megawidget base classes.

# This module provides a foundation for building megawidgets.  It
# contains the MegaArchetype class which manages component widgets and
# configuration options.  Also provided are the MegaToplevel and
# MegaWidget classes, derived from the MegaArchetype class.  The
# MegaToplevel class contains a Tkinter Toplevel widget to act as the
# container of the megawidget.  This is used as the base class of all
# megawidgets that are contained in their own top level window, such
# as a Dialog window.  The MegaWidget class contains a Tkinter Frame
# to act as the container of the megawidget.  This is used as the base
# class of all other megawidgets, such as a ComboBox or ButtonBox.
#
# Megawidgets are built by creating a class that inherits from either
# the MegaToplevel or MegaWidget class.

import os
import string
import sys
import traceback
import types
import Tkinter

# Special values used in index() methods of several megawidgets.
END = ['end']
SELECT = ['select']
DEFAULT = ['default']

# Constant used to indicate that an option can only be set by a call
# to the constructor.
INITOPT = ['initopt']
_DEFAULT_OPTION_VALUE = ['default_option_value']
_useTkOptionDb = 0

# Symbolic constants for the indexes into an optionInfo list.
_OPT_DEFAULT         = 0
_OPT_VALUE           = 1
_OPT_FUNCTION        = 2

# Stacks

_busyStack = []
    # Stack which tracks nested calls to show/hidebusycursor (called
    # either directly or from activate()/deactivate()).  Each element
    # is a dictionary containing:
    #   'newBusyWindows' :  List of windows which had busy_hold called
    #                       on them during a call to showbusycursor(). 
    #                       The corresponding call to hidebusycursor()
    #                       will call busy_release on these windows.
    #   'busyFocus' :       The blt _Busy window which showbusycursor()
    #                       set the focus to.
    #   'previousFocus' :   The focus as it was when showbusycursor()
    #                       was called.  The corresponding call to
    #                       hidebusycursor() will restore this focus if
    #                       the focus has not been changed from busyFocus.

_grabStack = []
    # Stack of grabbed windows.  It tracks calls to push/popgrab()
    # (called either directly or from activate()/deactivate()).  The
    # window on the top of the stack is the window currently with the
    # grab.  Each element is a dictionary containing:
    #   'grabWindow' :      The window grabbed by pushgrab().  The
    #                       corresponding call to popgrab() will release
    #                       the grab on this window and restore the grab
    #                       on the next window in the stack (if there is one).
    #   'globalMode' :      True if the grabWindow was grabbed with a
    #                       global grab, false if the grab was local
    #                       and 'nograb' if no grab was performed.
    #   'previousFocus' :   The focus as it was when pushgrab()
    #                       was called.  The corresponding call to
    #                       popgrab() will restore this focus.
    #   'deactivateFunction' :
    #       The function to call (usually grabWindow.deactivate) if
    #       popgrab() is called (usually from a deactivate() method)
    #       on a window which is not at the top of the stack (that is,
    #       does not have the grab or focus).  For example, if a modal
    #       dialog is deleted by the window manager or deactivated by
    #       a timer.  In this case, all dialogs above and including
    #       this one are deactivated, starting at the top of the
    #       stack.

    # Note that when dealing with focus windows, the name of the Tk
    # widget is used, since it may be the '_Busy' window, which has no
    # python instance associated with it.

#=============================================================================

# Functions used to forward methods from a class to a component.

# Fill in a flattened method resolution dictionary for a class (attributes are 
# filtered out). Flattening honours the MI method resolution rules 
# (depth-first search of bases in order). The dictionary has method names
# for keys and functions for values.
def __methodDict(cls, dict):

    # the strategy is to traverse the class in the _reverse_ of the normal
    # order, and overwrite any duplicates.
    baseList = list(cls.__bases__)
    baseList.reverse()
    
    # do bases in reverse order, so first base overrides last base
    for super in baseList:
	__methodDict(super, dict)

    # do my methods last to override base classes
    for key, value in cls.__dict__.items():
	# ignore class attributes
	if type(value) == types.FunctionType:
	    dict[key] = value

def __methods(cls):
    # Return all method names for a class.

    # Return all method names for a class (attributes are filtered
    # out).  Base classes are searched recursively.

    dict = {}
    __methodDict(cls, dict)
    return dict.keys()
	
# Function body to resolve a forwarding given the target method name and the 
# attribute name. The resulting lambda requires only self, but will forward 
# any other parameters.
__stringBody = (
    'def %(method)s(this, *args, **kw): return ' +
    'apply(this.%(attribute)s.%(method)s, args, kw)')

# Get a unique id
__counter = 0
def __unique():
  global __counter
  __counter = __counter + 1
  return str(__counter)

# Function body to resolve a forwarding given the target method name and the
# index of the resolution function. The resulting lambda requires only self, 
# but will forward any other parameters. The target instance is identified 
# by invoking the resolution function.
__funcBody = (
    'def %(method)s(this, *args, **kw): return ' +
    'apply(this.%(forwardFunc)s().%(method)s, args, kw)')

def forwardmethods(fromClass, toClass, toPart, exclude = ()):
    # Forward all methods from one class to another.

    # Forwarders will be created in fromClass to forward method
    # invocations to toClass.  The methods to be forwarded are
    # identified by flattening the interface of toClass, and excluding
    # methods identified in the exclude list.  Methods already defined
    # in fromClass, or special methods with one or more leading or
    # trailing underscores will not be forwarded.

    # For a given object of class fromClass, the corresponding toClass
    # object is identified using toPart.  This can either be a String
    # denoting an attribute of fromClass objects, or a function taking
    # a fromClass object and returning a toClass object.

    # Example:
    #     class MyClass:
    #     ...
    #         def __init__(self):
    #             ...
    #             self.__target = TargetClass()
    #             ...
    #         def findtarget(self):
    #             return self.__target
    #     forwardmethods(MyClass, TargetClass, '__target', ['dangerous1', 'dangerous2'])
    #     # ...or...
    #     forwardmethods(MyClass, TargetClass, MyClass.findtarget, 
    #             ['dangerous1', 'dangerous2'])

    # In both cases, all TargetClass methods will be forwarded from
    # MyClass except for dangerous1, dangerous2, special methods like
    # __str__, and pre-existing methods like findtarget.


    # Allow an attribute name (String) or a function to determine the instance
    if type(toPart) != types.StringType:

	# check that it is something like a function
	if callable(toPart):

	    # If a method is passed, use the function within it
	    if hasattr(toPart, 'im_func'):
		toPart = toPart.im_func
		
	    # After this is set up, forwarders in this class will use
	    # the forwarding function. The forwarding function name is
	    # guaranteed to be unique, so that it can't be hidden by subclasses
	    forwardName = '__fwdfunc__' + __unique()
	    fromClass.__dict__[forwardName] = toPart

	# It's not a valid type
	else:
	    raise TypeError, 'toPart must be attribute name, function or method'

    # get the full set of candidate methods
    dict = {}
    __methodDict(toClass, dict)

    # discard special methods
    for ex in dict.keys():
	if ex[:1] == '_' or ex[-1:] == '_':
	    del dict[ex]
    # discard dangerous methods supplied by the caller
    for ex in exclude:
	if dict.has_key(ex):
	    del dict[ex]
    # discard methods already defined in fromClass
    for ex in __methods(fromClass):
	if dict.has_key(ex):
	    del dict[ex]

    for method, func in dict.items():
	d = {'method': method, 'func': func}
	if type(toPart) == types.StringType:
	    execString = \
		__stringBody % {'method' : method, 'attribute' : toPart}
	else:
	    execString = \
		__funcBody % {'forwardFunc' : forwardName, 'method' : method}

	exec execString in d

	# this creates a method
	fromClass.__dict__[method] = d[method]

#=============================================================================

def setgeometryanddeiconify(window, geom):
    # To avoid flashes on X and to position the window correctly on NT
    # (caused by Tk bugs).

    if os.name == 'nt' or \
            (os.name == 'posix' and sys.platform[:6] == 'cygwin'):
        # Require overrideredirect trick to stop window frame
        # appearing momentarily.
        redirect = window.overrideredirect()
        if not redirect:
            window.overrideredirect(1)
        window.deiconify()
        if geom is not None:
            window.geometry(geom)
        # Call update_idletasks to ensure NT moves the window to the
        # correct position it is raised.
        window.update_idletasks()
        window.tkraise()
        if not redirect:
            window.overrideredirect(0)
    else:
        if geom is not None:
            window.geometry(geom)

        # Problem!?  Which way around should the following two calls
        # go?  If deiconify() is called first then I get complaints
        # from people using the enlightenment or sawfish window
        # managers that when a dialog is activated it takes about 2
        # seconds for the contents of the window to appear.  But if
        # tkraise() is called first then I get complaints from people
        # using the twm window manager that when a dialog is activated
        # it appears in the top right corner of the screen and also
        # takes about 2 seconds to appear.

        #window.tkraise()
        # Call update_idletasks to ensure certain window managers (eg: 
        # enlightenment and sawfish) do not cause Tk to delay for
        # about two seconds before displaying window.
        #window.update_idletasks()
        #window.deiconify()

        window.deiconify()
        if window.overrideredirect():
            # The window is not under the control of the window manager
            # and so we need to raise it ourselves.
            window.tkraise()

#=============================================================================

class MegaArchetype:
    # Megawidget abstract root class.

    # This class provides methods which are inherited by classes
    # implementing useful bases (this class doesn't provide a
    # container widget inside which the megawidget can be built).

    def __init__(self, parent = None, hullClass = None):

	# Mapping from each megawidget option to a list of information
	# about the option
	#   - default value
	#   - current value
	#   - function to call when the option is initialised in the
	#     call to initialiseoptions() in the constructor or
	#     modified via configure().  If this is INITOPT, the
	#     option is an initialisation option (an option that can
	#     be set by the call to the constructor but can not be
	#     used with configure).
	# This mapping is not initialised here, but in the call to
	# defineoptions() which precedes construction of this base class.
	#
	# self._optionInfo = {}

	# Mapping from each component name to a tuple of information
	# about the component.
	#   - component widget instance
	#   - configure function of widget instance
	#   - the class of the widget (Frame, EntryField, etc)
	#   - cget function of widget instance
	#   - the name of the component group of this component, if any
	self.__componentInfo = {}

	# Mapping from alias names to the names of components or
	# sub-components.
	self.__componentAliases = {}

	# Contains information about the keywords provided to the
	# constructor.  It is a mapping from the keyword to a tuple
	# containing:
	#    - value of keyword
	#    - a boolean indicating if the keyword has been used.
	# A keyword is used if, during the construction of a megawidget,
	#    - it is defined in a call to defineoptions() or addoptions(), or
	#    - it references, by name, a component of the megawidget, or
	#    - it references, by group, at least one component
	# At the end of megawidget construction, a call is made to
	# initialiseoptions() which reports an error if there are
	# unused options given to the constructor.
        #
        # After megawidget construction, the dictionary contains
        # keywords which refer to a dynamic component group, so that
        # these components can be created after megawidget
        # construction and still use the group options given to the
        # constructor.
	#
	# self._constructorKeywords = {}

        # List of dynamic component groups.  If a group is included in
        # this list, then it not an error if a keyword argument for
        # the group is given to the constructor or to configure(), but
        # no components with this group have been created.
        # self._dynamicGroups = ()

	if hullClass is None:
	    self._hull = None
	else:
	    if parent is None:
		parent = Tkinter._default_root

	    # Create the hull.
	    self._hull = self.createcomponent('hull',
		    (), None,
		    hullClass, (parent,))
	    _hullToMegaWidget[self._hull] = self

	    if _useTkOptionDb:
		# Now that a widget has been created, query the Tk
		# option database to get the default values for the
		# options which have not been set in the call to the
		# constructor.  This assumes that defineoptions() is
		# called before the __init__().
		option_get = self.option_get
		_VALUE = _OPT_VALUE
		_DEFAULT = _OPT_DEFAULT
		for name, info in self._optionInfo.items():
		    value = info[_VALUE]
		    if value is _DEFAULT_OPTION_VALUE:
			resourceClass = string.upper(name[0]) + name[1:]
			value = option_get(name, resourceClass)
			if value != '':
			    try:
				# Convert the string to int/float/tuple, etc
				value = eval(value, {'__builtins__': {}})
			    except:
				pass
			    info[_VALUE] = value
			else:
			    info[_VALUE] = info[_DEFAULT]

    def destroy(self):
        # Clean up optionInfo in case it contains circular references
        # in the function field, such as self._settitle in class
        # MegaToplevel.

	self._optionInfo = {}
        if self._hull is not None:
            del _hullToMegaWidget[self._hull]
            self._hull.destroy()

    #======================================================================
    # Methods used (mainly) during the construction of the megawidget.

    def defineoptions(self, keywords, optionDefs, dynamicGroups = ()):
	# Create options, providing the default value and the method
	# to call when the value is changed.  If any option created by
	# base classes has the same name as one in <optionDefs>, the
	# base class's value and function will be overriden.

	# This should be called before the constructor of the base
	# class, so that default values defined in the derived class
	# override those in the base class.

	if not hasattr(self, '_constructorKeywords'):
            # First time defineoptions has been called.
	    tmp = {}
	    for option, value in keywords.items():
		tmp[option] = [value, 0]
	    self._constructorKeywords = tmp
	    self._optionInfo = {}
	    self._initialiseoptions_counter = 0
        self._initialiseoptions_counter = self._initialiseoptions_counter + 1

        if not hasattr(self, '_dynamicGroups'):
            self._dynamicGroups = ()
        self._dynamicGroups = self._dynamicGroups + tuple(dynamicGroups)
	self.addoptions(optionDefs)

    def addoptions(self, optionDefs):
	# Add additional options, providing the default value and the
	# method to call when the value is changed.  See
	# "defineoptions" for more details

	# optimisations:
	optionInfo = self._optionInfo
	optionInfo_has_key = optionInfo.has_key
	keywords = self._constructorKeywords
	keywords_has_key = keywords.has_key
	FUNCTION = _OPT_FUNCTION

	for name, default, function in optionDefs:
	    if '_' not in name:
		# The option will already exist if it has been defined
		# in a derived class.  In this case, do not override the
		# default value of the option or the callback function
		# if it is not None.
		if not optionInfo_has_key(name):
		    if keywords_has_key(name):
			value = keywords[name][0]
			optionInfo[name] = [default, value, function]
			del keywords[name]
		    else:
			if _useTkOptionDb:
			    optionInfo[name] = \
				    [default, _DEFAULT_OPTION_VALUE, function]
			else:
			    optionInfo[name] = [default, default, function]
		elif optionInfo[name][FUNCTION] is None:
		    optionInfo[name][FUNCTION] = function
	    else:
		# This option is of the form "component_option".  If this is
		# not already defined in self._constructorKeywords add it.
		# This allows a derived class to override the default value
		# of an option of a component of a base class.
		if not keywords_has_key(name):
		    keywords[name] = [default, 0]

    def createcomponent(self, componentName, componentAliases,
            componentGroup, widgetClass, *widgetArgs, **kw):
	# Create a component (during construction or later).

	if self.__componentInfo.has_key(componentName):
	    raise ValueError, 'Component "%s" already exists' % componentName

	if '_' in componentName:
	    raise ValueError, \
                    'Component name "%s" must not contain "_"' % componentName

	if hasattr(self, '_constructorKeywords'):
	    keywords = self._constructorKeywords
	else:
	    keywords = {}
	for alias, component in componentAliases:
	    # Create aliases to the component and its sub-components.
	    index = string.find(component, '_')
	    if index < 0:
		self.__componentAliases[alias] = (component, None)
	    else:
		mainComponent = component[:index]
		subComponent = component[(index + 1):]
		self.__componentAliases[alias] = (mainComponent, subComponent)

	    # Remove aliases from the constructor keyword arguments by
	    # replacing any keyword arguments that begin with *alias*
	    # with corresponding keys beginning with *component*.

	    alias = alias + '_'
	    aliasLen = len(alias)
	    for option in keywords.keys():
		if len(option) > aliasLen and option[:aliasLen] == alias:
		    newkey = component + '_' + option[aliasLen:]
		    keywords[newkey] = keywords[option]
		    del keywords[option]

	componentPrefix = componentName + '_'
	nameLen = len(componentPrefix)
	for option in keywords.keys():
	    if len(option) > nameLen and option[:nameLen] == componentPrefix:
		# The keyword argument refers to this component, so add
		# this to the options to use when constructing the widget.
		kw[option[nameLen:]] = keywords[option][0]
		del keywords[option]
	    else:
		# Check if this keyword argument refers to the group
		# of this component.  If so, add this to the options
		# to use when constructing the widget.  Mark the
		# keyword argument as being used, but do not remove it
		# since it may be required when creating another
		# component.
		index = string.find(option, '_')
		if index >= 0 and componentGroup == option[:index]:
		    rest = option[(index + 1):]
		    kw[rest] = keywords[option][0]
		    keywords[option][1] = 1

	if kw.has_key('pyclass'):
	    widgetClass = kw['pyclass']
	    del kw['pyclass']
	if widgetClass is None:
	    return None
        if len(widgetArgs) == 1 and type(widgetArgs[0]) == types.TupleType:
            # Arguments to the constructor can be specified as either
            # multiple trailing arguments to createcomponent() or as a
            # single tuple argument.
            widgetArgs = widgetArgs[0]
	widget = apply(widgetClass, widgetArgs, kw)
	componentClass = widget.__class__.__name__
	self.__componentInfo[componentName] = (widget, widget.configure,
		componentClass, widget.cget, componentGroup)

	return widget

    def destroycomponent(self, name):
	# Remove a megawidget component.

	# This command is for use by megawidget designers to destroy a
	# megawidget component.

	self.__componentInfo[name][0].destroy()
	del self.__componentInfo[name]

    def createlabel(self, parent, childCols = 1, childRows = 1):

	labelpos = self['labelpos']
	labelmargin = self['labelmargin']
	if labelpos is None:
	    return

	label = self.createcomponent('label',
		(), None,
		Tkinter.Label, (parent,))

	if labelpos[0] in 'ns':
	    # vertical layout
	    if labelpos[0] == 'n':
		row = 0
		margin = 1
	    else:
		row = childRows + 3
		margin = row - 1
	    label.grid(column=2, row=row, columnspan=childCols, sticky=labelpos)
	    parent.grid_rowconfigure(margin, minsize=labelmargin)
	else:
	    # horizontal layout
	    if labelpos[0] == 'w':
		col = 0
		margin = 1
	    else:
		col = childCols + 3
		margin = col - 1
	    label.grid(column=col, row=2, rowspan=childRows, sticky=labelpos)
	    parent.grid_columnconfigure(margin, minsize=labelmargin)

    def initialiseoptions(self, dummy = None):
        self._initialiseoptions_counter = self._initialiseoptions_counter - 1
	if self._initialiseoptions_counter == 0:
	    unusedOptions = []
	    keywords = self._constructorKeywords
	    for name in keywords.keys():
		used = keywords[name][1]
		if not used:
                    # This keyword argument has not been used.  If it
                    # does not refer to a dynamic group, mark it as
                    # unused.
                    index = string.find(name, '_')
                    if index < 0 or name[:index] not in self._dynamicGroups:
                        unusedOptions.append(name)
	    if len(unusedOptions) > 0:
		if len(unusedOptions) == 1:
		    text = 'Unknown option "'
		else:
		    text = 'Unknown options "'
		raise KeyError, text + string.join(unusedOptions, ', ') + \
			'" for ' + self.__class__.__name__

	    # Call the configuration callback function for every option.
	    FUNCTION = _OPT_FUNCTION
	    for info in self._optionInfo.values():
		func = info[FUNCTION]
		if func is not None and func is not INITOPT:
		    func()

    #======================================================================
    # Method used to configure the megawidget.

    def configure(self, option=None, **kw):
	# Query or configure the megawidget options.
	#
	# If not empty, *kw* is a dictionary giving new
	# values for some of the options of this megawidget or its
	# components.  For options defined for this megawidget, set
	# the value of the option to the new value and call the
	# configuration callback function, if any.  For options of the
	# form <component>_<option>, where <component> is a component
	# of this megawidget, call the configure method of the
	# component giving it the new value of the option.  The
	# <component> part may be an alias or a component group name.
	#
	# If *option* is None, return all megawidget configuration
	# options and settings.  Options are returned as standard 5
	# element tuples
	#
	# If *option* is a string, return the 5 element tuple for the
	# given configuration option.

	# First, deal with the option queries.
	if len(kw) == 0:
	    # This configure call is querying the values of one or all options.
	    # Return 5-tuples:
	    #     (optionName, resourceName, resourceClass, default, value)
	    if option is None:
		rtn = {}
		for option, config in self._optionInfo.items():
		    resourceClass = string.upper(option[0]) + option[1:]
		    rtn[option] = (option, option, resourceClass,
			    config[_OPT_DEFAULT], config[_OPT_VALUE])
		return rtn
	    else:
		config = self._optionInfo[option]
		resourceClass = string.upper(option[0]) + option[1:]
		return (option, option, resourceClass, config[_OPT_DEFAULT],
			config[_OPT_VALUE])

	# optimisations:
	optionInfo = self._optionInfo
	optionInfo_has_key = optionInfo.has_key
	componentInfo = self.__componentInfo
	componentInfo_has_key = componentInfo.has_key
	componentAliases = self.__componentAliases
	componentAliases_has_key = componentAliases.has_key
	VALUE = _OPT_VALUE
	FUNCTION = _OPT_FUNCTION

	# This will contain a list of options in *kw* which
	# are known to this megawidget.
	directOptions = []

	# This will contain information about the options in
	# *kw* of the form <component>_<option>, where
	# <component> is a component of this megawidget.  It is a
	# dictionary whose keys are the configure method of each
	# component and whose values are a dictionary of options and
	# values for the component.
	indirectOptions = {}
	indirectOptions_has_key = indirectOptions.has_key

	for option, value in kw.items():
	    if optionInfo_has_key(option):
		# This is one of the options of this megawidget. 
		# Make sure it is not an initialisation option.
		if optionInfo[option][FUNCTION] is INITOPT:
		    raise KeyError, \
			    'Cannot configure initialisation option "' \
			    + option + '" for ' + self.__class__.__name__
		optionInfo[option][VALUE] = value
		directOptions.append(option)
	    else:
		index = string.find(option, '_')
		if index >= 0:
		    # This option may be of the form <component>_<option>.
		    component = option[:index]
		    componentOption = option[(index + 1):]

		    # Expand component alias
		    if componentAliases_has_key(component):
			component, subComponent = componentAliases[component]
			if subComponent is not None:
			    componentOption = subComponent + '_' \
				    + componentOption

			# Expand option string to write on error
			option = component + '_' + componentOption

		    if componentInfo_has_key(component):
			# Configure the named component
			componentConfigFuncs = [componentInfo[component][1]]
		    else:
			# Check if this is a group name and configure all
			# components in the group.
			componentConfigFuncs = []
			for info in componentInfo.values():
			    if info[4] == component:
			        componentConfigFuncs.append(info[1])

                        if len(componentConfigFuncs) == 0 and \
                                component not in self._dynamicGroups:
			    raise KeyError, 'Unknown option "' + option + \
				    '" for ' + self.__class__.__name__

		    # Add the configure method(s) (may be more than
		    # one if this is configuring a component group)
		    # and option/value to dictionary.
		    for componentConfigFunc in componentConfigFuncs:
			if not indirectOptions_has_key(componentConfigFunc):
			    indirectOptions[componentConfigFunc] = {}
			indirectOptions[componentConfigFunc][componentOption] \
				= value
		else:
		    raise KeyError, 'Unknown option "' + option + \
			    '" for ' + self.__class__.__name__

	# Call the configure methods for any components.
	map(apply, indirectOptions.keys(),
		((),) * len(indirectOptions), indirectOptions.values())

	# Call the configuration callback function for each option.
	for option in directOptions:
	    info = optionInfo[option]
	    func = info[_OPT_FUNCTION]
	    if func is not None:
	      func()

    def __setitem__(self, key, value):
        apply(self.configure, (), {key: value})

    #======================================================================
    # Methods used to query the megawidget.

    def component(self, name):
	# Return a component widget of the megawidget given the
	# component's name
	# This allows the user of a megawidget to access and configure
	# widget components directly.

	# Find the main component and any subcomponents
	index = string.find(name, '_')
	if index < 0:
	    component = name
	    remainingComponents = None
	else:
	    component = name[:index]
	    remainingComponents = name[(index + 1):]

	# Expand component alias
	if self.__componentAliases.has_key(component):
	    component, subComponent = self.__componentAliases[component]
	    if subComponent is not None:
		if remainingComponents is None:
		    remainingComponents = subComponent
		else:
		    remainingComponents = subComponent + '_' \
			    + remainingComponents

	widget = self.__componentInfo[component][0]
	if remainingComponents is None:
	    return widget
	else:
	    return widget.component(remainingComponents)

    def interior(self):
	return self._hull

    def hulldestroyed(self):
	return not _hullToMegaWidget.has_key(self._hull)

    def __str__(self):
	return str(self._hull)

    def cget(self, option):
	# Get current configuration setting.

	# Return the value of an option, for example myWidget['font']. 

	if self._optionInfo.has_key(option):
	    return self._optionInfo[option][_OPT_VALUE]
	else:
	    index = string.find(option, '_')
	    if index >= 0:
		component = option[:index]
		componentOption = option[(index + 1):]

		# Expand component alias
		if self.__componentAliases.has_key(component):
		    component, subComponent = self.__componentAliases[component]
		    if subComponent is not None:
			componentOption = subComponent + '_' + componentOption

		    # Expand option string to write on error
		    option = component + '_' + componentOption

		if self.__componentInfo.has_key(component):
		    # Call cget on the component.
		    componentCget = self.__componentInfo[component][3]
		    return componentCget(componentOption)
		else:
		    # If this is a group name, call cget for one of
		    # the components in the group.
		    for info in self.__componentInfo.values():
			if info[4] == component:
			    componentCget = info[3]
			    return componentCget(componentOption)

	raise KeyError, 'Unknown option "' + option + \
		'" for ' + self.__class__.__name__

    __getitem__ = cget

    def isinitoption(self, option):
	return self._optionInfo[option][_OPT_FUNCTION] is INITOPT

    def options(self):
	options = []
	if hasattr(self, '_optionInfo'):
	    for option, info in self._optionInfo.items():
		isinit = info[_OPT_FUNCTION] is INITOPT
		default = info[_OPT_DEFAULT]
		options.append((option, default, isinit))
	    options.sort()
	return options

    def components(self):
	# Return a list of all components.

	# This list includes the 'hull' component and all widget subcomponents

	names = self.__componentInfo.keys()
	names.sort()
	return names

    def componentaliases(self):
	# Return a list of all component aliases.

	componentAliases = self.__componentAliases

	names = componentAliases.keys()
	names.sort()
	rtn = []
	for alias in names:
	    (mainComponent, subComponent) = componentAliases[alias]
	    if subComponent is None:
		rtn.append((alias, mainComponent))
	    else:
		rtn.append((alias, mainComponent + '_' + subComponent))
	    
	return rtn

    def componentgroup(self, name):
	return self.__componentInfo[name][4]

#=============================================================================

# The grab functions are mainly called by the activate() and
# deactivate() methods.
#
# Use pushgrab() to add a new window to the grab stack.  This
# releases the grab by the window currently on top of the stack (if
# there is one) and gives the grab and focus to the new widget.
#
# To remove the grab from the window on top of the grab stack, call
# popgrab().
#
# Use releasegrabs() to release the grab and clear the grab stack.

def pushgrab(grabWindow, globalMode, deactivateFunction):
    prevFocus = grabWindow.tk.call('focus')
    grabInfo = {
        'grabWindow' : grabWindow,
        'globalMode' : globalMode,
        'previousFocus' : prevFocus,
        'deactivateFunction' : deactivateFunction,
    }
    _grabStack.append(grabInfo)
    _grabtop()
    grabWindow.focus_set()

def popgrab(window):
    # Return the grab to the next window in the grab stack, if any.

    # If this window is not at the top of the grab stack, then it has
    # just been deleted by the window manager or deactivated by a
    # timer.  Call the deactivate method for the modal dialog above
    # this one on the stack. 
    if _grabStack[-1]['grabWindow'] != window:
        for index in range(len(_grabStack)):
            if _grabStack[index]['grabWindow'] == window:
                _grabStack[index + 1]['deactivateFunction']()
                break

    grabInfo = _grabStack[-1]
    del _grabStack[-1]

    topWidget = grabInfo['grabWindow']
    prevFocus = grabInfo['previousFocus']
    globalMode = grabInfo['globalMode']

    if globalMode != 'nograb':
        topWidget.grab_release()

    if len(_grabStack) > 0:
        _grabtop()
    if prevFocus != '':
        try:
            topWidget.tk.call('focus', prevFocus)
        except Tkinter.TclError:
            # Previous focus widget has been deleted. Set focus
            # to root window.
            Tkinter._default_root.focus_set()
    else:
        # Make sure that focus does not remain on the released widget.
        if len(_grabStack) > 0:
            topWidget = _grabStack[-1]['grabWindow']
            topWidget.focus_set()
        else:
            Tkinter._default_root.focus_set()

def grabstacktopwindow():
    if len(_grabStack) == 0:
        return None
    else:
        return _grabStack[-1]['grabWindow']

def releasegrabs():
    # Release grab and clear the grab stack.

    current = Tkinter._default_root.grab_current()
    if current is not None:
        current.grab_release()
    _grabStack[:] = []

def _grabtop():
    grabInfo = _grabStack[-1]
    topWidget = grabInfo['grabWindow']
    globalMode = grabInfo['globalMode']

    if globalMode == 'nograb':
        return

    while 1:
        try:
            if globalMode:
                topWidget.grab_set_global()
            else:
                topWidget.grab_set()
            break
        except Tkinter.TclError:
            # Another application has grab.  Keep trying until
            # grab can succeed.
            topWidget.after(100)

#=============================================================================

class MegaToplevel(MegaArchetype):

    def __init__(self, parent = None, **kw):
	# Define the options for this megawidget.
	optiondefs = (
            ('activatecommand',   None,                     None),
            ('deactivatecommand', None,                     None),
            ('master',            None,                     None),
            ('title',             None,                     self._settitle),
            ('hull_class',        self.__class__.__name__,  None),
	)
	self.defineoptions(kw, optiondefs)

	# Initialise the base class (after defining the options).
	MegaArchetype.__init__(self, parent, Tkinter.Toplevel)

	# Initialise instance.

        # Set WM_DELETE_WINDOW protocol, deleting any old callback, so
        # memory does not leak.
        if hasattr(self._hull, '_Pmw_WM_DELETE_name'):
            self._hull.tk.deletecommand(self._hull._Pmw_WM_DELETE_name)
        self._hull._Pmw_WM_DELETE_name = \
                self.register(self._userDeleteWindow, needcleanup = 0)
	self.protocol('WM_DELETE_WINDOW', self._hull._Pmw_WM_DELETE_name)

	# Initialise instance variables.

	self._firstShowing = 1
	# Used by show() to ensure window retains previous position on screen.

	# The IntVar() variable to wait on during a modal dialog.
	self._wait = None

	self._active = 0
	self._userDeleteFunc = self.destroy
	self._userModalDeleteFunc = self.deactivate

	# Check keywords and initialise options.
	self.initialiseoptions()

    def _settitle(self):
	title = self['title']
	if title is not None:
	    self.title(title)

    def userdeletefunc(self, func=None):
        if func:
	    self._userDeleteFunc = func
	else:
	    return self._userDeleteFunc

    def usermodaldeletefunc(self, func=None):
        if func:
	    self._userModalDeleteFunc = func
	else:
	    return self._userModalDeleteFunc

    def _userDeleteWindow(self):
	if self.active():
	    self._userModalDeleteFunc()
	else:
	    self._userDeleteFunc()

    def destroy(self):
	# Allow this to be called more than once.
	if _hullToMegaWidget.has_key(self._hull):
	    self.deactivate()

            # Remove circular references, so that object can get cleaned up.
            del self._userDeleteFunc
            del self._userModalDeleteFunc

            MegaArchetype.destroy(self)

    def show(self, master = None):
	if self.state() != 'normal':
	    if self._firstShowing:
		# Just let the window manager determine the window
		# position for the first time.
		geom = None
	    else:
		# Position the window at the same place it was last time.
		geom = self._sameposition()
            setgeometryanddeiconify(self, geom)

        if self._firstShowing:
            self._firstShowing = 0
        else:
            if self.transient() == '':
                self.tkraise()

        # Do this last, otherwise get flashing on NT:
        if master is not None:
            if master == 'parent':
                parent = self.winfo_parent()
                # winfo_parent() should return the parent widget, but the
                # the current version of Tkinter returns a string.
                if type(parent) == types.StringType:
                    parent = self._hull._nametowidget(parent)
                master = parent.winfo_toplevel()
            self.transient(master)

        self.focus()

    def _centreonscreen(self):
	# Centre the window on the screen.  (Actually halfway across
	# and one third down.)

        parent = self.winfo_parent()
        if type(parent) == types.StringType:
            parent = self._hull._nametowidget(parent)

        # Find size of window.
	self.update_idletasks()
        width = self.winfo_width()
        height = self.winfo_height()
        if width == 1 and height == 1:
            # If the window has not yet been displayed, its size is
            # reported as 1x1, so use requested size.
            width = self.winfo_reqwidth()
            height = self.winfo_reqheight()

        # Place in centre of screen:
	x = (self.winfo_screenwidth() - width) / 2 - parent.winfo_vrootx()
	y = (self.winfo_screenheight() - height) / 3 - parent.winfo_vrooty()
	if x < 0:
	    x = 0
	if y < 0:
	    y = 0
        return '+%d+%d' % (x, y)

    def _sameposition(self):
	# Position the window at the same place it was last time.

	geometry = self.geometry()
	index = string.find(geometry, '+')
	if index >= 0:
	    return geometry[index:]
        else:
	    return None

    def activate(self, globalMode = 0, geometry = 'centerscreenfirst'):
	if self._active:
	    raise ValueError, 'Window is already active'
	if self.state() == 'normal':
	    self.withdraw()

	self._active = 1

	showbusycursor()

	if self._wait is None:
	    self._wait = Tkinter.IntVar()
	self._wait.set(0)

	if geometry == 'centerscreenalways':
	    geom = self._centreonscreen()
	elif geometry == 'centerscreenfirst':
	    if self._firstShowing:
		# Centre the window the first time it is displayed.
		geom = self._centreonscreen()
	    else:
		# Position the window at the same place it was last time.
		geom = self._sameposition()
	elif geometry[:5] == 'first':
	    if self._firstShowing:
                geom = geometry[5:]
	    else:
		# Position the window at the same place it was last time.
		geom = self._sameposition()
        else:
            geom = geometry

	self._firstShowing = 0

        setgeometryanddeiconify(self, geom)

        # Do this last, otherwise get flashing on NT:
        master = self['master']
        if master is not None:
            if master == 'parent':
                parent = self.winfo_parent()
                # winfo_parent() should return the parent widget, but the
                # the current version of Tkinter returns a string.
                if type(parent) == types.StringType:
                    parent = self._hull._nametowidget(parent)
                master = parent.winfo_toplevel()
            self.transient(master)

        pushgrab(self._hull, globalMode, self.deactivate)
	command = self['activatecommand']
	if callable(command):
	    command()
	self.wait_variable(self._wait)

	return self._result

    def deactivate(self, result=None):
	if not self._active:
	    return
	self._active = 0

        # Restore the focus before withdrawing the window, since
        # otherwise the window manager may take the focus away so we
        # can't redirect it.  Also, return the grab to the next active
        # window in the stack, if any.
        popgrab(self._hull)

        command = self['deactivatecommand']
        if callable(command):
            command()

        self.withdraw()
        hidebusycursor(forceFocusRestore = 1)

        self._result = result
        self._wait.set(1)

    def active(self):
	return self._active

forwardmethods(MegaToplevel, Tkinter.Toplevel, '_hull')

#=============================================================================

class MegaWidget(MegaArchetype):
    def __init__(self, parent = None, **kw):
	# Define the options for this megawidget.
	optiondefs = (
	    ('hull_class',       self.__class__.__name__,  None),
	)
	self.defineoptions(kw, optiondefs)

	# Initialise the base class (after defining the options).
	MegaArchetype.__init__(self, parent, Tkinter.Frame)

	# Check keywords and initialise options.
	self.initialiseoptions()

forwardmethods(MegaWidget, Tkinter.Frame, '_hull')

#=============================================================================

# Public functions
#-----------------

_traceTk = 0
def tracetk(root = None, on = 1, withStackTrace = 0, file=None):
    global _withStackTrace
    global _traceTkFile
    global _traceTk

    if root is None:
        root = Tkinter._default_root

    _withStackTrace = withStackTrace
    _traceTk = on
    if on:
	if hasattr(root.tk, '__class__'):
	    # Tracing already on
	    return
	if file is None:
	    _traceTkFile = sys.stderr
	else:
	    _traceTkFile = file
	tk = _TraceTk(root.tk)
    else:
	if not hasattr(root.tk, '__class__'):
	    # Tracing already off
	    return
	tk = root.tk.getTclInterp()
    _setTkInterps(root, tk)

def showbusycursor():

    _addRootToToplevelBusyInfo()
    root = Tkinter._default_root

    busyInfo = {
        'newBusyWindows' : [],
        'previousFocus' : None,
        'busyFocus' : None,
    }
    _busyStack.append(busyInfo)

    if _disableKeyboardWhileBusy:
        # Remember the focus as it is now, before it is changed.
        busyInfo['previousFocus'] = root.tk.call('focus')

    if not _havebltbusy(root):
        # No busy command, so don't call busy hold on any windows.
        return

    for (window, winInfo) in _toplevelBusyInfo.items():
	if (window.state() != 'withdrawn' and not winInfo['isBusy']
                and not winInfo['excludeFromBusy']):
            busyInfo['newBusyWindows'].append(window)
            winInfo['isBusy'] = 1
            _busy_hold(window, winInfo['busyCursorName'])

            # Make sure that no events for the busy window get
            # through to Tkinter, otherwise it will crash in
            # _nametowidget with a 'KeyError: _Busy' if there is
            # a binding on the toplevel window.
            window.tk.call('bindtags', winInfo['busyWindow'], 'Pmw_Dummy_Tag')

            if _disableKeyboardWhileBusy:
                # Remember previous focus widget for this toplevel window
                # and set focus to the busy window, which will ignore all
                # keyboard events.
                winInfo['windowFocus'] = \
                        window.tk.call('focus', '-lastfor', window._w)
                window.tk.call('focus', winInfo['busyWindow'])
                busyInfo['busyFocus'] = winInfo['busyWindow']

    if len(busyInfo['newBusyWindows']) > 0:
        if os.name == 'nt':
            # NT needs an "update" before it will change the cursor.
            window.update()
        else:
            window.update_idletasks()

def hidebusycursor(forceFocusRestore = 0):

    # Remember the focus as it is now, before it is changed.
    root = Tkinter._default_root
    if _disableKeyboardWhileBusy:
        currentFocus = root.tk.call('focus')

    # Pop the busy info off the stack.
    busyInfo = _busyStack[-1]
    del _busyStack[-1]

    for window in busyInfo['newBusyWindows']:
        # If this window has not been deleted, release the busy cursor.
        if _toplevelBusyInfo.has_key(window):
            winInfo = _toplevelBusyInfo[window]
            winInfo['isBusy'] = 0
            _busy_release(window)

            if _disableKeyboardWhileBusy:
                # Restore previous focus window for this toplevel window,
                # but only if is still set to the busy window (it may have
                # been changed).
                windowFocusNow = window.tk.call('focus', '-lastfor', window._w)
                if windowFocusNow == winInfo['busyWindow']:
                    try:
                        window.tk.call('focus', winInfo['windowFocus'])
                    except Tkinter.TclError:
                        # Previous focus widget has been deleted. Set focus
                        # to toplevel window instead (can't leave focus on
                        # busy window).
                        window.focus_set()

    if _disableKeyboardWhileBusy:
        # Restore the focus, depending on whether the focus had changed
        # between the calls to showbusycursor and hidebusycursor.
        if forceFocusRestore or busyInfo['busyFocus'] == currentFocus:
            # The focus had not changed, so restore it to as it was before
            # the call to showbusycursor,
            previousFocus = busyInfo['previousFocus']
            if previousFocus is not None:
                try:
                    root.tk.call('focus', previousFocus)
                except Tkinter.TclError:
                    # Previous focus widget has been deleted; forget it.
                    pass
        else:
            # The focus had changed, so restore it to what it had been
            # changed to before the call to hidebusycursor.
            root.tk.call('focus', currentFocus)

def clearbusycursor():
    while len(_busyStack) > 0:
        hidebusycursor()

def setbusycursorattributes(window, **kw):
    _addRootToToplevelBusyInfo()
    for name, value in kw.items():
        if name == 'exclude':
            _toplevelBusyInfo[window]['excludeFromBusy'] = value
        elif name == 'cursorName':
            _toplevelBusyInfo[window]['busyCursorName'] = value
        else:
            raise KeyError, 'Unknown busycursor attribute "' + name + '"'

def _addRootToToplevelBusyInfo():
    # Include the Tk root window in the list of toplevels.  This must
    # not be called before Tkinter has had a chance to be initialised by
    # the application.

    root = Tkinter._default_root
    if root == None:
        root = Tkinter.Tk()
    if not _toplevelBusyInfo.has_key(root):
        _addToplevelBusyInfo(root)

def busycallback(command, updateFunction = None):
    if not callable(command):
	raise ValueError, \
	    'cannot register non-command busy callback %s %s' % \
	        (repr(command), type(command))
    wrapper = _BusyWrapper(command, updateFunction)
    return wrapper.callback

_errorReportFile = None
_errorWindow = None

def reporterrorstofile(file = None):
    global _errorReportFile
    _errorReportFile = file

def displayerror(text):
    global _errorWindow

    if _errorReportFile is not None:
	_errorReportFile.write(text + '\n')
    else:
        # Print error on standard error as well as to error window. 
        # Useful if error window fails to be displayed, for example
        # when exception is triggered in a <Destroy> binding for root
        # window.
        sys.stderr.write(text + '\n')

	if _errorWindow is None:
	    # The error window has not yet been created.
	    _errorWindow = _ErrorWindow()

	_errorWindow.showerror(text)

_root = None
_disableKeyboardWhileBusy = 1

def initialise(
        root = None,
        size = None,
        fontScheme = None,
        useTkOptionDb = 0,
        noBltBusy = 0,
        disableKeyboardWhileBusy = None,
):
    # Remember if show/hidebusycursor should ignore keyboard events.
    global _disableKeyboardWhileBusy
    if disableKeyboardWhileBusy is not None:
        _disableKeyboardWhileBusy = disableKeyboardWhileBusy

    # Do not use blt busy command if noBltBusy is set.  Otherwise,
    # use blt busy if it is available.
    global _haveBltBusy
    if noBltBusy:
        _haveBltBusy = 0

    # Save flag specifying whether the Tk option database should be
    # queried when setting megawidget option default values.
    global _useTkOptionDb
    _useTkOptionDb = useTkOptionDb

    # If we haven't been given a root window, use the default or
    # create one.
    if root is None:
	if Tkinter._default_root is None:
	    root = Tkinter.Tk()
	else:
	    root = Tkinter._default_root

    # If this call is initialising a different Tk interpreter than the
    # last call, then re-initialise all global variables.  Assume the
    # last interpreter has been destroyed - ie:  Pmw does not (yet)
    # support multiple simultaneous interpreters.
    global _root
    if _root is not None and _root != root:
        global _busyStack
        global _errorWindow
        global _grabStack
        global _hullToMegaWidget
        global _toplevelBusyInfo
        _busyStack = []
        _errorWindow = None
        _grabStack = []
        _hullToMegaWidget = {}
        _toplevelBusyInfo = {}
    _root = root

    # Trap Tkinter Toplevel constructors so that a list of Toplevels
    # can be maintained.
    Tkinter.Toplevel.title = __TkinterToplevelTitle

    # Trap Tkinter widget destruction so that megawidgets can be
    # destroyed when their hull widget is destoyed and the list of
    # Toplevels can be pruned.
    Tkinter.Toplevel.destroy = __TkinterToplevelDestroy
    Tkinter.Widget.destroy = __TkinterWidgetDestroy

    # Modify Tkinter's CallWrapper class to improve the display of
    # errors which occur in callbacks.
    Tkinter.CallWrapper = __TkinterCallWrapper

    # Make sure we get to know when the window manager deletes the
    # root window.  Only do this if the protocol has not yet been set. 
    # This is required if there is a modal dialog displayed and the
    # window manager deletes the root window.  Otherwise the
    # application will not exit, even though there are no windows.
    if root.protocol('WM_DELETE_WINDOW') == '':
	root.protocol('WM_DELETE_WINDOW', root.destroy)

    # Set the base font size for the application and set the
    # Tk option database font resources.
    import PmwLogicalFont
    PmwLogicalFont._font_initialise(root, size, fontScheme)

    return root

def alignlabels(widgets, sticky = None):
    if len(widgets) == 0:
    	return

    widgets[0].update_idletasks()

    # Determine the size of the maximum length label string.
    maxLabelWidth = 0
    for iwid in widgets:
	labelWidth = iwid.grid_bbox(0, 1)[2]
	if labelWidth > maxLabelWidth:
	    maxLabelWidth = labelWidth

    # Adjust the margins for the labels such that the child sites and
    # labels line up.
    for iwid in widgets:
	if sticky is not None:
	    iwid.component('label').grid(sticky=sticky)
	iwid.grid_columnconfigure(0, minsize = maxLabelWidth)
#=============================================================================

# Private routines
#-----------------
_callToTkReturned = 1
_recursionCounter = 1

class _TraceTk:
    def __init__(self, tclInterp):
        self.tclInterp = tclInterp

    def getTclInterp(self):
        return self.tclInterp

    # Calling from python into Tk.
    def call(self, *args, **kw):
        global _callToTkReturned
        global _recursionCounter

        _callToTkReturned = 0
        if len(args) == 1 and type(args[0]) == types.TupleType:
            argStr = str(args[0])
        else:
            argStr = str(args)
	_traceTkFile.write('CALL  TK> %d:%s%s' %
                (_recursionCounter, '  ' * _recursionCounter, argStr))
	_recursionCounter = _recursionCounter + 1
        try:
            result = apply(self.tclInterp.call, args, kw)
	except Tkinter.TclError, errorString:
            _callToTkReturned = 1
            _recursionCounter = _recursionCounter - 1
            _traceTkFile.write('\nTK ERROR> %d:%s-> %s\n' %
                    (_recursionCounter, '  ' * _recursionCounter,
                            repr(errorString)))
            if _withStackTrace:
                _traceTkFile.write('CALL  TK> stack:\n')
                traceback.print_stack()
            raise Tkinter.TclError, errorString

        _recursionCounter = _recursionCounter - 1
        if _callToTkReturned:
            _traceTkFile.write('CALL RTN> %d:%s-> %s' %
                    (_recursionCounter, '  ' * _recursionCounter, repr(result)))
        else:
            _callToTkReturned = 1
            if result:
                _traceTkFile.write(' -> %s' % repr(result))
        _traceTkFile.write('\n')
        if _withStackTrace:
            _traceTkFile.write('CALL  TK> stack:\n')
            traceback.print_stack()

        _traceTkFile.flush()
        return result

    def __getattr__(self, key):
        return getattr(self.tclInterp, key)

def _setTkInterps(window, tk):
    window.tk = tk
    for child in window.children.values():
      _setTkInterps(child, tk)

#=============================================================================

# Functions to display a busy cursor.  Keep a list of all toplevels
# and display the busy cursor over them.  The list will contain the Tk
# root toplevel window as well as all other toplevel windows.
# Also keep a list of the widget which last had focus for each
# toplevel.

# Map from toplevel windows to
#     {'isBusy', 'windowFocus', 'busyWindow',
#         'excludeFromBusy', 'busyCursorName'}
_toplevelBusyInfo = {}

# Pmw needs to know all toplevel windows, so that it can call blt busy
# on them.  This is a hack so we get notified when a Tk topevel is
# created.  Ideally, the __init__ 'method' should be overridden, but
# it is a 'read-only special attribute'.  Luckily, title() is always
# called from the Tkinter Toplevel constructor.

def _addToplevelBusyInfo(window):
    if window._w == '.':
        busyWindow = '._Busy'
    else:
        busyWindow = window._w + '._Busy'

    _toplevelBusyInfo[window] = {
        'isBusy' : 0,
        'windowFocus' : None,
        'busyWindow' : busyWindow,
        'excludeFromBusy' : 0,
        'busyCursorName' : None,
    }

def __TkinterToplevelTitle(self, *args):
    # If this is being called from the constructor, include this
    # Toplevel in the list of toplevels and set the initial
    # WM_DELETE_WINDOW protocol to destroy() so that we get to know
    # about it.
    if not _toplevelBusyInfo.has_key(self):
        _addToplevelBusyInfo(self)
        self._Pmw_WM_DELETE_name = self.register(self.destroy, None, 0)
	self.protocol('WM_DELETE_WINDOW', self._Pmw_WM_DELETE_name)

    return apply(Tkinter.Wm.title, (self,) + args)

_haveBltBusy = None
def _havebltbusy(window):
    global _busy_hold, _busy_release, _haveBltBusy
    if _haveBltBusy is None:
        import PmwBlt
        _haveBltBusy = PmwBlt.havebltbusy(window)
        _busy_hold = PmwBlt.busy_hold
        if os.name == 'nt':
            # There is a bug in Blt 2.4i on NT where the busy window
            # does not follow changes in the children of a window.
            # Using forget works around the problem.
            _busy_release = PmwBlt.busy_forget
        else:
            _busy_release = PmwBlt.busy_release
    return _haveBltBusy

class _BusyWrapper:
    def __init__(self, command, updateFunction):
	self._command = command
	self._updateFunction = updateFunction

    def callback(self, *args):
	showbusycursor()
	rtn = apply(self._command, args)

	# Call update before hiding the busy windows to clear any
	# events that may have occurred over the busy windows.
	if callable(self._updateFunction):
	    self._updateFunction()

	hidebusycursor()
	return rtn

#=============================================================================

def drawarrow(canvas, color, direction, tag, baseOffset = 0.25, edgeOffset = 0.15):
    canvas.delete(tag)

    bw = (string.atoi(canvas['borderwidth']) + 
            string.atoi(canvas['highlightthickness']))
    width = string.atoi(canvas['width'])
    height = string.atoi(canvas['height'])

    if direction in ('up', 'down'):
        majorDimension = height
        minorDimension = width
    else:
        majorDimension = width
        minorDimension = height

    offset = round(baseOffset * majorDimension)
    if direction in ('down', 'right'):
        base = bw + offset
        apex = bw + majorDimension - offset
    else:
        base = bw + majorDimension - offset
        apex = bw + offset

    if minorDimension > 3 and minorDimension % 2 == 0:
        minorDimension = minorDimension - 1
    half = int(minorDimension * (1 - 2 * edgeOffset)) / 2
    low = round(bw + edgeOffset * minorDimension)
    middle = low + half
    high = low + 2 * half

    if direction in ('up', 'down'):
        coords = (low, base, high, base, middle, apex)
    else:
        coords = (base, low, base, high, apex, middle)
    kw = {'fill' : color, 'outline' : color, 'tag' : tag}
    apply(canvas.create_polygon, coords, kw)

#=============================================================================

# Modify the Tkinter destroy methods so that it notifies us when a Tk
# toplevel or frame is destroyed.

# A map from the 'hull' component of a megawidget to the megawidget. 
# This is used to clean up a megawidget when its hull is destroyed.
_hullToMegaWidget = {}

def __TkinterToplevelDestroy(tkWidget):
    if _hullToMegaWidget.has_key(tkWidget):
        mega = _hullToMegaWidget[tkWidget]
        try:
	    mega.destroy()
        except:
	    _reporterror(mega.destroy, ())
    else:
        # Delete the busy info structure for this toplevel (if the
        # window was created before Pmw.initialise() was called, it
        # will not have any.
        if _toplevelBusyInfo.has_key(tkWidget):
            del _toplevelBusyInfo[tkWidget]
        if hasattr(tkWidget, '_Pmw_WM_DELETE_name'):
            tkWidget.tk.deletecommand(tkWidget._Pmw_WM_DELETE_name)
            del tkWidget._Pmw_WM_DELETE_name
        Tkinter.BaseWidget.destroy(tkWidget)

def __TkinterWidgetDestroy(tkWidget):
    if _hullToMegaWidget.has_key(tkWidget):
        mega = _hullToMegaWidget[tkWidget]
        try:
	    mega.destroy()
        except:
	    _reporterror(mega.destroy, ())
    else:
        Tkinter.BaseWidget.destroy(tkWidget)

#=============================================================================

# Add code to Tkinter to improve the display of errors which occur in
# callbacks.

class __TkinterCallWrapper:
    def __init__(self, func, subst, widget):
	self.func = func
	self.subst = subst
	self.widget = widget

    # Calling back from Tk into python.
    def __call__(self, *args):
	try:
	    if self.subst:
		args = apply(self.subst, args)
            if _traceTk:
                if not _callToTkReturned:
                    _traceTkFile.write('\n')
                if hasattr(self.func, 'im_class'):
                    name = self.func.im_class.__name__ + '.' + \
                        self.func.__name__
                else:
                    name = self.func.__name__
                if len(args) == 1 and hasattr(args[0], 'type'):
                    # The argument to the callback is an event.
                    eventName = _eventTypeToName[string.atoi(args[0].type)]
                    if eventName in ('KeyPress', 'KeyRelease',):
                        argStr = '(%s %s Event: %s)' % \
                            (eventName, args[0].keysym, args[0].widget)
                    else:
                        argStr = '(%s Event, %s)' % (eventName, args[0].widget)
                else:
                    argStr = str(args)
                _traceTkFile.write('CALLBACK> %d:%s%s%s\n' %
                    (_recursionCounter, '  ' * _recursionCounter, name, argStr))
                _traceTkFile.flush()
	    return apply(self.func, args)
	except SystemExit, msg:
	    raise SystemExit, msg
	except:
	    _reporterror(self.func, args)

_eventTypeToName = {
    2 : 'KeyPress',         15 : 'VisibilityNotify',   28 : 'PropertyNotify',
    3 : 'KeyRelease',       16 : 'CreateNotify',       29 : 'SelectionClear',
    4 : 'ButtonPress',      17 : 'DestroyNotify',      30 : 'SelectionRequest',
    5 : 'ButtonRelease',    18 : 'UnmapNotify',        31 : 'SelectionNotify',
    6 : 'MotionNotify',     19 : 'MapNotify',          32 : 'ColormapNotify',
    7 : 'EnterNotify',      20 : 'MapRequest',         33 : 'ClientMessage',
    8 : 'LeaveNotify',      21 : 'ReparentNotify',     34 : 'MappingNotify',
    9 : 'FocusIn',          22 : 'ConfigureNotify',    35 : 'VirtualEvents',
    10 : 'FocusOut',        23 : 'ConfigureRequest',   36 : 'ActivateNotify',
    11 : 'KeymapNotify',    24 : 'GravityNotify',      37 : 'DeactivateNotify',
    12 : 'Expose',          25 : 'ResizeRequest',      38 : 'MouseWheelEvent',
    13 : 'GraphicsExpose',  26 : 'CirculateNotify',
    14 : 'NoExpose',        27 : 'CirculateRequest',
}

def _reporterror(func, args):
    # Fetch current exception values.
    exc_type, exc_value, exc_traceback = sys.exc_info()

    # Give basic information about the callback exception.
    if type(exc_type) == types.ClassType:
	# Handle python 1.5 class exceptions.
	exc_type = exc_type.__name__
    msg = exc_type + ' Exception in Tk callback\n'
    msg = msg + '  Function: %s (type: %s)\n' % (repr(func), type(func))
    msg = msg + '  Args: %s\n' % str(args)

    if type(args) == types.TupleType and len(args) > 0 and \
	    hasattr(args[0], 'type'):
        eventArg = 1
    else:
        eventArg = 0

    # If the argument to the callback is an event, add the event type.
    if eventArg:
	eventNum = string.atoi(args[0].type)
        if eventNum in _eventTypeToName.keys():
            msg = msg + '  Event type: %s (type num: %d)\n' % \
                    (_eventTypeToName[eventNum], eventNum)
        else:
            msg = msg + '  Unknown event type (type num: %d)\n' % eventNum

    # Add the traceback.
    msg = msg + 'Traceback (innermost last):\n'
    for tr in traceback.extract_tb(exc_traceback):
	msg = msg + '  File "%s", line %s, in %s\n' % (tr[0], tr[1], tr[2])
	msg = msg + '    %s\n' % tr[3]
    msg = msg + '%s: %s\n' % (exc_type, exc_value)

    # If the argument to the callback is an event, add the event contents.
    if eventArg:
	msg = msg + '\n================================================\n'
	msg = msg + '  Event contents:\n'
	keys = args[0].__dict__.keys()
	keys.sort()
	for key in keys:
	    msg = msg + '    %s: %s\n' % (key, args[0].__dict__[key])

    clearbusycursor()
    try:
	displayerror(msg)
    except:
        pass

class _ErrorWindow:
    def __init__(self):

	self._errorQueue = []
	self._errorCount = 0
	self._open = 0
        self._firstShowing = 1

	# Create the toplevel window
	self._top = Tkinter.Toplevel()
	self._top.protocol('WM_DELETE_WINDOW', self._hide)
	self._top.title('Error in background function')
	self._top.iconname('Background error')

	# Create the text widget and scrollbar in a frame
	upperframe = Tkinter.Frame(self._top)

	scrollbar = Tkinter.Scrollbar(upperframe, orient='vertical')
	scrollbar.pack(side = 'right', fill = 'y')

	self._text = Tkinter.Text(upperframe, yscrollcommand=scrollbar.set)
	self._text.pack(fill = 'both', expand = 1)
	scrollbar.configure(command=self._text.yview)

	# Create the buttons and label in a frame
	lowerframe = Tkinter.Frame(self._top)

	ignore = Tkinter.Button(lowerframe,
	        text = 'Ignore remaining errors', command = self._hide)
	ignore.pack(side='left')

	self._nextError = Tkinter.Button(lowerframe,
	        text = 'Show next error', command = self._next)
	self._nextError.pack(side='left')

	self._label = Tkinter.Label(lowerframe, relief='ridge')
	self._label.pack(side='left', fill='x', expand=1)

	# Pack the lower frame first so that it does not disappear
	# when the window is resized.
	lowerframe.pack(side = 'bottom', fill = 'x')
	upperframe.pack(side = 'bottom', fill = 'both', expand = 1)

    def showerror(self, text):
	if self._open:
	    self._errorQueue.append(text)
	else:
	    self._display(text)
	    self._open = 1

	# Display the error window in the same place it was before.
	if self._top.state() == 'normal':
	    # If update_idletasks is not called here, the window may
	    # be placed partially off the screen.  Also, if it is not
	    # called and many errors are generated quickly in
	    # succession, the error window may not display errors
	    # until the last one is generated and the interpreter
	    # becomes idle.
	    # XXX: remove this, since it causes omppython to go into an
	    # infinite loop if an error occurs in an omp callback.
	    # self._top.update_idletasks()

            pass
	else:
	    if self._firstShowing:
		geom = None
	    else:
                geometry = self._top.geometry()
                index = string.find(geometry, '+')
                if index >= 0:
                    geom = geometry[index:]
                else:
                    geom = None
            setgeometryanddeiconify(self._top, geom)

        if self._firstShowing:
            self._firstShowing = 0
        else:
            self._top.tkraise()

        self._top.focus()

	self._updateButtons()

	# Release any grab, so that buttons in the error window work.
        releasegrabs()

    def _hide(self):
	self._errorCount = self._errorCount + len(self._errorQueue)
	self._errorQueue = []
	self._top.withdraw()
	self._open = 0

    def _next(self):
	# Display the next error in the queue. 

	text = self._errorQueue[0]
	del self._errorQueue[0]

	self._display(text)
	self._updateButtons()

    def _display(self, text):
	self._errorCount = self._errorCount + 1
	text = 'Error: %d\n%s' % (self._errorCount, text)
	self._text.delete('1.0', 'end')
	self._text.insert('end', text)

    def _updateButtons(self):
	numQueued = len(self._errorQueue)
	if numQueued > 0:
	    self._label.configure(text='%d more errors' % numQueued)
	    self._nextError.configure(state='normal')
	else:
	    self._label.configure(text='No more errors')
	    self._nextError.configure(state='disabled')
