# 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.
# Special values used in index() methods of several megawidgets.
# Constant used to indicate that an option can only be set by a call
_DEFAULT_OPTION_VALUE
= ['default_option_value']
# Symbolic constants for the indexes into an optionInfo list.
# 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()
# '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.
# 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.
# 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
# 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
__)
# do bases in reverse order, so first base overrides last base
__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
:
# Return all method names for a class.
# Return all method names for a class (attributes are filtered
# out). Base classes are searched recursively.
# Function body to resolve a forwarding given the target method name and the
# attribute name. The resulting lambda requires only self, but will forward
'def %(method)s(this, *args, **kw): return ' +
'apply(this.%(attribute)s.%(method)s, args, kw)')
__counter
= __counter
+ 1
# 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.
'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.
# self.__target = TargetClass()
# forwardmethods(MyClass, TargetClass, '__target', ['dangerous1', 'dangerous2'])
# 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 a method is passed, use the function within it
if hasattr(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
raise TypeError, 'toPart must be attribute name, function or method'
# get the full set of candidate methods
__methodDict(toClass
, dict)
# discard special methods
if ex
[:1] == '_' or ex
[-1:] == '_':
# discard dangerous methods supplied by the caller
# discard methods already defined in fromClass
for ex
in __methods(fromClass
):
for method
, func
in dict.items():
d
= {'method': method
, 'func': func
}
if type(toPart
) == types
.StringType
:
__stringBody
% {'method' : method
, 'attribute' : toPart
}
__funcBody
% {'forwardFunc' : forwardName
, 'method' : method
}
fromClass
.__dict
__[method
] = d
[method
]
#=============================================================================
def setgeometryanddeiconify(window
, geom
):
# To avoid flashes on X and to position the window correctly on NT
(os
.name
== 'posix' and sys
.platform
[:6] == 'cygwin'):
# Require overrideredirect trick to stop window frame
redirect
= window
.overrideredirect()
window
.overrideredirect(1)
# Call update_idletasks to ensure NT moves the window to the
# correct position it is raised.
window
.update_idletasks()
window
.overrideredirect(0)
# 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.
# 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()
if window
.overrideredirect():
# The window is not under the control of the window manager
# and so we need to raise it ourselves.
#=============================================================================
# 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
# - 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
# This mapping is not initialised here, but in the call to
# defineoptions() which precedes construction of this base class.
# Mapping from each component name to a tuple of information
# - 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
self
.__componentAliases
= {}
# Contains information about the keywords provided to the
# constructor. It is a mapping from the keyword to a tuple
# - 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
# 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 = ()
parent
= Tkinter
._default
_root
self
._hull
= self
.createcomponent('hull',
_hullToMegaWidget
[self
._hull
] = self
# 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
for name
, info
in self
._optionInfo
.items():
if value
is _DEFAULT_OPTION_VALUE
:
resourceClass
= string
.upper(name
[0]) + name
[1:]
value
= option_get(name
, resourceClass
)
# Convert the string to int/float/tuple, etc
value
= eval(value
, {'__builtins__': {}})
info
[_VALUE
] = info
[_DEFAULT
]
# Clean up optionInfo in case it contains circular references
# in the function field, such as self._settitle in class
if self
._hull
is not None:
del _hullToMegaWidget
[self
._hull
]
#======================================================================
# 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.
for option
, value
in keywords
.items():
self
._constructorKeywords
= tmp
self
._initialiseoptions
_counter
= 0
self
._initialiseoptions
_counter
= self
._initialiseoptions
_counter
+ 1
if not hasattr(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
optionInfo
= self
._optionInfo
optionInfo_has_key
= optionInfo
.has_key
keywords
= self
._constructorKeywords
keywords_has_key
= keywords
.has_key
for name
, default
, function
in optionDefs
:
# 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 not optionInfo_has_key(name
):
if keywords_has_key(name
):
value
= keywords
[name
][0]
optionInfo
[name
] = [default
, value
, function
]
[default
, _DEFAULT_OPTION_VALUE
, function
]
optionInfo
[name
] = [default
, default
, function
]
elif optionInfo
[name
][FUNCTION
] is None:
optionInfo
[name
][FUNCTION
] = function
# 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
'Component name "%s" must not contain "_"' % componentName
if hasattr(self
, '_constructorKeywords'):
keywords
= self
._constructorKeywords
for alias
, component
in componentAliases
:
# Create aliases to the component and its sub-components.
index
= string
.find(component
, '_')
self
.__componentAliases
[alias
] = (component
, None)
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*.
for option
in keywords
.keys():
if len(option
) > aliasLen
and option
[:aliasLen
] == alias
:
newkey
= component
+ '_' + option
[aliasLen
:]
keywords
[newkey
] = 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]
# 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
index
= string
.find(option
, '_')
if index
>= 0 and componentGroup
== option
[:index
]:
rest
= option
[(index
+ 1):]
kw
[rest
] = keywords
[option
][0]
if kw
.has_key('pyclass'):
widgetClass
= kw
['pyclass']
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
widgetArgs
= widgetArgs
[0]
widget
= apply(widgetClass
, widgetArgs
, kw
)
componentClass
= widget
.__class
__.__name
__
self
.__componentInfo
[componentName
] = (widget
, widget
.configure
,
componentClass
, widget
.cget
, componentGroup
)
def destroycomponent(self
, name
):
# Remove a megawidget component.
# This command is for use by megawidget designers to destroy a
self
.__componentInfo
[name
][0].destroy()
del self
.__componentInfo
[name
]
def createlabel(self
, parent
, childCols
= 1, childRows
= 1):
labelpos
= self
['labelpos']
labelmargin
= self
['labelmargin']
label
= self
.createcomponent('label',
Tkinter
.Label
, (parent
,))
label
.grid(column
=2, row
=row
, columnspan
=childCols
, sticky
=labelpos
)
parent
.grid_rowconfigure(margin
, minsize
=labelmargin
)
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:
keywords
= self
._constructorKeywords
for name
in keywords
.keys():
# This keyword argument has not been used. If it
# does not refer to a dynamic group, mark it as
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 "'
text
= 'Unknown options "'
raise KeyError, text
+ string
.join(unusedOptions
, ', ') + \
'" for ' + self
.__class
__.__name
__
# Call the configuration callback function for every option.
for info
in self
._optionInfo
.values():
if func
is not None and func
is not INITOPT
:
#======================================================================
# 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
# If *option* is a string, return the 5 element tuple for the
# given configuration option.
# First, deal with the option queries.
# This configure call is querying the values of one or all options.
# (optionName, resourceName, resourceClass, default, value)
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
])
config
= self
._optionInfo
[option
]
resourceClass
= string
.upper(option
[0]) + option
[1:]
return (option
, option
, resourceClass
, config
[_OPT_DEFAULT
],
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
# This will contain a list of options in *kw* which
# are known to this megawidget.
# 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_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
:
'Cannot configure initialisation option "' \
+ option
+ '" for ' + self
.__class
__.__name
__
optionInfo
[option
][VALUE
] = value
directOptions
.append(option
)
index
= string
.find(option
, '_')
# This option may be of the form <component>_<option>.
component
= option
[:index
]
componentOption
= option
[(index
+ 1):]
if componentAliases_has_key(component
):
component
, subComponent
= componentAliases
[component
]
if subComponent
is not None:
componentOption
= subComponent
+ '_' \
# Expand option string to write on error
option
= component
+ '_' + componentOption
if componentInfo_has_key(component
):
# Configure the named component
componentConfigFuncs
= [componentInfo
[component
][1]]
# Check if this is a group name and configure all
# components in the group.
componentConfigFuncs
= []
for info
in componentInfo
.values():
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
] \
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
]
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
# 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
, '_')
remainingComponents
= None
remainingComponents
= name
[(index
+ 1):]
if self
.__componentAliases
.has_key(component
):
component
, subComponent
= self
.__componentAliases
[component
]
if subComponent
is not None:
if remainingComponents
is None:
remainingComponents
= subComponent
remainingComponents
= subComponent
+ '_' \
widget
= self
.__componentInfo
[component
][0]
if remainingComponents
is None:
return widget
.component(remainingComponents
)
return not _hullToMegaWidget
.has_key(self
._hull
)
# 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
]
index
= string
.find(option
, '_')
component
= option
[:index
]
componentOption
= option
[(index
+ 1):]
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
)
# If this is a group name, call cget for one of
# the components in the group.
for info
in self
.__componentInfo
.values():
return componentCget(componentOption
)
raise KeyError, 'Unknown option "' + option
+ \
'" for ' + self
.__class
__.__name
__
def isinitoption(self
, option
):
return self
._optionInfo
[option
][_OPT_FUNCTION
] is INITOPT
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
))
# Return a list of all components.
# This list includes the 'hull' component and all widget subcomponents
names
= self
.__componentInfo
.keys()
def componentaliases(self
):
# Return a list of all component aliases.
componentAliases
= self
.__componentAliases
names
= componentAliases
.keys()
(mainComponent
, subComponent
) = componentAliases
[alias
]
rtn
.append((alias
, mainComponent
))
rtn
.append((alias
, mainComponent
+ '_' + subComponent
))
def componentgroup(self
, name
):
return self
.__componentInfo
[name
][4]
#=============================================================================
# The grab functions are mainly called by the activate() and
# 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
# Use releasegrabs() to release the grab and clear the grab stack.
def pushgrab(grabWindow
, globalMode
, deactivateFunction
):
prevFocus
= grabWindow
.tk
.call('focus')
'grabWindow' : grabWindow
,
'globalMode' : globalMode
,
'previousFocus' : prevFocus
,
'deactivateFunction' : deactivateFunction
,
_grabStack
.append(grabInfo
)
# 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
if _grabStack
[-1]['grabWindow'] != window
:
for index
in range(len(_grabStack
)):
if _grabStack
[index
]['grabWindow'] == window
:
_grabStack
[index
+ 1]['deactivateFunction']()
grabInfo
= _grabStack
[-1]
topWidget
= grabInfo
['grabWindow']
prevFocus
= grabInfo
['previousFocus']
globalMode
= grabInfo
['globalMode']
if globalMode
!= 'nograb':
topWidget
.tk
.call('focus', prevFocus
)
# Previous focus widget has been deleted. Set focus
Tkinter
._default
_root
.focus_set()
# Make sure that focus does not remain on the released widget.
topWidget
= _grabStack
[-1]['grabWindow']
Tkinter
._default
_root
.focus_set()
def grabstacktopwindow():
return _grabStack
[-1]['grabWindow']
# Release grab and clear the grab stack.
current
= Tkinter
._default
_root
.grab_current()
grabInfo
= _grabStack
[-1]
topWidget
= grabInfo
['grabWindow']
globalMode
= grabInfo
['globalMode']
if globalMode
== 'nograb':
topWidget
.grab_set_global()
# Another application has grab. Keep trying until
#=============================================================================
class MegaToplevel(MegaArchetype
):
def __init__(self
, parent
= None, **kw
):
# Define the options for this megawidget.
('activatecommand', None, None),
('deactivatecommand', 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
)
# Set WM_DELETE_WINDOW protocol, deleting any old callback, so
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.
# Used by show() to ensure window retains previous position on screen.
# The IntVar() variable to wait on during a modal dialog.
self
._userDeleteFunc
= self
.destroy
self
._userModalDeleteFunc
= self
.deactivate
# Check keywords and initialise options.
def userdeletefunc(self
, func
=None):
self
._userDeleteFunc
= func
return self
._userDeleteFunc
def usermodaldeletefunc(self
, func
=None):
self
._userModalDeleteFunc
= func
return self
._userModalDeleteFunc
def _userDeleteWindow(self
):
self
._userModalDeleteFunc
()
# Allow this to be called more than once.
if _hullToMegaWidget
.has_key(self
._hull
):
# Remove circular references, so that object can get cleaned up.
del self
._userModalDeleteFunc
MegaArchetype
.destroy(self
)
def show(self
, master
= None):
if self
.state() != 'normal':
# Just let the window manager determine the window
# position for the first time.
# Position the window at the same place it was last time.
geom
= self
._sameposition
()
setgeometryanddeiconify(self
, geom
)
if self
.transient() == '':
# Do this last, otherwise get flashing on NT:
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()
def _centreonscreen(self
):
# Centre the window on the screen. (Actually halfway across
parent
= self
.winfo_parent()
if type(parent
) == types
.StringType
:
parent
= self
._hull
._nametowidget
(parent
)
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()
# Position the window at the same place it was last time.
geometry
= self
.geometry()
index
= string
.find(geometry
, '+')
def activate(self
, globalMode
= 0, geometry
= 'centerscreenfirst'):
raise ValueError, 'Window is already active'
if self
.state() == 'normal':
self
._wait
= Tkinter
.IntVar()
if geometry
== 'centerscreenalways':
geom
= self
._centreonscreen
()
elif geometry
== 'centerscreenfirst':
# Centre the window the first time it is displayed.
geom
= self
._centreonscreen
()
# Position the window at the same place it was last time.
geom
= self
._sameposition
()
elif geometry
[:5] == 'first':
# Position the window at the same place it was last time.
geom
= self
._sameposition
()
setgeometryanddeiconify(self
, geom
)
# Do this last, otherwise get flashing on NT:
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()
pushgrab(self
._hull
, globalMode
, self
.deactivate
)
command
= self
['activatecommand']
self
.wait_variable(self
._wait
)
def deactivate(self
, result
=None):
# 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.
command
= self
['deactivatecommand']
hidebusycursor(forceFocusRestore
= 1)
forwardmethods(MegaToplevel
, Tkinter
.Toplevel
, '_hull')
#=============================================================================
class MegaWidget(MegaArchetype
):
def __init__(self
, parent
= None, **kw
):
# Define the options for this megawidget.
('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.
forwardmethods(MegaWidget
, Tkinter
.Frame
, '_hull')
#=============================================================================
def tracetk(root
= None, on
= 1, withStackTrace
= 0, file=None):
root
= Tkinter
._default
_root
_withStackTrace
= withStackTrace
if hasattr(root
.tk
, '__class__'):
_traceTkFile
= sys
.stderr
if not hasattr(root
.tk
, '__class__'):
tk
= root
.tk
.getTclInterp()
_addRootToToplevelBusyInfo()
root
= Tkinter
._default
_root
_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.
for (window
, winInfo
) in _toplevelBusyInfo
.items():
if (window
.state() != 'withdrawn' and not winInfo
['isBusy']
and not winInfo
['excludeFromBusy']):
busyInfo
['newBusyWindows'].append(window
)
_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
winInfo
['windowFocus'] = \
window
.tk
.call('focus', '-lastfor', window
._w
)
window
.tk
.call('focus', winInfo
['busyWindow'])
busyInfo
['busyFocus'] = winInfo
['busyWindow']
if len(busyInfo
['newBusyWindows']) > 0:
# NT needs an "update" before it will change the cursor.
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]
for window
in busyInfo
['newBusyWindows']:
# If this window has not been deleted, release the busy cursor.
if _toplevelBusyInfo
.has_key(window
):
winInfo
= _toplevelBusyInfo
[window
]
if _disableKeyboardWhileBusy
:
# Restore previous focus window for this toplevel window,
# but only if is still set to the busy window (it may have
windowFocusNow
= window
.tk
.call('focus', '-lastfor', window
._w
)
if windowFocusNow
== winInfo
['busyWindow']:
window
.tk
.call('focus', winInfo
['windowFocus'])
# Previous focus widget has been deleted. Set focus
# to toplevel window instead (can't leave focus on
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:
root
.tk
.call('focus', previousFocus
)
# Previous focus widget has been deleted; forget it.
# The focus had changed, so restore it to what it had been
# changed to before the call to hidebusycursor.
root
.tk
.call('focus', currentFocus
)
while len(_busyStack
) > 0:
def setbusycursorattributes(window
, **kw
):
_addRootToToplevelBusyInfo()
for name
, value
in kw
.items():
_toplevelBusyInfo
[window
]['excludeFromBusy'] = value
elif name
== 'cursorName':
_toplevelBusyInfo
[window
]['busyCursorName'] = value
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
root
= Tkinter
._default
_root
if not _toplevelBusyInfo
.has_key(root
):
_addToplevelBusyInfo(root
)
def busycallback(command
, updateFunction
= None):
if not callable(command
):
'cannot register non-command busy callback %s %s' % \
(repr(command
), type(command
))
wrapper
= _BusyWrapper(command
, updateFunction
)
def reporterrorstofile(file = None):
if _errorReportFile
is not None:
_errorReportFile
.write(text
+ '\n')
# 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
sys
.stderr
.write(text
+ '\n')
# The error window has not yet been created.
_errorWindow
= _ErrorWindow()
_errorWindow
.showerror(text
)
_disableKeyboardWhileBusy
= 1
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.
# Save flag specifying whether the Tk option database should be
# queried when setting megawidget option default values.
_useTkOptionDb
= useTkOptionDb
# If we haven't been given a root window, use the default or
if Tkinter
._default
_root
is None:
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.
if _root
is not None and _root
!= root
:
# Trap Tkinter Toplevel constructors so that a list of Toplevels
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.
PmwLogicalFont
._font
_initialise
(root
, size
, fontScheme
)
def alignlabels(widgets
, sticky
= None):
widgets
[0].update_idletasks()
# Determine the size of the maximum length label string.
labelWidth
= iwid
.grid_bbox(0, 1)[2]
if labelWidth
> maxLabelWidth
:
maxLabelWidth
= labelWidth
# Adjust the margins for the labels such that the child sites and
iwid
.component('label').grid(sticky
=sticky
)
iwid
.grid_columnconfigure(0, minsize
= maxLabelWidth
)
#=============================================================================
def __init__(self
, tclInterp
):
self
.tclInterp
= tclInterp
# Calling from python into Tk.
def call(self
, *args
, **kw
):
if len(args
) == 1 and type(args
[0]) == types
.TupleType
:
_traceTkFile
.write('CALL TK> %d:%s%s' %
(_recursionCounter
, ' ' * _recursionCounter
, argStr
))
_recursionCounter
= _recursionCounter
+ 1
result
= apply(self
.tclInterp
.call
, args
, kw
)
except Tkinter
.TclError
, errorString
:
_recursionCounter
= _recursionCounter
- 1
_traceTkFile
.write('\nTK ERROR> %d:%s-> %s\n' %
(_recursionCounter
, ' ' * _recursionCounter
,
_traceTkFile
.write('CALL TK> stack:\n')
raise Tkinter
.TclError
, errorString
_recursionCounter
= _recursionCounter
- 1
_traceTkFile
.write('CALL RTN> %d:%s-> %s' %
(_recursionCounter
, ' ' * _recursionCounter
, repr(result
)))
_traceTkFile
.write(' -> %s' % repr(result
))
_traceTkFile
.write('CALL TK> stack:\n')
def __getattr__(self
, key
):
return getattr(self
.tclInterp
, key
)
def _setTkInterps(window
, tk
):
for child
in window
.children
.values():
#=============================================================================
# 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
# Map from toplevel windows to
# {'isBusy', 'windowFocus', 'busyWindow',
# 'excludeFromBusy', 'busyCursorName'}
# 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
):
busyWindow
= window
._w
+ '._Busy'
_toplevelBusyInfo
[window
] = {
'busyWindow' : busyWindow
,
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
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
)
def _havebltbusy(window
):
global _busy_hold
, _busy_release
, _haveBltBusy
_haveBltBusy
= PmwBlt
.havebltbusy(window
)
_busy_hold
= PmwBlt
.busy_hold
# 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
_busy_release
= PmwBlt
.busy_release
def __init__(self
, command
, updateFunction
):
self
._updateFunction
= updateFunction
def callback(self
, *args
):
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
):
#=============================================================================
def drawarrow(canvas
, color
, direction
, tag
, baseOffset
= 0.25, edgeOffset
= 0.15):
bw
= (string
.atoi(canvas
['borderwidth']) +
string
.atoi(canvas
['highlightthickness']))
width
= string
.atoi(canvas
['width'])
height
= string
.atoi(canvas
['height'])
if direction
in ('up', 'down'):
offset
= round(baseOffset
* majorDimension
)
if direction
in ('down', 'right'):
apex
= bw
+ majorDimension
- offset
base
= bw
+ majorDimension
- offset
if minorDimension
> 3 and minorDimension
% 2 == 0:
minorDimension
= minorDimension
- 1
half
= int(minorDimension
* (1 - 2 * edgeOffset
)) / 2
low
= round(bw
+ edgeOffset
* minorDimension
)
if direction
in ('up', 'down'):
coords
= (low
, base
, high
, base
, middle
, apex
)
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.
def __TkinterToplevelDestroy(tkWidget
):
if _hullToMegaWidget
.has_key(tkWidget
):
mega
= _hullToMegaWidget
[tkWidget
]
_reporterror(mega
.destroy
, ())
# Delete the busy info structure for this toplevel (if the
# window was created before Pmw.initialise() was called, it
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
]
_reporterror(mega
.destroy
, ())
Tkinter
.BaseWidget
.destroy(tkWidget
)
#=============================================================================
# Add code to Tkinter to improve the display of errors which occur in
class __TkinterCallWrapper
:
def __init__(self
, func
, subst
, widget
):
# Calling back from Tk into python.
def __call__(self
, *args
):
args
= apply(self
.subst
, args
)
if not _callToTkReturned
:
if hasattr(self
.func
, 'im_class'):
name
= self
.func
.im_class
.__name
__ + '.' + \
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
)
argStr
= '(%s Event, %s)' % (eventName
, args
[0].widget
)
_traceTkFile
.write('CALLBACK> %d:%s%s%s\n' %
(_recursionCounter
, ' ' * _recursionCounter
, name
, argStr
))
return apply(self
.func
, args
)
_reporterror(self
.func
, args
)
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'):
# If the argument to the callback is an event, add the event type.
eventNum
= string
.atoi(args
[0].type)
if eventNum
in _eventTypeToName
.keys():
msg
= msg
+ ' Event type: %s (type num: %d)\n' % \
(_eventTypeToName
[eventNum
], eventNum
)
msg
= msg
+ ' Unknown event type (type num: %d)\n' % eventNum
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.
msg
= msg
+ '\n================================================\n'
msg
= msg
+ ' Event contents:\n'
keys
= args
[0].__dict
__.keys()
msg
= msg
+ ' %s: %s\n' % (key
, args
[0].__dict
__[key
])
# 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
)
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
):
self
._errorQueue
.append(text
)
# 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
# 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()
geometry
= self
._top
.geometry()
index
= string
.find(geometry
, '+')
setgeometryanddeiconify(self
._top
, geom
)
# Release any grab, so that buttons in the error window work.
self
._errorCount
= self
._errorCount
+ len(self
._errorQueue
)
# Display the next error in the queue.
text
= self
._errorQueue
[0]
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
)
self
._label
.configure(text
='%d more errors' % numQueued
)
self
._nextError
.configure(state
='normal')
self
._label
.configure(text
='No more errors')
self
._nextError
.configure(state
='disabled')