mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-31 13:10:19 -04:00 
			
		
		
		
	
		
			
	
	
		
			9234 lines
		
	
	
		
			294 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			9234 lines
		
	
	
		
			294 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|  | 
 | ||
|  | import PmwColor | ||
|  | Color = PmwColor | ||
|  | del PmwColor | ||
|  | 
 | ||
|  | import PmwBlt | ||
|  | Blt = PmwBlt | ||
|  | del PmwBlt | ||
|  | 
 | ||
|  | 
 | ||
|  | ### Loader functions: | ||
|  | 
 | ||
|  | _VERSION = '1.2' | ||
|  | 
 | ||
|  | def setversion(version): | ||
|  |     if version != _VERSION: | ||
|  |         raise ValueError, 'Dynamic versioning not available' | ||
|  | 
 | ||
|  | def setalphaversions(*alpha_versions): | ||
|  |     if alpha_versions != (): | ||
|  | 	raise ValueError, 'Dynamic versioning not available' | ||
|  | 
 | ||
|  | def version(alpha = 0): | ||
|  |     if alpha: | ||
|  |         return () | ||
|  |     else: | ||
|  |         return _VERSION | ||
|  | 
 | ||
|  | def installedversions(alpha = 0): | ||
|  |     if alpha: | ||
|  |         return () | ||
|  |     else: | ||
|  |         return (_VERSION,) | ||
|  | 
 | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwBase.py | ||
|  | # 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. | ||
|  |      | ||
|  |     _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 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') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwDialog.py | ||
|  | # Based on iwidgets2.2.0/dialog.itk and iwidgets2.2.0/dialogshell.itk code. | ||
|  | 
 | ||
|  | # Convention: | ||
|  | #   Each dialog window should have one of these as the rightmost button: | ||
|  | #     Close         Close a window which only displays information. | ||
|  | #     Cancel        Close a window which may be used to change the state of | ||
|  | #                   the application. | ||
|  | 
 | ||
|  | import sys | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | # A Toplevel with a ButtonBox and child site. | ||
|  | 
 | ||
|  | class Dialog(MegaToplevel): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('buttonbox_hull_borderwidth',   1,         None), | ||
|  | 	    ('buttonbox_hull_relief',        'raised',  None), | ||
|  | 	    ('buttonboxpos',                 's',       INITOPT), | ||
|  | 	    ('buttons',                      ('OK',),   self._buttons), | ||
|  | 	    ('command',                      None,      None), | ||
|  | 	    ('dialogchildsite_borderwidth',  1,         None), | ||
|  | 	    ('dialogchildsite_relief',       'raised',  None), | ||
|  | 	    ('defaultbutton',                None,      self._defaultButton), | ||
|  |             ('master',                       'parent',  None), | ||
|  | 	    ('separatorwidth',               0,         INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaToplevel.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 
 | ||
|  | 	oldInterior = MegaToplevel.interior(self) | ||
|  | 
 | ||
|  | 	# Set up pack options according to the position of the button box. | ||
|  |         pos = self['buttonboxpos'] | ||
|  | 	if pos not in 'nsew': | ||
|  | 	    raise ValueError, \ | ||
|  | 	        'bad buttonboxpos option "%s":  should be n, s, e, or w' \ | ||
|  | 		    % pos | ||
|  | 
 | ||
|  | 	if pos in 'ns': | ||
|  | 	    orient = 'horizontal' | ||
|  | 	    fill = 'x' | ||
|  | 	    if pos == 'n': | ||
|  | 	        side = 'top' | ||
|  | 	    else: | ||
|  | 	        side = 'bottom' | ||
|  | 	else: | ||
|  | 	    orient = 'vertical' | ||
|  | 	    fill = 'y' | ||
|  | 	    if pos == 'w': | ||
|  | 	        side = 'left' | ||
|  | 	    else: | ||
|  | 	        side = 'right' | ||
|  | 
 | ||
|  | 	# Create the button box. | ||
|  | 	self._buttonBox = self.createcomponent('buttonbox', | ||
|  | 		(), None, | ||
|  | 		ButtonBox, (oldInterior,), orient = orient) | ||
|  | 	self._buttonBox.pack(side = side, fill = fill) | ||
|  | 
 | ||
|  | 	# Create the separating line. | ||
|  | 	width = self['separatorwidth'] | ||
|  | 	if width > 0: | ||
|  | 	    self._separator = self.createcomponent('separator', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Frame, (oldInterior,), relief = 'sunken', | ||
|  | 		    height = width, width = width, borderwidth = width / 2) | ||
|  | 	    self._separator.pack(side = side, fill = fill) | ||
|  | 	 | ||
|  | 	# Create the child site. | ||
|  | 	self.__dialogChildSite = self.createcomponent('dialogchildsite', | ||
|  | 		(), None, | ||
|  | 		Tkinter.Frame, (oldInterior,)) | ||
|  | 	self.__dialogChildSite.pack(side=side, fill='both', expand=1) | ||
|  | 
 | ||
|  | 	self.oldButtons = () | ||
|  | 	self.oldDefault = None | ||
|  | 
 | ||
|  | 	self.bind('<Return>', self._invokeDefault) | ||
|  |         self.userdeletefunc(self._doCommand) | ||
|  |         self.usermodaldeletefunc(self._doCommand) | ||
|  | 	 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def interior(self): | ||
|  | 	return self.__dialogChildSite | ||
|  | 
 | ||
|  |     def invoke(self, index = DEFAULT): | ||
|  | 	return self._buttonBox.invoke(index) | ||
|  | 
 | ||
|  |     def _invokeDefault(self, event): | ||
|  | 	try: | ||
|  | 	    self._buttonBox.index(DEFAULT) | ||
|  | 	except ValueError: | ||
|  | 	    return | ||
|  | 	self._buttonBox.invoke() | ||
|  | 
 | ||
|  |     def _doCommand(self, name = None): | ||
|  |         if name is not None and self.active() and \ | ||
|  |                 grabstacktopwindow() != self.component('hull'): | ||
|  |             # This is a modal dialog but is not on the top of the grab | ||
|  |             # stack (ie:  should not have the grab), so ignore this | ||
|  |             # event.  This seems to be a bug in Tk and may occur in | ||
|  |             # nested modal dialogs. | ||
|  |             # | ||
|  |             # An example is the PromptDialog demonstration.  To | ||
|  |             # trigger the problem, start the demo, then move the mouse | ||
|  |             # to the main window, hit <TAB> and then <TAB> again.  The | ||
|  |             # highlight border of the "Show prompt dialog" button | ||
|  |             # should now be displayed.  Now hit <SPACE>, <RETURN>, | ||
|  |             # <RETURN> rapidly several times.  Eventually, hitting the | ||
|  |             # return key invokes the password dialog "OK" button even | ||
|  |             # though the confirm dialog is active (and therefore | ||
|  |             # should have the keyboard focus).  Observed under Solaris | ||
|  |             # 2.5.1, python 1.5.2 and Tk8.0. | ||
|  | 
 | ||
|  |             # TODO:  Give focus to the window on top of the grabstack. | ||
|  |             return | ||
|  | 
 | ||
|  | 	command = self['command'] | ||
|  | 	if callable(command): | ||
|  | 	    return command(name) | ||
|  | 	else: | ||
|  | 	    if self.active(): | ||
|  | 	        self.deactivate(name) | ||
|  | 	    else: | ||
|  | 	        self.withdraw() | ||
|  | 
 | ||
|  |     def _buttons(self): | ||
|  | 	buttons = self['buttons'] | ||
|  | 	if type(buttons) != types.TupleType and type(buttons) != types.ListType: | ||
|  | 	    raise ValueError, \ | ||
|  | 	        'bad buttons option "%s": should be a tuple' % str(buttons) | ||
|  | 	if self.oldButtons == buttons: | ||
|  | 	  return | ||
|  | 
 | ||
|  | 	self.oldButtons = buttons | ||
|  | 
 | ||
|  | 	for index in range(self._buttonBox.numbuttons()): | ||
|  | 	    self._buttonBox.delete(0) | ||
|  | 	for name in buttons: | ||
|  | 	    self._buttonBox.add(name, | ||
|  | 		command=lambda self=self, name=name: self._doCommand(name)) | ||
|  | 
 | ||
|  | 	if len(buttons) > 0: | ||
|  | 	    defaultbutton = self['defaultbutton'] | ||
|  | 	    if defaultbutton is None: | ||
|  | 		self._buttonBox.setdefault(None) | ||
|  | 	    else: | ||
|  | 		try: | ||
|  | 		    self._buttonBox.index(defaultbutton) | ||
|  | 		except ValueError: | ||
|  | 		    pass | ||
|  | 		else: | ||
|  | 		    self._buttonBox.setdefault(defaultbutton) | ||
|  | 	self._buttonBox.alignbuttons() | ||
|  | 
 | ||
|  |     def _defaultButton(self): | ||
|  | 	defaultbutton = self['defaultbutton'] | ||
|  | 	if self.oldDefault == defaultbutton: | ||
|  | 	  return | ||
|  | 
 | ||
|  | 	self.oldDefault = defaultbutton | ||
|  | 
 | ||
|  | 	if len(self['buttons']) > 0: | ||
|  | 	    if defaultbutton is None: | ||
|  | 		self._buttonBox.setdefault(None) | ||
|  | 	    else: | ||
|  | 		try: | ||
|  | 		    self._buttonBox.index(defaultbutton) | ||
|  | 		except ValueError: | ||
|  | 		    pass | ||
|  | 		else: | ||
|  | 		    self._buttonBox.setdefault(defaultbutton) | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwTimeFuncs.py | ||
|  | # Functions for dealing with dates and times. | ||
|  | 
 | ||
|  | import re | ||
|  | import string | ||
|  | 
 | ||
|  | def timestringtoseconds(text, separator = ':'): | ||
|  |   inputList = string.split(string.strip(text), separator) | ||
|  |   if len(inputList) != 3: | ||
|  |     raise ValueError, 'invalid value: ' + text | ||
|  | 
 | ||
|  |   sign = 1 | ||
|  |   if len(inputList[0]) > 0 and inputList[0][0] in ('+', '-'): | ||
|  |     if inputList[0][0] == '-': | ||
|  |       sign = -1 | ||
|  |     inputList[0] = inputList[0][1:] | ||
|  | 
 | ||
|  |   if re.search('[^0-9]', string.join(inputList, '')) is not None: | ||
|  |     raise ValueError, 'invalid value: ' + text | ||
|  | 
 | ||
|  |   hour = string.atoi(inputList[0]) | ||
|  |   minute = string.atoi(inputList[1]) | ||
|  |   second = string.atoi(inputList[2]) | ||
|  | 
 | ||
|  |   if minute >= 60 or second >= 60: | ||
|  |     raise ValueError, 'invalid value: ' + text | ||
|  |   return sign * (hour * 60 * 60 + minute * 60 + second) | ||
|  | 
 | ||
|  | _year_pivot = 50 | ||
|  | _century = 2000 | ||
|  | 
 | ||
|  | def setyearpivot(pivot, century = None): | ||
|  |     global _year_pivot | ||
|  |     global _century | ||
|  |     oldvalues = (_year_pivot, _century) | ||
|  |     _year_pivot = pivot | ||
|  |     if century is not None: | ||
|  | 	_century = century | ||
|  |     return oldvalues | ||
|  | 
 | ||
|  | def datestringtojdn(text, format = 'ymd', separator = '/'): | ||
|  |   inputList = string.split(string.strip(text), separator) | ||
|  |   if len(inputList) != 3: | ||
|  |     raise ValueError, 'invalid value: ' + text | ||
|  | 
 | ||
|  |   if re.search('[^0-9]', string.join(inputList, '')) is not None: | ||
|  |     raise ValueError, 'invalid value: ' + text | ||
|  |   formatList = list(format) | ||
|  |   day = string.atoi(inputList[formatList.index('d')]) | ||
|  |   month = string.atoi(inputList[formatList.index('m')]) | ||
|  |   year = string.atoi(inputList[formatList.index('y')]) | ||
|  | 
 | ||
|  |   if _year_pivot is not None: | ||
|  |     if year >= 0 and year < 100: | ||
|  |       if year <= _year_pivot: | ||
|  | 	year = year + _century | ||
|  |       else: | ||
|  | 	year = year + _century - 100 | ||
|  | 
 | ||
|  |   jdn = ymdtojdn(year, month, day) | ||
|  |   if jdntoymd(jdn) != (year, month, day): | ||
|  |     raise ValueError, 'invalid value: ' + text | ||
|  |   return jdn | ||
|  | 
 | ||
|  | def _cdiv(a, b): | ||
|  |     # Return a / b as calculated by most C language implementations, | ||
|  |     # assuming both a and b are integers. | ||
|  | 
 | ||
|  |     if a * b > 0: | ||
|  | 	return a / b | ||
|  |     else: | ||
|  | 	return -(abs(a) / abs(b)) | ||
|  | 
 | ||
|  | def ymdtojdn(year, month, day, julian = -1, papal = 1): | ||
|  | 
 | ||
|  |     # set Julian flag if auto set | ||
|  |     if julian < 0: | ||
|  | 	if papal:                          # Pope Gregory XIII's decree | ||
|  | 	    lastJulianDate = 15821004L     # last day to use Julian calendar | ||
|  | 	else:                              # British-American usage | ||
|  | 	    lastJulianDate = 17520902L     # last day to use Julian calendar | ||
|  | 
 | ||
|  | 	julian = ((year * 100L) + month) * 100 + day  <=  lastJulianDate | ||
|  | 
 | ||
|  |     if year < 0: | ||
|  | 	# Adjust BC year | ||
|  | 	year = year + 1 | ||
|  | 
 | ||
|  |     if julian: | ||
|  | 	return 367L * year - _cdiv(7 * (year + 5001L + _cdiv((month - 9), 7)), 4) + \ | ||
|  | 	    _cdiv(275 * month, 9) + day + 1729777L | ||
|  |     else: | ||
|  | 	return (day - 32076L) + \ | ||
|  | 	    _cdiv(1461L * (year + 4800L + _cdiv((month - 14), 12)), 4) + \ | ||
|  | 	    _cdiv(367 * (month - 2 - _cdiv((month - 14), 12) * 12), 12) - \ | ||
|  | 	    _cdiv((3 * _cdiv((year + 4900L + _cdiv((month - 14), 12)), 100)), 4) + \ | ||
|  | 	    1            # correction by rdg | ||
|  | 
 | ||
|  | def jdntoymd(jdn, julian = -1, papal = 1): | ||
|  | 
 | ||
|  |     # set Julian flag if auto set | ||
|  |     if julian < 0: | ||
|  | 	if papal:                          # Pope Gregory XIII's decree | ||
|  | 	    lastJulianJdn = 2299160L       # last jdn to use Julian calendar | ||
|  | 	else:                              # British-American usage | ||
|  | 	    lastJulianJdn = 2361221L       # last jdn to use Julian calendar | ||
|  | 
 | ||
|  | 	julian = (jdn <= lastJulianJdn); | ||
|  | 
 | ||
|  |     x = jdn + 68569L | ||
|  |     if julian: | ||
|  | 	x = x + 38 | ||
|  | 	daysPer400Years = 146100L | ||
|  | 	fudgedDaysPer4000Years = 1461000L + 1 | ||
|  |     else: | ||
|  | 	daysPer400Years = 146097L | ||
|  | 	fudgedDaysPer4000Years = 1460970L + 31 | ||
|  | 
 | ||
|  |     z = _cdiv(4 * x, daysPer400Years) | ||
|  |     x = x - _cdiv((daysPer400Years * z + 3), 4) | ||
|  |     y = _cdiv(4000 * (x + 1), fudgedDaysPer4000Years) | ||
|  |     x = x - _cdiv(1461 * y, 4) + 31 | ||
|  |     m = _cdiv(80 * x, 2447) | ||
|  |     d = x - _cdiv(2447 * m, 80) | ||
|  |     x = _cdiv(m, 11) | ||
|  |     m = m + 2 - 12 * x | ||
|  |     y = 100 * (z - 49) + y + x | ||
|  | 
 | ||
|  |     # Convert from longs to integers. | ||
|  |     yy = int(y) | ||
|  |     mm = int(m) | ||
|  |     dd = int(d) | ||
|  | 
 | ||
|  |     if yy <= 0: | ||
|  | 	# Adjust BC years. | ||
|  | 	    yy = yy - 1 | ||
|  | 
 | ||
|  |     return (yy, mm, dd) | ||
|  | 
 | ||
|  | def stringtoreal(text, separator = '.'): | ||
|  |     if separator != '.': | ||
|  | 	if string.find(text, '.') >= 0: | ||
|  | 	    raise ValueError, 'invalid value: ' + text | ||
|  | 	index = string.find(text, separator) | ||
|  | 	if index >= 0: | ||
|  | 	    text = text[:index] + '.' + text[index + 1:] | ||
|  |     return string.atof(text) | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwBalloon.py | ||
|  | import os | ||
|  | import string | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class Balloon(MegaToplevel): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	optiondefs = ( | ||
|  |             ('initwait',                 500,            None), # milliseconds | ||
|  |             ('label_background',         'lightyellow',  None), | ||
|  |             ('label_foreground',         'black',        None), | ||
|  |             ('label_justify',            'left',         None), | ||
|  |             ('master',                   'parent',       None), | ||
|  |             ('relmouse',                 'none',         self._relmouse), | ||
|  |             ('state',                    'both',         self._state), | ||
|  |             ('statuscommand',            None,           None), | ||
|  |             ('xoffset',                  20,             None), # pixels | ||
|  |             ('yoffset',                  1,              None), # pixels | ||
|  | 	    ('hull_highlightthickness',  1,              None), | ||
|  | 	    ('hull_highlightbackground', 'black',        None), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaToplevel.__init__(self, parent) | ||
|  | 
 | ||
|  | 	self.withdraw() | ||
|  | 	self.overrideredirect(1) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 	self._label = self.createcomponent('label', | ||
|  | 		(), None, | ||
|  | 		Tkinter.Label, (interior,)) | ||
|  | 	self._label.pack() | ||
|  | 
 | ||
|  |         # The default hull configuration options give a black border | ||
|  |         # around the balloon, but avoids a black 'flash' when the | ||
|  |         # balloon is deiconified, before the text appears. | ||
|  |         if not kw.has_key('hull_background'): | ||
|  |             self.configure(hull_background = \ | ||
|  |                     str(self._label.cget('background'))) | ||
|  | 
 | ||
|  | 	# Initialise instance variables. | ||
|  | 	self._timer = None | ||
|  | 
 | ||
|  |         # The widget or item that is currently triggering the balloon.  | ||
|  |         # It is None if the balloon is not being displayed.  It is a | ||
|  |         # one-tuple if the balloon is being displayed in response to a | ||
|  |         # widget binding (value is the widget).  It is a two-tuple if | ||
|  |         # the balloon is being displayed in response to a canvas or | ||
|  |         # text item binding (value is the widget and the item). | ||
|  |         self._currentTrigger = None | ||
|  | 	 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  | 	if self._timer is not None: | ||
|  | 	    self.after_cancel(self._timer) | ||
|  | 	    self._timer = None | ||
|  | 	MegaToplevel.destroy(self) | ||
|  | 
 | ||
|  |     def bind(self, widget, balloonHelp, statusHelp = None): | ||
|  | 
 | ||
|  |         # If a previous bind for this widget exists, remove it. | ||
|  |         self.unbind(widget) | ||
|  | 
 | ||
|  | 	if balloonHelp is None and statusHelp is None: | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	if statusHelp is None: | ||
|  | 	    statusHelp = balloonHelp | ||
|  | 	enterId = widget.bind('<Enter>',  | ||
|  | 		lambda event, self = self, w = widget, | ||
|  | 			sHelp = statusHelp, bHelp = balloonHelp: | ||
|  | 				self._enter(event, w, sHelp, bHelp, 0)) | ||
|  | 
 | ||
|  |         # Set Motion binding so that if the pointer remains at rest | ||
|  |         # within the widget until the status line removes the help and | ||
|  |         # then the pointer moves again, then redisplay the help in the | ||
|  |         # status line. | ||
|  | 	# Note:  The Motion binding only works for basic widgets, and | ||
|  |         # the hull of megawidgets but not for other megawidget components. | ||
|  | 	motionId = widget.bind('<Motion>',  | ||
|  | 		lambda event = None, self = self, statusHelp = statusHelp: | ||
|  | 			self.showstatus(statusHelp)) | ||
|  | 
 | ||
|  | 	leaveId = widget.bind('<Leave>', self._leave) | ||
|  | 	buttonId = widget.bind('<ButtonPress>', self._buttonpress) | ||
|  | 
 | ||
|  |         # Set Destroy binding so that the balloon can be withdrawn and | ||
|  |         # the timer can be cancelled if the widget is destroyed. | ||
|  | 	destroyId = widget.bind('<Destroy>', self._destroy) | ||
|  | 
 | ||
|  |         # Use the None item in the widget's private Pmw dictionary to | ||
|  |         # store the widget's bind callbacks, for later clean up. | ||
|  |         if not hasattr(widget, '_Pmw_BalloonBindIds'): | ||
|  |             widget._Pmw_BalloonBindIds = {} | ||
|  |         widget._Pmw_BalloonBindIds[None] = \ | ||
|  |                 (enterId, motionId, leaveId, buttonId, destroyId) | ||
|  | 
 | ||
|  |     def unbind(self, widget): | ||
|  |         if hasattr(widget, '_Pmw_BalloonBindIds'): | ||
|  |             if widget._Pmw_BalloonBindIds.has_key(None): | ||
|  |                 (enterId, motionId, leaveId, buttonId, destroyId) = \ | ||
|  |                         widget._Pmw_BalloonBindIds[None] | ||
|  |                 # Need to pass in old bindings, so that Tkinter can | ||
|  |                 # delete the commands.  Otherwise, memory is leaked. | ||
|  |                 widget.unbind('<Enter>', enterId) | ||
|  |                 widget.unbind('<Motion>', motionId) | ||
|  |                 widget.unbind('<Leave>', leaveId) | ||
|  |                 widget.unbind('<ButtonPress>', buttonId) | ||
|  |                 widget.unbind('<Destroy>', destroyId) | ||
|  |                 del widget._Pmw_BalloonBindIds[None] | ||
|  | 
 | ||
|  |         if self._currentTrigger is not None and len(self._currentTrigger) == 1: | ||
|  |             # The balloon is currently being displayed and the current | ||
|  |             # trigger is a widget. | ||
|  |             triggerWidget = self._currentTrigger[0] | ||
|  |             if triggerWidget == widget: | ||
|  |                 if self._timer is not None: | ||
|  |                     self.after_cancel(self._timer) | ||
|  |                     self._timer = None | ||
|  |                 self.withdraw() | ||
|  |                 self.clearstatus() | ||
|  |                 self._currentTrigger = None | ||
|  | 
 | ||
|  |     def tagbind(self, widget, tagOrItem, balloonHelp, statusHelp = None): | ||
|  | 
 | ||
|  |         # If a previous bind for this widget's tagOrItem exists, remove it. | ||
|  |         self.tagunbind(widget, tagOrItem) | ||
|  | 
 | ||
|  | 	if balloonHelp is None and statusHelp is None: | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	if statusHelp is None: | ||
|  | 	    statusHelp = balloonHelp | ||
|  | 	enterId = widget.tag_bind(tagOrItem, '<Enter>',  | ||
|  | 		lambda event, self = self, w = widget, | ||
|  | 			sHelp = statusHelp, bHelp = balloonHelp: | ||
|  | 				self._enter(event, w, sHelp, bHelp, 1)) | ||
|  | 	motionId = widget.tag_bind(tagOrItem, '<Motion>',  | ||
|  | 		lambda event = None, self = self, statusHelp = statusHelp: | ||
|  | 			self.showstatus(statusHelp)) | ||
|  | 	leaveId = widget.tag_bind(tagOrItem, '<Leave>', self._leave) | ||
|  | 	buttonId = widget.tag_bind(tagOrItem, '<ButtonPress>', self._buttonpress) | ||
|  | 
 | ||
|  |         # Use the tagOrItem item in the widget's private Pmw dictionary to | ||
|  |         # store the tagOrItem's bind callbacks, for later clean up. | ||
|  |         if not hasattr(widget, '_Pmw_BalloonBindIds'): | ||
|  |             widget._Pmw_BalloonBindIds = {} | ||
|  |         widget._Pmw_BalloonBindIds[tagOrItem] = \ | ||
|  |                 (enterId, motionId, leaveId, buttonId) | ||
|  | 
 | ||
|  |     def tagunbind(self, widget, tagOrItem): | ||
|  |         if hasattr(widget, '_Pmw_BalloonBindIds'): | ||
|  |             if widget._Pmw_BalloonBindIds.has_key(tagOrItem): | ||
|  |                 (enterId, motionId, leaveId, buttonId) = \ | ||
|  |                         widget._Pmw_BalloonBindIds[tagOrItem] | ||
|  |                 widget.tag_unbind(tagOrItem, '<Enter>', enterId) | ||
|  |                 widget.tag_unbind(tagOrItem, '<Motion>', motionId) | ||
|  |                 widget.tag_unbind(tagOrItem, '<Leave>', leaveId) | ||
|  |                 widget.tag_unbind(tagOrItem, '<ButtonPress>', buttonId) | ||
|  |                 del widget._Pmw_BalloonBindIds[tagOrItem] | ||
|  | 
 | ||
|  |         if self._currentTrigger is None: | ||
|  |             # The balloon is not currently being displayed. | ||
|  |             return | ||
|  | 
 | ||
|  |         if len(self._currentTrigger) == 1: | ||
|  |             # The current trigger is a widget. | ||
|  |             return | ||
|  | 
 | ||
|  |         if len(self._currentTrigger) == 2: | ||
|  |             # The current trigger is a canvas item. | ||
|  |             (triggerWidget, triggerItem) = self._currentTrigger | ||
|  |             if triggerWidget == widget and triggerItem == tagOrItem: | ||
|  |                 if self._timer is not None: | ||
|  |                     self.after_cancel(self._timer) | ||
|  |                     self._timer = None | ||
|  |                 self.withdraw() | ||
|  |                 self.clearstatus() | ||
|  |                 self._currentTrigger = None | ||
|  |         else: # The current trigger is a text item. | ||
|  |             (triggerWidget, x, y) = self._currentTrigger | ||
|  |             if triggerWidget == widget: | ||
|  |                 currentPos = widget.index('@%d,%d' % (x, y)) | ||
|  |                 currentTags = widget.tag_names(currentPos) | ||
|  |                 if tagOrItem in currentTags: | ||
|  |                     if self._timer is not None: | ||
|  |                         self.after_cancel(self._timer) | ||
|  |                         self._timer = None | ||
|  |                     self.withdraw() | ||
|  |                     self.clearstatus() | ||
|  |                     self._currentTrigger = None | ||
|  | 
 | ||
|  |     def showstatus(self, statusHelp): | ||
|  | 	if self['state'] in ('status', 'both'): | ||
|  | 	    cmd = self['statuscommand'] | ||
|  | 	    if callable(cmd): | ||
|  | 		cmd(statusHelp) | ||
|  | 
 | ||
|  |     def clearstatus(self): | ||
|  |         self.showstatus(None) | ||
|  | 
 | ||
|  |     def _state(self): | ||
|  | 	if self['state'] not in ('both', 'balloon', 'status', 'none'): | ||
|  | 	    raise ValueError, 'bad state option ' + repr(self['state']) + \ | ||
|  | 		': should be one of \'both\', \'balloon\', ' + \ | ||
|  | 		'\'status\' or \'none\'' | ||
|  | 
 | ||
|  |     def _relmouse(self): | ||
|  | 	if self['relmouse'] not in ('both', 'x', 'y', 'none'): | ||
|  | 	    raise ValueError, 'bad relmouse option ' + repr(self['relmouse'])+ \ | ||
|  | 		': should be one of \'both\', \'x\', ' + '\'y\' or \'none\'' | ||
|  | 
 | ||
|  |     def _enter(self, event, widget, statusHelp, balloonHelp, isItem): | ||
|  | 
 | ||
|  |         # Do not display balloon if mouse button is pressed.  This | ||
|  |         # will only occur if the button was pressed inside a widget, | ||
|  |         # then the mouse moved out of and then back into the widget, | ||
|  |         # with the button still held down.  The number 0x1f00 is the | ||
|  |         # button mask for the 5 possible buttons in X. | ||
|  |         buttonPressed = (event.state & 0x1f00) != 0 | ||
|  | 
 | ||
|  | 	if not buttonPressed and balloonHelp is not None and \ | ||
|  |                 self['state'] in ('balloon', 'both'): | ||
|  | 	    if self._timer is not None: | ||
|  | 		self.after_cancel(self._timer) | ||
|  | 		self._timer = None | ||
|  | 
 | ||
|  | 	    self._timer = self.after(self['initwait'],  | ||
|  | 		    lambda self = self, widget = widget, help = balloonHelp, | ||
|  | 			    isItem = isItem: | ||
|  | 			    self._showBalloon(widget, help, isItem)) | ||
|  | 
 | ||
|  |         if isItem: | ||
|  |             if hasattr(widget, 'canvasx'): | ||
|  | 		# The widget is a canvas. | ||
|  |                 item = widget.find_withtag('current') | ||
|  |                 if len(item) > 0: | ||
|  |                     item = item[0] | ||
|  |                 else: | ||
|  |                     item = None | ||
|  |                 self._currentTrigger = (widget, item) | ||
|  |             else: | ||
|  | 		# The widget is a text widget. | ||
|  |                 self._currentTrigger = (widget, event.x, event.y) | ||
|  |         else: | ||
|  |             self._currentTrigger = (widget,) | ||
|  | 
 | ||
|  | 	self.showstatus(statusHelp) | ||
|  | 
 | ||
|  |     def _leave(self, event): | ||
|  | 	if self._timer is not None: | ||
|  | 	    self.after_cancel(self._timer) | ||
|  | 	    self._timer = None | ||
|  | 	self.withdraw() | ||
|  | 	self.clearstatus() | ||
|  |         self._currentTrigger = None | ||
|  | 
 | ||
|  |     def _destroy(self, event): | ||
|  | 
 | ||
|  |         # Only withdraw the balloon and cancel the timer if the widget | ||
|  |         # being destroyed is the widget that triggered the balloon.  | ||
|  |         # Note that in a Tkinter Destroy event, the widget field is a | ||
|  |         # string and not a widget as usual. | ||
|  | 
 | ||
|  |         if self._currentTrigger is None: | ||
|  |             # The balloon is not currently being displayed | ||
|  |             return | ||
|  | 
 | ||
|  |         if len(self._currentTrigger) == 1: | ||
|  |             # The current trigger is a widget (not an item) | ||
|  |             triggerWidget = self._currentTrigger[0] | ||
|  |             if str(triggerWidget) == event.widget: | ||
|  |                 if self._timer is not None: | ||
|  |                     self.after_cancel(self._timer) | ||
|  |                     self._timer = None | ||
|  |                 self.withdraw() | ||
|  |                 self.clearstatus() | ||
|  |                 self._currentTrigger = None | ||
|  | 
 | ||
|  |     def _buttonpress(self, event): | ||
|  | 	if self._timer is not None: | ||
|  | 	    self.after_cancel(self._timer) | ||
|  | 	    self._timer = None | ||
|  | 	self.withdraw() | ||
|  |         self._currentTrigger = None | ||
|  | 
 | ||
|  |     def _showBalloon(self, widget, balloonHelp, isItem): | ||
|  | 
 | ||
|  | 	self._label.configure(text = balloonHelp) | ||
|  | 
 | ||
|  |         # First, display the balloon offscreen to get dimensions. | ||
|  |         screenWidth = self.winfo_screenwidth() | ||
|  |         screenHeight = self.winfo_screenheight() | ||
|  |         self.geometry('+%d+0' % (screenWidth + 1)) | ||
|  |         self.update_idletasks() | ||
|  | 
 | ||
|  | 	if isItem: | ||
|  |             # Get the bounding box of the current item. | ||
|  |             bbox = widget.bbox('current') | ||
|  |             if bbox is None: | ||
|  |                 # The item that triggered the balloon has disappeared, | ||
|  |                 # perhaps by a user's timer event that occured between | ||
|  |                 # the <Enter> event and the 'initwait' timer calling | ||
|  |                 # this method. | ||
|  |                 return | ||
|  | 
 | ||
|  | 	    # The widget is either a text or canvas.  The meaning of | ||
|  | 	    # the values returned by the bbox method is different for | ||
|  | 	    # each, so use the existence of the 'canvasx' method to | ||
|  | 	    # distinguish between them. | ||
|  | 	    if hasattr(widget, 'canvasx'): | ||
|  | 		# The widget is a canvas.  Place balloon under canvas | ||
|  |                 # item.  The positions returned by bbox are relative | ||
|  |                 # to the entire canvas, not just the visible part, so | ||
|  |                 # need to convert to window coordinates. | ||
|  |                 leftrel = bbox[0] - widget.canvasx(0) | ||
|  |                 toprel = bbox[1] - widget.canvasy(0) | ||
|  |                 bottomrel = bbox[3] - widget.canvasy(0) | ||
|  | 	    else: | ||
|  | 		# The widget is a text widget.  Place balloon under | ||
|  |                 # the character closest to the mouse.  The positions | ||
|  |                 # returned by bbox are relative to the text widget | ||
|  |                 # window (ie the visible part of the text only). | ||
|  | 		leftrel = bbox[0] | ||
|  |                 toprel = bbox[1] | ||
|  | 		bottomrel = bbox[1] + bbox[3] | ||
|  | 	else: | ||
|  | 	    leftrel = 0 | ||
|  |             toprel = 0 | ||
|  | 	    bottomrel = widget.winfo_height() | ||
|  | 
 | ||
|  |         xpointer, ypointer = widget.winfo_pointerxy()   # -1 if off screen | ||
|  | 
 | ||
|  |         if xpointer >= 0 and self['relmouse'] in ('both', 'x'): | ||
|  |             x = xpointer | ||
|  |         else: | ||
|  |             x = leftrel + widget.winfo_rootx() | ||
|  |         x = x + self['xoffset'] | ||
|  | 
 | ||
|  |         if ypointer >= 0 and self['relmouse'] in ('both', 'y'): | ||
|  |             y = ypointer | ||
|  |         else: | ||
|  |             y = bottomrel + widget.winfo_rooty() | ||
|  |         y = y + self['yoffset'] | ||
|  | 
 | ||
|  |         edges = (string.atoi(str(self.cget('hull_highlightthickness'))) + | ||
|  |             string.atoi(str(self.cget('hull_borderwidth')))) * 2 | ||
|  |         if x + self._label.winfo_reqwidth() + edges > screenWidth: | ||
|  |             x = screenWidth - self._label.winfo_reqwidth() - edges | ||
|  | 
 | ||
|  |         if y + self._label.winfo_reqheight() + edges > screenHeight: | ||
|  |             if ypointer >= 0 and self['relmouse'] in ('both', 'y'): | ||
|  |                 y = ypointer | ||
|  |             else: | ||
|  |                 y = toprel + widget.winfo_rooty() | ||
|  |             y = y - self._label.winfo_reqheight() - self['yoffset'] - edges | ||
|  | 
 | ||
|  |         setgeometryanddeiconify(self, '+%d+%d' % (x, y)) | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwButtonBox.py | ||
|  | # Based on iwidgets2.2.0/buttonbox.itk code. | ||
|  | 
 | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class ButtonBox(MegaWidget): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('labelmargin',       0,              INITOPT), | ||
|  | 	    ('labelpos',          None,           INITOPT), | ||
|  | 	    ('orient',            'horizontal',   INITOPT), | ||
|  | 	    ('padx',              3,              INITOPT), | ||
|  | 	    ('pady',              3,              INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs, dynamicGroups = ('Button',)) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 	if self['labelpos'] is None: | ||
|  | 	    self._buttonBoxFrame = self._hull | ||
|  | 	    columnOrRow = 0 | ||
|  | 	else: | ||
|  | 	    self._buttonBoxFrame = self.createcomponent('frame', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Frame, (interior,)) | ||
|  | 	    self._buttonBoxFrame.grid(column=2, row=2, sticky='nsew') | ||
|  | 	    columnOrRow = 2 | ||
|  | 
 | ||
|  | 	    self.createlabel(interior) | ||
|  | 
 | ||
|  | 	orient = self['orient'] | ||
|  | 	if orient == 'horizontal': | ||
|  | 	    interior.grid_columnconfigure(columnOrRow, weight = 1) | ||
|  | 	elif orient == 'vertical': | ||
|  | 	    interior.grid_rowconfigure(columnOrRow, weight = 1) | ||
|  | 	else: | ||
|  | 	    raise ValueError, 'bad orient option ' + repr(orient) + \ | ||
|  | 		': must be either \'horizontal\' or \'vertical\'' | ||
|  | 
 | ||
|  | 	# Initialise instance variables. | ||
|  | 
 | ||
|  | 	# List of tuples describing the buttons: | ||
|  | 	#   - name | ||
|  | 	#   - button widget | ||
|  | 	self._buttonList = [] | ||
|  | 
 | ||
|  | 	# The index of the default button. | ||
|  | 	self._defaultButton = None | ||
|  | 
 | ||
|  | 	self._timerId = None | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  | 	if self._timerId: | ||
|  | 	    self.after_cancel(self._timerId) | ||
|  | 	    self._timerId = None | ||
|  | 	MegaWidget.destroy(self) | ||
|  | 
 | ||
|  |     def numbuttons(self): | ||
|  |         return len(self._buttonList) | ||
|  | 
 | ||
|  |     def index(self, index, forInsert = 0): | ||
|  | 	listLength = len(self._buttonList) | ||
|  | 	if type(index) == types.IntType: | ||
|  | 	    if forInsert and index <= listLength: | ||
|  | 		return index | ||
|  | 	    elif not forInsert and index < listLength: | ||
|  | 		return index | ||
|  | 	    else: | ||
|  | 		raise ValueError, 'index "%s" is out of range' % index | ||
|  | 	elif index is END: | ||
|  | 	    if forInsert: | ||
|  | 		return listLength | ||
|  | 	    elif listLength > 0: | ||
|  | 		return listLength - 1 | ||
|  | 	    else: | ||
|  | 		raise ValueError, 'ButtonBox has no buttons' | ||
|  | 	elif index is DEFAULT: | ||
|  | 	    if self._defaultButton is not None: | ||
|  | 		return self._defaultButton | ||
|  | 	    raise ValueError, 'ButtonBox has no default' | ||
|  | 	else: | ||
|  |             names = map(lambda t: t[0], self._buttonList) | ||
|  |             if index in names: | ||
|  |                 return names.index(index) | ||
|  | 	    validValues = 'a name, a number, END or DEFAULT' | ||
|  | 	    raise ValueError, \ | ||
|  | 		'bad index "%s": must be %s' % (index, validValues) | ||
|  | 
 | ||
|  |     def insert(self, componentName, beforeComponent = 0, **kw): | ||
|  | 	if componentName in self.components(): | ||
|  | 	    raise ValueError, 'button "%s" already exists' % componentName | ||
|  | 	if not kw.has_key('text'): | ||
|  | 	    kw['text'] = componentName | ||
|  |         kw['default'] = 'normal' | ||
|  | 	button = apply(self.createcomponent, (componentName, | ||
|  | 		(), 'Button', | ||
|  | 		Tkinter.Button, (self._buttonBoxFrame,)), kw) | ||
|  | 
 | ||
|  | 	index = self.index(beforeComponent, 1) | ||
|  | 	horizontal = self['orient'] == 'horizontal' | ||
|  | 	numButtons = len(self._buttonList) | ||
|  | 
 | ||
|  | 	# Shift buttons up one position. | ||
|  | 	for i in range(numButtons - 1, index - 1, -1): | ||
|  | 	    widget = self._buttonList[i][1] | ||
|  | 	    pos = i * 2 + 3 | ||
|  | 	    if horizontal: | ||
|  | 		widget.grid(column = pos, row = 0) | ||
|  | 	    else: | ||
|  | 		widget.grid(column = 0, row = pos) | ||
|  | 
 | ||
|  | 	# Display the new button. | ||
|  | 	if horizontal: | ||
|  | 	    button.grid(column = index * 2 + 1, row = 0, sticky = 'ew', | ||
|  | 		    padx = self['padx'], pady = self['pady']) | ||
|  | 	    self._buttonBoxFrame.grid_columnconfigure( | ||
|  | 		    numButtons * 2 + 2, weight = 1) | ||
|  | 	else: | ||
|  | 	    button.grid(column = 0, row = index * 2 + 1, sticky = 'ew', | ||
|  | 		    padx = self['padx'], pady = self['pady']) | ||
|  | 	    self._buttonBoxFrame.grid_rowconfigure( | ||
|  | 		    numButtons * 2 + 2, weight = 1) | ||
|  | 	self._buttonList.insert(index, (componentName, button)) | ||
|  | 
 | ||
|  | 	return button | ||
|  | 
 | ||
|  |     def add(self, componentName, **kw): | ||
|  |         return apply(self.insert, (componentName, len(self._buttonList)), kw) | ||
|  | 
 | ||
|  |     def delete(self, index): | ||
|  |         index = self.index(index) | ||
|  | 	(name, widget) = self._buttonList[index] | ||
|  | 	widget.grid_forget() | ||
|  | 	self.destroycomponent(name) | ||
|  | 
 | ||
|  | 	numButtons = len(self._buttonList) | ||
|  | 
 | ||
|  | 	# Shift buttons down one position. | ||
|  | 	horizontal = self['orient'] == 'horizontal' | ||
|  | 	for i in range(index + 1, numButtons): | ||
|  | 	    widget = self._buttonList[i][1] | ||
|  | 	    pos = i * 2 - 1 | ||
|  | 	    if horizontal: | ||
|  | 		widget.grid(column = pos, row = 0) | ||
|  | 	    else: | ||
|  | 		widget.grid(column = 0, row = pos) | ||
|  | 
 | ||
|  | 	if horizontal: | ||
|  | 	    self._buttonBoxFrame.grid_columnconfigure(numButtons * 2 - 1, | ||
|  | 		    minsize = 0) | ||
|  | 	    self._buttonBoxFrame.grid_columnconfigure(numButtons * 2, weight = 0) | ||
|  | 	else: | ||
|  | 	    self._buttonBoxFrame.grid_rowconfigure(numButtons * 2, weight = 0) | ||
|  | 	del self._buttonList[index] | ||
|  | 
 | ||
|  |     def setdefault(self, index): | ||
|  | 	# Turn off the default ring around the current default button. | ||
|  | 	if self._defaultButton is not None: | ||
|  | 	    button = self._buttonList[self._defaultButton][1] | ||
|  | 	    button.configure(default = 'normal') | ||
|  | 	    self._defaultButton = None | ||
|  | 
 | ||
|  | 	# Turn on the default ring around the new default button. | ||
|  | 	if index is not None: | ||
|  | 	    index = self.index(index) | ||
|  | 	    self._defaultButton = index | ||
|  | 	    button = self._buttonList[index][1] | ||
|  | 	    button.configure(default = 'active') | ||
|  | 
 | ||
|  |     def invoke(self, index = DEFAULT, noFlash = 0): | ||
|  | 	# Invoke the callback associated with the *index* button.  If | ||
|  | 	# *noFlash* is not set, flash the button to indicate to the | ||
|  | 	# user that something happened. | ||
|  | 
 | ||
|  | 	button = self._buttonList[self.index(index)][1] | ||
|  | 	if not noFlash: | ||
|  | 	    state = button.cget('state') | ||
|  | 	    relief = button.cget('relief') | ||
|  | 	    button.configure(state = 'active', relief = 'sunken') | ||
|  | 	    self.update_idletasks() | ||
|  | 	    self.after(100) | ||
|  | 	    button.configure(state = state, relief = relief) | ||
|  | 	return button.invoke() | ||
|  | 
 | ||
|  |     def button(self, buttonIndex): | ||
|  | 	return self._buttonList[self.index(buttonIndex)][1] | ||
|  | 
 | ||
|  |     def alignbuttons(self, when = 'later'): | ||
|  | 	if when == 'later': | ||
|  | 	    if not self._timerId: | ||
|  | 		self._timerId = self.after_idle(self.alignbuttons, 'now') | ||
|  | 	    return | ||
|  | 	self.update_idletasks() | ||
|  | 	self._timerId = None | ||
|  | 
 | ||
|  |         # Determine the width of the maximum length button. | ||
|  |         max = 0 | ||
|  |         horizontal = (self['orient'] == 'horizontal') | ||
|  |         for index in range(len(self._buttonList)): | ||
|  |             gridIndex = index * 2 + 1 | ||
|  |             if horizontal: | ||
|  |                 width = self._buttonBoxFrame.grid_bbox(gridIndex, 0)[2] | ||
|  |             else: | ||
|  |                 width = self._buttonBoxFrame.grid_bbox(0, gridIndex)[2] | ||
|  |             if width > max: | ||
|  |                 max = width | ||
|  | 
 | ||
|  |         # Set the width of all the buttons to be the same. | ||
|  | 	if horizontal: | ||
|  | 	    for index in range(len(self._buttonList)): | ||
|  | 		self._buttonBoxFrame.grid_columnconfigure(index * 2 + 1, | ||
|  | 			minsize = max) | ||
|  | 	else: | ||
|  | 	    self._buttonBoxFrame.grid_columnconfigure(0, minsize = max) | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwEntryField.py | ||
|  | # Based on iwidgets2.2.0/entryfield.itk code. | ||
|  | 
 | ||
|  | import re | ||
|  | import string | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | # Possible return values of validation functions. | ||
|  | OK = 1 | ||
|  | ERROR = 0 | ||
|  | PARTIAL = -1 | ||
|  | 
 | ||
|  | class EntryField(MegaWidget): | ||
|  |     _classBindingsDefinedFor = 0 | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('command',           None,        None), | ||
|  | 	    ('errorbackground',   'pink',      None), | ||
|  | 	    ('invalidcommand',    self.bell,   None), | ||
|  | 	    ('labelmargin',       0,           INITOPT), | ||
|  | 	    ('labelpos',          None,        INITOPT), | ||
|  | 	    ('modifiedcommand',   None,        None), | ||
|  | 	    ('sticky',            'ew',        INITOPT), | ||
|  | 	    ('validate',          None,        self._validate), | ||
|  | 	    ('extravalidators',   {},          None), | ||
|  | 	    ('value',             '',          INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 	self._entryFieldEntry = self.createcomponent('entry', | ||
|  | 		(), None, | ||
|  | 		Tkinter.Entry, (interior,)) | ||
|  | 	self._entryFieldEntry.grid(column=2, row=2, sticky=self['sticky']) | ||
|  | 	if self['value'] != '': | ||
|  | 	    self.__setEntry(self['value']) | ||
|  | 	interior.grid_columnconfigure(2, weight=1) | ||
|  | 	interior.grid_rowconfigure(2, weight=1) | ||
|  | 
 | ||
|  | 	self.createlabel(interior) | ||
|  | 
 | ||
|  | 	# Initialise instance variables. | ||
|  | 
 | ||
|  | 	self.normalBackground = None | ||
|  |         self._previousText = None | ||
|  | 
 | ||
|  | 	# Initialise instance. | ||
|  | 
 | ||
|  | 	_registerEntryField(self._entryFieldEntry, self) | ||
|  | 
 | ||
|  |         # Establish the special class bindings if not already done. | ||
|  |         # Also create bindings if the Tkinter default interpreter has | ||
|  |         # changed.  Use Tkinter._default_root to create class | ||
|  |         # bindings, so that a reference to root is created by | ||
|  |         # bind_class rather than a reference to self, which would | ||
|  |         # prevent object cleanup. | ||
|  |         if EntryField._classBindingsDefinedFor != Tkinter._default_root: | ||
|  | 	    tagList = self._entryFieldEntry.bindtags() | ||
|  |             root  = Tkinter._default_root | ||
|  | 	    	     | ||
|  | 	    allSequences = {} | ||
|  | 	    for tag in tagList: | ||
|  | 
 | ||
|  |                 sequences = root.bind_class(tag) | ||
|  |                 if type(sequences) is types.StringType: | ||
|  |                     # In old versions of Tkinter, bind_class returns a string | ||
|  |                     sequences = root.tk.splitlist(sequences) | ||
|  | 
 | ||
|  | 		for sequence in sequences: | ||
|  | 		    allSequences[sequence] = None | ||
|  | 	    for sequence in allSequences.keys(): | ||
|  | 		root.bind_class('EntryFieldPre', sequence, _preProcess) | ||
|  | 		root.bind_class('EntryFieldPost', sequence, _postProcess) | ||
|  | 
 | ||
|  | 	    EntryField._classBindingsDefinedFor = root | ||
|  | 
 | ||
|  | 	self._entryFieldEntry.bindtags(('EntryFieldPre',) + | ||
|  | 		self._entryFieldEntry.bindtags() + ('EntryFieldPost',)) | ||
|  | 	self._entryFieldEntry.bind('<Return>', self._executeCommand) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  | 	_deregisterEntryField(self._entryFieldEntry) | ||
|  |         MegaWidget.destroy(self) | ||
|  | 
 | ||
|  |     def _getValidatorFunc(self, validator, index): | ||
|  | 	# Search the extra and standard validator lists for the | ||
|  | 	# given 'validator'.  If 'validator' is an alias, then | ||
|  | 	# continue the search using the alias.  Make sure that | ||
|  | 	# self-referencial aliases do not cause infinite loops. | ||
|  | 
 | ||
|  | 	extraValidators = self['extravalidators'] | ||
|  | 	traversedValidators = [] | ||
|  | 
 | ||
|  | 	while 1: | ||
|  | 	    traversedValidators.append(validator) | ||
|  | 	    if extraValidators.has_key(validator): | ||
|  | 		validator = extraValidators[validator][index] | ||
|  | 	    elif _standardValidators.has_key(validator): | ||
|  | 		validator = _standardValidators[validator][index] | ||
|  | 	    else: | ||
|  | 		return validator | ||
|  | 	    if validator in traversedValidators: | ||
|  | 		return validator | ||
|  | 
 | ||
|  |     def _validate(self): | ||
|  | 	dict = { | ||
|  | 	    'validator' : None, | ||
|  | 	    'min' : None, | ||
|  | 	    'max' : None, | ||
|  | 	    'minstrict' : 1, | ||
|  | 	    'maxstrict' : 1, | ||
|  | 	} | ||
|  | 	opt = self['validate'] | ||
|  | 	if type(opt) is types.DictionaryType: | ||
|  | 	    dict.update(opt) | ||
|  | 	else: | ||
|  | 	    dict['validator'] = opt | ||
|  | 
 | ||
|  | 	# Look up validator maps and replace 'validator' field with | ||
|  | 	# the corresponding function. | ||
|  | 	validator = dict['validator'] | ||
|  | 	valFunction = self._getValidatorFunc(validator, 0) | ||
|  | 	self._checkValidateFunction(valFunction, 'validate', validator) | ||
|  | 	dict['validator'] = valFunction | ||
|  | 
 | ||
|  | 	# Look up validator maps and replace 'stringtovalue' field | ||
|  | 	# with the corresponding function. | ||
|  | 	if dict.has_key('stringtovalue'): | ||
|  | 	    stringtovalue = dict['stringtovalue']  | ||
|  | 	    strFunction = self._getValidatorFunc(stringtovalue, 1) | ||
|  | 	    self._checkValidateFunction( | ||
|  | 		    strFunction, 'stringtovalue', stringtovalue) | ||
|  | 	else: | ||
|  | 	    strFunction = self._getValidatorFunc(validator, 1) | ||
|  | 	    if strFunction == validator: | ||
|  | 		strFunction = len | ||
|  | 	dict['stringtovalue'] = strFunction | ||
|  | 
 | ||
|  | 	self._validationInfo = dict | ||
|  | 	args = dict.copy() | ||
|  | 	del args['validator'] | ||
|  | 	del args['min'] | ||
|  | 	del args['max'] | ||
|  | 	del args['minstrict'] | ||
|  | 	del args['maxstrict'] | ||
|  | 	del args['stringtovalue'] | ||
|  | 	self._validationArgs = args | ||
|  |         self._previousText = None | ||
|  | 
 | ||
|  | 	if type(dict['min']) == types.StringType and strFunction is not None: | ||
|  | 	    dict['min'] = apply(strFunction, (dict['min'],), args) | ||
|  | 	if type(dict['max']) == types.StringType and strFunction is not None: | ||
|  | 	    dict['max'] = apply(strFunction, (dict['max'],), args) | ||
|  | 
 | ||
|  | 	self._checkValidity() | ||
|  | 
 | ||
|  |     def _checkValidateFunction(self, function, option, validator): | ||
|  | 	# Raise an error if 'function' is not a function or None. | ||
|  | 
 | ||
|  | 	if function is not None and not callable(function): | ||
|  | 	    extraValidators = self['extravalidators'] | ||
|  | 	    extra = extraValidators.keys() | ||
|  | 	    extra.sort() | ||
|  | 	    extra = tuple(extra) | ||
|  | 	    standard = _standardValidators.keys() | ||
|  | 	    standard.sort() | ||
|  | 	    standard = tuple(standard) | ||
|  | 	    msg = 'bad %s value "%s":  must be a function or one of ' \ | ||
|  | 		'the standard validators %s or extra validators %s' | ||
|  | 	    raise ValueError, msg % (option, validator, standard, extra) | ||
|  | 
 | ||
|  |     def _executeCommand(self, event = None): | ||
|  | 	cmd = self['command'] | ||
|  | 	if callable(cmd): | ||
|  |             if event is None: | ||
|  |                 # Return result of command for invoke() method. | ||
|  |                 return cmd() | ||
|  |             else: | ||
|  |                 cmd() | ||
|  | 	     | ||
|  |     def _preProcess(self): | ||
|  | 
 | ||
|  |         self._previousText = self._entryFieldEntry.get() | ||
|  |         self._previousICursor = self._entryFieldEntry.index('insert') | ||
|  |         self._previousXview = self._entryFieldEntry.index('@0') | ||
|  | 	if self._entryFieldEntry.selection_present(): | ||
|  | 	    self._previousSel= (self._entryFieldEntry.index('sel.first'), | ||
|  | 		self._entryFieldEntry.index('sel.last')) | ||
|  | 	else: | ||
|  | 	    self._previousSel = None | ||
|  | 
 | ||
|  |     def _postProcess(self): | ||
|  | 
 | ||
|  | 	# No need to check if text has not changed. | ||
|  | 	previousText = self._previousText | ||
|  | 	if previousText == self._entryFieldEntry.get(): | ||
|  | 	    return self.valid() | ||
|  | 
 | ||
|  | 	valid = self._checkValidity() | ||
|  |         if self.hulldestroyed(): | ||
|  |             # The invalidcommand called by _checkValidity() destroyed us. | ||
|  |             return valid | ||
|  | 
 | ||
|  | 	cmd = self['modifiedcommand'] | ||
|  | 	if callable(cmd) and previousText != self._entryFieldEntry.get(): | ||
|  | 	    cmd() | ||
|  | 	return valid | ||
|  | 	     | ||
|  |     def checkentry(self): | ||
|  | 	# If there is a variable specified by the entry_textvariable | ||
|  | 	# option, checkentry() should be called after the set() method | ||
|  | 	# of the variable is called. | ||
|  | 
 | ||
|  | 	self._previousText = None | ||
|  | 	return self._postProcess() | ||
|  | 
 | ||
|  |     def _getValidity(self): | ||
|  | 	text = self._entryFieldEntry.get() | ||
|  | 	dict = self._validationInfo | ||
|  | 	args = self._validationArgs | ||
|  | 
 | ||
|  | 	if dict['validator'] is not None: | ||
|  | 	    status = apply(dict['validator'], (text,), args) | ||
|  | 	    if status != OK: | ||
|  | 		return status | ||
|  | 
 | ||
|  | 	# Check for out of (min, max) range. | ||
|  | 	if dict['stringtovalue'] is not None: | ||
|  | 	    min = dict['min'] | ||
|  | 	    max = dict['max'] | ||
|  | 	    if min is None and max is None: | ||
|  | 		return OK | ||
|  | 	    val = apply(dict['stringtovalue'], (text,), args) | ||
|  | 	    if min is not None and val < min: | ||
|  | 		if dict['minstrict']: | ||
|  | 		    return ERROR | ||
|  | 		else: | ||
|  | 		    return PARTIAL | ||
|  | 	    if max is not None and val > max: | ||
|  | 		if dict['maxstrict']: | ||
|  | 		    return ERROR | ||
|  | 		else: | ||
|  | 		    return PARTIAL | ||
|  | 	return OK | ||
|  | 
 | ||
|  |     def _checkValidity(self): | ||
|  | 	valid = self._getValidity() | ||
|  | 	oldValidity = valid | ||
|  | 
 | ||
|  | 	if valid == ERROR: | ||
|  | 	    # The entry is invalid. | ||
|  | 	    cmd = self['invalidcommand'] | ||
|  | 	    if callable(cmd): | ||
|  | 		cmd() | ||
|  |             if self.hulldestroyed(): | ||
|  |                 # The invalidcommand destroyed us. | ||
|  |                 return oldValidity | ||
|  | 
 | ||
|  | 	    # Restore the entry to its previous value. | ||
|  | 	    if self._previousText is not None: | ||
|  | 		self.__setEntry(self._previousText) | ||
|  | 		self._entryFieldEntry.icursor(self._previousICursor) | ||
|  | 		self._entryFieldEntry.xview(self._previousXview) | ||
|  | 		if self._previousSel is not None: | ||
|  | 		    self._entryFieldEntry.selection_range(self._previousSel[0], | ||
|  | 			self._previousSel[1]) | ||
|  | 
 | ||
|  | 		# Check if the saved text is valid as well. | ||
|  | 		valid = self._getValidity() | ||
|  | 
 | ||
|  | 	self._valid = valid | ||
|  | 
 | ||
|  |         if self.hulldestroyed(): | ||
|  |             # The validator or stringtovalue commands called by | ||
|  |             # _checkValidity() destroyed us. | ||
|  |             return oldValidity | ||
|  | 
 | ||
|  | 	if valid == OK: | ||
|  | 	    if self.normalBackground is not None: | ||
|  | 		self._entryFieldEntry.configure( | ||
|  | 			background = self.normalBackground) | ||
|  | 		self.normalBackground = None | ||
|  | 	else: | ||
|  | 	    if self.normalBackground is None: | ||
|  | 		self.normalBackground = self._entryFieldEntry.cget('background') | ||
|  | 		self._entryFieldEntry.configure( | ||
|  | 			background = self['errorbackground']) | ||
|  | 
 | ||
|  |         return oldValidity | ||
|  | 
 | ||
|  |     def invoke(self): | ||
|  | 	return self._executeCommand() | ||
|  | 
 | ||
|  |     def valid(self): | ||
|  |         return self._valid == OK | ||
|  | 
 | ||
|  |     def clear(self): | ||
|  |         self.setentry('') | ||
|  | 
 | ||
|  |     def __setEntry(self, text): | ||
|  | 	oldState = str(self._entryFieldEntry.cget('state')) | ||
|  | 	if oldState != 'normal': | ||
|  | 	    self._entryFieldEntry.configure(state='normal') | ||
|  | 	self._entryFieldEntry.delete(0, 'end') | ||
|  | 	self._entryFieldEntry.insert(0, text) | ||
|  | 	if oldState != 'normal': | ||
|  | 	    self._entryFieldEntry.configure(state=oldState) | ||
|  | 
 | ||
|  |     def setentry(self, text): | ||
|  | 	self._preProcess() | ||
|  |         self.__setEntry(text) | ||
|  | 	return self._postProcess() | ||
|  | 
 | ||
|  |     def getvalue(self): | ||
|  |         return self._entryFieldEntry.get() | ||
|  | 
 | ||
|  |     def setvalue(self, text): | ||
|  |         return self.setentry(text) | ||
|  | 
 | ||
|  | forwardmethods(EntryField, Tkinter.Entry, '_entryFieldEntry') | ||
|  | 
 | ||
|  | # ====================================================================== | ||
|  | 
 | ||
|  | 
 | ||
|  | # Entry field validation functions | ||
|  | 
 | ||
|  | _numericregex = re.compile('^[0-9]*$') | ||
|  | _alphabeticregex = re.compile('^[a-z]*$', re.IGNORECASE) | ||
|  | _alphanumericregex = re.compile('^[0-9a-z]*$', re.IGNORECASE) | ||
|  | 
 | ||
|  | def numericvalidator(text): | ||
|  |     if text == '': | ||
|  |         return PARTIAL | ||
|  |     else: | ||
|  | 	if _numericregex.match(text) is None: | ||
|  | 	    return ERROR | ||
|  | 	else: | ||
|  | 	    return OK | ||
|  |      | ||
|  | def integervalidator(text): | ||
|  |     if text in ('', '-', '+'): | ||
|  |         return PARTIAL | ||
|  |     try: | ||
|  | 	string.atol(text) | ||
|  | 	return OK | ||
|  |     except ValueError: | ||
|  | 	return ERROR | ||
|  |      | ||
|  | def alphabeticvalidator(text): | ||
|  |     if _alphabeticregex.match(text) is None: | ||
|  | 	return ERROR | ||
|  |     else: | ||
|  | 	return OK | ||
|  |      | ||
|  | def alphanumericvalidator(text): | ||
|  |     if _alphanumericregex.match(text) is None: | ||
|  | 	return ERROR | ||
|  |     else: | ||
|  | 	return OK | ||
|  |      | ||
|  | def hexadecimalvalidator(text): | ||
|  |     if text in ('', '0x', '0X', '+', '+0x', '+0X', '-', '-0x', '-0X'): | ||
|  |         return PARTIAL | ||
|  |     try: | ||
|  | 	string.atol(text, 16) | ||
|  | 	return OK | ||
|  |     except ValueError: | ||
|  | 	return ERROR | ||
|  |      | ||
|  | def realvalidator(text, separator = '.'): | ||
|  |     if separator != '.': | ||
|  | 	if string.find(text, '.') >= 0: | ||
|  | 	    return ERROR | ||
|  | 	index = string.find(text, separator) | ||
|  | 	if index >= 0: | ||
|  | 	    text = text[:index] + '.' + text[index + 1:] | ||
|  |     try: | ||
|  | 	string.atof(text) | ||
|  | 	return OK | ||
|  |     except ValueError: | ||
|  | 	# Check if the string could be made valid by appending a digit | ||
|  | 	# eg ('-', '+', '.', '-.', '+.', '1.23e', '1E-'). | ||
|  | 	if len(text) == 0: | ||
|  | 	    return PARTIAL | ||
|  | 	if text[-1] in string.digits: | ||
|  | 	    return ERROR | ||
|  | 	try: | ||
|  | 	    string.atof(text + '0') | ||
|  | 	    return PARTIAL | ||
|  | 	except ValueError: | ||
|  | 	    return ERROR | ||
|  |      | ||
|  | def timevalidator(text, separator = ':'): | ||
|  |     try: | ||
|  | 	timestringtoseconds(text, separator) | ||
|  | 	return OK | ||
|  |     except ValueError: | ||
|  | 	if len(text) > 0 and text[0] in ('+', '-'): | ||
|  | 	    text = text[1:] | ||
|  | 	if re.search('[^0-9' + separator + ']', text) is not None: | ||
|  | 	    return ERROR | ||
|  | 	return PARTIAL | ||
|  | 
 | ||
|  | def datevalidator(text, format = 'ymd', separator = '/'): | ||
|  |     try: | ||
|  | 	datestringtojdn(text, format, separator) | ||
|  | 	return OK | ||
|  |     except ValueError: | ||
|  | 	if re.search('[^0-9' + separator + ']', text) is not None: | ||
|  | 	    return ERROR | ||
|  | 	return PARTIAL | ||
|  | 
 | ||
|  | _standardValidators = { | ||
|  |     'numeric'      : (numericvalidator,      string.atol), | ||
|  |     'integer'      : (integervalidator,      string.atol), | ||
|  |     'hexadecimal'  : (hexadecimalvalidator,  lambda s: string.atol(s, 16)), | ||
|  |     'real'         : (realvalidator,         stringtoreal), | ||
|  |     'alphabetic'   : (alphabeticvalidator,   len), | ||
|  |     'alphanumeric' : (alphanumericvalidator, len), | ||
|  |     'time'         : (timevalidator,         timestringtoseconds), | ||
|  |     'date'         : (datevalidator,         datestringtojdn), | ||
|  | } | ||
|  | 
 | ||
|  | _entryCache = {} | ||
|  | 
 | ||
|  | def _registerEntryField(entry, entryField): | ||
|  |     # Register an EntryField widget for an Entry widget | ||
|  | 
 | ||
|  |     _entryCache[entry] = entryField | ||
|  | 
 | ||
|  | def _deregisterEntryField(entry): | ||
|  |     # Deregister an Entry widget | ||
|  |     del _entryCache[entry] | ||
|  | 
 | ||
|  | def _preProcess(event): | ||
|  |     # Forward preprocess events for an Entry to it's EntryField | ||
|  | 
 | ||
|  |     _entryCache[event.widget]._preProcess() | ||
|  | 
 | ||
|  | def _postProcess(event): | ||
|  |     # Forward postprocess events for an Entry to it's EntryField | ||
|  | 
 | ||
|  |     # The function specified by the 'command' option may have destroyed | ||
|  |     # the megawidget in a binding earlier in bindtags, so need to check. | ||
|  |     if _entryCache.has_key(event.widget): | ||
|  |         _entryCache[event.widget]._postProcess() | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwGroup.py | ||
|  | import string | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | def aligngrouptags(groups): | ||
|  |     # Adjust the y position of the tags in /groups/ so that they all | ||
|  |     # have the height of the highest tag. | ||
|  | 
 | ||
|  |     maxTagHeight = 0 | ||
|  |     for group in groups: | ||
|  | 	if group._tag is None: | ||
|  | 	    height = (string.atoi(str(group._ring.cget('borderwidth'))) + | ||
|  | 		    string.atoi(str(group._ring.cget('highlightthickness')))) | ||
|  | 	else: | ||
|  | 	    height = group._tag.winfo_reqheight() | ||
|  | 	if maxTagHeight < height: | ||
|  | 	    maxTagHeight = height | ||
|  | 
 | ||
|  |     for group in groups: | ||
|  | 	ringBorder = (string.atoi(str(group._ring.cget('borderwidth'))) + | ||
|  | 		string.atoi(str(group._ring.cget('highlightthickness')))) | ||
|  | 	topBorder = maxTagHeight / 2 - ringBorder / 2 | ||
|  | 	group._hull.grid_rowconfigure(0, minsize = topBorder) | ||
|  | 	group._ring.grid_rowconfigure(0, | ||
|  | 		minsize = maxTagHeight - topBorder - ringBorder) | ||
|  | 	if group._tag is not None: | ||
|  | 	    group._tag.place(y = maxTagHeight / 2) | ||
|  | 
 | ||
|  | class Group( MegaWidget ): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  |         # Define the megawidget options. | ||
|  | 	 | ||
|  |         optiondefs = ( | ||
|  | 	    ('collapsedsize',    6,         INITOPT), | ||
|  | 	    ('ring_borderwidth', 2,         None), | ||
|  | 	    ('ring_relief',      'groove',  None), | ||
|  | 	    ('tagindent',        10,        INITOPT), | ||
|  |         ) | ||
|  |         self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  |         # Initialise the base class (after defining the options). | ||
|  |         MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  |         # Create the components. | ||
|  |         interior = MegaWidget.interior(self) | ||
|  | 
 | ||
|  | 	self._ring = self.createcomponent( | ||
|  | 	    'ring',  | ||
|  | 	    (), None, | ||
|  | 	    Tkinter.Frame, (interior,),  | ||
|  | 	    ) | ||
|  | 
 | ||
|  | 	self._groupChildSite = self.createcomponent( | ||
|  | 	    'groupchildsite', | ||
|  | 	    (), None, | ||
|  | 	    Tkinter.Frame, (self._ring,) | ||
|  | 	    ) | ||
|  | 
 | ||
|  |         self._tag = self.createcomponent( | ||
|  | 	    'tag', | ||
|  | 	    (), None, | ||
|  | 	    Tkinter.Label, (interior,), | ||
|  | 	    ) | ||
|  | 
 | ||
|  | 	ringBorder = (string.atoi(str(self._ring.cget('borderwidth'))) + | ||
|  | 		string.atoi(str(self._ring.cget('highlightthickness')))) | ||
|  | 	if self._tag is None: | ||
|  | 	    tagHeight = ringBorder | ||
|  | 	else: | ||
|  | 	    tagHeight = self._tag.winfo_reqheight() | ||
|  | 	    self._tag.place( | ||
|  | 		    x = ringBorder + self['tagindent'], | ||
|  | 		    y = tagHeight / 2, | ||
|  | 		    anchor = 'w') | ||
|  | 
 | ||
|  | 	topBorder = tagHeight / 2 - ringBorder / 2 | ||
|  | 	self._ring.grid(column = 0, row = 1, sticky = 'nsew') | ||
|  | 	interior.grid_columnconfigure(0, weight = 1) | ||
|  | 	interior.grid_rowconfigure(1, weight = 1) | ||
|  | 	interior.grid_rowconfigure(0, minsize = topBorder) | ||
|  | 
 | ||
|  | 	self._groupChildSite.grid(column = 0, row = 1, sticky = 'nsew') | ||
|  | 	self._ring.grid_columnconfigure(0, weight = 1) | ||
|  | 	self._ring.grid_rowconfigure(1, weight = 1) | ||
|  | 	self._ring.grid_rowconfigure(0, | ||
|  | 		minsize = tagHeight - topBorder - ringBorder) | ||
|  | 
 | ||
|  |         self.showing = 1 | ||
|  | 
 | ||
|  |         # Check keywords and initialise options. | ||
|  |         self.initialiseoptions() | ||
|  | 
 | ||
|  |     def toggle(self): | ||
|  |         if self.showing: | ||
|  |             self.collapse() | ||
|  |         else: | ||
|  |             self.expand() | ||
|  |         self.showing = not self.showing | ||
|  | 
 | ||
|  |     def expand(self): | ||
|  |         self._groupChildSite.grid(column = 0, row = 1, sticky = 'nsew') | ||
|  | 
 | ||
|  |     def collapse(self): | ||
|  |         self._groupChildSite.grid_forget() | ||
|  | 	if self._tag is None: | ||
|  | 	    tagHeight = 0 | ||
|  | 	else: | ||
|  |             tagHeight = self._tag.winfo_reqheight() | ||
|  |         self._ring.configure(height=(tagHeight / 2) + self['collapsedsize']) | ||
|  | 
 | ||
|  |     def interior(self): | ||
|  |         return self._groupChildSite | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwLabeledWidget.py | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class LabeledWidget(MegaWidget): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('labelmargin',            0,      INITOPT), | ||
|  | 	    ('labelpos',               None,   INITOPT), | ||
|  | 	    ('sticky',                 'nsew', INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = MegaWidget.interior(self) | ||
|  | 	self._labelChildSite = self.createcomponent('labelchildsite', | ||
|  | 		(), None, | ||
|  | 		Tkinter.Frame, (interior,)) | ||
|  | 	self._labelChildSite.grid(column=2, row=2, sticky=self['sticky']) | ||
|  | 	interior.grid_columnconfigure(2, weight=1) | ||
|  | 	interior.grid_rowconfigure(2, weight=1) | ||
|  | 
 | ||
|  | 	self.createlabel(interior) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def interior(self): | ||
|  | 	return self._labelChildSite | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwMainMenuBar.py | ||
|  | # Main menubar | ||
|  | 
 | ||
|  | import string | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class MainMenuBar(MegaArchetype): | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  |         # Define the megawidget options. | ||
|  |          | ||
|  |         optiondefs = ( | ||
|  |             ('balloon',      None,       None), | ||
|  |             ('hotkeys',      1,          INITOPT), | ||
|  |             ('hull_tearoff', 0,          None), | ||
|  |         ) | ||
|  |         self.defineoptions(kw, optiondefs, dynamicGroups = ('Menu',)) | ||
|  | 
 | ||
|  |         # Initialise the base class (after defining the options). | ||
|  |         MegaArchetype.__init__(self, parent, Tkinter.Menu) | ||
|  | 
 | ||
|  |         self._menuInfo = {} | ||
|  |         self._menuInfo[None] = (None, []) | ||
|  |         # Map from a menu name to a tuple of information about the menu. | ||
|  |         # The first item in the tuple is the name of the parent menu (for | ||
|  |         # toplevel menus this is None). The second item in the tuple is | ||
|  |         # a list of status help messages for each item in the menu. | ||
|  |         # The key for the information for the main menubar is None. | ||
|  | 
 | ||
|  |         self._menu = self.interior() | ||
|  |         self._menu.bind('<Leave>', self._resetHelpmessage) | ||
|  |         self._menu.bind('<Motion>', | ||
|  |             lambda event=None, self=self: self._menuHelp(event, None)) | ||
|  | 
 | ||
|  |         # Check keywords and initialise options. | ||
|  |         self.initialiseoptions() | ||
|  | 
 | ||
|  |     def deletemenuitems(self, menuName, start, end = None): | ||
|  |         self.component(menuName).delete(start, end) | ||
|  |         if end is None: | ||
|  |             del self._menuInfo[menuName][1][start] | ||
|  |         else: | ||
|  |             self._menuInfo[menuName][1][start:end+1] = [] | ||
|  | 
 | ||
|  |     def deletemenu(self, menuName): | ||
|  |         """Delete should be called for cascaded menus before main menus.
 | ||
|  |         """
 | ||
|  | 
 | ||
|  |         parentName = self._menuInfo[menuName][0] | ||
|  |         del self._menuInfo[menuName] | ||
|  |         if parentName is None: | ||
|  |             parentMenu = self._menu | ||
|  |         else: | ||
|  |             parentMenu = self.component(parentName) | ||
|  | 
 | ||
|  |         menu = self.component(menuName) | ||
|  |         menuId = str(menu) | ||
|  |         for item in range(parentMenu.index('end') + 1): | ||
|  |             if parentMenu.type(item) == 'cascade': | ||
|  |                 itemMenu = str(parentMenu.entrycget(item, 'menu')) | ||
|  |                 if itemMenu == menuId: | ||
|  |                     parentMenu.delete(item) | ||
|  |                     del self._menuInfo[parentName][1][item] | ||
|  |                     break | ||
|  | 
 | ||
|  |         self.destroycomponent(menuName) | ||
|  | 
 | ||
|  |     def disableall(self): | ||
|  |         for index in range(len(self._menuInfo[None][1])): | ||
|  |             self.entryconfigure(index, state = 'disabled') | ||
|  | 
 | ||
|  |     def enableall(self): | ||
|  |         for index in range(len(self._menuInfo[None][1])): | ||
|  |             self.entryconfigure(index, state = 'normal') | ||
|  | 
 | ||
|  |     def addmenu(self, menuName, balloonHelp, statusHelp = None, | ||
|  |             traverseSpec = None, **kw): | ||
|  |         if statusHelp is None: | ||
|  |             statusHelp = balloonHelp | ||
|  |         self._addmenu(None, menuName, balloonHelp, statusHelp, | ||
|  |             traverseSpec, kw) | ||
|  | 
 | ||
|  |     def addcascademenu(self, parentMenuName, menuName, statusHelp='', | ||
|  |             traverseSpec = None, **kw): | ||
|  |         self._addmenu(parentMenuName, menuName, None, statusHelp, | ||
|  |             traverseSpec, kw) | ||
|  | 
 | ||
|  |     def _addmenu(self, parentMenuName, menuName, balloonHelp, statusHelp, | ||
|  |             traverseSpec, kw): | ||
|  | 
 | ||
|  |         if (menuName) in self.components(): | ||
|  |             raise ValueError, 'menu "%s" already exists' % menuName | ||
|  | 
 | ||
|  |         menukw = {} | ||
|  |         if kw.has_key('tearoff'): | ||
|  |             menukw['tearoff'] = kw['tearoff'] | ||
|  |             del kw['tearoff'] | ||
|  |         else: | ||
|  |             menukw['tearoff'] = 0 | ||
|  |         if kw.has_key('name'): | ||
|  |             menukw['name'] = kw['name'] | ||
|  |             del kw['name'] | ||
|  | 
 | ||
|  |         if not kw.has_key('label'): | ||
|  |             kw['label'] = menuName | ||
|  | 
 | ||
|  |         self._addHotkeyToOptions(parentMenuName, kw, traverseSpec) | ||
|  | 
 | ||
|  |         if parentMenuName is None: | ||
|  |             parentMenu = self._menu | ||
|  |             balloon = self['balloon'] | ||
|  |             # Bug in Tk: balloon help not implemented | ||
|  |             # if balloon is not None: | ||
|  |             #     balloon.mainmenubind(parentMenu, balloonHelp, statusHelp) | ||
|  |         else: | ||
|  |             parentMenu = self.component(parentMenuName) | ||
|  | 
 | ||
|  |         apply(parentMenu.add_cascade, (), kw) | ||
|  | 
 | ||
|  |         menu = apply(self.createcomponent, (menuName, | ||
|  |                 (), 'Menu', | ||
|  |                 Tkinter.Menu, (parentMenu,)), menukw) | ||
|  |         parentMenu.entryconfigure('end', menu = menu) | ||
|  | 
 | ||
|  |         self._menuInfo[parentMenuName][1].append(statusHelp) | ||
|  |         self._menuInfo[menuName] = (parentMenuName, []) | ||
|  | 
 | ||
|  |         menu.bind('<Leave>', self._resetHelpmessage) | ||
|  |         menu.bind('<Motion>',  | ||
|  |             lambda event=None, self=self, menuName=menuName: | ||
|  |                     self._menuHelp(event, menuName)) | ||
|  | 
 | ||
|  |     def addmenuitem(self, menuName, itemType, statusHelp = '', | ||
|  |             traverseSpec = None, **kw): | ||
|  | 
 | ||
|  |         menu = self.component(menuName) | ||
|  |         if itemType != 'separator': | ||
|  |             self._addHotkeyToOptions(menuName, kw, traverseSpec) | ||
|  | 
 | ||
|  |         if itemType == 'command': | ||
|  |             command = menu.add_command | ||
|  |         elif itemType == 'separator': | ||
|  |             command = menu.add_separator | ||
|  |         elif itemType == 'checkbutton': | ||
|  |             command = menu.add_checkbutton | ||
|  |         elif itemType == 'radiobutton': | ||
|  |             command = menu.add_radiobutton | ||
|  |         elif itemType == 'cascade': | ||
|  |             command = menu.add_cascade | ||
|  |         else: | ||
|  |             raise ValueError, 'unknown menuitem type "%s"' % itemType | ||
|  | 
 | ||
|  |         self._menuInfo[menuName][1].append(statusHelp) | ||
|  |         apply(command, (), kw) | ||
|  | 
 | ||
|  |     def _addHotkeyToOptions(self, menuName, kw, traverseSpec): | ||
|  | 
 | ||
|  |         if (not self['hotkeys'] or kw.has_key('underline') or | ||
|  |                 not kw.has_key('label')): | ||
|  |             return | ||
|  | 
 | ||
|  |         if type(traverseSpec) == types.IntType: | ||
|  |             kw['underline'] = traverseSpec | ||
|  |             return | ||
|  | 
 | ||
|  |         if menuName is None: | ||
|  |             menu = self._menu | ||
|  |         else: | ||
|  |             menu = self.component(menuName) | ||
|  |         hotkeyList = [] | ||
|  |         end = menu.index('end') | ||
|  |         if end is not None: | ||
|  |             for item in range(end + 1): | ||
|  |                 if menu.type(item) not in ('separator', 'tearoff'): | ||
|  |                     underline = \ | ||
|  |                             string.atoi(str(menu.entrycget(item, 'underline'))) | ||
|  |                     if underline != -1: | ||
|  |                         label = str(menu.entrycget(item, 'label')) | ||
|  |                         if underline < len(label): | ||
|  |                             hotkey = string.lower(label[underline]) | ||
|  |                             if hotkey not in hotkeyList: | ||
|  |                                 hotkeyList.append(hotkey) | ||
|  | 
 | ||
|  |         name = kw['label'] | ||
|  | 
 | ||
|  |         if type(traverseSpec) == types.StringType: | ||
|  |             lowerLetter = string.lower(traverseSpec) | ||
|  |             if traverseSpec in name and lowerLetter not in hotkeyList: | ||
|  |                 kw['underline'] = string.index(name, traverseSpec) | ||
|  |         else: | ||
|  |             targets = string.digits + string.letters | ||
|  |             lowerName = string.lower(name) | ||
|  |             for letter_index in range(len(name)): | ||
|  |                 letter = lowerName[letter_index] | ||
|  |                 if letter in targets and letter not in hotkeyList: | ||
|  |                     kw['underline'] = letter_index | ||
|  |                     break | ||
|  | 
 | ||
|  |     def _menuHelp(self, event, menuName): | ||
|  |         if menuName is None: | ||
|  |             menu = self._menu | ||
|  |             index = menu.index('@%d'% event.x) | ||
|  |         else: | ||
|  |             menu = self.component(menuName) | ||
|  |             index = menu.index('@%d'% event.y) | ||
|  | 
 | ||
|  |         balloon = self['balloon'] | ||
|  |         if balloon is not None: | ||
|  |             if index is None: | ||
|  |                 balloon.showstatus('') | ||
|  |             else: | ||
|  |                 if str(menu.cget('tearoff')) == '1': | ||
|  |                     index = index - 1 | ||
|  |                 if index >= 0: | ||
|  |                     help = self._menuInfo[menuName][1][index] | ||
|  |                     balloon.showstatus(help) | ||
|  | 
 | ||
|  |     def _resetHelpmessage(self, event=None): | ||
|  |         balloon = self['balloon'] | ||
|  |         if balloon is not None: | ||
|  |             balloon.clearstatus() | ||
|  | 
 | ||
|  | forwardmethods(MainMenuBar, Tkinter.Menu, '_hull') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwMenuBar.py | ||
|  | # Manager widget for menus. | ||
|  | 
 | ||
|  | import string | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class MenuBar(MegaWidget): | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  |         # Define the megawidget options. | ||
|  |          | ||
|  |         optiondefs = ( | ||
|  |             ('balloon',      None,       None), | ||
|  |             ('hotkeys',      1,          INITOPT), | ||
|  |             ('padx',         0,          INITOPT), | ||
|  |         ) | ||
|  |         self.defineoptions(kw, optiondefs, dynamicGroups = ('Menu', 'Button')) | ||
|  | 
 | ||
|  |         # Initialise the base class (after defining the options). | ||
|  |         MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  |         self._menuInfo = {} | ||
|  |         # Map from a menu name to a tuple of information about the menu. | ||
|  |         # The first item in the tuple is the name of the parent menu (for | ||
|  |         # toplevel menus this is None). The second item in the tuple is | ||
|  |         # a list of status help messages for each item in the menu. | ||
|  |         # The third item in the tuple is the id of the binding used | ||
|  |         # to detect mouse motion to display status help. | ||
|  |         # Information for the toplevel menubuttons is not stored here. | ||
|  | 
 | ||
|  |         self._mydeletecommand = self.component('hull').tk.deletecommand | ||
|  |         # Cache this method for use later. | ||
|  | 
 | ||
|  |         # Check keywords and initialise options. | ||
|  |         self.initialiseoptions() | ||
|  | 
 | ||
|  |     def deletemenuitems(self, menuName, start, end = None): | ||
|  |         self.component(menuName + '-menu').delete(start, end) | ||
|  |         if end is None: | ||
|  |             del self._menuInfo[menuName][1][start] | ||
|  |         else: | ||
|  |             self._menuInfo[menuName][1][start:end+1] = [] | ||
|  | 
 | ||
|  |     def deletemenu(self, menuName): | ||
|  |         """Delete should be called for cascaded menus before main menus.
 | ||
|  |         """
 | ||
|  | 
 | ||
|  |         # Clean up binding for this menu. | ||
|  |         parentName = self._menuInfo[menuName][0] | ||
|  |         bindId = self._menuInfo[menuName][2] | ||
|  |         _bindtag = 'PmwMenuBar' + str(self) + menuName | ||
|  |         self.unbind_class(_bindtag, '<Motion>') | ||
|  |         self._mydeletecommand(bindId) # unbind_class does not clean up | ||
|  |         del self._menuInfo[menuName] | ||
|  | 
 | ||
|  |         if parentName is None: | ||
|  |             self.destroycomponent(menuName + '-button') | ||
|  |         else: | ||
|  |             parentMenu = self.component(parentName + '-menu') | ||
|  | 
 | ||
|  |             menu = self.component(menuName + '-menu') | ||
|  |             menuId = str(menu) | ||
|  |             for item in range(parentMenu.index('end') + 1): | ||
|  |                 if parentMenu.type(item) == 'cascade': | ||
|  |                     itemMenu = str(parentMenu.entrycget(item, 'menu')) | ||
|  |                     if itemMenu == menuId: | ||
|  |                         parentMenu.delete(item) | ||
|  |                         del self._menuInfo[parentName][1][item] | ||
|  |                         break | ||
|  | 
 | ||
|  |         self.destroycomponent(menuName + '-menu') | ||
|  | 
 | ||
|  |     def disableall(self): | ||
|  |         for menuName in self._menuInfo.keys(): | ||
|  |             if self._menuInfo[menuName][0] is None: | ||
|  |                 menubutton = self.component(menuName + '-button') | ||
|  |                 menubutton.configure(state = 'disabled') | ||
|  | 
 | ||
|  |     def enableall(self): | ||
|  |         for menuName in self._menuInfo.keys(): | ||
|  |             if self._menuInfo[menuName][0] is None: | ||
|  |                 menubutton = self.component(menuName + '-button') | ||
|  |                 menubutton.configure(state = 'normal') | ||
|  | 
 | ||
|  |     def addmenu(self, menuName, balloonHelp, statusHelp = None, | ||
|  |             side = 'left', traverseSpec = None, **kw): | ||
|  | 
 | ||
|  |         self._addmenu(None, menuName, balloonHelp, statusHelp, | ||
|  |             traverseSpec, side, 'text', kw) | ||
|  | 
 | ||
|  |     def addcascademenu(self, parentMenuName, menuName, statusHelp = '', | ||
|  |             traverseSpec = None, **kw): | ||
|  | 
 | ||
|  |         self._addmenu(parentMenuName, menuName, None, statusHelp, | ||
|  |             traverseSpec, None, 'label', kw) | ||
|  | 
 | ||
|  |     def _addmenu(self, parentMenuName, menuName, balloonHelp, statusHelp, | ||
|  |             traverseSpec, side, textKey, kw): | ||
|  | 
 | ||
|  |         if (menuName + '-menu') in self.components(): | ||
|  |             raise ValueError, 'menu "%s" already exists' % menuName | ||
|  | 
 | ||
|  |         menukw = {} | ||
|  |         if kw.has_key('tearoff'): | ||
|  |             menukw['tearoff'] = kw['tearoff'] | ||
|  |             del kw['tearoff'] | ||
|  |         else: | ||
|  |             menukw['tearoff'] = 0 | ||
|  | 
 | ||
|  |         if not kw.has_key(textKey): | ||
|  |             kw[textKey] = menuName | ||
|  | 
 | ||
|  |         self._addHotkeyToOptions(parentMenuName, kw, textKey, traverseSpec) | ||
|  | 
 | ||
|  |         if parentMenuName is None: | ||
|  |             button = apply(self.createcomponent, (menuName + '-button', | ||
|  |                     (), 'Button', | ||
|  |                     Tkinter.Menubutton, (self.interior(),)), kw) | ||
|  |             button.pack(side=side, padx = self['padx']) | ||
|  |             balloon = self['balloon'] | ||
|  |             if balloon is not None: | ||
|  |                 balloon.bind(button, balloonHelp, statusHelp) | ||
|  |             parentMenu = button | ||
|  |         else: | ||
|  |             parentMenu = self.component(parentMenuName + '-menu') | ||
|  |             apply(parentMenu.add_cascade, (), kw) | ||
|  |             self._menuInfo[parentMenuName][1].append(statusHelp) | ||
|  | 
 | ||
|  |         menu = apply(self.createcomponent, (menuName + '-menu', | ||
|  |                 (), 'Menu', | ||
|  |                 Tkinter.Menu, (parentMenu,)), menukw) | ||
|  |         if parentMenuName is None: | ||
|  |             button.configure(menu = menu) | ||
|  |         else: | ||
|  |             parentMenu.entryconfigure('end', menu = menu) | ||
|  | 
 | ||
|  |         # Need to put this binding after the class bindings so that | ||
|  |         # menu.index() does not lag behind. | ||
|  |         _bindtag = 'PmwMenuBar' + str(self) + menuName | ||
|  |         bindId = self.bind_class(_bindtag, '<Motion>', | ||
|  |             lambda event=None, self=self, menuName=menuName: | ||
|  |                     self._menuHelp(menuName)) | ||
|  |         menu.bindtags(menu.bindtags() + (_bindtag,)) | ||
|  |         menu.bind('<Leave>', self._resetHelpmessage) | ||
|  | 
 | ||
|  |         self._menuInfo[menuName] = (parentMenuName, [], bindId) | ||
|  | 
 | ||
|  |     def addmenuitem(self, menuName, itemType, statusHelp = '', | ||
|  |             traverseSpec = None, **kw): | ||
|  | 
 | ||
|  |         menu = self.component(menuName + '-menu') | ||
|  |         if itemType != 'separator': | ||
|  |             self._addHotkeyToOptions(menuName, kw, 'label', traverseSpec) | ||
|  | 
 | ||
|  |         if itemType == 'command': | ||
|  |             command = menu.add_command | ||
|  |         elif itemType == 'separator': | ||
|  |             command = menu.add_separator | ||
|  |         elif itemType == 'checkbutton': | ||
|  |             command = menu.add_checkbutton | ||
|  |         elif itemType == 'radiobutton': | ||
|  |             command = menu.add_radiobutton | ||
|  |         elif itemType == 'cascade': | ||
|  |             command = menu.add_cascade | ||
|  |         else: | ||
|  |             raise ValueError, 'unknown menuitem type "%s"' % itemType | ||
|  | 
 | ||
|  |         self._menuInfo[menuName][1].append(statusHelp) | ||
|  |         apply(command, (), kw) | ||
|  | 
 | ||
|  |     def _addHotkeyToOptions(self, menuName, kw, textKey, traverseSpec): | ||
|  | 
 | ||
|  |         if (not self['hotkeys'] or kw.has_key('underline') or | ||
|  |                 not kw.has_key(textKey)): | ||
|  |             return | ||
|  | 
 | ||
|  |         if type(traverseSpec) == types.IntType: | ||
|  |             kw['underline'] = traverseSpec | ||
|  |             return | ||
|  | 
 | ||
|  |         hotkeyList = [] | ||
|  |         if menuName is None: | ||
|  |             for menuName in self._menuInfo.keys(): | ||
|  |                 if self._menuInfo[menuName][0] is None: | ||
|  |                     menubutton = self.component(menuName + '-button') | ||
|  |                     underline = string.atoi(str(menubutton.cget('underline'))) | ||
|  |                     if underline != -1: | ||
|  |                         label = str(menubutton.cget(textKey)) | ||
|  |                         if underline < len(label): | ||
|  |                             hotkey = string.lower(label[underline]) | ||
|  |                             if hotkey not in hotkeyList: | ||
|  |                                 hotkeyList.append(hotkey) | ||
|  |         else: | ||
|  |             menu = self.component(menuName + '-menu') | ||
|  |             end = menu.index('end') | ||
|  |             if end is not None: | ||
|  |                 for item in range(end + 1): | ||
|  |                     if menu.type(item) not in ('separator', 'tearoff'): | ||
|  |                         underline = string.atoi( | ||
|  |                             str(menu.entrycget(item, 'underline'))) | ||
|  |                         if underline != -1: | ||
|  |                             label = str(menu.entrycget(item, textKey)) | ||
|  |                             if underline < len(label): | ||
|  |                                 hotkey = string.lower(label[underline]) | ||
|  |                                 if hotkey not in hotkeyList: | ||
|  |                                     hotkeyList.append(hotkey) | ||
|  | 
 | ||
|  |         name = kw[textKey] | ||
|  | 
 | ||
|  |         if type(traverseSpec) == types.StringType: | ||
|  |             lowerLetter = string.lower(traverseSpec) | ||
|  |             if traverseSpec in name and lowerLetter not in hotkeyList: | ||
|  |                 kw['underline'] = string.index(name, traverseSpec) | ||
|  |         else: | ||
|  |             targets = string.digits + string.letters | ||
|  |             lowerName = string.lower(name) | ||
|  |             for letter_index in range(len(name)): | ||
|  |                 letter = lowerName[letter_index] | ||
|  |                 if letter in targets and letter not in hotkeyList: | ||
|  |                     kw['underline'] = letter_index | ||
|  |                     break | ||
|  | 
 | ||
|  |     def _menuHelp(self, menuName): | ||
|  |         menu = self.component(menuName + '-menu') | ||
|  |         index = menu.index('active') | ||
|  | 
 | ||
|  |         balloon = self['balloon'] | ||
|  |         if balloon is not None: | ||
|  |             if index is None: | ||
|  |                 balloon.showstatus('') | ||
|  |             else: | ||
|  |                 if str(menu.cget('tearoff')) == '1': | ||
|  |                     index = index - 1 | ||
|  |                 if index >= 0: | ||
|  |                     help = self._menuInfo[menuName][1][index] | ||
|  |                     balloon.showstatus(help) | ||
|  | 
 | ||
|  |     def _resetHelpmessage(self, event=None): | ||
|  |         balloon = self['balloon'] | ||
|  |         if balloon is not None: | ||
|  |             balloon.clearstatus() | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwMessageBar.py | ||
|  | # Class to display messages in an information line. | ||
|  | 
 | ||
|  | import string | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class MessageBar(MegaWidget): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	defaultMessageTypes = { | ||
|  | 			   # (priority, showtime, bells, logmessage) | ||
|  | 	    'systemerror'  : (5, 10, 2, 1), | ||
|  | 	    'usererror'    : (4, 5, 1, 0), | ||
|  | 	    'busy'         : (3, 0, 0, 0), | ||
|  | 	    'systemevent'  : (2, 5, 0, 0), | ||
|  | 	    'userevent'    : (2, 5, 0, 0), | ||
|  | 	    'help'         : (1, 5, 0, 0), | ||
|  | 	    'state'        : (0, 0, 0, 0), | ||
|  | 	} | ||
|  | 	optiondefs = ( | ||
|  | 	    ('labelmargin',    0,                     INITOPT), | ||
|  | 	    ('labelpos',       None,                  INITOPT), | ||
|  | 	    ('messagetypes',   defaultMessageTypes,   INITOPT), | ||
|  | 	    ('silent',         0,                     None), | ||
|  | 	    ('sticky',         'ew',                  INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 	self._messageBarEntry = self.createcomponent('entry', | ||
|  | 		(), None, | ||
|  | 		Tkinter.Entry, (interior,)) | ||
|  | 
 | ||
|  |         # Can't always use 'disabled', since this greys out text in Tk 8.4.2 | ||
|  |         try: | ||
|  |             self._messageBarEntry.configure(state = 'readonly') | ||
|  |         except Tkinter.TclError: | ||
|  |             self._messageBarEntry.configure(state = 'disabled') | ||
|  | 
 | ||
|  | 	self._messageBarEntry.grid(column=2, row=2, sticky=self['sticky']) | ||
|  | 	interior.grid_columnconfigure(2, weight=1) | ||
|  | 	interior.grid_rowconfigure(2, weight=1) | ||
|  | 
 | ||
|  | 	self.createlabel(interior) | ||
|  | 
 | ||
|  | 	# Initialise instance variables. | ||
|  | 	self._numPriorities = 0 | ||
|  | 	for info in self['messagetypes'].values(): | ||
|  | 	    if self._numPriorities < info[0]: | ||
|  | 		self._numPriorities = info[0] | ||
|  | 
 | ||
|  | 	self._numPriorities = self._numPriorities + 1 | ||
|  | 	self._timer = [None] * self._numPriorities | ||
|  | 	self._messagetext = [''] * self._numPriorities | ||
|  | 	self._activemessage = [0] * self._numPriorities | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  | 	for timerId in self._timer: | ||
|  | 	    if timerId is not None: | ||
|  | 		self.after_cancel(timerId) | ||
|  | 	self._timer = [None] * self._numPriorities | ||
|  | 	MegaWidget.destroy(self) | ||
|  | 
 | ||
|  |     def message(self, type, text): | ||
|  |         # Display a message in the message bar. | ||
|  | 
 | ||
|  | 	(priority, showtime, bells, logmessage) = self['messagetypes'][type] | ||
|  | 
 | ||
|  | 	if not self['silent']: | ||
|  | 	    for i in range(bells): | ||
|  | 		if i != 0: | ||
|  | 		    self.after(100) | ||
|  | 		self.bell() | ||
|  | 
 | ||
|  | 	self._activemessage[priority] = 1 | ||
|  | 	if text is None: | ||
|  | 	    text = '' | ||
|  | 	self._messagetext[priority] = string.replace(text, '\n', ' ') | ||
|  | 	self._redisplayInfoMessage() | ||
|  | 
 | ||
|  | 	if logmessage: | ||
|  | 	    # Should log this text to a text widget. | ||
|  | 	    pass | ||
|  | 
 | ||
|  | 	if showtime > 0: | ||
|  | 	    if self._timer[priority] is not None: | ||
|  | 		self.after_cancel(self._timer[priority]) | ||
|  | 
 | ||
|  | 	    # Define a callback to clear this message after a time. | ||
|  | 	    def _clearmessage(self=self, priority=priority): | ||
|  | 		self._clearActivemessage(priority) | ||
|  | 
 | ||
|  | 	    mseconds = int(showtime * 1000) | ||
|  | 	    self._timer[priority] = self.after(mseconds, _clearmessage) | ||
|  | 
 | ||
|  |     def helpmessage(self, text): | ||
|  |         if text is None: | ||
|  |             self.resetmessages('help') | ||
|  |         else: | ||
|  |             self.message('help', text) | ||
|  | 
 | ||
|  |     def resetmessages(self, type): | ||
|  | 	priority = self['messagetypes'][type][0] | ||
|  | 	self._clearActivemessage(priority) | ||
|  | 	for messagetype, info in self['messagetypes'].items(): | ||
|  | 	    thisPriority = info[0] | ||
|  | 	    showtime = info[1] | ||
|  | 	    if thisPriority < priority and showtime != 0: | ||
|  | 		self._clearActivemessage(thisPriority) | ||
|  | 
 | ||
|  |     def _clearActivemessage(self, priority): | ||
|  | 	self._activemessage[priority] = 0 | ||
|  | 	if self._timer[priority] is not None: | ||
|  | 	    self.after_cancel(self._timer[priority]) | ||
|  | 	    self._timer[priority] = None | ||
|  | 	self._redisplayInfoMessage() | ||
|  | 
 | ||
|  |     def _redisplayInfoMessage(self): | ||
|  | 	text = '' | ||
|  |         for priority in range(self._numPriorities - 1, -1, -1): | ||
|  | 	    if self._activemessage[priority]: | ||
|  | 		text = self._messagetext[priority] | ||
|  | 	        break | ||
|  | 	self._messageBarEntry.configure(state = 'normal') | ||
|  | 	self._messageBarEntry.delete(0, 'end') | ||
|  | 	self._messageBarEntry.insert('end', text) | ||
|  | 
 | ||
|  |         # Can't always use 'disabled', since this greys out text in Tk 8.4.2 | ||
|  |         try: | ||
|  |             self._messageBarEntry.configure(state = 'readonly') | ||
|  |         except Tkinter.TclError: | ||
|  |             self._messageBarEntry.configure(state = 'disabled') | ||
|  | 
 | ||
|  | forwardmethods(MessageBar, Tkinter.Entry, '_messageBarEntry') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwMessageDialog.py | ||
|  | # Based on iwidgets2.2.0/messagedialog.itk code. | ||
|  | 
 | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class MessageDialog(Dialog): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('borderx',       20,    INITOPT), | ||
|  | 	    ('bordery',       20,    INITOPT), | ||
|  | 	    ('iconmargin',    20,    INITOPT), | ||
|  | 	    ('iconpos',       None,  INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	Dialog.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 
 | ||
|  | 	self._message = self.createcomponent('message', | ||
|  | 		(), None, | ||
|  | 		Tkinter.Label, (interior,)) | ||
|  | 
 | ||
|  |         iconpos = self['iconpos'] | ||
|  | 	iconmargin = self['iconmargin'] | ||
|  | 	borderx = self['borderx'] | ||
|  | 	bordery = self['bordery'] | ||
|  | 	border_right = 2 | ||
|  | 	border_bottom = 2 | ||
|  | 	if iconpos is None: | ||
|  | 	    self._message.grid(column = 1, row = 1) | ||
|  | 	else: | ||
|  | 	    self._icon = self.createcomponent('icon', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Label, (interior,)) | ||
|  | 	    if iconpos not in 'nsew': | ||
|  | 		raise ValueError, \ | ||
|  | 		    'bad iconpos option "%s":  should be n, s, e, or w' \ | ||
|  | 			% iconpos | ||
|  | 
 | ||
|  | 	    if iconpos in 'nw': | ||
|  | 		icon = 1 | ||
|  | 		message = 3 | ||
|  | 	    else: | ||
|  | 		icon = 3 | ||
|  | 		message = 1 | ||
|  | 
 | ||
|  | 	    if iconpos in 'ns': | ||
|  | 		# vertical layout | ||
|  | 		self._icon.grid(column = 1, row = icon) | ||
|  | 		self._message.grid(column = 1, row = message) | ||
|  | 		interior.grid_rowconfigure(2, minsize = iconmargin) | ||
|  | 		border_bottom = 4 | ||
|  | 	    else: | ||
|  | 		# horizontal layout | ||
|  | 		self._icon.grid(column = icon, row = 1) | ||
|  | 		self._message.grid(column = message, row = 1) | ||
|  | 		interior.grid_columnconfigure(2, minsize = iconmargin) | ||
|  | 		border_right = 4 | ||
|  | 
 | ||
|  | 	interior.grid_columnconfigure(0, minsize = borderx) | ||
|  | 	interior.grid_rowconfigure(0, minsize = bordery) | ||
|  | 	interior.grid_columnconfigure(border_right, minsize = borderx) | ||
|  | 	interior.grid_rowconfigure(border_bottom, minsize = bordery) | ||
|  | 
 | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwNoteBook.py | ||
|  | import string | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class NoteBook(MegaArchetype): | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  |         optiondefs = ( | ||
|  | 	    ('hull_highlightthickness',  0,           None), | ||
|  | 	    ('hull_borderwidth',         0,           None), | ||
|  |             ('arrownavigation',          1,           INITOPT), | ||
|  |             ('borderwidth',              2,           INITOPT), | ||
|  |             ('createcommand',            None,        None), | ||
|  |             ('lowercommand',             None,        None), | ||
|  |             ('pagemargin',               4,           INITOPT), | ||
|  |             ('raisecommand',             None,        None), | ||
|  | 	    ('tabpos',                   'n',         INITOPT), | ||
|  |         ) | ||
|  | 	self.defineoptions(kw, optiondefs, dynamicGroups = ('Page', 'Tab')) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaArchetype.__init__(self, parent, Tkinter.Canvas) | ||
|  | 
 | ||
|  |         self.bind('<Map>', self._handleMap) | ||
|  |         self.bind('<Configure>', self._handleConfigure) | ||
|  | 
 | ||
|  |         tabpos = self['tabpos'] | ||
|  | 	if tabpos is not None and tabpos != 'n': | ||
|  |             raise ValueError, \ | ||
|  |                 'bad tabpos option %s:  should be n or None' % repr(tabpos) | ||
|  |         self._withTabs = (tabpos is not None) | ||
|  |         self._pageMargin = self['pagemargin'] | ||
|  |         self._borderWidth = self['borderwidth'] | ||
|  | 
 | ||
|  |         # Use a dictionary as a set of bits indicating what needs to | ||
|  |         # be redisplayed the next time _layout() is called.  If | ||
|  |         # dictionary contains 'topPage' key, the value is the new top | ||
|  |         # page to be displayed.  None indicates that all pages have | ||
|  |         # been deleted and that _layout() should draw a border under where | ||
|  |         # the tabs should be. | ||
|  |         self._pending = {} | ||
|  |         self._pending['size'] = 1 | ||
|  |         self._pending['borderColor'] = 1 | ||
|  |         self._pending['topPage'] = None | ||
|  |         if self._withTabs: | ||
|  |             self._pending['tabs'] = 1 | ||
|  | 
 | ||
|  |         self._canvasSize = None       # This gets set by <Configure> events | ||
|  | 
 | ||
|  |         # Set initial height of space for tabs | ||
|  |         if self._withTabs: | ||
|  |             self.tabBottom = 35 | ||
|  |         else: | ||
|  |             self.tabBottom = 0 | ||
|  | 
 | ||
|  |         self._lightBorderColor, self._darkBorderColor = \ | ||
|  |                 Color.bordercolors(self, self['hull_background']) | ||
|  | 
 | ||
|  | 	self._pageNames   = []        # List of page names | ||
|  | 
 | ||
|  |         # Map from page name to page info.  Each item is itself a | ||
|  |         # dictionary containing the following items: | ||
|  |         #   page           the Tkinter.Frame widget for the page | ||
|  |         #   created        set to true the first time the page is raised | ||
|  |         #   tabbutton      the Tkinter.Button widget for the button (if any) | ||
|  |         #   tabreqwidth    requested width of the tab | ||
|  |         #   tabreqheight   requested height of the tab | ||
|  |         #   tabitems       the canvas items for the button: the button | ||
|  |         #                  window item, the lightshadow and the darkshadow | ||
|  |         #   left           the left and right canvas coordinates of the tab | ||
|  |         #   right | ||
|  | 	self._pageAttrs   = {} | ||
|  | 
 | ||
|  |         # Name of page currently on top (actually displayed, using | ||
|  |         # create_window, not pending).  Ignored if current top page | ||
|  |         # has been deleted or new top page is pending.  None indicates | ||
|  |         # no pages in notebook. | ||
|  | 	self._topPageName = None | ||
|  | 
 | ||
|  |         # Canvas items used: | ||
|  |         #   Per tab:  | ||
|  |         #       top and left shadow | ||
|  |         #       right shadow | ||
|  |         #       button | ||
|  |         #   Per notebook:  | ||
|  |         #       page | ||
|  |         #       top page | ||
|  |         #       left shadow | ||
|  |         #       bottom and right shadow | ||
|  |         #       top (one or two items) | ||
|  | 
 | ||
|  |         # Canvas tags used: | ||
|  |         #   lighttag      - top and left shadows of tabs and page | ||
|  |         #   darktag       - bottom and right shadows of tabs and page | ||
|  |         #                   (if no tabs then these are reversed) | ||
|  |         #                   (used to color the borders by recolorborders) | ||
|  | 
 | ||
|  |         # Create page border shadows. | ||
|  |         if self._withTabs: | ||
|  |             self._pageLeftBorder = self.create_polygon(0, 0, 0, 0, 0, 0, | ||
|  |                 fill = self._lightBorderColor, tags = 'lighttag') | ||
|  |             self._pageBottomRightBorder = self.create_polygon(0, 0, 0, 0, 0, 0, | ||
|  |                 fill = self._darkBorderColor, tags = 'darktag') | ||
|  |             self._pageTop1Border = self.create_polygon(0, 0, 0, 0, 0, 0, | ||
|  |                 fill = self._darkBorderColor, tags = 'lighttag') | ||
|  |             self._pageTop2Border = self.create_polygon(0, 0, 0, 0, 0, 0, | ||
|  |                 fill = self._darkBorderColor, tags = 'lighttag') | ||
|  |         else: | ||
|  |             self._pageLeftBorder = self.create_polygon(0, 0, 0, 0, 0, 0, | ||
|  |                 fill = self._darkBorderColor, tags = 'darktag') | ||
|  |             self._pageBottomRightBorder = self.create_polygon(0, 0, 0, 0, 0, 0, | ||
|  |                 fill = self._lightBorderColor, tags = 'lighttag') | ||
|  |             self._pageTopBorder = self.create_polygon(0, 0, 0, 0, 0, 0, | ||
|  |                 fill = self._darkBorderColor, tags = 'darktag') | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def insert(self, pageName, before = 0, **kw): | ||
|  | 	if self._pageAttrs.has_key(pageName): | ||
|  | 	    msg = 'Page "%s" already exists.' % pageName | ||
|  | 	    raise ValueError, msg | ||
|  | 
 | ||
|  |         # Do this early to catch bad <before> spec before creating any items. | ||
|  | 	beforeIndex = self.index(before, 1) | ||
|  | 
 | ||
|  |         pageOptions = {} | ||
|  |         if self._withTabs: | ||
|  |             # Default tab button options. | ||
|  |             tabOptions = { | ||
|  |                 'text' : pageName, | ||
|  |                 'borderwidth' : 0, | ||
|  |             } | ||
|  | 
 | ||
|  |         # Divide the keyword options into the 'page_' and 'tab_' options. | ||
|  |         for key in kw.keys(): | ||
|  |             if key[:5] == 'page_': | ||
|  |                 pageOptions[key[5:]] = kw[key] | ||
|  |                 del kw[key] | ||
|  |             elif self._withTabs and key[:4] == 'tab_': | ||
|  |                 tabOptions[key[4:]] = kw[key] | ||
|  |                 del kw[key] | ||
|  |             else: | ||
|  | 		raise KeyError, 'Unknown option "' + key + '"' | ||
|  | 
 | ||
|  |         # Create the frame to contain the page. | ||
|  | 	page = apply(self.createcomponent, (pageName, | ||
|  | 		(), 'Page', | ||
|  | 		Tkinter.Frame, self._hull), pageOptions) | ||
|  | 
 | ||
|  |         attributes = {} | ||
|  |         attributes['page'] = page | ||
|  |         attributes['created'] = 0 | ||
|  | 
 | ||
|  |         if self._withTabs: | ||
|  |             # Create the button for the tab. | ||
|  |             def raiseThisPage(self = self, pageName = pageName): | ||
|  |                 self.selectpage(pageName) | ||
|  |             tabOptions['command'] = raiseThisPage | ||
|  |             tab = apply(self.createcomponent, (pageName + '-tab', | ||
|  |                     (), 'Tab', | ||
|  |                     Tkinter.Button, self._hull), tabOptions) | ||
|  | 
 | ||
|  |             if self['arrownavigation']: | ||
|  |                 # Allow the use of the arrow keys for Tab navigation: | ||
|  |                 def next(event, self = self, pageName = pageName): | ||
|  |                     self.nextpage(pageName) | ||
|  |                 def prev(event, self = self, pageName = pageName): | ||
|  |                     self.previouspage(pageName) | ||
|  |                 tab.bind('<Left>', prev) | ||
|  |                 tab.bind('<Right>', next) | ||
|  | 
 | ||
|  |             attributes['tabbutton'] = tab | ||
|  |             attributes['tabreqwidth'] = tab.winfo_reqwidth() | ||
|  |             attributes['tabreqheight'] = tab.winfo_reqheight() | ||
|  | 
 | ||
|  |             # Create the canvas item to manage the tab's button and the items | ||
|  |             # for the tab's shadow. | ||
|  |             windowitem = self.create_window(0, 0, window = tab, anchor = 'nw') | ||
|  |             lightshadow = self.create_polygon(0, 0, 0, 0, 0, 0, | ||
|  |                 tags = 'lighttag', fill = self._lightBorderColor) | ||
|  |             darkshadow = self.create_polygon(0, 0, 0, 0, 0, 0, | ||
|  |                 tags = 'darktag', fill = self._darkBorderColor) | ||
|  |             attributes['tabitems'] = (windowitem, lightshadow, darkshadow) | ||
|  |             self._pending['tabs'] = 1 | ||
|  | 
 | ||
|  |         self._pageAttrs[pageName] = attributes | ||
|  | 	self._pageNames.insert(beforeIndex, pageName) | ||
|  | 
 | ||
|  |         # If this is the first page added, make it the new top page | ||
|  |         # and call the create and raise callbacks. | ||
|  |         if self.getcurselection() is None: | ||
|  |             self._pending['topPage'] = pageName | ||
|  |             self._raiseNewTop(pageName) | ||
|  | 
 | ||
|  |         self._layout() | ||
|  |         return page | ||
|  |   		 | ||
|  |     def add(self, pageName, **kw): | ||
|  |         return apply(self.insert, (pageName, len(self._pageNames)), kw) | ||
|  | 
 | ||
|  |     def delete(self, *pageNames): | ||
|  |         newTopPage = 0 | ||
|  |         for page in pageNames: | ||
|  |             pageIndex = self.index(page) | ||
|  |             pageName = self._pageNames[pageIndex] | ||
|  |             pageInfo = self._pageAttrs[pageName] | ||
|  | 
 | ||
|  |             if self.getcurselection() == pageName: | ||
|  |                 if len(self._pageNames) == 1: | ||
|  |                     newTopPage = 0 | ||
|  |                     self._pending['topPage'] = None | ||
|  |                 elif pageIndex == len(self._pageNames) - 1: | ||
|  |                     newTopPage = 1 | ||
|  |                     self._pending['topPage'] = self._pageNames[pageIndex - 1] | ||
|  |                 else: | ||
|  |                     newTopPage = 1 | ||
|  |                     self._pending['topPage'] = self._pageNames[pageIndex + 1] | ||
|  | 
 | ||
|  |             if self._topPageName == pageName: | ||
|  |                 self._hull.delete(self._topPageItem) | ||
|  |                 self._topPageName = None | ||
|  |                                  | ||
|  |             if self._withTabs: | ||
|  |                 self.destroycomponent(pageName + '-tab') | ||
|  |                 apply(self._hull.delete, pageInfo['tabitems']) | ||
|  |             self.destroycomponent(pageName) | ||
|  |             del self._pageAttrs[pageName] | ||
|  |             del self._pageNames[pageIndex] | ||
|  | 
 | ||
|  |         # If the old top page was deleted and there are still pages | ||
|  |         # left in the notebook, call the create and raise callbacks. | ||
|  |         if newTopPage: | ||
|  |             pageName = self._pending['topPage'] | ||
|  |             self._raiseNewTop(pageName) | ||
|  | 
 | ||
|  |         if self._withTabs: | ||
|  |             self._pending['tabs'] = 1 | ||
|  |         self._layout() | ||
|  | 
 | ||
|  |     def page(self, pageIndex): | ||
|  |         pageName = self._pageNames[self.index(pageIndex)] | ||
|  | 	return self._pageAttrs[pageName]['page'] | ||
|  | 
 | ||
|  |     def pagenames(self): | ||
|  | 	return list(self._pageNames) | ||
|  | 
 | ||
|  |     def getcurselection(self): | ||
|  |         if self._pending.has_key('topPage'): | ||
|  |             return self._pending['topPage'] | ||
|  |         else: | ||
|  |             return self._topPageName | ||
|  | 
 | ||
|  |     def tab(self, pageIndex): | ||
|  |         if self._withTabs: | ||
|  |             pageName = self._pageNames[self.index(pageIndex)] | ||
|  |             return self._pageAttrs[pageName]['tabbutton'] | ||
|  |         else: | ||
|  |             return None | ||
|  | 
 | ||
|  |     def index(self, index, forInsert = 0): | ||
|  | 	listLength = len(self._pageNames) | ||
|  | 	if type(index) == types.IntType: | ||
|  | 	    if forInsert and index <= listLength: | ||
|  | 		return index | ||
|  | 	    elif not forInsert and index < listLength: | ||
|  | 		return index | ||
|  | 	    else: | ||
|  | 		raise ValueError, 'index "%s" is out of range' % index | ||
|  | 	elif index is END: | ||
|  | 	    if forInsert: | ||
|  | 		return listLength | ||
|  | 	    elif listLength > 0: | ||
|  | 		return listLength - 1 | ||
|  | 	    else: | ||
|  | 		raise ValueError, 'NoteBook has no pages' | ||
|  | 	elif index is SELECT: | ||
|  | 	    if listLength == 0: | ||
|  | 		raise ValueError, 'NoteBook has no pages' | ||
|  |             return self._pageNames.index(self.getcurselection()) | ||
|  | 	else: | ||
|  |             if index in self._pageNames: | ||
|  |                 return self._pageNames.index(index) | ||
|  | 	    validValues = 'a name, a number, END or SELECT' | ||
|  | 	    raise ValueError, \ | ||
|  |                 'bad index "%s": must be %s' % (index, validValues) | ||
|  | 
 | ||
|  |     def selectpage(self, page): | ||
|  |         pageName = self._pageNames[self.index(page)] | ||
|  |         oldTopPage = self.getcurselection() | ||
|  |         if pageName != oldTopPage: | ||
|  |             self._pending['topPage'] = pageName | ||
|  |             if oldTopPage == self._topPageName: | ||
|  |                 self._hull.delete(self._topPageItem) | ||
|  |             cmd = self['lowercommand'] | ||
|  |             if cmd is not None: | ||
|  |                 cmd(oldTopPage) | ||
|  |             self._raiseNewTop(pageName) | ||
|  | 
 | ||
|  |             self._layout() | ||
|  | 
 | ||
|  |         # Set focus to the tab of new top page: | ||
|  |         if self._withTabs and self['arrownavigation']: | ||
|  |             self._pageAttrs[pageName]['tabbutton'].focus_set() | ||
|  | 
 | ||
|  |     def previouspage(self, pageIndex = None): | ||
|  |         if pageIndex is None: | ||
|  |             curpage = self.index(SELECT) | ||
|  |         else: | ||
|  |             curpage = self.index(pageIndex) | ||
|  | 	if curpage > 0: | ||
|  | 	    self.selectpage(curpage - 1) | ||
|  | 
 | ||
|  |     def nextpage(self, pageIndex = None): | ||
|  |         if pageIndex is None: | ||
|  |             curpage = self.index(SELECT) | ||
|  |         else: | ||
|  |             curpage = self.index(pageIndex) | ||
|  | 	if curpage < len(self._pageNames) - 1: | ||
|  | 	    self.selectpage(curpage + 1) | ||
|  | 
 | ||
|  |     def setnaturalsize(self, pageNames = None): | ||
|  |         self.update_idletasks() | ||
|  |         maxPageWidth = 1 | ||
|  |         maxPageHeight = 1 | ||
|  |         if pageNames is None: | ||
|  |             pageNames = self.pagenames() | ||
|  |         for pageName in pageNames: | ||
|  |             pageInfo = self._pageAttrs[pageName] | ||
|  |             page = pageInfo['page'] | ||
|  |             w = page.winfo_reqwidth() | ||
|  |             h = page.winfo_reqheight() | ||
|  |             if maxPageWidth < w: | ||
|  |                 maxPageWidth = w | ||
|  |             if maxPageHeight < h: | ||
|  |                 maxPageHeight = h | ||
|  |         pageBorder = self._borderWidth + self._pageMargin | ||
|  |         width = maxPageWidth + pageBorder * 2 | ||
|  |         height = maxPageHeight + pageBorder * 2 | ||
|  | 
 | ||
|  |         if self._withTabs: | ||
|  |             maxTabHeight = 0 | ||
|  |             for pageInfo in self._pageAttrs.values(): | ||
|  |                 if maxTabHeight < pageInfo['tabreqheight']: | ||
|  |                     maxTabHeight = pageInfo['tabreqheight'] | ||
|  |             height = height + maxTabHeight + self._borderWidth * 1.5 | ||
|  | 
 | ||
|  |         # Note that, since the hull is a canvas, the width and height | ||
|  |         # options specify the geometry *inside* the borderwidth and | ||
|  |         # highlightthickness. | ||
|  |         self.configure(hull_width = width, hull_height = height) | ||
|  | 
 | ||
|  |     def recolorborders(self): | ||
|  |         self._pending['borderColor'] = 1 | ||
|  |         self._layout() | ||
|  | 
 | ||
|  |     def _handleMap(self, event): | ||
|  |         self._layout() | ||
|  | 
 | ||
|  |     def _handleConfigure(self, event): | ||
|  |         self._canvasSize = (event.width, event.height) | ||
|  |         self._pending['size'] = 1 | ||
|  |         self._layout() | ||
|  | 
 | ||
|  |     def _raiseNewTop(self, pageName): | ||
|  |         if not self._pageAttrs[pageName]['created']: | ||
|  |             self._pageAttrs[pageName]['created'] = 1 | ||
|  |             cmd = self['createcommand'] | ||
|  |             if cmd is not None: | ||
|  |                 cmd(pageName) | ||
|  |         cmd = self['raisecommand'] | ||
|  |         if cmd is not None: | ||
|  |             cmd(pageName) | ||
|  | 
 | ||
|  |     # This is the vertical layout of the notebook, from top (assuming | ||
|  |     # tabpos is 'n'): | ||
|  |     #     hull highlightthickness (top) | ||
|  |     #     hull borderwidth (top) | ||
|  |     #     borderwidth (top border of tabs) | ||
|  |     #     borderwidth * 0.5 (space for bevel) | ||
|  |     #     tab button (maximum of requested height of all tab buttons) | ||
|  |     #     borderwidth (border between tabs and page) | ||
|  |     #     pagemargin (top) | ||
|  |     #     the page itself | ||
|  |     #     pagemargin (bottom) | ||
|  |     #     borderwidth (border below page) | ||
|  |     #     hull borderwidth (bottom) | ||
|  |     #     hull highlightthickness (bottom) | ||
|  |     # | ||
|  |     # canvasBorder is sum of top two elements. | ||
|  |     # tabBottom is sum of top five elements. | ||
|  |     # | ||
|  |     # Horizontal layout (and also vertical layout when tabpos is None): | ||
|  |     #     hull highlightthickness | ||
|  |     #     hull borderwidth | ||
|  |     #     borderwidth | ||
|  |     #     pagemargin | ||
|  |     #     the page itself | ||
|  |     #     pagemargin | ||
|  |     #     borderwidth | ||
|  |     #     hull borderwidth | ||
|  |     #     hull highlightthickness | ||
|  |     # | ||
|  |     def _layout(self): | ||
|  |         if not self.winfo_ismapped() or self._canvasSize is None: | ||
|  |             # Don't layout if the window is not displayed, or we | ||
|  |             # haven't yet received a <Configure> event. | ||
|  |             return | ||
|  | 
 | ||
|  |         hullWidth, hullHeight = self._canvasSize | ||
|  |         borderWidth = self._borderWidth | ||
|  |         canvasBorder = string.atoi(self._hull['borderwidth']) + \ | ||
|  |             string.atoi(self._hull['highlightthickness']) | ||
|  |         if not self._withTabs: | ||
|  |             self.tabBottom = canvasBorder | ||
|  |         oldTabBottom = self.tabBottom | ||
|  | 
 | ||
|  |         if self._pending.has_key('borderColor'): | ||
|  |             self._lightBorderColor, self._darkBorderColor = \ | ||
|  |                     Color.bordercolors(self, self['hull_background']) | ||
|  | 
 | ||
|  |         # Draw all the tabs. | ||
|  |         if self._withTabs and (self._pending.has_key('tabs') or | ||
|  |                 self._pending.has_key('size')): | ||
|  |             # Find total requested width and maximum requested height | ||
|  |             # of tabs. | ||
|  |             sumTabReqWidth = 0 | ||
|  |             maxTabHeight = 0 | ||
|  |             for pageInfo in self._pageAttrs.values(): | ||
|  |                 sumTabReqWidth = sumTabReqWidth + pageInfo['tabreqwidth'] | ||
|  |                 if maxTabHeight < pageInfo['tabreqheight']: | ||
|  |                     maxTabHeight = pageInfo['tabreqheight'] | ||
|  |             if maxTabHeight != 0: | ||
|  |                 # Add the top tab border plus a bit for the angled corners | ||
|  |                 self.tabBottom = canvasBorder + maxTabHeight + borderWidth * 1.5 | ||
|  | 
 | ||
|  |             # Prepare for drawing the border around each tab button. | ||
|  |             tabTop = canvasBorder | ||
|  |             tabTop2 = tabTop + borderWidth | ||
|  |             tabTop3 = tabTop + borderWidth * 1.5 | ||
|  |             tabBottom2 = self.tabBottom | ||
|  |             tabBottom = self.tabBottom + borderWidth | ||
|  | 
 | ||
|  |             numTabs = len(self._pageNames) | ||
|  |             availableWidth = hullWidth - 2 * canvasBorder - \ | ||
|  |                 numTabs * 2 * borderWidth | ||
|  |             x = canvasBorder | ||
|  |             cumTabReqWidth = 0 | ||
|  |             cumTabWidth = 0 | ||
|  | 
 | ||
|  |             # Position all the tabs. | ||
|  |             for pageName in self._pageNames: | ||
|  |                 pageInfo = self._pageAttrs[pageName] | ||
|  |                 (windowitem, lightshadow, darkshadow) = pageInfo['tabitems'] | ||
|  |                 if sumTabReqWidth <= availableWidth: | ||
|  |                     tabwidth = pageInfo['tabreqwidth'] | ||
|  |                 else: | ||
|  |                     # This ugly calculation ensures that, when the | ||
|  |                     # notebook is not wide enough for the requested | ||
|  |                     # widths of the tabs, the total width given to | ||
|  |                     # the tabs exactly equals the available width, | ||
|  |                     # without rounding errors. | ||
|  |                     cumTabReqWidth = cumTabReqWidth + pageInfo['tabreqwidth'] | ||
|  |                     tmp = (2*cumTabReqWidth*availableWidth + sumTabReqWidth) \ | ||
|  |                             / (2 * sumTabReqWidth) | ||
|  |                     tabwidth = tmp - cumTabWidth | ||
|  |                     cumTabWidth = tmp | ||
|  | 
 | ||
|  |                 # Position the tab's button canvas item. | ||
|  |                 self.coords(windowitem, x + borderWidth, tabTop3) | ||
|  |                 self.itemconfigure(windowitem, | ||
|  |                     width = tabwidth, height = maxTabHeight) | ||
|  | 
 | ||
|  |                 # Make a beautiful border around the tab. | ||
|  |                 left = x | ||
|  |                 left2 = left + borderWidth | ||
|  |                 left3 = left + borderWidth * 1.5 | ||
|  |                 right = left + tabwidth + 2 * borderWidth | ||
|  |                 right2 = left + tabwidth + borderWidth | ||
|  |                 right3 = left + tabwidth + borderWidth * 0.5 | ||
|  | 
 | ||
|  |                 self.coords(lightshadow,  | ||
|  |                     left, tabBottom2, left, tabTop2, left2, tabTop, | ||
|  |                     right2, tabTop, right3, tabTop2, left3, tabTop2, | ||
|  |                     left2, tabTop3, left2, tabBottom, | ||
|  |                     ) | ||
|  |                 self.coords(darkshadow,  | ||
|  |                     right2, tabTop, right, tabTop2, right, tabBottom2, | ||
|  |                     right2, tabBottom, right2, tabTop3, right3, tabTop2, | ||
|  |                     ) | ||
|  |                 pageInfo['left'] = left | ||
|  |                 pageInfo['right'] = right | ||
|  | 
 | ||
|  |                 x = x + tabwidth + 2 * borderWidth | ||
|  | 
 | ||
|  |         # Redraw shadow under tabs so that it appears that tab for old | ||
|  |         # top page is lowered and that tab for new top page is raised. | ||
|  |         if self._withTabs and (self._pending.has_key('topPage') or | ||
|  |                 self._pending.has_key('tabs') or self._pending.has_key('size')): | ||
|  | 
 | ||
|  |             if self.getcurselection() is None: | ||
|  |                 # No pages, so draw line across top of page area. | ||
|  |                 self.coords(self._pageTop1Border, | ||
|  |                     canvasBorder, self.tabBottom, | ||
|  |                     hullWidth - canvasBorder, self.tabBottom, | ||
|  |                     hullWidth - canvasBorder - borderWidth, | ||
|  |                         self.tabBottom + borderWidth, | ||
|  |                     borderWidth + canvasBorder, self.tabBottom + borderWidth, | ||
|  |                     ) | ||
|  | 
 | ||
|  |                 # Ignore second top border. | ||
|  |                 self.coords(self._pageTop2Border, 0, 0, 0, 0, 0, 0) | ||
|  |             else: | ||
|  |                 # Draw two lines, one on each side of the tab for the | ||
|  |                 # top page, so that the tab appears to be raised. | ||
|  |                 pageInfo = self._pageAttrs[self.getcurselection()] | ||
|  |                 left = pageInfo['left'] | ||
|  |                 right = pageInfo['right'] | ||
|  |                 self.coords(self._pageTop1Border, | ||
|  |                     canvasBorder, self.tabBottom, | ||
|  |                     left, self.tabBottom, | ||
|  |                     left + borderWidth, self.tabBottom + borderWidth, | ||
|  |                     canvasBorder + borderWidth, self.tabBottom + borderWidth, | ||
|  |                     ) | ||
|  | 
 | ||
|  |                 self.coords(self._pageTop2Border, | ||
|  |                     right, self.tabBottom, | ||
|  |                     hullWidth - canvasBorder, self.tabBottom, | ||
|  |                     hullWidth - canvasBorder - borderWidth, | ||
|  |                         self.tabBottom + borderWidth, | ||
|  |                     right - borderWidth, self.tabBottom + borderWidth, | ||
|  |                     ) | ||
|  | 
 | ||
|  |             # Prevent bottom of dark border of tabs appearing over | ||
|  |             # page top border. | ||
|  |             self.tag_raise(self._pageTop1Border) | ||
|  |             self.tag_raise(self._pageTop2Border) | ||
|  | 
 | ||
|  |         # Position the page border shadows. | ||
|  |         if self._pending.has_key('size') or oldTabBottom != self.tabBottom: | ||
|  | 
 | ||
|  |             self.coords(self._pageLeftBorder, | ||
|  |                 canvasBorder, self.tabBottom, | ||
|  |                 borderWidth + canvasBorder, | ||
|  |                     self.tabBottom + borderWidth, | ||
|  |                 borderWidth + canvasBorder, | ||
|  |                     hullHeight - canvasBorder - borderWidth, | ||
|  |                 canvasBorder, hullHeight - canvasBorder, | ||
|  |                 ) | ||
|  | 
 | ||
|  |             self.coords(self._pageBottomRightBorder, | ||
|  |                 hullWidth - canvasBorder, self.tabBottom, | ||
|  |                 hullWidth - canvasBorder, hullHeight - canvasBorder, | ||
|  |                 canvasBorder, hullHeight - canvasBorder, | ||
|  |                 borderWidth + canvasBorder, | ||
|  |                     hullHeight - canvasBorder - borderWidth, | ||
|  |                 hullWidth - canvasBorder - borderWidth, | ||
|  |                     hullHeight - canvasBorder - borderWidth, | ||
|  |                 hullWidth - canvasBorder - borderWidth, | ||
|  |                     self.tabBottom + borderWidth, | ||
|  |                 ) | ||
|  | 
 | ||
|  |             if not self._withTabs: | ||
|  |                 self.coords(self._pageTopBorder, | ||
|  |                     canvasBorder, self.tabBottom, | ||
|  |                     hullWidth - canvasBorder, self.tabBottom, | ||
|  |                     hullWidth - canvasBorder - borderWidth, | ||
|  |                         self.tabBottom + borderWidth, | ||
|  |                     borderWidth + canvasBorder, self.tabBottom + borderWidth, | ||
|  |                     ) | ||
|  | 
 | ||
|  |         # Color borders. | ||
|  |         if self._pending.has_key('borderColor'): | ||
|  |             self.itemconfigure('lighttag', fill = self._lightBorderColor) | ||
|  |             self.itemconfigure('darktag', fill = self._darkBorderColor) | ||
|  | 
 | ||
|  |         newTopPage = self._pending.get('topPage') | ||
|  |         pageBorder = borderWidth + self._pageMargin | ||
|  | 
 | ||
|  |         # Raise new top page. | ||
|  |         if newTopPage is not None: | ||
|  |             self._topPageName = newTopPage | ||
|  |             self._topPageItem = self.create_window( | ||
|  |                 pageBorder + canvasBorder, self.tabBottom + pageBorder, | ||
|  |                 window = self._pageAttrs[newTopPage]['page'], | ||
|  |                 anchor = 'nw', | ||
|  |                 ) | ||
|  | 
 | ||
|  |         # Change position of top page if tab height has changed. | ||
|  |         if self._topPageName is not None and oldTabBottom != self.tabBottom: | ||
|  |             self.coords(self._topPageItem, | ||
|  |                     pageBorder + canvasBorder, self.tabBottom + pageBorder) | ||
|  | 
 | ||
|  |         # Change size of top page if, | ||
|  |         #   1) there is a new top page. | ||
|  |         #   2) canvas size has changed, but not if there is no top | ||
|  |         #      page (eg:  initially or when all pages deleted). | ||
|  |         #   3) tab height has changed, due to difference in the height of a tab | ||
|  |         if (newTopPage is not None or \ | ||
|  |                 self._pending.has_key('size') and self._topPageName is not None | ||
|  |                 or oldTabBottom != self.tabBottom): | ||
|  |             self.itemconfigure(self._topPageItem, | ||
|  |                 width = hullWidth - 2 * canvasBorder - pageBorder * 2, | ||
|  |                 height = hullHeight - 2 * canvasBorder - pageBorder * 2 - | ||
|  |                     (self.tabBottom - canvasBorder), | ||
|  |                 ) | ||
|  | 
 | ||
|  |         self._pending = {} | ||
|  | 
 | ||
|  | # Need to do forwarding to get the pack, grid, etc methods.  | ||
|  | # Unfortunately this means that all the other canvas methods are also | ||
|  | # forwarded. | ||
|  | forwardmethods(NoteBook, Tkinter.Canvas, '_hull') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwOptionMenu.py | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class OptionMenu(MegaWidget): | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('command',        None,       None), | ||
|  |             ('items',          (),         INITOPT), | ||
|  |             ('initialitem',    None,       INITOPT), | ||
|  | 	    ('labelmargin',    0,          INITOPT), | ||
|  | 	    ('labelpos',       None,       INITOPT), | ||
|  | 	    ('sticky',         'ew',       INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 
 | ||
|  | 	self._menubutton = self.createcomponent('menubutton', | ||
|  | 		(), None, | ||
|  | 		Tkinter.Menubutton, (interior,), | ||
|  | 		borderwidth = 2, | ||
|  | 		indicatoron = 1, | ||
|  | 		relief = 'raised', | ||
|  | 		anchor = 'c', | ||
|  | 		highlightthickness = 2, | ||
|  | 		direction = 'flush', | ||
|  |                 takefocus = 1, | ||
|  | 	) | ||
|  | 	self._menubutton.grid(column = 2, row = 2, sticky = self['sticky']) | ||
|  | 
 | ||
|  | 	self._menu = self.createcomponent('menu', | ||
|  | 		(), None, | ||
|  | 		Tkinter.Menu, (self._menubutton,), | ||
|  | 		tearoff=0 | ||
|  | 	) | ||
|  | 	self._menubutton.configure(menu = self._menu) | ||
|  | 
 | ||
|  | 	interior.grid_columnconfigure(2, weight = 1) | ||
|  | 	interior.grid_rowconfigure(2, weight = 1) | ||
|  | 
 | ||
|  |         # Create the label. | ||
|  |         self.createlabel(interior) | ||
|  | 
 | ||
|  |         # Add the items specified by the initialisation option. | ||
|  | 	self._itemList = [] | ||
|  |         self.setitems(self['items'], self['initialitem']) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def setitems(self, items, index = None): | ||
|  | 
 | ||
|  |         # Clean up old items and callback commands. | ||
|  |         for oldIndex in range(len(self._itemList)): | ||
|  |             tclCommandName = str(self._menu.entrycget(oldIndex, 'command')) | ||
|  |             if tclCommandName != '':    | ||
|  |                 self._menu.deletecommand(tclCommandName) | ||
|  |         self._menu.delete(0, 'end') | ||
|  | 	self._itemList = list(items) | ||
|  | 
 | ||
|  | 	# Set the items in the menu component. | ||
|  |         for item in items: | ||
|  |             self._menu.add_command(label = item, | ||
|  | 		command = lambda self = self, item = item: self._invoke(item)) | ||
|  | 
 | ||
|  | 	# Set the currently selected value. | ||
|  | 	if index is None: | ||
|  |             var = str(self._menubutton.cget('textvariable')) | ||
|  | 	    if var != '': | ||
|  | 		# None means do not change text variable. | ||
|  | 		return | ||
|  | 	    if len(items) == 0: | ||
|  | 		text = '' | ||
|  | 	    elif str(self._menubutton.cget('text')) in items: | ||
|  |                 # Do not change selection if it is still valid | ||
|  | 		return | ||
|  | 	    else: | ||
|  | 		text = items[0] | ||
|  | 	else: | ||
|  | 	    index = self.index(index) | ||
|  | 	    text = self._itemList[index] | ||
|  | 
 | ||
|  |         self.setvalue(text) | ||
|  | 
 | ||
|  |     def getcurselection(self): | ||
|  | 	var = str(self._menubutton.cget('textvariable')) | ||
|  | 	if var == '': | ||
|  | 	    return str(self._menubutton.cget('text')) | ||
|  | 	else: | ||
|  | 	    return self._menu.tk.globalgetvar(var) | ||
|  | 
 | ||
|  |     def getvalue(self): | ||
|  |         return self.getcurselection() | ||
|  | 
 | ||
|  |     def setvalue(self, text): | ||
|  | 	var = str(self._menubutton.cget('textvariable')) | ||
|  | 	if var == '': | ||
|  | 	    self._menubutton.configure(text = text) | ||
|  | 	else: | ||
|  | 	    self._menu.tk.globalsetvar(var, text) | ||
|  | 
 | ||
|  |     def index(self, index): | ||
|  | 	listLength = len(self._itemList) | ||
|  | 	if type(index) == types.IntType: | ||
|  | 	    if index < listLength: | ||
|  | 		return index | ||
|  | 	    else: | ||
|  | 		raise ValueError, 'index "%s" is out of range' % index | ||
|  | 	elif index is END: | ||
|  | 	    if listLength > 0: | ||
|  | 		return listLength - 1 | ||
|  | 	    else: | ||
|  | 		raise ValueError, 'OptionMenu has no items' | ||
|  | 	else: | ||
|  | 	    if index is SELECT: | ||
|  | 		if listLength > 0: | ||
|  | 		    index = self.getcurselection() | ||
|  | 		else: | ||
|  | 		    raise ValueError, 'OptionMenu has no items' | ||
|  |             if index in self._itemList: | ||
|  |                 return self._itemList.index(index) | ||
|  | 	    raise ValueError, \ | ||
|  | 		    'bad index "%s": must be a ' \ | ||
|  |                     'name, a number, END or SELECT' % (index,) | ||
|  | 
 | ||
|  |     def invoke(self, index = SELECT): | ||
|  | 	index = self.index(index) | ||
|  | 	text = self._itemList[index] | ||
|  | 
 | ||
|  |         return self._invoke(text) | ||
|  | 
 | ||
|  |     def _invoke(self, text): | ||
|  |         self.setvalue(text) | ||
|  | 
 | ||
|  | 	command = self['command'] | ||
|  | 	if callable(command): | ||
|  | 	    return command(text) | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwPanedWidget.py | ||
|  | # PanedWidget | ||
|  | # a frame which may contain several resizable sub-frames | ||
|  | 
 | ||
|  | import string | ||
|  | import sys | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class PanedWidget(MegaWidget): | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  |             ('command',            None,         None), | ||
|  |             ('orient',             'vertical',   INITOPT), | ||
|  |             ('separatorrelief',    'sunken',     INITOPT), | ||
|  |             ('separatorthickness', 2,            INITOPT), | ||
|  |             ('handlesize',         8,            INITOPT), | ||
|  |             ('hull_width',         400,          None), | ||
|  |             ('hull_height',        400,          None), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs, | ||
|  |                 dynamicGroups = ('Frame', 'Separator', 'Handle')) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	self.bind('<Configure>', self._handleConfigure) | ||
|  | 
 | ||
|  | 	if self['orient'] not in ('horizontal', 'vertical'): | ||
|  | 	    raise ValueError, 'bad orient option ' + repr(self['orient']) + \ | ||
|  | 		': must be either \'horizontal\' or \'vertical\'' | ||
|  | 
 | ||
|  |         self._separatorThickness = self['separatorthickness'] | ||
|  |         self._handleSize = self['handlesize'] | ||
|  | 	self._paneNames = []            # List of pane names | ||
|  | 	self._paneAttrs = {}            # Map from pane name to pane info | ||
|  | 
 | ||
|  | 	self._timerId = None | ||
|  | 	self._frame = {} | ||
|  | 	self._separator = [] | ||
|  | 	self._button = [] | ||
|  | 	self._totalSize = 0 | ||
|  | 	self._movePending = 0 | ||
|  | 	self._relsize = {} | ||
|  | 	self._relmin = {} | ||
|  | 	self._relmax = {} | ||
|  | 	self._size = {} | ||
|  | 	self._min = {} | ||
|  | 	self._max = {} | ||
|  | 	self._rootp = None | ||
|  | 	self._curSize = None | ||
|  | 	self._beforeLimit = None | ||
|  | 	self._afterLimit = None | ||
|  | 	self._buttonIsDown = 0 | ||
|  | 	self._majorSize = 100 | ||
|  | 	self._minorSize = 100 | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def insert(self, name, before = 0, **kw): | ||
|  | 	# Parse <kw> for options. | ||
|  |         self._initPaneOptions(name) | ||
|  | 	self._parsePaneOptions(name, kw) | ||
|  | 
 | ||
|  | 	insertPos = self._nameToIndex(before) | ||
|  | 	atEnd = (insertPos == len(self._paneNames)) | ||
|  | 
 | ||
|  | 	# Add the frame. | ||
|  | 	self._paneNames[insertPos:insertPos] = [name] | ||
|  | 	self._frame[name] = self.createcomponent(name, | ||
|  | 		(), 'Frame', | ||
|  | 		Tkinter.Frame, (self.interior(),)) | ||
|  | 
 | ||
|  | 	# Add separator, if necessary. | ||
|  | 	if len(self._paneNames) > 1: | ||
|  | 	    self._addSeparator() | ||
|  | 	else: | ||
|  | 	    self._separator.append(None) | ||
|  | 	    self._button.append(None) | ||
|  | 
 | ||
|  | 	# Add the new frame and adjust the PanedWidget | ||
|  | 	if atEnd: | ||
|  | 	    size = self._size[name] | ||
|  | 	    if size > 0 or self._relsize[name] is not None: | ||
|  | 		if self['orient'] == 'vertical': | ||
|  | 		    self._frame[name].place(x=0, relwidth=1, | ||
|  | 					    height=size, y=self._totalSize) | ||
|  | 		else: | ||
|  | 		    self._frame[name].place(y=0, relheight=1, | ||
|  | 					    width=size, x=self._totalSize) | ||
|  | 	    else: | ||
|  | 		if self['orient'] == 'vertical': | ||
|  | 		    self._frame[name].place(x=0, relwidth=1, | ||
|  | 					    y=self._totalSize) | ||
|  | 		else: | ||
|  | 		    self._frame[name].place(y=0, relheight=1, | ||
|  | 					    x=self._totalSize) | ||
|  | 	else: | ||
|  | 	    self._updateSizes() | ||
|  | 
 | ||
|  | 	self._totalSize = self._totalSize + self._size[name] | ||
|  | 	return self._frame[name] | ||
|  | 
 | ||
|  |     def add(self, name, **kw): | ||
|  |         return apply(self.insert, (name, len(self._paneNames)), kw) | ||
|  | 
 | ||
|  |     def delete(self, name): | ||
|  | 	deletePos = self._nameToIndex(name) | ||
|  | 	name = self._paneNames[deletePos] | ||
|  | 	self.destroycomponent(name) | ||
|  | 	del self._paneNames[deletePos] | ||
|  | 	del self._frame[name] | ||
|  | 	del self._size[name] | ||
|  | 	del self._min[name] | ||
|  | 	del self._max[name] | ||
|  | 	del self._relsize[name] | ||
|  | 	del self._relmin[name] | ||
|  | 	del self._relmax[name] | ||
|  | 
 | ||
|  | 	last = len(self._paneNames) | ||
|  | 	del self._separator[last] | ||
|  | 	del self._button[last] | ||
|  |         if last > 0: | ||
|  |             self.destroycomponent(self._sepName(last)) | ||
|  |             self.destroycomponent(self._buttonName(last)) | ||
|  | 
 | ||
|  | 	self._plotHandles() | ||
|  | 
 | ||
|  |     def setnaturalsize(self): | ||
|  |         self.update_idletasks() | ||
|  |         totalWidth = 0 | ||
|  |         totalHeight = 0 | ||
|  |         maxWidth = 0 | ||
|  |         maxHeight = 0 | ||
|  | 	for name in self._paneNames: | ||
|  |             frame = self._frame[name] | ||
|  |             w = frame.winfo_reqwidth() | ||
|  |             h = frame.winfo_reqheight() | ||
|  |             totalWidth = totalWidth + w | ||
|  |             totalHeight = totalHeight + h | ||
|  |             if maxWidth < w: | ||
|  |                 maxWidth = w | ||
|  |             if maxHeight < h: | ||
|  |                 maxHeight = h | ||
|  | 
 | ||
|  |         # Note that, since the hull is a frame, the width and height | ||
|  |         # options specify the geometry *outside* the borderwidth and | ||
|  |         # highlightthickness. | ||
|  |         bw = string.atoi(str(self.cget('hull_borderwidth'))) | ||
|  |         hl = string.atoi(str(self.cget('hull_highlightthickness'))) | ||
|  |         extra = (bw + hl) * 2 | ||
|  |         if str(self.cget('orient')) == 'horizontal': | ||
|  |             totalWidth = totalWidth + extra | ||
|  |             maxHeight = maxHeight + extra | ||
|  |             self.configure(hull_width = totalWidth, hull_height = maxHeight) | ||
|  |         else: | ||
|  |             totalHeight = (totalHeight + extra + | ||
|  |                     (len(self._paneNames) - 1) * self._separatorThickness) | ||
|  |             maxWidth = maxWidth + extra | ||
|  |             self.configure(hull_width = maxWidth, hull_height = totalHeight) | ||
|  | 
 | ||
|  |     def move(self, name, newPos, newPosOffset = 0): | ||
|  | 
 | ||
|  |         # see if we can spare ourselves some work | ||
|  |         numPanes = len(self._paneNames) | ||
|  |         if numPanes < 2: | ||
|  |             return | ||
|  | 
 | ||
|  |         newPos = self._nameToIndex(newPos) + newPosOffset | ||
|  |         if newPos < 0 or newPos >=numPanes: | ||
|  |             return | ||
|  | 
 | ||
|  |         deletePos = self._nameToIndex(name) | ||
|  | 
 | ||
|  |         if deletePos == newPos: | ||
|  |             # inserting over ourself is a no-op | ||
|  |             return | ||
|  | 
 | ||
|  |         # delete name from old position in list | ||
|  |         name = self._paneNames[deletePos] | ||
|  |         del self._paneNames[deletePos] | ||
|  | 
 | ||
|  |         # place in new position | ||
|  |         self._paneNames[newPos:newPos] = [name] | ||
|  | 
 | ||
|  |         # force everything to redraw | ||
|  |         self._plotHandles() | ||
|  |         self._updateSizes() | ||
|  | 
 | ||
|  |     def _nameToIndex(self, nameOrIndex): | ||
|  | 	try: | ||
|  | 	    pos = self._paneNames.index(nameOrIndex) | ||
|  | 	except ValueError: | ||
|  | 	    pos = nameOrIndex | ||
|  | 
 | ||
|  | 	return pos | ||
|  | 
 | ||
|  |     def _initPaneOptions(self, name): | ||
|  | 	# Set defaults. | ||
|  | 	self._size[name] = 0 | ||
|  | 	self._relsize[name] = None | ||
|  | 	self._min[name] = 0 | ||
|  | 	self._relmin[name] = None | ||
|  | 	self._max[name] = 100000 | ||
|  | 	self._relmax[name] = None | ||
|  | 
 | ||
|  |     def _parsePaneOptions(self, name, args): | ||
|  | 	# Parse <args> for options. | ||
|  | 	for arg, value in args.items(): | ||
|  | 	    if type(value) == types.FloatType: | ||
|  | 		relvalue = value | ||
|  | 		value = self._absSize(relvalue) | ||
|  | 	    else: | ||
|  | 		relvalue = None | ||
|  | 
 | ||
|  | 	    if arg == 'size': | ||
|  | 		self._size[name], self._relsize[name] = value, relvalue | ||
|  | 	    elif arg == 'min': | ||
|  | 		self._min[name], self._relmin[name] = value, relvalue | ||
|  | 	    elif arg == 'max': | ||
|  | 		self._max[name], self._relmax[name] = value, relvalue | ||
|  | 	    else: | ||
|  | 		raise ValueError, 'keyword must be "size", "min", or "max"' | ||
|  | 
 | ||
|  |     def _absSize(self, relvalue): | ||
|  | 	return int(round(relvalue * self._majorSize)) | ||
|  | 
 | ||
|  |     def _sepName(self, n): | ||
|  | 	return 'separator-%d' % n | ||
|  | 
 | ||
|  |     def _buttonName(self, n): | ||
|  | 	return 'handle-%d' % n | ||
|  | 
 | ||
|  |     def _addSeparator(self): | ||
|  | 	n = len(self._paneNames) - 1 | ||
|  | 
 | ||
|  | 	downFunc = lambda event, s = self, num=n: s._btnDown(event, num) | ||
|  | 	upFunc = lambda event, s = self, num=n: s._btnUp(event, num) | ||
|  | 	moveFunc = lambda event, s = self, num=n: s._btnMove(event, num) | ||
|  | 
 | ||
|  | 	# Create the line dividing the panes. | ||
|  | 	sep = self.createcomponent(self._sepName(n), | ||
|  | 		(), 'Separator', | ||
|  | 		Tkinter.Frame, (self.interior(),), | ||
|  | 		borderwidth = 1, | ||
|  | 		relief = self['separatorrelief']) | ||
|  | 	self._separator.append(sep) | ||
|  | 
 | ||
|  | 	sep.bind('<ButtonPress-1>', downFunc) | ||
|  | 	sep.bind('<Any-ButtonRelease-1>', upFunc) | ||
|  | 	sep.bind('<B1-Motion>', moveFunc) | ||
|  | 
 | ||
|  | 	if self['orient'] == 'vertical': | ||
|  | 	    cursor = 'sb_v_double_arrow' | ||
|  | 	    sep.configure(height = self._separatorThickness, | ||
|  |                     width = 10000, cursor = cursor) | ||
|  | 	else: | ||
|  | 	    cursor = 'sb_h_double_arrow' | ||
|  | 	    sep.configure(width = self._separatorThickness, | ||
|  |                     height = 10000, cursor = cursor) | ||
|  | 
 | ||
|  | 	self._totalSize = self._totalSize + self._separatorThickness | ||
|  | 
 | ||
|  | 	# Create the handle on the dividing line. | ||
|  | 	handle = self.createcomponent(self._buttonName(n), | ||
|  | 		(), 'Handle', | ||
|  | 		Tkinter.Frame, (self.interior(),), | ||
|  | 		    relief = 'raised', | ||
|  | 		    borderwidth = 1, | ||
|  | 		    width = self._handleSize, | ||
|  | 		    height = self._handleSize, | ||
|  | 		    cursor = cursor, | ||
|  | 		) | ||
|  | 	self._button.append(handle) | ||
|  | 
 | ||
|  | 	handle.bind('<ButtonPress-1>', downFunc) | ||
|  | 	handle.bind('<Any-ButtonRelease-1>', upFunc) | ||
|  | 	handle.bind('<B1-Motion>', moveFunc) | ||
|  | 
 | ||
|  | 	self._plotHandles() | ||
|  | 
 | ||
|  | 	for i in range(1, len(self._paneNames)): | ||
|  | 	    self._separator[i].tkraise() | ||
|  | 	for i in range(1, len(self._paneNames)): | ||
|  | 	    self._button[i].tkraise() | ||
|  | 
 | ||
|  |     def _btnUp(self, event, item): | ||
|  | 	self._buttonIsDown = 0 | ||
|  | 	self._updateSizes() | ||
|  | 	try: | ||
|  | 	    self._button[item].configure(relief='raised') | ||
|  | 	except: | ||
|  | 	    pass | ||
|  | 
 | ||
|  |     def _btnDown(self, event, item): | ||
|  | 	self._button[item].configure(relief='sunken') | ||
|  | 	self._getMotionLimit(item) | ||
|  | 	self._buttonIsDown = 1 | ||
|  | 	self._movePending = 0 | ||
|  | 
 | ||
|  |     def _handleConfigure(self, event = None): | ||
|  | 	self._getNaturalSizes() | ||
|  | 	if self._totalSize == 0: | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	iterRange = list(self._paneNames) | ||
|  | 	iterRange.reverse() | ||
|  | 	if self._majorSize > self._totalSize: | ||
|  | 	    n = self._majorSize - self._totalSize | ||
|  | 	    self._iterate(iterRange, self._grow, n) | ||
|  | 	elif self._majorSize < self._totalSize: | ||
|  | 	    n = self._totalSize - self._majorSize | ||
|  | 	    self._iterate(iterRange, self._shrink, n) | ||
|  | 
 | ||
|  | 	self._plotHandles() | ||
|  | 	self._updateSizes() | ||
|  | 
 | ||
|  |     def _getNaturalSizes(self): | ||
|  | 	# Must call this in order to get correct winfo_width, winfo_height | ||
|  | 	self.update_idletasks() | ||
|  | 
 | ||
|  | 	self._totalSize = 0 | ||
|  | 
 | ||
|  | 	if self['orient'] == 'vertical': | ||
|  | 	    self._majorSize = self.winfo_height() | ||
|  | 	    self._minorSize = self.winfo_width() | ||
|  | 	    majorspec = Tkinter.Frame.winfo_reqheight | ||
|  | 	else: | ||
|  | 	    self._majorSize = self.winfo_width() | ||
|  | 	    self._minorSize = self.winfo_height() | ||
|  | 	    majorspec = Tkinter.Frame.winfo_reqwidth | ||
|  | 
 | ||
|  |         bw = string.atoi(str(self.cget('hull_borderwidth'))) | ||
|  |         hl = string.atoi(str(self.cget('hull_highlightthickness'))) | ||
|  |         extra = (bw + hl) * 2 | ||
|  |         self._majorSize = self._majorSize - extra | ||
|  |         self._minorSize = self._minorSize - extra | ||
|  | 
 | ||
|  | 	if self._majorSize < 0: | ||
|  | 	    self._majorSize = 0 | ||
|  | 	if self._minorSize < 0: | ||
|  | 	    self._minorSize = 0 | ||
|  | 
 | ||
|  | 	for name in self._paneNames: | ||
|  | 	    # adjust the absolute sizes first... | ||
|  | 	    if self._relsize[name] is None: | ||
|  | 		#special case | ||
|  | 		if self._size[name] == 0: | ||
|  | 		    self._size[name] = apply(majorspec, (self._frame[name],)) | ||
|  | 		    self._setrel(name) | ||
|  | 	    else: | ||
|  | 		self._size[name] = self._absSize(self._relsize[name]) | ||
|  | 
 | ||
|  | 	    if self._relmin[name] is not None: | ||
|  | 		self._min[name] = self._absSize(self._relmin[name]) | ||
|  | 	    if self._relmax[name] is not None: | ||
|  | 		self._max[name] = self._absSize(self._relmax[name]) | ||
|  | 
 | ||
|  | 	    # now adjust sizes | ||
|  | 	    if self._size[name] < self._min[name]: | ||
|  | 		self._size[name] = self._min[name] | ||
|  | 		self._setrel(name) | ||
|  | 
 | ||
|  | 	    if self._size[name] > self._max[name]: | ||
|  | 		self._size[name] = self._max[name] | ||
|  | 		self._setrel(name) | ||
|  | 
 | ||
|  | 	    self._totalSize = self._totalSize + self._size[name] | ||
|  | 
 | ||
|  | 	# adjust for separators | ||
|  | 	self._totalSize = (self._totalSize + | ||
|  |                 (len(self._paneNames) - 1) * self._separatorThickness) | ||
|  | 
 | ||
|  |     def _setrel(self, name): | ||
|  | 	if self._relsize[name] is not None: | ||
|  | 	    if self._majorSize != 0: | ||
|  | 		self._relsize[name] = round(self._size[name]) / self._majorSize | ||
|  | 
 | ||
|  |     def _iterate(self, names, proc, n): | ||
|  | 	for i in names: | ||
|  | 	    n = apply(proc, (i, n)) | ||
|  | 	    if n == 0: | ||
|  | 		break | ||
|  | 
 | ||
|  |     def _grow(self, name, n): | ||
|  | 	canGrow = self._max[name] - self._size[name] | ||
|  | 
 | ||
|  | 	if canGrow > n: | ||
|  | 	    self._size[name] = self._size[name] + n | ||
|  | 	    self._setrel(name) | ||
|  | 	    return 0 | ||
|  | 	elif canGrow > 0: | ||
|  | 	    self._size[name] = self._max[name] | ||
|  | 	    self._setrel(name) | ||
|  | 	    n = n - canGrow | ||
|  | 
 | ||
|  | 	return n | ||
|  | 
 | ||
|  |     def _shrink(self, name, n): | ||
|  | 	canShrink = self._size[name] - self._min[name] | ||
|  | 
 | ||
|  | 	if canShrink > n: | ||
|  | 	    self._size[name] = self._size[name] - n | ||
|  | 	    self._setrel(name) | ||
|  | 	    return 0 | ||
|  | 	elif canShrink > 0: | ||
|  | 	    self._size[name] = self._min[name] | ||
|  | 	    self._setrel(name) | ||
|  | 	    n = n - canShrink | ||
|  | 
 | ||
|  | 	return n | ||
|  | 
 | ||
|  |     def _updateSizes(self): | ||
|  | 	totalSize = 0 | ||
|  | 
 | ||
|  | 	for name in self._paneNames: | ||
|  | 	    size = self._size[name] | ||
|  | 	    if self['orient'] == 'vertical': | ||
|  | 		self._frame[name].place(x = 0, relwidth = 1, | ||
|  | 					y = totalSize, | ||
|  | 					height = size) | ||
|  | 	    else: | ||
|  | 		self._frame[name].place(y = 0, relheight = 1, | ||
|  | 					x = totalSize, | ||
|  | 					width = size) | ||
|  | 
 | ||
|  | 	    totalSize = totalSize + size + self._separatorThickness | ||
|  | 
 | ||
|  | 	# Invoke the callback command | ||
|  | 	cmd = self['command'] | ||
|  | 	if callable(cmd): | ||
|  | 	    cmd(map(lambda x, s = self: s._size[x], self._paneNames)) | ||
|  | 
 | ||
|  |     def _plotHandles(self): | ||
|  | 	if len(self._paneNames) == 0: | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	if self['orient'] == 'vertical': | ||
|  | 	    btnp = self._minorSize - 13 | ||
|  | 	else: | ||
|  | 	    h = self._minorSize | ||
|  | 
 | ||
|  | 	    if h > 18: | ||
|  | 		btnp = 9 | ||
|  | 	    else: | ||
|  | 		btnp = h - 9 | ||
|  | 
 | ||
|  | 	firstPane = self._paneNames[0] | ||
|  | 	totalSize = self._size[firstPane] | ||
|  | 
 | ||
|  | 	first = 1 | ||
|  | 	last = len(self._paneNames) - 1 | ||
|  | 
 | ||
|  | 	# loop from first to last, inclusive | ||
|  | 	for i in range(1, last + 1): | ||
|  | 
 | ||
|  | 	    handlepos = totalSize - 3 | ||
|  | 	    prevSize = self._size[self._paneNames[i - 1]] | ||
|  | 	    nextSize = self._size[self._paneNames[i]] | ||
|  | 
 | ||
|  | 	    offset1 = 0 | ||
|  | 
 | ||
|  | 	    if i == first: | ||
|  | 		if prevSize < 4: | ||
|  | 		    offset1 = 4 - prevSize | ||
|  | 	    else: | ||
|  | 		if prevSize < 8: | ||
|  | 		    offset1 = (8 - prevSize) / 2 | ||
|  | 
 | ||
|  | 	    offset2 = 0 | ||
|  | 
 | ||
|  | 	    if i == last: | ||
|  | 		if nextSize < 4: | ||
|  | 		    offset2 = nextSize - 4 | ||
|  | 	    else: | ||
|  | 		if nextSize < 8: | ||
|  | 		    offset2 = (nextSize - 8) / 2 | ||
|  | 
 | ||
|  | 	    handlepos = handlepos + offset1 | ||
|  | 
 | ||
|  | 	    if self['orient'] == 'vertical': | ||
|  | 		height = 8 - offset1 + offset2 | ||
|  | 
 | ||
|  | 		if height > 1: | ||
|  | 		    self._button[i].configure(height = height) | ||
|  | 		    self._button[i].place(x = btnp, y = handlepos) | ||
|  | 		else: | ||
|  | 		    self._button[i].place_forget() | ||
|  | 
 | ||
|  | 		self._separator[i].place(x = 0, y = totalSize, | ||
|  | 					 relwidth = 1) | ||
|  | 	    else: | ||
|  | 		width = 8 - offset1 + offset2 | ||
|  | 
 | ||
|  | 		if width > 1: | ||
|  | 		    self._button[i].configure(width = width) | ||
|  | 		    self._button[i].place(y = btnp, x = handlepos) | ||
|  | 		else: | ||
|  | 		    self._button[i].place_forget() | ||
|  | 
 | ||
|  | 		self._separator[i].place(y = 0, x = totalSize, | ||
|  | 					 relheight = 1) | ||
|  | 
 | ||
|  | 	    totalSize = totalSize + nextSize + self._separatorThickness | ||
|  | 
 | ||
|  |     def pane(self, name): | ||
|  | 	return self._frame[self._paneNames[self._nameToIndex(name)]] | ||
|  | 
 | ||
|  |     # Return the name of all panes | ||
|  |     def panes(self): | ||
|  | 	return list(self._paneNames) | ||
|  | 
 | ||
|  |     def configurepane(self, name, **kw): | ||
|  | 	name = self._paneNames[self._nameToIndex(name)] | ||
|  | 	self._parsePaneOptions(name, kw) | ||
|  | 	self._handleConfigure() | ||
|  | 
 | ||
|  |     def updatelayout(self): | ||
|  | 	self._handleConfigure() | ||
|  | 
 | ||
|  |     def _getMotionLimit(self, item): | ||
|  | 	curBefore = (item - 1) * self._separatorThickness | ||
|  | 	minBefore, maxBefore = curBefore, curBefore | ||
|  | 
 | ||
|  | 	for name in self._paneNames[:item]: | ||
|  | 	    curBefore = curBefore + self._size[name] | ||
|  | 	    minBefore = minBefore + self._min[name] | ||
|  | 	    maxBefore = maxBefore + self._max[name] | ||
|  | 
 | ||
|  | 	curAfter = (len(self._paneNames) - item) * self._separatorThickness | ||
|  | 	minAfter, maxAfter = curAfter, curAfter | ||
|  | 	for name in self._paneNames[item:]: | ||
|  | 	    curAfter = curAfter + self._size[name] | ||
|  | 	    minAfter = minAfter + self._min[name] | ||
|  | 	    maxAfter = maxAfter + self._max[name] | ||
|  | 
 | ||
|  | 	beforeToGo = min(curBefore - minBefore, maxAfter - curAfter) | ||
|  | 	afterToGo = min(curAfter - minAfter, maxBefore - curBefore) | ||
|  | 
 | ||
|  | 	self._beforeLimit = curBefore - beforeToGo | ||
|  | 	self._afterLimit = curBefore + afterToGo | ||
|  | 	self._curSize = curBefore | ||
|  | 
 | ||
|  | 	self._plotHandles() | ||
|  | 
 | ||
|  |     # Compress the motion so that update is quick even on slow machines | ||
|  |     # | ||
|  |     # theRootp = root position (either rootx or rooty) | ||
|  |     def _btnMove(self, event, item): | ||
|  | 	self._rootp = event | ||
|  | 
 | ||
|  | 	if self._movePending == 0: | ||
|  | 	    self._timerId = self.after_idle( | ||
|  | 		    lambda s = self, i = item: s._btnMoveCompressed(i)) | ||
|  | 	    self._movePending = 1 | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  |         if self._timerId is not None: | ||
|  |           self.after_cancel(self._timerId) | ||
|  | 	  self._timerId = None | ||
|  |         MegaWidget.destroy(self) | ||
|  | 
 | ||
|  |     def _btnMoveCompressed(self, item): | ||
|  | 	if not self._buttonIsDown: | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	if self['orient'] == 'vertical': | ||
|  | 	    p = self._rootp.y_root - self.winfo_rooty() | ||
|  | 	else: | ||
|  | 	    p = self._rootp.x_root - self.winfo_rootx() | ||
|  | 
 | ||
|  | 	if p == self._curSize: | ||
|  | 	    self._movePending = 0 | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	if p < self._beforeLimit: | ||
|  | 	    p = self._beforeLimit | ||
|  | 
 | ||
|  | 	if p >= self._afterLimit: | ||
|  | 	    p = self._afterLimit | ||
|  | 
 | ||
|  | 	self._calculateChange(item, p) | ||
|  | 	self.update_idletasks() | ||
|  | 	self._movePending = 0 | ||
|  | 
 | ||
|  |     # Calculate the change in response to mouse motions | ||
|  |     def _calculateChange(self, item, p): | ||
|  | 
 | ||
|  | 	if p < self._curSize: | ||
|  | 	    self._moveBefore(item, p) | ||
|  | 	elif p > self._curSize: | ||
|  | 	    self._moveAfter(item, p) | ||
|  | 
 | ||
|  | 	self._plotHandles() | ||
|  | 
 | ||
|  |     def _moveBefore(self, item, p): | ||
|  | 	n = self._curSize - p | ||
|  | 
 | ||
|  | 	# Shrink the frames before | ||
|  | 	iterRange = list(self._paneNames[:item]) | ||
|  | 	iterRange.reverse() | ||
|  | 	self._iterate(iterRange, self._shrink, n) | ||
|  | 
 | ||
|  | 	# Adjust the frames after | ||
|  | 	iterRange = self._paneNames[item:] | ||
|  | 	self._iterate(iterRange, self._grow, n) | ||
|  | 
 | ||
|  | 	self._curSize = p | ||
|  | 
 | ||
|  |     def _moveAfter(self, item, p): | ||
|  | 	n = p - self._curSize | ||
|  | 
 | ||
|  | 	# Shrink the frames after | ||
|  | 	iterRange = self._paneNames[item:] | ||
|  | 	self._iterate(iterRange, self._shrink, n) | ||
|  | 
 | ||
|  | 	# Adjust the frames before | ||
|  | 	iterRange = list(self._paneNames[:item]) | ||
|  | 	iterRange.reverse() | ||
|  | 	self._iterate(iterRange, self._grow, n) | ||
|  | 
 | ||
|  | 	self._curSize = p | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwPromptDialog.py | ||
|  | # Based on iwidgets2.2.0/promptdialog.itk code. | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | # A Dialog with an entryfield | ||
|  | 
 | ||
|  | class PromptDialog(Dialog): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('borderx',     20,    INITOPT), | ||
|  | 	    ('bordery',     20,    INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	Dialog.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 	aliases = ( | ||
|  | 	    ('entry', 'entryfield_entry'), | ||
|  | 	    ('label', 'entryfield_label'), | ||
|  | 	) | ||
|  | 	self._promptDialogEntry = self.createcomponent('entryfield', | ||
|  | 		aliases, None, | ||
|  | 		EntryField, (interior,)) | ||
|  | 	self._promptDialogEntry.pack(fill='x', expand=1, | ||
|  | 		padx = self['borderx'], pady = self['bordery']) | ||
|  | 	 | ||
|  |         if not kw.has_key('activatecommand'): | ||
|  |             # Whenever this dialog is activated, set the focus to the | ||
|  |             # EntryField's entry widget. | ||
|  |             tkentry = self.component('entry') | ||
|  |             self.configure(activatecommand = tkentry.focus_set) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     # Supply aliases to some of the entry component methods. | ||
|  |     def insertentry(self, index, text): | ||
|  | 	self._promptDialogEntry.insert(index, text) | ||
|  | 
 | ||
|  |     def deleteentry(self, first, last=None): | ||
|  | 	self._promptDialogEntry.delete(first, last) | ||
|  | 
 | ||
|  |     def indexentry(self, index): | ||
|  | 	return self._promptDialogEntry.index(index) | ||
|  | 
 | ||
|  | forwardmethods(PromptDialog, EntryField, '_promptDialogEntry') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwRadioSelect.py | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class RadioSelect(MegaWidget): | ||
|  |     # A collection of several buttons.  In single mode, only one | ||
|  |     # button may be selected.  In multiple mode, any number of buttons | ||
|  |     # may be selected. | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('buttontype',    'button',      INITOPT), | ||
|  | 	    ('command',       None,          None), | ||
|  | 	    ('labelmargin',   0,             INITOPT), | ||
|  | 	    ('labelpos',      None,          INITOPT), | ||
|  | 	    ('orient',       'horizontal',   INITOPT), | ||
|  | 	    ('padx',          5,             INITOPT), | ||
|  | 	    ('pady',          5,             INITOPT), | ||
|  | 	    ('selectmode',    'single',      INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs, dynamicGroups = ('Button',)) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 	if self['labelpos'] is None: | ||
|  | 	    self._radioSelectFrame = self._hull | ||
|  | 	else: | ||
|  | 	    self._radioSelectFrame = self.createcomponent('frame', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Frame, (interior,)) | ||
|  | 	    self._radioSelectFrame.grid(column=2, row=2, sticky='nsew') | ||
|  | 	    interior.grid_columnconfigure(2, weight=1) | ||
|  | 	    interior.grid_rowconfigure(2, weight=1) | ||
|  | 
 | ||
|  | 	    self.createlabel(interior) | ||
|  | 
 | ||
|  | 	# Initialise instance variables. | ||
|  | 	self._buttonList = [] | ||
|  | 	if self['selectmode'] == 'single': | ||
|  | 	    self._singleSelect = 1 | ||
|  | 	elif self['selectmode'] == 'multiple': | ||
|  | 	    self._singleSelect = 0 | ||
|  | 	else:  | ||
|  | 	    raise ValueError, 'bad selectmode option "' + \ | ||
|  | 		    self['selectmode'] + '": should be single or multiple' | ||
|  | 
 | ||
|  | 	if self['buttontype'] == 'button': | ||
|  | 	    self.buttonClass = Tkinter.Button | ||
|  | 	elif self['buttontype'] == 'radiobutton': | ||
|  | 	    self._singleSelect = 1 | ||
|  | 	    self.var = Tkinter.StringVar() | ||
|  | 	    self.buttonClass = Tkinter.Radiobutton | ||
|  | 	elif self['buttontype'] == 'checkbutton': | ||
|  | 	    self._singleSelect = 0 | ||
|  | 	    self.buttonClass = Tkinter.Checkbutton | ||
|  | 	else: | ||
|  | 	    raise ValueError, 'bad buttontype option "' + \ | ||
|  | 		    self['buttontype'] + \ | ||
|  | 		    '": should be button, radiobutton or checkbutton' | ||
|  | 
 | ||
|  | 	if self._singleSelect: | ||
|  | 	    self.selection = None | ||
|  | 	else: | ||
|  | 	    self.selection = [] | ||
|  | 
 | ||
|  | 	if self['orient'] not in ('horizontal', 'vertical'): | ||
|  | 	    raise ValueError, 'bad orient option ' + repr(self['orient']) + \ | ||
|  | 		': must be either \'horizontal\' or \'vertical\'' | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def getcurselection(self): | ||
|  | 	if self._singleSelect: | ||
|  |             return self.selection | ||
|  |         else: | ||
|  |             return tuple(self.selection) | ||
|  | 
 | ||
|  |     def getvalue(self): | ||
|  |         return self.getcurselection() | ||
|  | 
 | ||
|  |     def setvalue(self, textOrList): | ||
|  | 	if self._singleSelect: | ||
|  |             self.__setSingleValue(textOrList) | ||
|  |         else: | ||
|  | 	    # Multiple selections | ||
|  |             oldselection = self.selection | ||
|  |             self.selection = textOrList | ||
|  |             for button in self._buttonList: | ||
|  |                 if button in oldselection: | ||
|  |                     if button not in self.selection: | ||
|  |                         # button is currently selected but should not be | ||
|  |                         widget = self.component(button) | ||
|  |                         if self['buttontype'] == 'checkbutton': | ||
|  |                             widget.deselect() | ||
|  |                         else:  # Button | ||
|  |                             widget.configure(relief='raised') | ||
|  |                 else: | ||
|  |                     if button in self.selection: | ||
|  |                         # button is not currently selected but should be | ||
|  |                         widget = self.component(button) | ||
|  |                         if self['buttontype'] == 'checkbutton': | ||
|  |                             widget.select() | ||
|  |                         else:  # Button | ||
|  |                             widget.configure(relief='sunken') | ||
|  | 
 | ||
|  |     def numbuttons(self): | ||
|  |         return len(self._buttonList) | ||
|  | 
 | ||
|  |     def index(self, index): | ||
|  | 	# Return the integer index of the button with the given index. | ||
|  | 
 | ||
|  | 	listLength = len(self._buttonList) | ||
|  | 	if type(index) == types.IntType: | ||
|  | 	    if index < listLength: | ||
|  | 		return index | ||
|  | 	    else: | ||
|  | 		raise ValueError, 'index "%s" is out of range' % index | ||
|  | 	elif index is END: | ||
|  | 	    if listLength > 0: | ||
|  | 		return listLength - 1 | ||
|  | 	    else: | ||
|  | 		raise ValueError, 'RadioSelect has no buttons' | ||
|  | 	else: | ||
|  | 	    for count in range(listLength): | ||
|  | 		name = self._buttonList[count] | ||
|  | 		if index == name: | ||
|  | 		    return count | ||
|  | 	    validValues = 'a name, a number or END' | ||
|  | 	    raise ValueError, \ | ||
|  | 		    'bad index "%s": must be %s' % (index, validValues) | ||
|  | 
 | ||
|  |     def button(self, buttonIndex): | ||
|  | 	name = self._buttonList[self.index(buttonIndex)] | ||
|  |         return self.component(name) | ||
|  | 
 | ||
|  |     def add(self, componentName, **kw): | ||
|  | 	if componentName in self._buttonList: | ||
|  | 	    raise ValueError, 'button "%s" already exists' % componentName | ||
|  | 
 | ||
|  | 	kw['command'] = \ | ||
|  |                 lambda self=self, name=componentName: self.invoke(name) | ||
|  | 	if not kw.has_key('text'): | ||
|  | 	    kw['text'] = componentName | ||
|  | 
 | ||
|  | 	if self['buttontype'] == 'radiobutton': | ||
|  | 	    if not kw.has_key('anchor'): | ||
|  | 		kw['anchor'] = 'w' | ||
|  | 	    if not kw.has_key('variable'): | ||
|  | 		kw['variable'] = self.var | ||
|  | 	    if not kw.has_key('value'): | ||
|  | 		kw['value'] = kw['text'] | ||
|  | 	elif self['buttontype'] == 'checkbutton': | ||
|  | 	    if not kw.has_key('anchor'): | ||
|  | 		kw['anchor'] = 'w' | ||
|  | 
 | ||
|  | 	button = apply(self.createcomponent, (componentName, | ||
|  | 		(), 'Button', | ||
|  | 		self.buttonClass, (self._radioSelectFrame,)), kw) | ||
|  | 
 | ||
|  | 	if self['orient'] == 'horizontal': | ||
|  | 	    self._radioSelectFrame.grid_rowconfigure(0, weight=1) | ||
|  | 	    col = len(self._buttonList) | ||
|  | 	    button.grid(column=col, row=0, padx = self['padx'], | ||
|  | 		    pady = self['pady'], sticky='nsew') | ||
|  | 	    self._radioSelectFrame.grid_columnconfigure(col, weight=1) | ||
|  | 	else: | ||
|  | 	    self._radioSelectFrame.grid_columnconfigure(0, weight=1) | ||
|  | 	    row = len(self._buttonList) | ||
|  | 	    button.grid(column=0, row=row, padx = self['padx'], | ||
|  | 		    pady = self['pady'], sticky='ew') | ||
|  | 	    self._radioSelectFrame.grid_rowconfigure(row, weight=1) | ||
|  | 
 | ||
|  | 	self._buttonList.append(componentName) | ||
|  | 	return button | ||
|  | 
 | ||
|  |     def deleteall(self): | ||
|  | 	for name in self._buttonList: | ||
|  | 	    self.destroycomponent(name) | ||
|  | 	self._buttonList = [] | ||
|  | 	if self._singleSelect: | ||
|  | 	    self.selection = None | ||
|  | 	else:  | ||
|  | 	    self.selection = [] | ||
|  | 
 | ||
|  |     def __setSingleValue(self, value): | ||
|  |             self.selection = value | ||
|  |             if self['buttontype'] == 'radiobutton': | ||
|  |                 widget = self.component(value) | ||
|  |                 widget.select() | ||
|  |             else:  # Button | ||
|  |                 for button in self._buttonList: | ||
|  |                     widget = self.component(button) | ||
|  |                     if button == value: | ||
|  |                         widget.configure(relief='sunken') | ||
|  |                     else: | ||
|  |                         widget.configure(relief='raised') | ||
|  | 
 | ||
|  |     def invoke(self, index): | ||
|  | 	index = self.index(index) | ||
|  | 	name = self._buttonList[index] | ||
|  | 
 | ||
|  | 	if self._singleSelect: | ||
|  |             self.__setSingleValue(name) | ||
|  | 	    command = self['command'] | ||
|  | 	    if callable(command): | ||
|  | 		return command(name) | ||
|  |         else: | ||
|  | 	    # Multiple selections | ||
|  | 	    widget = self.component(name) | ||
|  | 	    if name in self.selection: | ||
|  | 		if self['buttontype'] == 'checkbutton': | ||
|  | 		    widget.deselect() | ||
|  | 		else: | ||
|  | 		    widget.configure(relief='raised') | ||
|  | 		self.selection.remove(name) | ||
|  | 		state = 0 | ||
|  | 	    else: | ||
|  | 		if self['buttontype'] == 'checkbutton': | ||
|  | 		    widget.select() | ||
|  | 		else: | ||
|  | 		    widget.configure(relief='sunken') | ||
|  | 		self.selection.append(name) | ||
|  | 		state = 1 | ||
|  | 
 | ||
|  | 	    command = self['command'] | ||
|  | 	    if callable(command): | ||
|  | 	      return command(name, state) | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwScrolledCanvas.py | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class ScrolledCanvas(MegaWidget): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('borderframe',    0,            INITOPT), | ||
|  | 	    ('canvasmargin',   0,            INITOPT), | ||
|  | 	    ('hscrollmode',    'dynamic',    self._hscrollMode), | ||
|  | 	    ('labelmargin',    0,            INITOPT), | ||
|  | 	    ('labelpos',       None,         INITOPT), | ||
|  | 	    ('scrollmargin',   2,            INITOPT), | ||
|  | 	    ('usehullsize',    0,            INITOPT), | ||
|  | 	    ('vscrollmode',    'dynamic',    self._vscrollMode), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	self.origInterior = MegaWidget.interior(self) | ||
|  | 
 | ||
|  | 	if self['usehullsize']: | ||
|  | 	    self.origInterior.grid_propagate(0) | ||
|  | 
 | ||
|  | 	if self['borderframe']: | ||
|  | 	    # Create a frame widget to act as the border of the canvas.  | ||
|  | 	    self._borderframe = self.createcomponent('borderframe', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Frame, (self.origInterior,), | ||
|  | 		    relief = 'sunken', | ||
|  | 		    borderwidth = 2, | ||
|  | 	    ) | ||
|  | 	    self._borderframe.grid(row = 2, column = 2, sticky = 'news') | ||
|  | 
 | ||
|  | 	    # Create the canvas widget. | ||
|  | 	    self._canvas = self.createcomponent('canvas', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Canvas, (self._borderframe,), | ||
|  | 		    highlightthickness = 0, | ||
|  | 		    borderwidth = 0, | ||
|  | 	    ) | ||
|  | 	    self._canvas.pack(fill = 'both', expand = 1) | ||
|  | 	else: | ||
|  | 	    # Create the canvas widget. | ||
|  | 	    self._canvas = self.createcomponent('canvas', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Canvas, (self.origInterior,), | ||
|  | 		    relief = 'sunken', | ||
|  | 		    borderwidth = 2, | ||
|  | 	    ) | ||
|  | 	    self._canvas.grid(row = 2, column = 2, sticky = 'news') | ||
|  | 
 | ||
|  | 	self.origInterior.grid_rowconfigure(2, weight = 1, minsize = 0) | ||
|  | 	self.origInterior.grid_columnconfigure(2, weight = 1, minsize = 0) | ||
|  | 	 | ||
|  | 	# Create the horizontal scrollbar | ||
|  | 	self._horizScrollbar = self.createcomponent('horizscrollbar', | ||
|  | 		(), 'Scrollbar', | ||
|  | 		Tkinter.Scrollbar, (self.origInterior,), | ||
|  | 	        orient='horizontal', | ||
|  | 		command=self._canvas.xview | ||
|  | 	) | ||
|  | 
 | ||
|  | 	# Create the vertical scrollbar | ||
|  | 	self._vertScrollbar = self.createcomponent('vertscrollbar', | ||
|  | 		(), 'Scrollbar', | ||
|  | 		Tkinter.Scrollbar, (self.origInterior,), | ||
|  | 		orient='vertical', | ||
|  | 		command=self._canvas.yview | ||
|  | 	) | ||
|  | 
 | ||
|  | 	self.createlabel(self.origInterior, childCols = 3, childRows = 3) | ||
|  | 
 | ||
|  | 	# Initialise instance variables. | ||
|  | 	self._horizScrollbarOn = 0 | ||
|  | 	self._vertScrollbarOn = 0 | ||
|  | 	self.scrollTimer = None | ||
|  |         self._scrollRecurse = 0 | ||
|  | 	self._horizScrollbarNeeded = 0 | ||
|  | 	self._vertScrollbarNeeded = 0 | ||
|  | 	self.setregionTimer = None | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  | 	if self.scrollTimer is not None: | ||
|  | 	    self.after_cancel(self.scrollTimer) | ||
|  | 	    self.scrollTimer = None | ||
|  | 	if self.setregionTimer is not None: | ||
|  | 	    self.after_cancel(self.setregionTimer) | ||
|  | 	    self.setregionTimer = None | ||
|  | 	MegaWidget.destroy(self) | ||
|  | 
 | ||
|  |     # ====================================================================== | ||
|  | 
 | ||
|  |     # Public methods. | ||
|  | 
 | ||
|  |     def interior(self): | ||
|  | 	return self._canvas | ||
|  | 
 | ||
|  |     def resizescrollregion(self): | ||
|  | 	if self.setregionTimer is None: | ||
|  | 	    self.setregionTimer = self.after_idle(self._setRegion) | ||
|  | 
 | ||
|  |     # ====================================================================== | ||
|  | 
 | ||
|  |     # Configuration methods. | ||
|  | 
 | ||
|  |     def _hscrollMode(self): | ||
|  | 	# The horizontal scroll mode has been configured. | ||
|  | 
 | ||
|  | 	mode = self['hscrollmode'] | ||
|  | 
 | ||
|  | 	if mode == 'static': | ||
|  | 	    if not self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	elif mode == 'dynamic': | ||
|  | 	    if self._horizScrollbarNeeded != self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	elif mode == 'none': | ||
|  | 	    if self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	else: | ||
|  | 	    message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode | ||
|  | 	    raise ValueError, message | ||
|  | 
 | ||
|  |         self._configureScrollCommands() | ||
|  | 
 | ||
|  |     def _vscrollMode(self): | ||
|  | 	# The vertical scroll mode has been configured. | ||
|  | 
 | ||
|  | 	mode = self['vscrollmode'] | ||
|  | 
 | ||
|  | 	if mode == 'static': | ||
|  | 	    if not self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	elif mode == 'dynamic': | ||
|  | 	    if self._vertScrollbarNeeded != self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	elif mode == 'none': | ||
|  | 	    if self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	else: | ||
|  | 	    message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode | ||
|  | 	    raise ValueError, message | ||
|  | 
 | ||
|  |         self._configureScrollCommands() | ||
|  | 
 | ||
|  |     # ====================================================================== | ||
|  | 
 | ||
|  |     # Private methods. | ||
|  | 
 | ||
|  |     def _configureScrollCommands(self): | ||
|  |         # If both scrollmodes are not dynamic we can save a lot of | ||
|  |         # time by not having to create an idle job to handle the | ||
|  |         # scroll commands. | ||
|  | 
 | ||
|  |         # Clean up previous scroll commands to prevent memory leak. | ||
|  |         tclCommandName = str(self._canvas.cget('xscrollcommand')) | ||
|  |         if tclCommandName != '':    | ||
|  |             self._canvas.deletecommand(tclCommandName) | ||
|  |         tclCommandName = str(self._canvas.cget('yscrollcommand')) | ||
|  |         if tclCommandName != '':    | ||
|  |             self._canvas.deletecommand(tclCommandName) | ||
|  | 
 | ||
|  | 	if self['hscrollmode'] == self['vscrollmode'] == 'dynamic': | ||
|  |             self._canvas.configure( | ||
|  |                     xscrollcommand=self._scrollBothLater, | ||
|  |                     yscrollcommand=self._scrollBothLater | ||
|  |             ) | ||
|  |         else: | ||
|  |             self._canvas.configure( | ||
|  |                     xscrollcommand=self._scrollXNow, | ||
|  |                     yscrollcommand=self._scrollYNow | ||
|  |             ) | ||
|  | 
 | ||
|  |     def _scrollXNow(self, first, last): | ||
|  |         self._horizScrollbar.set(first, last) | ||
|  |         self._horizScrollbarNeeded = ((first, last) != ('0', '1')) | ||
|  | 
 | ||
|  | 	if self['hscrollmode'] == 'dynamic': | ||
|  | 	    if self._horizScrollbarNeeded != self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 
 | ||
|  |     def _scrollYNow(self, first, last): | ||
|  |         self._vertScrollbar.set(first, last) | ||
|  |         self._vertScrollbarNeeded = ((first, last) != ('0', '1')) | ||
|  | 
 | ||
|  |         if self['vscrollmode'] == 'dynamic': | ||
|  |             if self._vertScrollbarNeeded != self._vertScrollbarOn: | ||
|  |                 self._toggleVertScrollbar() | ||
|  | 
 | ||
|  |     def _scrollBothLater(self, first, last): | ||
|  | 	# Called by the canvas to set the horizontal or vertical | ||
|  | 	# scrollbar when it has scrolled or changed scrollregion. | ||
|  | 
 | ||
|  | 	if self.scrollTimer is None: | ||
|  | 	    self.scrollTimer = self.after_idle(self._scrollBothNow) | ||
|  | 
 | ||
|  |     def _scrollBothNow(self): | ||
|  |         # This performs the function of _scrollXNow and _scrollYNow. | ||
|  |         # If one is changed, the other should be updated to match. | ||
|  | 	self.scrollTimer = None | ||
|  | 
 | ||
|  |         # Call update_idletasks to make sure that the containing frame | ||
|  |         # has been resized before we attempt to set the scrollbars.  | ||
|  |         # Otherwise the scrollbars may be mapped/unmapped continuously. | ||
|  |         self._scrollRecurse = self._scrollRecurse + 1 | ||
|  |         self.update_idletasks() | ||
|  |         self._scrollRecurse = self._scrollRecurse - 1 | ||
|  |         if self._scrollRecurse != 0: | ||
|  |             return | ||
|  | 
 | ||
|  | 	xview = self._canvas.xview() | ||
|  | 	yview = self._canvas.yview() | ||
|  | 	self._horizScrollbar.set(xview[0], xview[1]) | ||
|  | 	self._vertScrollbar.set(yview[0], yview[1]) | ||
|  | 
 | ||
|  | 	self._horizScrollbarNeeded = (xview != (0.0, 1.0)) | ||
|  | 	self._vertScrollbarNeeded = (yview != (0.0, 1.0)) | ||
|  | 
 | ||
|  | 	# If both horizontal and vertical scrollmodes are dynamic and | ||
|  | 	# currently only one scrollbar is mapped and both should be | ||
|  | 	# toggled, then unmap the mapped scrollbar.  This prevents a | ||
|  | 	# continuous mapping and unmapping of the scrollbars.  | ||
|  | 	if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and | ||
|  | 		self._horizScrollbarNeeded != self._horizScrollbarOn and | ||
|  | 		self._vertScrollbarNeeded != self._vertScrollbarOn and | ||
|  | 		self._vertScrollbarOn != self._horizScrollbarOn): | ||
|  | 	    if self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	    else: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	if self['hscrollmode'] == 'dynamic': | ||
|  | 	    if self._horizScrollbarNeeded != self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 
 | ||
|  | 	if self['vscrollmode'] == 'dynamic': | ||
|  | 	    if self._vertScrollbarNeeded != self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 
 | ||
|  |     def _toggleHorizScrollbar(self): | ||
|  | 
 | ||
|  | 	self._horizScrollbarOn = not self._horizScrollbarOn | ||
|  | 
 | ||
|  | 	interior = self.origInterior | ||
|  | 	if self._horizScrollbarOn: | ||
|  | 	    self._horizScrollbar.grid(row = 4, column = 2, sticky = 'news') | ||
|  | 	    interior.grid_rowconfigure(3, minsize = self['scrollmargin']) | ||
|  | 	else: | ||
|  | 	    self._horizScrollbar.grid_forget() | ||
|  | 	    interior.grid_rowconfigure(3, minsize = 0) | ||
|  | 
 | ||
|  |     def _toggleVertScrollbar(self): | ||
|  | 
 | ||
|  | 	self._vertScrollbarOn = not self._vertScrollbarOn | ||
|  | 
 | ||
|  | 	interior = self.origInterior | ||
|  | 	if self._vertScrollbarOn: | ||
|  | 	    self._vertScrollbar.grid(row = 2, column = 4, sticky = 'news') | ||
|  | 	    interior.grid_columnconfigure(3, minsize = self['scrollmargin']) | ||
|  | 	else: | ||
|  | 	    self._vertScrollbar.grid_forget() | ||
|  | 	    interior.grid_columnconfigure(3, minsize = 0) | ||
|  | 
 | ||
|  |     def _setRegion(self): | ||
|  | 	self.setregionTimer = None | ||
|  | 
 | ||
|  | 	region = self._canvas.bbox('all') | ||
|  |         if region is not None: | ||
|  | 	    canvasmargin = self['canvasmargin'] | ||
|  | 	    region = (region[0] - canvasmargin, region[1] - canvasmargin, | ||
|  | 		region[2] + canvasmargin, region[3] + canvasmargin) | ||
|  | 	    self._canvas.configure(scrollregion = region) | ||
|  | 
 | ||
|  |     # Need to explicitly forward this to override the stupid | ||
|  |     # (grid_)bbox method inherited from Tkinter.Frame.Grid. | ||
|  |     def bbox(self, *args): | ||
|  | 	return apply(self._canvas.bbox, args) | ||
|  | 
 | ||
|  | forwardmethods(ScrolledCanvas, Tkinter.Canvas, '_canvas') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwScrolledField.py | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class ScrolledField(MegaWidget): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('labelmargin',   0,      INITOPT), | ||
|  | 	    ('labelpos',      None,   INITOPT), | ||
|  | 	    ('sticky',        'ew',   INITOPT), | ||
|  | 	    ('text',          '',     self._text), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 	self._scrolledFieldEntry = self.createcomponent('entry', | ||
|  | 		(), None, | ||
|  | 		Tkinter.Entry, (interior,)) | ||
|  | 
 | ||
|  |         # Can't always use 'disabled', since this greys out text in Tk 8.4.2 | ||
|  |         try: | ||
|  |             self._scrolledFieldEntry.configure(state = 'readonly') | ||
|  |         except Tkinter.TclError: | ||
|  |             self._scrolledFieldEntry.configure(state = 'disabled') | ||
|  | 
 | ||
|  | 	self._scrolledFieldEntry.grid(column=2, row=2, sticky=self['sticky']) | ||
|  | 	interior.grid_columnconfigure(2, weight=1) | ||
|  | 	interior.grid_rowconfigure(2, weight=1) | ||
|  | 
 | ||
|  | 	self.createlabel(interior) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def _text(self): | ||
|  |         text = self['text'] | ||
|  |         self._scrolledFieldEntry.configure(state = 'normal') | ||
|  |         self._scrolledFieldEntry.delete(0, 'end') | ||
|  |         self._scrolledFieldEntry.insert('end', text) | ||
|  | 
 | ||
|  |         # Can't always use 'disabled', since this greys out text in Tk 8.4.2 | ||
|  |         try: | ||
|  |             self._scrolledFieldEntry.configure(state = 'readonly') | ||
|  |         except Tkinter.TclError: | ||
|  |             self._scrolledFieldEntry.configure(state = 'disabled') | ||
|  | 
 | ||
|  | forwardmethods(ScrolledField, Tkinter.Entry, '_scrolledFieldEntry') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwScrolledFrame.py | ||
|  | import string | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class ScrolledFrame(MegaWidget): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('borderframe',    1,            INITOPT), | ||
|  | 	    ('horizflex',      'fixed',      self._horizflex), | ||
|  | 	    ('horizfraction',  0.05,         INITOPT), | ||
|  | 	    ('hscrollmode',    'dynamic',    self._hscrollMode), | ||
|  | 	    ('labelmargin',    0,            INITOPT), | ||
|  | 	    ('labelpos',       None,         INITOPT), | ||
|  | 	    ('scrollmargin',   2,            INITOPT), | ||
|  | 	    ('usehullsize',    0,            INITOPT), | ||
|  | 	    ('vertflex',       'fixed',      self._vertflex), | ||
|  | 	    ('vertfraction',   0.05,         INITOPT), | ||
|  | 	    ('vscrollmode',    'dynamic',    self._vscrollMode), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	self.origInterior = MegaWidget.interior(self) | ||
|  | 
 | ||
|  | 	if self['usehullsize']: | ||
|  | 	    self.origInterior.grid_propagate(0) | ||
|  | 
 | ||
|  | 	if self['borderframe']: | ||
|  | 	    # Create a frame widget to act as the border of the clipper.  | ||
|  | 	    self._borderframe = self.createcomponent('borderframe', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Frame, (self.origInterior,), | ||
|  | 		    relief = 'sunken', | ||
|  | 		    borderwidth = 2, | ||
|  | 	    ) | ||
|  | 	    self._borderframe.grid(row = 2, column = 2, sticky = 'news') | ||
|  | 
 | ||
|  | 	    # Create the clipping window. | ||
|  | 	    self._clipper = self.createcomponent('clipper', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Frame, (self._borderframe,), | ||
|  | 		    width = 400, | ||
|  | 		    height = 300, | ||
|  | 		    highlightthickness = 0, | ||
|  | 		    borderwidth = 0, | ||
|  | 	    ) | ||
|  | 	    self._clipper.pack(fill = 'both', expand = 1) | ||
|  | 	else: | ||
|  | 	    # Create the clipping window. | ||
|  | 	    self._clipper = self.createcomponent('clipper', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Frame, (self.origInterior,), | ||
|  | 		    width = 400, | ||
|  | 		    height = 300, | ||
|  | 		    relief = 'sunken', | ||
|  | 		    borderwidth = 2, | ||
|  | 	    ) | ||
|  | 	    self._clipper.grid(row = 2, column = 2, sticky = 'news') | ||
|  | 
 | ||
|  | 	self.origInterior.grid_rowconfigure(2, weight = 1, minsize = 0) | ||
|  | 	self.origInterior.grid_columnconfigure(2, weight = 1, minsize = 0) | ||
|  | 	 | ||
|  | 	# Create the horizontal scrollbar | ||
|  | 	self._horizScrollbar = self.createcomponent('horizscrollbar', | ||
|  | 		(), 'Scrollbar', | ||
|  | 		Tkinter.Scrollbar, (self.origInterior,), | ||
|  | 	        orient='horizontal', | ||
|  | 		command=self.xview | ||
|  | 	) | ||
|  | 
 | ||
|  | 	# Create the vertical scrollbar | ||
|  | 	self._vertScrollbar = self.createcomponent('vertscrollbar', | ||
|  | 		(), 'Scrollbar', | ||
|  | 		Tkinter.Scrollbar, (self.origInterior,), | ||
|  | 		orient='vertical', | ||
|  | 		command=self.yview | ||
|  | 	) | ||
|  | 
 | ||
|  | 	self.createlabel(self.origInterior, childCols = 3, childRows = 3) | ||
|  | 
 | ||
|  | 	# Initialise instance variables. | ||
|  | 	self._horizScrollbarOn = 0 | ||
|  | 	self._vertScrollbarOn = 0 | ||
|  | 	self.scrollTimer = None | ||
|  | 	self._scrollRecurse = 0 | ||
|  | 	self._horizScrollbarNeeded = 0 | ||
|  | 	self._vertScrollbarNeeded = 0 | ||
|  | 	self.startX = 0 | ||
|  | 	self.startY = 0 | ||
|  | 	self._flexoptions = ('fixed', 'expand', 'shrink', 'elastic') | ||
|  | 
 | ||
|  | 	# Create a frame in the clipper to contain the widgets to be | ||
|  | 	# scrolled. | ||
|  | 	self._frame = self.createcomponent('frame', | ||
|  | 		(), None, | ||
|  | 		Tkinter.Frame, (self._clipper,) | ||
|  | 	) | ||
|  | 
 | ||
|  | 	# Whenever the clipping window or scrolled frame change size, | ||
|  | 	# update the scrollbars. | ||
|  | 	self._frame.bind('<Configure>', self._reposition) | ||
|  | 	self._clipper.bind('<Configure>', self._reposition) | ||
|  | 
 | ||
|  |         # Work around a bug in Tk where the value returned by the | ||
|  |         # scrollbar get() method is (0.0, 0.0, 0.0, 0.0) rather than | ||
|  |         # the expected 2-tuple.  This occurs if xview() is called soon | ||
|  |         # after the ScrolledFrame has been created. | ||
|  |         self._horizScrollbar.set(0.0, 1.0) | ||
|  |         self._vertScrollbar.set(0.0, 1.0) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  | 	if self.scrollTimer is not None: | ||
|  | 	    self.after_cancel(self.scrollTimer) | ||
|  | 	    self.scrollTimer = None | ||
|  | 	MegaWidget.destroy(self) | ||
|  | 
 | ||
|  |     # ====================================================================== | ||
|  | 
 | ||
|  |     # Public methods. | ||
|  | 
 | ||
|  |     def interior(self): | ||
|  | 	return self._frame | ||
|  | 
 | ||
|  |     # Set timer to call real reposition method, so that it is not | ||
|  |     # called multiple times when many things are reconfigured at the | ||
|  |     # same time. | ||
|  |     def reposition(self): | ||
|  | 	if self.scrollTimer is None: | ||
|  | 	    self.scrollTimer = self.after_idle(self._scrollBothNow) | ||
|  | 
 | ||
|  |     # Called when the user clicks in the horizontal scrollbar.  | ||
|  |     # Calculates new position of frame then calls reposition() to | ||
|  |     # update the frame and the scrollbar. | ||
|  |     def xview(self, mode = None, value = None, units = None): | ||
|  | 
 | ||
|  |         if type(value) == types.StringType: | ||
|  |             value = string.atof(value) | ||
|  |         if mode is None: | ||
|  |             return self._horizScrollbar.get() | ||
|  | 	elif mode == 'moveto': | ||
|  | 	    frameWidth = self._frame.winfo_reqwidth() | ||
|  | 	    self.startX = value * float(frameWidth) | ||
|  | 	else: # mode == 'scroll' | ||
|  | 	    clipperWidth = self._clipper.winfo_width() | ||
|  | 	    if units == 'units': | ||
|  | 		jump = int(clipperWidth * self['horizfraction']) | ||
|  | 	    else: | ||
|  | 		jump = clipperWidth | ||
|  |             self.startX = self.startX + value * jump | ||
|  | 
 | ||
|  | 	self.reposition() | ||
|  | 
 | ||
|  |     # Called when the user clicks in the vertical scrollbar.  | ||
|  |     # Calculates new position of frame then calls reposition() to | ||
|  |     # update the frame and the scrollbar. | ||
|  |     def yview(self, mode = None, value = None, units = None): | ||
|  | 
 | ||
|  |         if type(value) == types.StringType: | ||
|  |             value = string.atof(value) | ||
|  |         if mode is None: | ||
|  |             return self._vertScrollbar.get() | ||
|  | 	elif mode == 'moveto': | ||
|  | 	    frameHeight = self._frame.winfo_reqheight() | ||
|  | 	    self.startY = value * float(frameHeight) | ||
|  | 	else: # mode == 'scroll' | ||
|  | 	    clipperHeight = self._clipper.winfo_height() | ||
|  | 	    if units == 'units': | ||
|  | 		jump = int(clipperHeight * self['vertfraction']) | ||
|  | 	    else: | ||
|  | 		jump = clipperHeight | ||
|  |             self.startY = self.startY + value * jump | ||
|  | 
 | ||
|  | 	self.reposition() | ||
|  | 
 | ||
|  |     # ====================================================================== | ||
|  | 
 | ||
|  |     # Configuration methods. | ||
|  | 
 | ||
|  |     def _hscrollMode(self): | ||
|  | 	# The horizontal scroll mode has been configured. | ||
|  | 
 | ||
|  | 	mode = self['hscrollmode'] | ||
|  | 
 | ||
|  | 	if mode == 'static': | ||
|  | 	    if not self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	elif mode == 'dynamic': | ||
|  | 	    if self._horizScrollbarNeeded != self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	elif mode == 'none': | ||
|  | 	    if self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	else: | ||
|  | 	    message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode | ||
|  | 	    raise ValueError, message | ||
|  | 
 | ||
|  |     def _vscrollMode(self): | ||
|  | 	# The vertical scroll mode has been configured. | ||
|  | 
 | ||
|  | 	mode = self['vscrollmode'] | ||
|  | 
 | ||
|  | 	if mode == 'static': | ||
|  | 	    if not self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	elif mode == 'dynamic': | ||
|  | 	    if self._vertScrollbarNeeded != self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	elif mode == 'none': | ||
|  | 	    if self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	else: | ||
|  | 	    message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode | ||
|  | 	    raise ValueError, message | ||
|  | 
 | ||
|  |     def _horizflex(self): | ||
|  | 	# The horizontal flex mode has been configured. | ||
|  | 
 | ||
|  | 	flex = self['horizflex'] | ||
|  | 
 | ||
|  | 	if flex not in self._flexoptions: | ||
|  | 	    message = 'bad horizflex option "%s": should be one of %s' % \ | ||
|  | 		    (flex, str(self._flexoptions)) | ||
|  | 	    raise ValueError, message | ||
|  | 
 | ||
|  | 	self.reposition() | ||
|  | 
 | ||
|  |     def _vertflex(self): | ||
|  | 	# The vertical flex mode has been configured. | ||
|  | 
 | ||
|  | 	flex = self['vertflex'] | ||
|  | 
 | ||
|  | 	if flex not in self._flexoptions: | ||
|  | 	    message = 'bad vertflex option "%s": should be one of %s' % \ | ||
|  | 		    (flex, str(self._flexoptions)) | ||
|  | 	    raise ValueError, message | ||
|  | 
 | ||
|  | 	self.reposition() | ||
|  | 
 | ||
|  |     # ====================================================================== | ||
|  | 
 | ||
|  |     # Private methods. | ||
|  | 
 | ||
|  |     def _reposition(self, event): | ||
|  | 	self.reposition() | ||
|  | 
 | ||
|  |     def _getxview(self): | ||
|  | 
 | ||
|  | 	# Horizontal dimension. | ||
|  | 	clipperWidth = self._clipper.winfo_width() | ||
|  | 	frameWidth = self._frame.winfo_reqwidth() | ||
|  | 	if frameWidth <= clipperWidth: | ||
|  | 	    # The scrolled frame is smaller than the clipping window. | ||
|  | 
 | ||
|  | 	    self.startX = 0 | ||
|  | 	    endScrollX = 1.0 | ||
|  | 
 | ||
|  | 	    if self['horizflex'] in ('expand', 'elastic'): | ||
|  | 		relwidth = 1 | ||
|  | 	    else: | ||
|  | 		relwidth = '' | ||
|  | 	else: | ||
|  | 	    # The scrolled frame is larger than the clipping window. | ||
|  | 
 | ||
|  | 	    if self['horizflex'] in ('shrink', 'elastic'): | ||
|  | 		self.startX = 0 | ||
|  | 		endScrollX = 1.0 | ||
|  | 		relwidth = 1 | ||
|  | 	    else: | ||
|  | 		if self.startX + clipperWidth > frameWidth: | ||
|  | 		    self.startX = frameWidth - clipperWidth | ||
|  | 		    endScrollX = 1.0 | ||
|  | 		else: | ||
|  | 		    if self.startX < 0: | ||
|  | 			self.startX = 0 | ||
|  | 		    endScrollX = (self.startX + clipperWidth) / float(frameWidth) | ||
|  | 		relwidth = '' | ||
|  | 
 | ||
|  | 	# Position frame relative to clipper. | ||
|  | 	self._frame.place(x = -self.startX, relwidth = relwidth) | ||
|  | 	return (self.startX / float(frameWidth), endScrollX) | ||
|  | 
 | ||
|  |     def _getyview(self): | ||
|  | 
 | ||
|  | 	# Vertical dimension. | ||
|  | 	clipperHeight = self._clipper.winfo_height() | ||
|  | 	frameHeight = self._frame.winfo_reqheight() | ||
|  | 	if frameHeight <= clipperHeight: | ||
|  | 	    # The scrolled frame is smaller than the clipping window. | ||
|  | 
 | ||
|  | 	    self.startY = 0 | ||
|  | 	    endScrollY = 1.0 | ||
|  | 
 | ||
|  | 	    if self['vertflex'] in ('expand', 'elastic'): | ||
|  | 		relheight = 1 | ||
|  | 	    else: | ||
|  | 		relheight = '' | ||
|  | 	else: | ||
|  | 	    # The scrolled frame is larger than the clipping window. | ||
|  | 
 | ||
|  | 	    if self['vertflex'] in ('shrink', 'elastic'): | ||
|  | 		self.startY = 0 | ||
|  | 		endScrollY = 1.0 | ||
|  | 		relheight = 1 | ||
|  | 	    else: | ||
|  | 		if self.startY + clipperHeight > frameHeight: | ||
|  | 		    self.startY = frameHeight - clipperHeight | ||
|  | 		    endScrollY = 1.0 | ||
|  | 		else: | ||
|  | 		    if self.startY < 0: | ||
|  | 			self.startY = 0 | ||
|  | 		    endScrollY = (self.startY + clipperHeight) / float(frameHeight) | ||
|  | 		relheight = '' | ||
|  | 
 | ||
|  | 	# Position frame relative to clipper. | ||
|  | 	self._frame.place(y = -self.startY, relheight = relheight) | ||
|  | 	return (self.startY / float(frameHeight), endScrollY) | ||
|  | 
 | ||
|  |     # According to the relative geometries of the frame and the | ||
|  |     # clipper, reposition the frame within the clipper and reset the | ||
|  |     # scrollbars. | ||
|  |     def _scrollBothNow(self): | ||
|  | 	self.scrollTimer = None | ||
|  | 
 | ||
|  |         # Call update_idletasks to make sure that the containing frame | ||
|  |         # has been resized before we attempt to set the scrollbars.  | ||
|  |         # Otherwise the scrollbars may be mapped/unmapped continuously. | ||
|  |         self._scrollRecurse = self._scrollRecurse + 1 | ||
|  |         self.update_idletasks() | ||
|  |         self._scrollRecurse = self._scrollRecurse - 1 | ||
|  |         if self._scrollRecurse != 0: | ||
|  |             return | ||
|  | 
 | ||
|  | 	xview = self._getxview() | ||
|  | 	yview = self._getyview() | ||
|  | 	self._horizScrollbar.set(xview[0], xview[1]) | ||
|  | 	self._vertScrollbar.set(yview[0], yview[1]) | ||
|  | 
 | ||
|  | 	self._horizScrollbarNeeded = (xview != (0.0, 1.0)) | ||
|  | 	self._vertScrollbarNeeded = (yview != (0.0, 1.0)) | ||
|  | 
 | ||
|  | 	# If both horizontal and vertical scrollmodes are dynamic and | ||
|  | 	# currently only one scrollbar is mapped and both should be | ||
|  | 	# toggled, then unmap the mapped scrollbar.  This prevents a | ||
|  | 	# continuous mapping and unmapping of the scrollbars.  | ||
|  | 	if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and | ||
|  | 		self._horizScrollbarNeeded != self._horizScrollbarOn and | ||
|  | 		self._vertScrollbarNeeded != self._vertScrollbarOn and | ||
|  | 		self._vertScrollbarOn != self._horizScrollbarOn): | ||
|  | 	    if self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	    else: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	if self['hscrollmode'] == 'dynamic': | ||
|  | 	    if self._horizScrollbarNeeded != self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 
 | ||
|  | 	if self['vscrollmode'] == 'dynamic': | ||
|  | 	    if self._vertScrollbarNeeded != self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 
 | ||
|  |     def _toggleHorizScrollbar(self): | ||
|  | 
 | ||
|  | 	self._horizScrollbarOn = not self._horizScrollbarOn | ||
|  | 
 | ||
|  | 	interior = self.origInterior | ||
|  | 	if self._horizScrollbarOn: | ||
|  | 	    self._horizScrollbar.grid(row = 4, column = 2, sticky = 'news') | ||
|  | 	    interior.grid_rowconfigure(3, minsize = self['scrollmargin']) | ||
|  | 	else: | ||
|  | 	    self._horizScrollbar.grid_forget() | ||
|  | 	    interior.grid_rowconfigure(3, minsize = 0) | ||
|  | 
 | ||
|  |     def _toggleVertScrollbar(self): | ||
|  | 
 | ||
|  | 	self._vertScrollbarOn = not self._vertScrollbarOn | ||
|  | 
 | ||
|  | 	interior = self.origInterior | ||
|  | 	if self._vertScrollbarOn: | ||
|  | 	    self._vertScrollbar.grid(row = 2, column = 4, sticky = 'news') | ||
|  | 	    interior.grid_columnconfigure(3, minsize = self['scrollmargin']) | ||
|  | 	else: | ||
|  | 	    self._vertScrollbar.grid_forget() | ||
|  | 	    interior.grid_columnconfigure(3, minsize = 0) | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwScrolledListBox.py | ||
|  | # Based on iwidgets2.2.0/scrolledlistbox.itk code. | ||
|  | 
 | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class ScrolledListBox(MegaWidget): | ||
|  |     _classBindingsDefinedFor = 0 | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('dblclickcommand',    None,            None), | ||
|  | 	    ('hscrollmode',        'dynamic',       self._hscrollMode), | ||
|  | 	    ('items',              (),              INITOPT), | ||
|  | 	    ('labelmargin',        0,               INITOPT), | ||
|  | 	    ('labelpos',           None,            INITOPT), | ||
|  | 	    ('scrollmargin',       2,               INITOPT), | ||
|  | 	    ('selectioncommand',   None,            None), | ||
|  | 	    ('usehullsize',        0,               INITOPT), | ||
|  | 	    ('vscrollmode',        'dynamic',       self._vscrollMode), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 
 | ||
|  | 	if self['usehullsize']: | ||
|  | 	    interior.grid_propagate(0) | ||
|  | 
 | ||
|  | 	# Create the listbox widget. | ||
|  | 	self._listbox = self.createcomponent('listbox', | ||
|  | 		(), None, | ||
|  | 		Tkinter.Listbox, (interior,)) | ||
|  | 	self._listbox.grid(row = 2, column = 2, sticky = 'news') | ||
|  | 	interior.grid_rowconfigure(2, weight = 1, minsize = 0) | ||
|  | 	interior.grid_columnconfigure(2, weight = 1, minsize = 0) | ||
|  | 
 | ||
|  | 	# Create the horizontal scrollbar | ||
|  | 	self._horizScrollbar = self.createcomponent('horizscrollbar', | ||
|  | 		(), 'Scrollbar', | ||
|  | 		Tkinter.Scrollbar, (interior,), | ||
|  | 	        orient='horizontal', | ||
|  | 		command=self._listbox.xview | ||
|  | 	) | ||
|  | 
 | ||
|  | 	# Create the vertical scrollbar | ||
|  | 	self._vertScrollbar = self.createcomponent('vertscrollbar', | ||
|  | 		(), 'Scrollbar', | ||
|  | 		Tkinter.Scrollbar, (interior,), | ||
|  | 		orient='vertical', | ||
|  | 		command=self._listbox.yview | ||
|  | 	) | ||
|  | 
 | ||
|  | 	self.createlabel(interior, childCols = 3, childRows = 3) | ||
|  | 
 | ||
|  | 	# Add the items specified by the initialisation option. | ||
|  | 	items = self['items'] | ||
|  | 	if type(items) != types.TupleType: | ||
|  | 	    items = tuple(items) | ||
|  | 	if len(items) > 0: | ||
|  | 	    apply(self._listbox.insert, ('end',) + items) | ||
|  | 
 | ||
|  | 	_registerScrolledList(self._listbox, self) | ||
|  | 
 | ||
|  |         # Establish the special class bindings if not already done. | ||
|  |         # Also create bindings if the Tkinter default interpreter has | ||
|  |         # changed.  Use Tkinter._default_root to create class | ||
|  |         # bindings, so that a reference to root is created by | ||
|  |         # bind_class rather than a reference to self, which would | ||
|  |         # prevent object cleanup. | ||
|  |         theTag = 'ScrolledListBoxTag' | ||
|  |         if ScrolledListBox._classBindingsDefinedFor != Tkinter._default_root: | ||
|  |             root  = Tkinter._default_root | ||
|  | 	    	     | ||
|  |             def doubleEvent(event): | ||
|  |                 _handleEvent(event, 'double') | ||
|  |             def keyEvent(event): | ||
|  |                 _handleEvent(event, 'key') | ||
|  |             def releaseEvent(event): | ||
|  |                 _handleEvent(event, 'release') | ||
|  | 
 | ||
|  |             # Bind space and return keys and button 1 to the selectioncommand. | ||
|  |             root.bind_class(theTag, '<Key-space>', keyEvent) | ||
|  |             root.bind_class(theTag, '<Key-Return>', keyEvent) | ||
|  |             root.bind_class(theTag, '<ButtonRelease-1>', releaseEvent) | ||
|  | 
 | ||
|  |             # Bind double button 1 click to the dblclickcommand. | ||
|  |             root.bind_class(theTag, '<Double-ButtonRelease-1>', doubleEvent) | ||
|  | 
 | ||
|  | 	    ScrolledListBox._classBindingsDefinedFor = root | ||
|  | 
 | ||
|  | 	bindtags = self._listbox.bindtags() | ||
|  | 	self._listbox.bindtags(bindtags + (theTag,)) | ||
|  | 
 | ||
|  | 	# Initialise instance variables. | ||
|  | 	self._horizScrollbarOn = 0 | ||
|  | 	self._vertScrollbarOn = 0 | ||
|  | 	self.scrollTimer = None | ||
|  |         self._scrollRecurse = 0 | ||
|  | 	self._horizScrollbarNeeded = 0 | ||
|  | 	self._vertScrollbarNeeded = 0 | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  | 	if self.scrollTimer is not None: | ||
|  | 	    self.after_cancel(self.scrollTimer) | ||
|  | 	    self.scrollTimer = None | ||
|  | 	_deregisterScrolledList(self._listbox) | ||
|  | 	MegaWidget.destroy(self) | ||
|  | 
 | ||
|  |     # ====================================================================== | ||
|  | 
 | ||
|  |     # Public methods. | ||
|  | 
 | ||
|  |     def clear(self): | ||
|  | 	self.setlist(()) | ||
|  | 
 | ||
|  |     def getcurselection(self): | ||
|  | 	rtn = [] | ||
|  | 	for sel in self.curselection(): | ||
|  | 	    rtn.append(self._listbox.get(sel)) | ||
|  | 	return tuple(rtn) | ||
|  | 
 | ||
|  |     def getvalue(self): | ||
|  |         return self.getcurselection() | ||
|  | 
 | ||
|  |     def setvalue(self, textOrList): | ||
|  |         self._listbox.selection_clear(0, 'end') | ||
|  |         listitems = list(self._listbox.get(0, 'end')) | ||
|  |         if type(textOrList) == types.StringType: | ||
|  |             if textOrList in listitems: | ||
|  |                 self._listbox.selection_set(listitems.index(textOrList)) | ||
|  |             else: | ||
|  |                 raise ValueError, 'no such item "%s"' % textOrList | ||
|  |         else: | ||
|  |             for item in textOrList: | ||
|  |                 if item in listitems: | ||
|  |                     self._listbox.selection_set(listitems.index(item)) | ||
|  |                 else: | ||
|  |                     raise ValueError, 'no such item "%s"' % item | ||
|  | 
 | ||
|  |     def setlist(self, items): | ||
|  |         self._listbox.delete(0, 'end') | ||
|  | 	if len(items) > 0: | ||
|  | 	    if type(items) != types.TupleType: | ||
|  | 		items = tuple(items) | ||
|  | 	    apply(self._listbox.insert, (0,) + items) | ||
|  | 
 | ||
|  |     # Override Tkinter.Listbox get method, so that if it is called with | ||
|  |     # no arguments, return all list elements (consistent with other widgets). | ||
|  |     def get(self, first=None, last=None): | ||
|  | 	if first is None: | ||
|  | 	    return self._listbox.get(0, 'end') | ||
|  | 	else: | ||
|  | 	    return self._listbox.get(first, last) | ||
|  | 
 | ||
|  |     # ====================================================================== | ||
|  | 
 | ||
|  |     # Configuration methods. | ||
|  | 
 | ||
|  |     def _hscrollMode(self): | ||
|  | 	# The horizontal scroll mode has been configured. | ||
|  | 
 | ||
|  | 	mode = self['hscrollmode'] | ||
|  | 
 | ||
|  | 	if mode == 'static': | ||
|  | 	    if not self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	elif mode == 'dynamic': | ||
|  | 	    if self._horizScrollbarNeeded != self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	elif mode == 'none': | ||
|  | 	    if self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	else: | ||
|  | 	    message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode | ||
|  | 	    raise ValueError, message | ||
|  | 
 | ||
|  |         self._configureScrollCommands() | ||
|  | 
 | ||
|  |     def _vscrollMode(self): | ||
|  | 	# The vertical scroll mode has been configured. | ||
|  | 
 | ||
|  | 	mode = self['vscrollmode'] | ||
|  | 
 | ||
|  | 	if mode == 'static': | ||
|  | 	    if not self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	elif mode == 'dynamic': | ||
|  | 	    if self._vertScrollbarNeeded != self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	elif mode == 'none': | ||
|  | 	    if self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	else: | ||
|  | 	    message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode | ||
|  | 	    raise ValueError, message | ||
|  | 
 | ||
|  |         self._configureScrollCommands() | ||
|  | 
 | ||
|  |     # ====================================================================== | ||
|  | 
 | ||
|  |     # Private methods. | ||
|  | 
 | ||
|  |     def _configureScrollCommands(self): | ||
|  |         # If both scrollmodes are not dynamic we can save a lot of | ||
|  |         # time by not having to create an idle job to handle the | ||
|  |         # scroll commands. | ||
|  | 
 | ||
|  |         # Clean up previous scroll commands to prevent memory leak. | ||
|  |         tclCommandName = str(self._listbox.cget('xscrollcommand')) | ||
|  |         if tclCommandName != '':    | ||
|  |             self._listbox.deletecommand(tclCommandName) | ||
|  |         tclCommandName = str(self._listbox.cget('yscrollcommand')) | ||
|  |         if tclCommandName != '':    | ||
|  |             self._listbox.deletecommand(tclCommandName) | ||
|  | 
 | ||
|  | 	if self['hscrollmode'] == self['vscrollmode'] == 'dynamic': | ||
|  |             self._listbox.configure( | ||
|  |                     xscrollcommand=self._scrollBothLater, | ||
|  |                     yscrollcommand=self._scrollBothLater | ||
|  |             ) | ||
|  |         else: | ||
|  |             self._listbox.configure( | ||
|  |                     xscrollcommand=self._scrollXNow, | ||
|  |                     yscrollcommand=self._scrollYNow | ||
|  |             ) | ||
|  | 
 | ||
|  |     def _scrollXNow(self, first, last): | ||
|  |         self._horizScrollbar.set(first, last) | ||
|  |         self._horizScrollbarNeeded = ((first, last) != ('0', '1')) | ||
|  | 
 | ||
|  | 	if self['hscrollmode'] == 'dynamic': | ||
|  | 	    if self._horizScrollbarNeeded != self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 
 | ||
|  |     def _scrollYNow(self, first, last): | ||
|  |         self._vertScrollbar.set(first, last) | ||
|  |         self._vertScrollbarNeeded = ((first, last) != ('0', '1')) | ||
|  | 
 | ||
|  |         if self['vscrollmode'] == 'dynamic': | ||
|  |             if self._vertScrollbarNeeded != self._vertScrollbarOn: | ||
|  |                 self._toggleVertScrollbar() | ||
|  | 
 | ||
|  |     def _scrollBothLater(self, first, last): | ||
|  | 	# Called by the listbox to set the horizontal or vertical | ||
|  | 	# scrollbar when it has scrolled or changed size or contents. | ||
|  | 
 | ||
|  | 	if self.scrollTimer is None: | ||
|  | 	    self.scrollTimer = self.after_idle(self._scrollBothNow) | ||
|  | 
 | ||
|  |     def _scrollBothNow(self): | ||
|  |         # This performs the function of _scrollXNow and _scrollYNow. | ||
|  |         # If one is changed, the other should be updated to match. | ||
|  | 	self.scrollTimer = None | ||
|  | 
 | ||
|  |         # Call update_idletasks to make sure that the containing frame | ||
|  |         # has been resized before we attempt to set the scrollbars.  | ||
|  |         # Otherwise the scrollbars may be mapped/unmapped continuously. | ||
|  |         self._scrollRecurse = self._scrollRecurse + 1 | ||
|  |         self.update_idletasks() | ||
|  |         self._scrollRecurse = self._scrollRecurse - 1 | ||
|  |         if self._scrollRecurse != 0: | ||
|  |             return | ||
|  | 
 | ||
|  | 	xview = self._listbox.xview() | ||
|  | 	yview = self._listbox.yview() | ||
|  | 	self._horizScrollbar.set(xview[0], xview[1]) | ||
|  | 	self._vertScrollbar.set(yview[0], yview[1]) | ||
|  | 
 | ||
|  | 	self._horizScrollbarNeeded = (xview != (0.0, 1.0)) | ||
|  | 	self._vertScrollbarNeeded = (yview != (0.0, 1.0)) | ||
|  | 
 | ||
|  | 	# If both horizontal and vertical scrollmodes are dynamic and | ||
|  | 	# currently only one scrollbar is mapped and both should be | ||
|  | 	# toggled, then unmap the mapped scrollbar.  This prevents a | ||
|  | 	# continuous mapping and unmapping of the scrollbars.  | ||
|  | 	if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and | ||
|  | 		self._horizScrollbarNeeded != self._horizScrollbarOn and | ||
|  | 		self._vertScrollbarNeeded != self._vertScrollbarOn and | ||
|  | 		self._vertScrollbarOn != self._horizScrollbarOn): | ||
|  | 	    if self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	    else: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	if self['hscrollmode'] == 'dynamic': | ||
|  | 	    if self._horizScrollbarNeeded != self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 
 | ||
|  | 	if self['vscrollmode'] == 'dynamic': | ||
|  | 	    if self._vertScrollbarNeeded != self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 
 | ||
|  |     def _toggleHorizScrollbar(self): | ||
|  | 
 | ||
|  | 	self._horizScrollbarOn = not self._horizScrollbarOn | ||
|  | 
 | ||
|  | 	interior = self.interior() | ||
|  | 	if self._horizScrollbarOn: | ||
|  | 	    self._horizScrollbar.grid(row = 4, column = 2, sticky = 'news') | ||
|  | 	    interior.grid_rowconfigure(3, minsize = self['scrollmargin']) | ||
|  | 	else: | ||
|  | 	    self._horizScrollbar.grid_forget() | ||
|  | 	    interior.grid_rowconfigure(3, minsize = 0) | ||
|  | 
 | ||
|  |     def _toggleVertScrollbar(self): | ||
|  | 
 | ||
|  | 	self._vertScrollbarOn = not self._vertScrollbarOn | ||
|  | 
 | ||
|  | 	interior = self.interior() | ||
|  | 	if self._vertScrollbarOn: | ||
|  | 	    self._vertScrollbar.grid(row = 2, column = 4, sticky = 'news') | ||
|  | 	    interior.grid_columnconfigure(3, minsize = self['scrollmargin']) | ||
|  | 	else: | ||
|  | 	    self._vertScrollbar.grid_forget() | ||
|  | 	    interior.grid_columnconfigure(3, minsize = 0) | ||
|  | 
 | ||
|  |     def _handleEvent(self, event, eventType): | ||
|  |         if eventType == 'double': | ||
|  |             command = self['dblclickcommand'] | ||
|  |         elif eventType == 'key': | ||
|  |             command = self['selectioncommand'] | ||
|  |         else: #eventType == 'release' | ||
|  |             # Do not execute the command if the mouse was released | ||
|  |             # outside the listbox. | ||
|  |             if (event.x < 0 or self._listbox.winfo_width() <= event.x or | ||
|  |                     event.y < 0 or self._listbox.winfo_height() <= event.y): | ||
|  |                 return | ||
|  | 
 | ||
|  |             command = self['selectioncommand'] | ||
|  | 
 | ||
|  |         if callable(command): | ||
|  |             command() | ||
|  | 
 | ||
|  |     # Need to explicitly forward this to override the stupid | ||
|  |     # (grid_)size method inherited from Tkinter.Frame.Grid. | ||
|  |     def size(self): | ||
|  | 	return self._listbox.size() | ||
|  | 
 | ||
|  |     # Need to explicitly forward this to override the stupid | ||
|  |     # (grid_)bbox method inherited from Tkinter.Frame.Grid. | ||
|  |     def bbox(self, index): | ||
|  | 	return self._listbox.bbox(index) | ||
|  | 
 | ||
|  | forwardmethods(ScrolledListBox, Tkinter.Listbox, '_listbox') | ||
|  | 
 | ||
|  | # ====================================================================== | ||
|  | 
 | ||
|  | _listboxCache = {} | ||
|  | 
 | ||
|  | def _registerScrolledList(listbox, scrolledList): | ||
|  |     # Register an ScrolledList widget for a Listbox widget | ||
|  | 
 | ||
|  |     _listboxCache[listbox] = scrolledList | ||
|  | 
 | ||
|  | def _deregisterScrolledList(listbox): | ||
|  |     # Deregister a Listbox widget | ||
|  |     del _listboxCache[listbox] | ||
|  | 
 | ||
|  | def _handleEvent(event, eventType): | ||
|  |     # Forward events for a Listbox to it's ScrolledListBox | ||
|  | 
 | ||
|  |     # A binding earlier in the bindtags list may have destroyed the | ||
|  |     # megawidget, so need to check. | ||
|  |     if _listboxCache.has_key(event.widget): | ||
|  |         _listboxCache[event.widget]._handleEvent(event, eventType) | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwScrolledText.py | ||
|  | # Based on iwidgets2.2.0/scrolledtext.itk code.    | ||
|  | 
 | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class ScrolledText(MegaWidget): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('borderframe',    0,            INITOPT), | ||
|  | 	    ('columnheader',   0,            INITOPT), | ||
|  | 	    ('hscrollmode',    'dynamic',    self._hscrollMode), | ||
|  | 	    ('labelmargin',    0,            INITOPT), | ||
|  | 	    ('labelpos',       None,         INITOPT), | ||
|  | 	    ('rowcolumnheader',0,            INITOPT), | ||
|  | 	    ('rowheader',      0,            INITOPT), | ||
|  | 	    ('scrollmargin',   2,            INITOPT), | ||
|  | 	    ('usehullsize',    0,            INITOPT), | ||
|  | 	    ('vscrollmode',    'dynamic',    self._vscrollMode), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 
 | ||
|  | 	if self['usehullsize']: | ||
|  | 	    interior.grid_propagate(0) | ||
|  | 
 | ||
|  | 	if self['borderframe']: | ||
|  | 	    # Create a frame widget to act as the border of the text  | ||
|  | 	    # widget.  Later, pack the text widget so that it fills | ||
|  | 	    # the frame.  This avoids a problem in Tk, where window | ||
|  | 	    # items in a text widget may overlap the border of the | ||
|  | 	    # text widget. | ||
|  | 	    self._borderframe = self.createcomponent('borderframe', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Frame, (interior,), | ||
|  | 		    relief = 'sunken', | ||
|  | 		    borderwidth = 2, | ||
|  | 	    ) | ||
|  | 	    self._borderframe.grid(row = 4, column = 4, sticky = 'news') | ||
|  | 
 | ||
|  | 	    # Create the text widget. | ||
|  | 	    self._textbox = self.createcomponent('text', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Text, (self._borderframe,), | ||
|  | 		    highlightthickness = 0, | ||
|  | 		    borderwidth = 0, | ||
|  | 	    ) | ||
|  | 	    self._textbox.pack(fill = 'both', expand = 1) | ||
|  | 
 | ||
|  |             bw = self._borderframe.cget('borderwidth'), | ||
|  |             ht = self._borderframe.cget('highlightthickness'), | ||
|  | 	else: | ||
|  | 	    # Create the text widget. | ||
|  | 	    self._textbox = self.createcomponent('text', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Text, (interior,), | ||
|  | 	    ) | ||
|  | 	    self._textbox.grid(row = 4, column = 4, sticky = 'news') | ||
|  | 
 | ||
|  |             bw = self._textbox.cget('borderwidth'), | ||
|  |             ht = self._textbox.cget('highlightthickness'), | ||
|  | 
 | ||
|  |         # Create the header text widgets | ||
|  |         if self['columnheader']: | ||
|  |             self._columnheader = self.createcomponent('columnheader', | ||
|  |                     (), 'Header', | ||
|  |                     Tkinter.Text, (interior,), | ||
|  |                     height=1, | ||
|  |                     wrap='none', | ||
|  |                     borderwidth = bw, | ||
|  |                     highlightthickness = ht, | ||
|  |             ) | ||
|  |             self._columnheader.grid(row = 2, column = 4, sticky = 'ew') | ||
|  |             self._columnheader.configure( | ||
|  |                     xscrollcommand = self._columnheaderscrolled) | ||
|  | 
 | ||
|  |         if self['rowheader']: | ||
|  |             self._rowheader = self.createcomponent('rowheader', | ||
|  |                     (), 'Header', | ||
|  |                     Tkinter.Text, (interior,), | ||
|  |                     wrap='none', | ||
|  |                     borderwidth = bw, | ||
|  |                     highlightthickness = ht, | ||
|  |             ) | ||
|  |             self._rowheader.grid(row = 4, column = 2, sticky = 'ns') | ||
|  |             self._rowheader.configure( | ||
|  |                     yscrollcommand = self._rowheaderscrolled) | ||
|  | 
 | ||
|  |         if self['rowcolumnheader']: | ||
|  |             self._rowcolumnheader = self.createcomponent('rowcolumnheader', | ||
|  |                     (), 'Header', | ||
|  |                     Tkinter.Text, (interior,), | ||
|  |                     height=1, | ||
|  |                     wrap='none', | ||
|  |                     borderwidth = bw, | ||
|  |                     highlightthickness = ht, | ||
|  |             ) | ||
|  |             self._rowcolumnheader.grid(row = 2, column = 2, sticky = 'nsew') | ||
|  | 
 | ||
|  | 	interior.grid_rowconfigure(4, weight = 1, minsize = 0) | ||
|  | 	interior.grid_columnconfigure(4, weight = 1, minsize = 0) | ||
|  | 
 | ||
|  | 	# Create the horizontal scrollbar | ||
|  | 	self._horizScrollbar = self.createcomponent('horizscrollbar', | ||
|  | 		(), 'Scrollbar', | ||
|  | 		Tkinter.Scrollbar, (interior,), | ||
|  | 	        orient='horizontal', | ||
|  | 		command=self._textbox.xview | ||
|  | 	) | ||
|  | 
 | ||
|  | 	# Create the vertical scrollbar | ||
|  | 	self._vertScrollbar = self.createcomponent('vertscrollbar', | ||
|  | 		(), 'Scrollbar', | ||
|  | 		Tkinter.Scrollbar, (interior,), | ||
|  | 		orient='vertical', | ||
|  | 		command=self._textbox.yview | ||
|  | 	) | ||
|  | 
 | ||
|  | 	self.createlabel(interior, childCols = 5, childRows = 5) | ||
|  | 
 | ||
|  | 	# Initialise instance variables. | ||
|  | 	self._horizScrollbarOn = 0 | ||
|  | 	self._vertScrollbarOn = 0 | ||
|  | 	self.scrollTimer = None | ||
|  |         self._scrollRecurse = 0 | ||
|  | 	self._horizScrollbarNeeded = 0 | ||
|  | 	self._vertScrollbarNeeded = 0 | ||
|  | 	self._textWidth = None | ||
|  | 
 | ||
|  |         # These four variables avoid an infinite loop caused by the | ||
|  |         # row or column header's scrollcommand causing the main text | ||
|  |         # widget's scrollcommand to be called and vice versa. | ||
|  | 	self._textboxLastX = None | ||
|  | 	self._textboxLastY = None | ||
|  | 	self._columnheaderLastX = None | ||
|  | 	self._rowheaderLastY = None | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  | 	if self.scrollTimer is not None: | ||
|  | 	    self.after_cancel(self.scrollTimer) | ||
|  | 	    self.scrollTimer = None | ||
|  | 	MegaWidget.destroy(self) | ||
|  | 
 | ||
|  |     # ====================================================================== | ||
|  | 
 | ||
|  |     # Public methods. | ||
|  | 
 | ||
|  |     def clear(self): | ||
|  | 	self.settext('') | ||
|  | 
 | ||
|  |     def importfile(self, fileName, where = 'end'): | ||
|  | 	file = open(fileName, 'r') | ||
|  | 	self._textbox.insert(where, file.read()) | ||
|  | 	file.close() | ||
|  | 
 | ||
|  |     def exportfile(self, fileName): | ||
|  | 	file = open(fileName, 'w') | ||
|  | 	file.write(self._textbox.get('1.0', 'end')) | ||
|  | 	file.close() | ||
|  | 
 | ||
|  |     def settext(self, text): | ||
|  | 	disabled = (str(self._textbox.cget('state')) == 'disabled') | ||
|  | 	if disabled: | ||
|  | 	    self._textbox.configure(state='normal') | ||
|  | 	self._textbox.delete('0.0', 'end') | ||
|  | 	self._textbox.insert('end', text) | ||
|  | 	if disabled: | ||
|  | 	    self._textbox.configure(state='disabled') | ||
|  | 
 | ||
|  |     # Override Tkinter.Text get method, so that if it is called with | ||
|  |     # no arguments, return all text (consistent with other widgets). | ||
|  |     def get(self, first=None, last=None): | ||
|  | 	if first is None: | ||
|  | 	    return self._textbox.get('1.0', 'end') | ||
|  | 	else: | ||
|  | 	    return self._textbox.get(first, last) | ||
|  | 
 | ||
|  |     def getvalue(self): | ||
|  |         return self.get() | ||
|  | 
 | ||
|  |     def setvalue(self, text): | ||
|  |         return self.settext(text) | ||
|  | 
 | ||
|  |     def appendtext(self, text): | ||
|  |         oldTop, oldBottom = self._textbox.yview() | ||
|  |       | ||
|  |         disabled = (str(self._textbox.cget('state')) == 'disabled') | ||
|  |         if disabled: | ||
|  |             self._textbox.configure(state='normal') | ||
|  |         self._textbox.insert('end', text) | ||
|  |         if disabled: | ||
|  |             self._textbox.configure(state='disabled') | ||
|  |       | ||
|  |         if oldBottom == 1.0: | ||
|  |             self._textbox.yview('moveto', 1.0) | ||
|  | 
 | ||
|  |     # ====================================================================== | ||
|  | 
 | ||
|  |     # Configuration methods. | ||
|  | 
 | ||
|  |     def _hscrollMode(self): | ||
|  | 	# The horizontal scroll mode has been configured. | ||
|  | 
 | ||
|  | 	mode = self['hscrollmode'] | ||
|  | 
 | ||
|  | 	if mode == 'static': | ||
|  | 	    if not self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	elif mode == 'dynamic': | ||
|  | 	    if self._horizScrollbarNeeded != self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	elif mode == 'none': | ||
|  | 	    if self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	else: | ||
|  | 	    message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode | ||
|  | 	    raise ValueError, message | ||
|  | 
 | ||
|  |         self._configureScrollCommands() | ||
|  | 
 | ||
|  |     def _vscrollMode(self): | ||
|  | 	# The vertical scroll mode has been configured. | ||
|  | 
 | ||
|  | 	mode = self['vscrollmode'] | ||
|  | 
 | ||
|  | 	if mode == 'static': | ||
|  | 	    if not self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	elif mode == 'dynamic': | ||
|  | 	    if self._vertScrollbarNeeded != self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	elif mode == 'none': | ||
|  | 	    if self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	else: | ||
|  | 	    message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode | ||
|  | 	    raise ValueError, message | ||
|  | 
 | ||
|  |         self._configureScrollCommands() | ||
|  | 
 | ||
|  |     # ====================================================================== | ||
|  | 
 | ||
|  |     # Private methods. | ||
|  | 
 | ||
|  |     def _configureScrollCommands(self): | ||
|  |         # If both scrollmodes are not dynamic we can save a lot of | ||
|  |         # time by not having to create an idle job to handle the | ||
|  |         # scroll commands. | ||
|  | 
 | ||
|  |         # Clean up previous scroll commands to prevent memory leak. | ||
|  |         tclCommandName = str(self._textbox.cget('xscrollcommand')) | ||
|  |         if tclCommandName != '':    | ||
|  |             self._textbox.deletecommand(tclCommandName) | ||
|  |         tclCommandName = str(self._textbox.cget('yscrollcommand')) | ||
|  |         if tclCommandName != '':    | ||
|  |             self._textbox.deletecommand(tclCommandName) | ||
|  | 
 | ||
|  | 	if self['hscrollmode'] == self['vscrollmode'] == 'dynamic': | ||
|  |             self._textbox.configure( | ||
|  |                     xscrollcommand=self._scrollBothLater, | ||
|  |                     yscrollcommand=self._scrollBothLater | ||
|  |             ) | ||
|  |         else: | ||
|  |             self._textbox.configure( | ||
|  |                     xscrollcommand=self._scrollXNow, | ||
|  |                     yscrollcommand=self._scrollYNow | ||
|  |             ) | ||
|  | 
 | ||
|  |     def _scrollXNow(self, first, last): | ||
|  |         self._horizScrollbar.set(first, last) | ||
|  |         self._horizScrollbarNeeded = ((first, last) != ('0', '1')) | ||
|  | 
 | ||
|  |         # This code is the same as in _scrollBothNow.  Keep it that way. | ||
|  |         if self['hscrollmode'] == 'dynamic': | ||
|  |             currentWidth = self._textbox.winfo_width() | ||
|  |             if self._horizScrollbarNeeded != self._horizScrollbarOn: | ||
|  |                 if self._horizScrollbarNeeded or \ | ||
|  |                         self._textWidth != currentWidth: | ||
|  |                     self._toggleHorizScrollbar() | ||
|  |             self._textWidth = currentWidth | ||
|  | 
 | ||
|  |         if self['columnheader']: | ||
|  | 	    if self._columnheaderLastX != first: | ||
|  | 		self._columnheaderLastX = first | ||
|  | 		self._columnheader.xview('moveto', first) | ||
|  | 
 | ||
|  |     def _scrollYNow(self, first, last): | ||
|  |         if first == '0' and last == '0': | ||
|  |             return | ||
|  |         self._vertScrollbar.set(first, last) | ||
|  |         self._vertScrollbarNeeded = ((first, last) != ('0', '1')) | ||
|  | 
 | ||
|  |         if self['vscrollmode'] == 'dynamic': | ||
|  |             if self._vertScrollbarNeeded != self._vertScrollbarOn: | ||
|  |                 self._toggleVertScrollbar() | ||
|  | 
 | ||
|  |         if self['rowheader']: | ||
|  | 	    if self._rowheaderLastY != first: | ||
|  | 		self._rowheaderLastY = first | ||
|  | 		self._rowheader.yview('moveto', first) | ||
|  | 
 | ||
|  |     def _scrollBothLater(self, first, last): | ||
|  | 	# Called by the text widget to set the horizontal or vertical | ||
|  | 	# scrollbar when it has scrolled or changed size or contents. | ||
|  | 
 | ||
|  | 	if self.scrollTimer is None: | ||
|  | 	    self.scrollTimer = self.after_idle(self._scrollBothNow) | ||
|  | 
 | ||
|  |     def _scrollBothNow(self): | ||
|  |         # This performs the function of _scrollXNow and _scrollYNow. | ||
|  |         # If one is changed, the other should be updated to match. | ||
|  | 	self.scrollTimer = None | ||
|  | 
 | ||
|  |         # Call update_idletasks to make sure that the containing frame | ||
|  |         # has been resized before we attempt to set the scrollbars.  | ||
|  |         # Otherwise the scrollbars may be mapped/unmapped continuously. | ||
|  |         self._scrollRecurse = self._scrollRecurse + 1 | ||
|  |         self.update_idletasks() | ||
|  |         self._scrollRecurse = self._scrollRecurse - 1 | ||
|  |         if self._scrollRecurse != 0: | ||
|  |             return | ||
|  | 
 | ||
|  | 	xview = self._textbox.xview() | ||
|  | 	yview = self._textbox.yview() | ||
|  | 
 | ||
|  | 	# The text widget returns a yview of (0.0, 0.0) just after it | ||
|  | 	# has been created. Ignore this. | ||
|  | 	if yview == (0.0, 0.0): | ||
|  | 	    return | ||
|  | 
 | ||
|  |         if self['columnheader']: | ||
|  | 	    if self._columnheaderLastX != xview[0]: | ||
|  | 		self._columnheaderLastX = xview[0] | ||
|  | 		self._columnheader.xview('moveto', xview[0]) | ||
|  |         if self['rowheader']: | ||
|  | 	    if self._rowheaderLastY != yview[0]: | ||
|  | 		self._rowheaderLastY = yview[0] | ||
|  | 		self._rowheader.yview('moveto', yview[0]) | ||
|  | 
 | ||
|  | 	self._horizScrollbar.set(xview[0], xview[1]) | ||
|  | 	self._vertScrollbar.set(yview[0], yview[1]) | ||
|  | 
 | ||
|  | 	self._horizScrollbarNeeded = (xview != (0.0, 1.0)) | ||
|  | 	self._vertScrollbarNeeded = (yview != (0.0, 1.0)) | ||
|  | 
 | ||
|  | 	# If both horizontal and vertical scrollmodes are dynamic and | ||
|  | 	# currently only one scrollbar is mapped and both should be | ||
|  | 	# toggled, then unmap the mapped scrollbar.  This prevents a | ||
|  | 	# continuous mapping and unmapping of the scrollbars.  | ||
|  | 	if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and | ||
|  | 		self._horizScrollbarNeeded != self._horizScrollbarOn and | ||
|  | 		self._vertScrollbarNeeded != self._vertScrollbarOn and | ||
|  | 		self._vertScrollbarOn != self._horizScrollbarOn): | ||
|  | 	    if self._horizScrollbarOn: | ||
|  | 		self._toggleHorizScrollbar() | ||
|  | 	    else: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	if self['hscrollmode'] == 'dynamic': | ||
|  | 
 | ||
|  | 	    # The following test is done to prevent continuous | ||
|  | 	    # mapping and unmapping of the horizontal scrollbar.  | ||
|  | 	    # This may occur when some event (scrolling, resizing | ||
|  | 	    # or text changes) modifies the displayed text such | ||
|  | 	    # that the bottom line in the window is the longest | ||
|  | 	    # line displayed.  If this causes the horizontal | ||
|  | 	    # scrollbar to be mapped, the scrollbar may "cover up" | ||
|  | 	    # the bottom line, which would mean that the scrollbar | ||
|  | 	    # is no longer required.  If the scrollbar is then | ||
|  | 	    # unmapped, the bottom line will then become visible | ||
|  | 	    # again, which would cause the scrollbar to be mapped | ||
|  | 	    # again, and so on... | ||
|  | 	    # | ||
|  | 	    # The idea is that, if the width of the text widget | ||
|  | 	    # has not changed and the scrollbar is currently | ||
|  | 	    # mapped, then do not unmap the scrollbar even if it | ||
|  | 	    # is no longer required.  This means that, during | ||
|  | 	    # normal scrolling of the text, once the horizontal | ||
|  | 	    # scrollbar has been mapped it will not be unmapped | ||
|  | 	    # (until the width of the text widget changes). | ||
|  | 
 | ||
|  | 	    currentWidth = self._textbox.winfo_width() | ||
|  | 	    if self._horizScrollbarNeeded != self._horizScrollbarOn: | ||
|  | 		if self._horizScrollbarNeeded or \ | ||
|  | 			self._textWidth != currentWidth: | ||
|  | 		    self._toggleHorizScrollbar() | ||
|  | 	    self._textWidth = currentWidth | ||
|  | 
 | ||
|  | 	if self['vscrollmode'] == 'dynamic': | ||
|  | 	    if self._vertScrollbarNeeded != self._vertScrollbarOn: | ||
|  | 		self._toggleVertScrollbar() | ||
|  | 
 | ||
|  |     def _columnheaderscrolled(self, first, last): | ||
|  | 	if self._textboxLastX != first: | ||
|  | 	    self._textboxLastX = first | ||
|  | 	    self._textbox.xview('moveto', first) | ||
|  | 
 | ||
|  |     def _rowheaderscrolled(self, first, last): | ||
|  | 	if self._textboxLastY != first: | ||
|  | 	    self._textboxLastY = first | ||
|  | 	    self._textbox.yview('moveto', first) | ||
|  | 
 | ||
|  |     def _toggleHorizScrollbar(self): | ||
|  | 
 | ||
|  | 	self._horizScrollbarOn = not self._horizScrollbarOn | ||
|  | 
 | ||
|  | 	interior = self.interior() | ||
|  | 	if self._horizScrollbarOn: | ||
|  | 	    self._horizScrollbar.grid(row = 6, column = 4, sticky = 'news') | ||
|  | 	    interior.grid_rowconfigure(5, minsize = self['scrollmargin']) | ||
|  | 	else: | ||
|  | 	    self._horizScrollbar.grid_forget() | ||
|  | 	    interior.grid_rowconfigure(5, minsize = 0) | ||
|  | 
 | ||
|  |     def _toggleVertScrollbar(self): | ||
|  | 
 | ||
|  | 	self._vertScrollbarOn = not self._vertScrollbarOn | ||
|  | 
 | ||
|  | 	interior = self.interior() | ||
|  | 	if self._vertScrollbarOn: | ||
|  | 	    self._vertScrollbar.grid(row = 4, column = 6, sticky = 'news') | ||
|  | 	    interior.grid_columnconfigure(5, minsize = self['scrollmargin']) | ||
|  | 	else: | ||
|  | 	    self._vertScrollbar.grid_forget() | ||
|  | 	    interior.grid_columnconfigure(5, minsize = 0) | ||
|  | 
 | ||
|  |     # Need to explicitly forward this to override the stupid | ||
|  |     # (grid_)bbox method inherited from Tkinter.Frame.Grid. | ||
|  |     def bbox(self, index): | ||
|  | 	return self._textbox.bbox(index) | ||
|  | 
 | ||
|  | forwardmethods(ScrolledText, Tkinter.Text, '_textbox') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwHistoryText.py | ||
|  | 
 | ||
|  | 
 | ||
|  | _ORIGINAL = 0 | ||
|  | _MODIFIED = 1 | ||
|  | _DISPLAY = 2 | ||
|  | 
 | ||
|  | class HistoryText(ScrolledText): | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  |         # Define the megawidget options. | ||
|  |         optiondefs = ( | ||
|  | 	    ('compressany',         1,          None), | ||
|  | 	    ('compresstail',        1,          None), | ||
|  |             ('historycommand',      None,       None), | ||
|  |         ) | ||
|  |         self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  |         # Initialise the base class (after defining the options). | ||
|  |         ScrolledText.__init__(self, parent) | ||
|  | 
 | ||
|  |         # Initialise instance variables. | ||
|  | 	self._list = [] | ||
|  | 	self._currIndex = 0 | ||
|  | 	self._pastIndex = None | ||
|  | 	self._lastIndex = 0          # pointer to end of history list | ||
|  | 
 | ||
|  |         # Check keywords and initialise options. | ||
|  |         self.initialiseoptions() | ||
|  | 
 | ||
|  |     def addhistory(self): | ||
|  | 	text = self.get() | ||
|  | 	if text[-1] == '\n': | ||
|  | 	    text = text[:-1] | ||
|  | 
 | ||
|  | 	if len(self._list) == 0: | ||
|  |             # This is the first history entry.  Add it. | ||
|  | 	    self._list.append([text, text, _MODIFIED]) | ||
|  |             return | ||
|  | 
 | ||
|  |         currentEntry =  self._list[self._currIndex] | ||
|  |         if text == currentEntry[_ORIGINAL]: | ||
|  |             # The current history entry has not been modified. Check if | ||
|  |             # we need to add it again. | ||
|  | 
 | ||
|  |             if self['compresstail'] and self._currIndex == self._lastIndex: | ||
|  |                 return | ||
|  | 
 | ||
|  |             if self['compressany']: | ||
|  |                 return | ||
|  | 
 | ||
|  |         # Undo any changes for the current history entry, since they | ||
|  |         # will now be available in the new entry. | ||
|  |         currentEntry[_MODIFIED] = currentEntry[_ORIGINAL] | ||
|  | 
 | ||
|  |         historycommand = self['historycommand'] | ||
|  |         if self._currIndex == self._lastIndex: | ||
|  |             # The last history entry is currently being displayed, | ||
|  |             # so disable the special meaning of the 'Next' button. | ||
|  |             self._pastIndex = None | ||
|  |             nextState = 'disabled' | ||
|  |         else: | ||
|  |             # A previous history entry is currently being displayed, | ||
|  |             # so allow the 'Next' button to go to the entry after this one. | ||
|  |             self._pastIndex = self._currIndex | ||
|  |             nextState = 'normal' | ||
|  |         if callable(historycommand): | ||
|  |             historycommand('normal', nextState) | ||
|  | 
 | ||
|  |         # Create the new history entry. | ||
|  |         self._list.append([text, text, _MODIFIED]) | ||
|  | 
 | ||
|  |         # Move the pointer into the history entry list to the end. | ||
|  |         self._lastIndex = self._lastIndex + 1 | ||
|  |         self._currIndex = self._lastIndex | ||
|  | 
 | ||
|  |     def next(self): | ||
|  | 	if self._currIndex == self._lastIndex and self._pastIndex is None: | ||
|  | 	    self.bell() | ||
|  |         else: | ||
|  |             self._modifyDisplay('next') | ||
|  | 
 | ||
|  |     def prev(self): | ||
|  |         self._pastIndex = None | ||
|  | 	if self._currIndex == 0: | ||
|  | 	    self.bell() | ||
|  |         else: | ||
|  |             self._modifyDisplay('prev') | ||
|  | 
 | ||
|  |     def undo(self): | ||
|  | 	if len(self._list) != 0: | ||
|  |             self._modifyDisplay('undo') | ||
|  | 
 | ||
|  |     def redo(self): | ||
|  | 	if len(self._list) != 0: | ||
|  |             self._modifyDisplay('redo') | ||
|  | 
 | ||
|  |     def gethistory(self): | ||
|  |         return self._list | ||
|  | 
 | ||
|  |     def _modifyDisplay(self, command): | ||
|  |         # Modify the display to show either the next or previous | ||
|  |         # history entry (next, prev) or the original or modified | ||
|  |         # version of the current history entry (undo, redo). | ||
|  | 
 | ||
|  |         # Save the currently displayed text. | ||
|  |         currentText = self.get() | ||
|  |         if currentText[-1] == '\n': | ||
|  |             currentText = currentText[:-1] | ||
|  | 
 | ||
|  |         currentEntry =  self._list[self._currIndex] | ||
|  |         if currentEntry[_DISPLAY] == _MODIFIED: | ||
|  |             currentEntry[_MODIFIED] = currentText | ||
|  |         elif currentEntry[_ORIGINAL] != currentText: | ||
|  |             currentEntry[_MODIFIED] = currentText | ||
|  |             if command in ('next', 'prev'): | ||
|  |                 currentEntry[_DISPLAY] = _MODIFIED | ||
|  | 
 | ||
|  |         if command in ('next', 'prev'): | ||
|  |             prevstate = 'normal' | ||
|  |             nextstate = 'normal' | ||
|  |             if command == 'next': | ||
|  |                 if self._pastIndex is not None: | ||
|  |                     self._currIndex = self._pastIndex | ||
|  |                     self._pastIndex = None | ||
|  |                 self._currIndex = self._currIndex + 1 | ||
|  |                 if self._currIndex == self._lastIndex: | ||
|  |                     nextstate = 'disabled' | ||
|  |             elif command == 'prev': | ||
|  |                 self._currIndex = self._currIndex - 1 | ||
|  |                 if self._currIndex == 0: | ||
|  |                     prevstate = 'disabled' | ||
|  |             historycommand = self['historycommand'] | ||
|  |             if callable(historycommand): | ||
|  |                 historycommand(prevstate, nextstate) | ||
|  |             currentEntry =  self._list[self._currIndex] | ||
|  |         else: | ||
|  |             if command == 'undo': | ||
|  |                 currentEntry[_DISPLAY] = _ORIGINAL | ||
|  |             elif command == 'redo': | ||
|  |                 currentEntry[_DISPLAY] = _MODIFIED | ||
|  | 
 | ||
|  |         # Display the new text. | ||
|  |         self.delete('1.0', 'end') | ||
|  |         self.insert('end', currentEntry[currentEntry[_DISPLAY]]) | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwSelectionDialog.py | ||
|  | # Not Based on iwidgets version. | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | class SelectionDialog(Dialog): | ||
|  |     # Dialog window with selection list. | ||
|  |      | ||
|  |     # Dialog window displaying a list and requesting the user to | ||
|  |     # select one. | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('borderx',     10,    INITOPT), | ||
|  | 	    ('bordery',     10,    INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	Dialog.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 	aliases = ( | ||
|  | 	    ('listbox', 'scrolledlist_listbox'), | ||
|  | 	    ('label', 'scrolledlist_label'), | ||
|  | 	) | ||
|  | 	self._list = self.createcomponent('scrolledlist', | ||
|  | 		aliases, None, | ||
|  | 		ScrolledListBox, (interior,), | ||
|  | 		dblclickcommand = self.invoke) | ||
|  | 	self._list.pack(side='top', expand='true', fill='both', | ||
|  | 		padx = self['borderx'], pady = self['bordery']) | ||
|  | 
 | ||
|  |         if not kw.has_key('activatecommand'): | ||
|  |             # Whenever this dialog is activated, set the focus to the | ||
|  |             # ScrolledListBox's listbox widget. | ||
|  |             listbox = self.component('listbox') | ||
|  |             self.configure(activatecommand = listbox.focus_set) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     # Need to explicitly forward this to override the stupid | ||
|  |     # (grid_)size method inherited from Tkinter.Toplevel.Grid. | ||
|  |     def size(self): | ||
|  | 	return self.component('listbox').size() | ||
|  | 
 | ||
|  |     # Need to explicitly forward this to override the stupid | ||
|  |     # (grid_)bbox method inherited from Tkinter.Toplevel.Grid. | ||
|  |     def bbox(self, index): | ||
|  | 	return self.component('listbox').size(index) | ||
|  | 
 | ||
|  | forwardmethods(SelectionDialog, ScrolledListBox, '_list') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwTextDialog.py | ||
|  | # A Dialog with a ScrolledText widget. | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | class TextDialog(Dialog): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('borderx',     10,    INITOPT), | ||
|  | 	    ('bordery',     10,    INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	Dialog.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 	aliases = ( | ||
|  | 	    ('text', 'scrolledtext_text'), | ||
|  | 	    ('label', 'scrolledtext_label'), | ||
|  | 	) | ||
|  | 	self._text = self.createcomponent('scrolledtext', | ||
|  | 		aliases, None, | ||
|  | 		ScrolledText, (interior,)) | ||
|  | 	self._text.pack(side='top', expand=1, fill='both', | ||
|  | 		padx = self['borderx'], pady = self['bordery']) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     # Need to explicitly forward this to override the stupid | ||
|  |     # (grid_)bbox method inherited from Tkinter.Toplevel.Grid. | ||
|  |     def bbox(self, index): | ||
|  | 	return self._text.bbox(index) | ||
|  | 
 | ||
|  | forwardmethods(TextDialog, ScrolledText, '_text') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwTimeCounter.py | ||
|  | # Authors: Joe VanAndel and Greg McFarlane | ||
|  | 
 | ||
|  | import string | ||
|  | import sys | ||
|  | import time | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class TimeCounter(MegaWidget): | ||
|  |     """Up-down counter
 | ||
|  | 
 | ||
|  |     A TimeCounter is a single-line entry widget with Up and Down arrows | ||
|  |     which increment and decrement the Time value in the entry.   | ||
|  |     """
 | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('autorepeat',    1,    None), | ||
|  | 	    ('buttonaspect',  1.0,  INITOPT), | ||
|  | 	    ('command',       None, None), | ||
|  | 	    ('initwait',      300,  None), | ||
|  | 	    ('labelmargin',   0,    INITOPT), | ||
|  | 	    ('labelpos',      None, INITOPT), | ||
|  | 	    ('max',           None, self._max), | ||
|  | 	    ('min',           None, self._min), | ||
|  | 	    ('padx',          0,    INITOPT), | ||
|  | 	    ('pady',          0,    INITOPT), | ||
|  | 	    ('repeatrate',    50,   None), | ||
|  | 	    ('value',         None, INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  |     	self.arrowDirection = {} | ||
|  | 	self._flag = 'stopped' | ||
|  | 	self._timerId = None | ||
|  | 
 | ||
|  | 	self._createComponents(kw) | ||
|  | 
 | ||
|  | 	value = self['value'] | ||
|  | 	if value is None: | ||
|  | 	    now = time.time() | ||
|  | 	    value = time.strftime('%H:%M:%S', time.localtime(now)) | ||
|  |     	self.setvalue(value) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def _createComponents(self, kw): | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 
 | ||
|  | 	# If there is no label, put the arrows and the entry directly | ||
|  | 	# into the interior, otherwise create a frame for them.  In | ||
|  | 	# either case the border around the arrows and the entry will | ||
|  | 	# be raised (but not around the label). | ||
|  | 	if self['labelpos'] is None: | ||
|  | 	    frame = interior | ||
|  |             if not kw.has_key('hull_relief'): | ||
|  |                 frame.configure(relief = 'raised') | ||
|  |             if not kw.has_key('hull_borderwidth'): | ||
|  |                 frame.configure(borderwidth = 1) | ||
|  | 	else: | ||
|  | 	    frame = self.createcomponent('frame', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Frame, (interior,), | ||
|  |                     relief = 'raised', borderwidth = 1) | ||
|  | 	    frame.grid(column=2, row=2, sticky='nsew') | ||
|  | 	    interior.grid_columnconfigure(2, weight=1) | ||
|  | 	    interior.grid_rowconfigure(2, weight=1) | ||
|  | 
 | ||
|  | 	# Create the down arrow buttons. | ||
|  | 
 | ||
|  | 	# Create the hour down arrow. | ||
|  | 	self._downHourArrowBtn = self.createcomponent('downhourarrow', | ||
|  | 		(), 'Arrow', | ||
|  | 		Tkinter.Canvas, (frame,), | ||
|  | 		width = 16, height = 16, relief = 'raised', borderwidth = 2) | ||
|  |     	self.arrowDirection[self._downHourArrowBtn] = 'down' | ||
|  | 	self._downHourArrowBtn.grid(column = 0, row = 2) | ||
|  | 
 | ||
|  | 	# Create the minute down arrow. | ||
|  | 	self._downMinuteArrowBtn = self.createcomponent('downminutearrow', | ||
|  | 		(), 'Arrow', | ||
|  | 		Tkinter.Canvas, (frame,), | ||
|  | 		width = 16, height = 16, relief = 'raised', borderwidth = 2) | ||
|  |     	self.arrowDirection[self._downMinuteArrowBtn] = 'down' | ||
|  | 	self._downMinuteArrowBtn.grid(column = 1, row = 2) | ||
|  | 
 | ||
|  | 	# Create the second down arrow. | ||
|  | 	self._downSecondArrowBtn = self.createcomponent('downsecondarrow', | ||
|  | 		(), 'Arrow', | ||
|  | 		Tkinter.Canvas, (frame,), | ||
|  | 		width = 16, height = 16, relief = 'raised', borderwidth = 2) | ||
|  |     	self.arrowDirection[self._downSecondArrowBtn] = 'down' | ||
|  | 	self._downSecondArrowBtn.grid(column = 2, row = 2) | ||
|  | 
 | ||
|  | 	# Create the entry fields. | ||
|  | 
 | ||
|  | 	# Create the hour entry field. | ||
|  | 	self._hourCounterEntry = self.createcomponent('hourentryfield', | ||
|  | 		(('hourentry', 'hourentryfield_entry'),), None, | ||
|  | 		EntryField, (frame,), validate='integer', entry_width = 2) | ||
|  | 	self._hourCounterEntry.grid(column = 0, row = 1, sticky = 'news') | ||
|  | 
 | ||
|  | 	# Create the minute entry field. | ||
|  | 	self._minuteCounterEntry = self.createcomponent('minuteentryfield', | ||
|  | 		(('minuteentry', 'minuteentryfield_entry'),), None, | ||
|  | 		EntryField, (frame,), validate='integer', entry_width = 2) | ||
|  | 	self._minuteCounterEntry.grid(column = 1, row = 1, sticky = 'news') | ||
|  | 
 | ||
|  | 	# Create the second entry field. | ||
|  | 	self._secondCounterEntry = self.createcomponent('secondentryfield', | ||
|  | 		(('secondentry', 'secondentryfield_entry'),), None, | ||
|  | 		EntryField, (frame,), validate='integer', entry_width = 2) | ||
|  | 	self._secondCounterEntry.grid(column = 2, row = 1, sticky = 'news') | ||
|  | 
 | ||
|  | 	# Create the up arrow buttons. | ||
|  | 
 | ||
|  | 	# Create the hour up arrow. | ||
|  | 	self._upHourArrowBtn = self.createcomponent('uphourarrow', | ||
|  | 		(), 'Arrow', | ||
|  | 		Tkinter.Canvas, (frame,), | ||
|  | 		width = 16, height = 16, relief = 'raised', borderwidth = 2) | ||
|  |     	self.arrowDirection[self._upHourArrowBtn] = 'up' | ||
|  | 	self._upHourArrowBtn.grid(column = 0, row = 0) | ||
|  | 
 | ||
|  | 	# Create the minute up arrow. | ||
|  | 	self._upMinuteArrowBtn = self.createcomponent('upminutearrow', | ||
|  | 		(), 'Arrow', | ||
|  | 		Tkinter.Canvas, (frame,), | ||
|  | 		width = 16, height = 16, relief = 'raised', borderwidth = 2) | ||
|  |     	self.arrowDirection[self._upMinuteArrowBtn] = 'up' | ||
|  | 	self._upMinuteArrowBtn.grid(column = 1, row = 0) | ||
|  | 
 | ||
|  | 	# Create the second up arrow. | ||
|  | 	self._upSecondArrowBtn = self.createcomponent('upsecondarrow', | ||
|  | 		(), 'Arrow', | ||
|  | 		Tkinter.Canvas, (frame,), | ||
|  | 		width = 16, height = 16, relief = 'raised', borderwidth = 2) | ||
|  |     	self.arrowDirection[self._upSecondArrowBtn] = 'up' | ||
|  | 	self._upSecondArrowBtn.grid(column = 2, row = 0) | ||
|  | 
 | ||
|  | 	# Make it resize nicely. | ||
|  | 	padx = self['padx'] | ||
|  | 	pady = self['pady'] | ||
|  | 	for col in range(3): | ||
|  | 	    frame.grid_columnconfigure(col, weight = 1, pad = padx) | ||
|  | 	frame.grid_rowconfigure(0, pad = pady) | ||
|  | 	frame.grid_rowconfigure(2, pad = pady) | ||
|  | 
 | ||
|  | 	frame.grid_rowconfigure(1, weight = 1) | ||
|  | 
 | ||
|  | 	# Create the label. | ||
|  | 	self.createlabel(interior) | ||
|  | 
 | ||
|  | 	# Set bindings. | ||
|  | 
 | ||
|  | 	# Up hour | ||
|  | 	self._upHourArrowBtn.bind('<Configure>',  | ||
|  | 		lambda  event, s=self,button=self._upHourArrowBtn:  | ||
|  | 		s._drawArrow(button, 'up')) | ||
|  | 
 | ||
|  | 	self._upHourArrowBtn.bind('<1>',  | ||
|  |     	    	lambda event, s=self,button=self._upHourArrowBtn:  | ||
|  | 		s._countUp(button, 3600)) | ||
|  | 
 | ||
|  | 	self._upHourArrowBtn.bind('<Any-ButtonRelease-1>',  | ||
|  | 		lambda event, s=self, button=self._upHourArrowBtn: | ||
|  | 		s._stopUpDown(button)) | ||
|  | 
 | ||
|  | 	# Up minute | ||
|  | 	self._upMinuteArrowBtn.bind('<Configure>',  | ||
|  | 		lambda  event, s=self,button=self._upMinuteArrowBtn:  | ||
|  | 		s._drawArrow(button, 'up')) | ||
|  | 	     | ||
|  | 
 | ||
|  | 	self._upMinuteArrowBtn.bind('<1>',  | ||
|  |     	    	lambda event, s=self,button=self._upMinuteArrowBtn:  | ||
|  | 		s._countUp(button, 60)) | ||
|  | 
 | ||
|  | 	self._upMinuteArrowBtn.bind('<Any-ButtonRelease-1>',  | ||
|  | 		lambda event, s=self, button=self._upMinuteArrowBtn: | ||
|  | 		s._stopUpDown(button)) | ||
|  | 
 | ||
|  | 	# Up second | ||
|  | 	self._upSecondArrowBtn.bind('<Configure>',  | ||
|  | 		lambda  event, s=self,button=self._upSecondArrowBtn:  | ||
|  | 		s._drawArrow(button, 'up')) | ||
|  | 	     | ||
|  | 
 | ||
|  | 	self._upSecondArrowBtn.bind('<1>',  | ||
|  |     	    	lambda event, s=self,button=self._upSecondArrowBtn:  | ||
|  | 		s._countUp(button, 1)) | ||
|  | 
 | ||
|  | 	self._upSecondArrowBtn.bind('<Any-ButtonRelease-1>',  | ||
|  | 		lambda event, s=self, button=self._upSecondArrowBtn: | ||
|  | 		s._stopUpDown(button)) | ||
|  | 
 | ||
|  | 	# Down hour | ||
|  | 	self._downHourArrowBtn.bind('<Configure>',  | ||
|  | 		lambda  event, s=self,button=self._downHourArrowBtn:  | ||
|  | 		s._drawArrow(button, 'down')) | ||
|  | 
 | ||
|  | 	self._downHourArrowBtn.bind('<1>',  | ||
|  |     	    	lambda event, s=self,button=self._downHourArrowBtn:  | ||
|  | 		s._countDown(button, 3600)) | ||
|  | 	self._downHourArrowBtn.bind('<Any-ButtonRelease-1>',  | ||
|  | 		lambda event, s=self, button=self._downHourArrowBtn: | ||
|  | 		s._stopUpDown(button)) | ||
|  | 
 | ||
|  | 
 | ||
|  | 	# Down minute | ||
|  | 	self._downMinuteArrowBtn.bind('<Configure>',  | ||
|  | 		lambda  event, s=self,button=self._downMinuteArrowBtn:  | ||
|  | 		s._drawArrow(button, 'down')) | ||
|  | 
 | ||
|  | 	self._downMinuteArrowBtn.bind('<1>',  | ||
|  |     	    	lambda event, s=self,button=self._downMinuteArrowBtn: | ||
|  |                 s._countDown(button, 60)) | ||
|  | 	self._downMinuteArrowBtn.bind('<Any-ButtonRelease-1>',  | ||
|  | 		lambda event, s=self, button=self._downMinuteArrowBtn: | ||
|  | 		s._stopUpDown(button)) | ||
|  | 
 | ||
|  | 	# Down second | ||
|  | 	self._downSecondArrowBtn.bind('<Configure>',  | ||
|  | 		lambda  event, s=self,button=self._downSecondArrowBtn:  | ||
|  | 		s._drawArrow(button, 'down')) | ||
|  | 
 | ||
|  | 	self._downSecondArrowBtn.bind('<1>',  | ||
|  |     	    	lambda event, s=self, button=self._downSecondArrowBtn:  | ||
|  | 		s._countDown(button,1)) | ||
|  | 	self._downSecondArrowBtn.bind('<Any-ButtonRelease-1>',  | ||
|  | 		lambda event, s=self, button=self._downSecondArrowBtn: | ||
|  | 		s._stopUpDown(button)) | ||
|  | 
 | ||
|  | 	self._hourCounterEntry.component('entry').bind( | ||
|  |                 '<Return>', self._invoke) | ||
|  | 	self._minuteCounterEntry.component('entry').bind( | ||
|  |         	'<Return>', self._invoke) | ||
|  | 	self._secondCounterEntry.component('entry').bind( | ||
|  |         	'<Return>', self._invoke) | ||
|  | 
 | ||
|  | 	self._hourCounterEntry.bind('<Configure>', self._resizeArrow) | ||
|  | 	self._minuteCounterEntry.bind('<Configure>', self._resizeArrow) | ||
|  | 	self._secondCounterEntry.bind('<Configure>', self._resizeArrow) | ||
|  | 
 | ||
|  |     def _drawArrow(self, arrow, direction): | ||
|  |         drawarrow(arrow, self['hourentry_foreground'], direction, 'arrow') | ||
|  | 
 | ||
|  |     def _resizeArrow(self, event = None): | ||
|  | 	for btn in (self._upHourArrowBtn, self._upMinuteArrowBtn, | ||
|  | 		self._upSecondArrowBtn, | ||
|  | 		self._downHourArrowBtn, | ||
|  | 		self._downMinuteArrowBtn, self._downSecondArrowBtn): | ||
|  | 	    bw = (string.atoi(btn['borderwidth']) + | ||
|  | 		    string.atoi(btn['highlightthickness'])) | ||
|  | 	    newHeight = self._hourCounterEntry.winfo_reqheight() - 2 * bw | ||
|  | 	    newWidth = int(newHeight * self['buttonaspect']) | ||
|  | 	    btn.configure(width=newWidth, height=newHeight) | ||
|  | 	    self._drawArrow(btn, self.arrowDirection[btn]) | ||
|  | 
 | ||
|  |     def _min(self): | ||
|  | 	min = self['min'] | ||
|  |         if min is None: | ||
|  | 	    self._minVal = 0 | ||
|  | 	else: | ||
|  | 	    self._minVal = timestringtoseconds(min) | ||
|  | 
 | ||
|  |     def _max(self): | ||
|  | 	max = self['max'] | ||
|  | 	if max is None: | ||
|  | 	    self._maxVal = None | ||
|  | 	else: | ||
|  | 	    self._maxVal = timestringtoseconds(max) | ||
|  | 
 | ||
|  |     def getvalue(self): | ||
|  |         return self.getstring() | ||
|  | 
 | ||
|  |     def setvalue(self, text): | ||
|  |         list = string.split(text, ':') | ||
|  | 	if len(list) != 3: | ||
|  | 	    raise ValueError, 'invalid value: ' + text | ||
|  | 
 | ||
|  | 	self._hour = string.atoi(list[0]) | ||
|  | 	self._minute = string.atoi(list[1]) | ||
|  | 	self._second = string.atoi(list[2])  | ||
|  | 
 | ||
|  |     	self._setHMS() | ||
|  | 
 | ||
|  |     def getstring(self): | ||
|  |     	return '%02d:%02d:%02d' % (self._hour, self._minute, self._second) | ||
|  | 
 | ||
|  |     def getint(self): | ||
|  |     	return self._hour * 3600 + self._minute * 60 + self._second | ||
|  | 
 | ||
|  |     def _countUp(self, button, increment): | ||
|  | 	self._relief = self._upHourArrowBtn.cget('relief') | ||
|  | 	button.configure(relief='sunken') | ||
|  | 	self._count(1, 'start', increment) | ||
|  | 
 | ||
|  |     def _countDown(self, button, increment): | ||
|  | 
 | ||
|  | 	self._relief = self._downHourArrowBtn.cget('relief') | ||
|  | 	button.configure(relief='sunken') | ||
|  | 	self._count(-1, 'start', increment) | ||
|  | 
 | ||
|  |     def increment(self, seconds = 1): | ||
|  | 	self._count(1, 'force', seconds) | ||
|  | 
 | ||
|  |     def decrement(self, seconds = 1): | ||
|  | 	self._count(-1, 'force', seconds) | ||
|  | 
 | ||
|  |     def _count(self, factor, newFlag = None, increment = 1): | ||
|  | 	if newFlag != 'force': | ||
|  | 	  if newFlag is not None: | ||
|  | 	    self._flag = newFlag | ||
|  | 
 | ||
|  | 	  if self._flag == 'stopped': | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	value = (string.atoi(self._hourCounterEntry.get()) *3600) + \ | ||
|  | 	      (string.atoi(self._minuteCounterEntry.get()) *60) + \ | ||
|  | 	      string.atoi(self._secondCounterEntry.get()) + \ | ||
|  | 	      factor * increment | ||
|  | 	min = self._minVal | ||
|  | 	max = self._maxVal | ||
|  | 	if value < min: | ||
|  | 	  value = min | ||
|  | 	if max is not None and value > max: | ||
|  | 	  value = max | ||
|  | 
 | ||
|  | 	self._hour = value /3600 | ||
|  | 	self._minute = (value - (self._hour*3600)) / 60 | ||
|  | 	self._second = value - (self._hour*3600) - (self._minute*60) | ||
|  | 	self._setHMS() | ||
|  | 
 | ||
|  | 	if newFlag != 'force': | ||
|  | 	  if self['autorepeat']: | ||
|  | 	    if self._flag == 'start': | ||
|  | 	      delay = self['initwait'] | ||
|  | 	      self._flag = 'running' | ||
|  | 	    else: | ||
|  | 	      delay = self['repeatrate'] | ||
|  | 	    self._timerId = self.after( | ||
|  | 		delay, lambda self=self, factor=factor,increment=increment:  | ||
|  | 		  self._count(factor,'running', increment)) | ||
|  | 
 | ||
|  |     def _setHMS(self): | ||
|  |         self._hourCounterEntry.setentry('%02d' % self._hour) | ||
|  |         self._minuteCounterEntry.setentry('%02d' % self._minute) | ||
|  |         self._secondCounterEntry.setentry('%02d' % self._second) | ||
|  | 
 | ||
|  |     def _stopUpDown(self, button): | ||
|  |         if self._timerId is not None: | ||
|  |             self.after_cancel(self._timerId) | ||
|  | 	    self._timerId = None | ||
|  |         button.configure(relief=self._relief) | ||
|  |         self._flag = 'stopped' | ||
|  | 
 | ||
|  |     def _invoke(self, event): | ||
|  |         cmd = self['command'] | ||
|  |         if callable(cmd): | ||
|  | 	    cmd() | ||
|  | 
 | ||
|  |     def invoke(self): | ||
|  |         cmd = self['command'] | ||
|  |         if callable(cmd): | ||
|  | 	    return cmd() | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  |         if self._timerId is not None: | ||
|  |             self.after_cancel(self._timerId) | ||
|  | 	    self._timerId = None | ||
|  |         MegaWidget.destroy(self) | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwAboutDialog.py | ||
|  | 
 | ||
|  | 
 | ||
|  | class AboutDialog(MessageDialog): | ||
|  |     # Window to display version and contact information. | ||
|  | 
 | ||
|  |     # Class members containing resettable 'default' values: | ||
|  |     _version = '' | ||
|  |     _copyright = '' | ||
|  |     _contact = '' | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('applicationname',   '',          INITOPT), | ||
|  | 	    ('iconpos',           'w',         None), | ||
|  | 	    ('icon_bitmap',       'info',      None), | ||
|  | 	    ('buttons',           ('Close',),  None), | ||
|  | 	    ('defaultbutton',     0,           None), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MessageDialog.__init__(self, parent) | ||
|  | 
 | ||
|  | 	applicationname = self['applicationname'] | ||
|  |         if not kw.has_key('title'): | ||
|  |             self.configure(title = 'About ' + applicationname) | ||
|  | 
 | ||
|  |         if not kw.has_key('message_text'): | ||
|  |             text = applicationname + '\n\n' | ||
|  |             if AboutDialog._version != '': | ||
|  |               text = text + 'Version ' + AboutDialog._version + '\n' | ||
|  |             if AboutDialog._copyright != '': | ||
|  |               text = text + AboutDialog._copyright + '\n\n' | ||
|  |             if AboutDialog._contact != '': | ||
|  |               text = text + AboutDialog._contact | ||
|  | 
 | ||
|  |             self.configure(message_text=text) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  | def aboutversion(value): | ||
|  |     AboutDialog._version = value | ||
|  | 
 | ||
|  | def aboutcopyright(value): | ||
|  |     AboutDialog._copyright = value | ||
|  | 
 | ||
|  | def aboutcontact(value): | ||
|  |     AboutDialog._contact = value | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwComboBox.py | ||
|  | # Based on iwidgets2.2.0/combobox.itk code. | ||
|  | 
 | ||
|  | import os | ||
|  | import string | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class ComboBox(MegaWidget): | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('autoclear',          0,          INITOPT), | ||
|  | 	    ('buttonaspect',       1.0,        INITOPT), | ||
|  | 	    ('dropdown',           1,          INITOPT), | ||
|  | 	    ('fliparrow',          0,          INITOPT), | ||
|  | 	    ('history',            1,          INITOPT), | ||
|  | 	    ('labelmargin',        0,          INITOPT), | ||
|  | 	    ('labelpos',           None,       INITOPT), | ||
|  | 	    ('listheight',         200,        INITOPT), | ||
|  | 	    ('selectioncommand',   None,       None), | ||
|  | 	    ('sticky',            'ew',        INITOPT), | ||
|  | 	    ('unique',             1,          INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 
 | ||
|  | 	self._entryfield = self.createcomponent('entryfield', | ||
|  | 		(('entry', 'entryfield_entry'),), None, | ||
|  | 		EntryField, (interior,)) | ||
|  | 	self._entryfield.grid(column=2, row=2, sticky=self['sticky']) | ||
|  | 	interior.grid_columnconfigure(2, weight = 1) | ||
|  | 	self._entryWidget = self._entryfield.component('entry') | ||
|  | 
 | ||
|  | 	if self['dropdown']: | ||
|  | 	    self._isPosted = 0 | ||
|  |             interior.grid_rowconfigure(2, weight = 1) | ||
|  | 
 | ||
|  | 	    # Create the arrow button. | ||
|  | 	    self._arrowBtn = self.createcomponent('arrowbutton', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Canvas, (interior,), borderwidth = 2, | ||
|  | 		    relief = 'raised', | ||
|  | 		    width = 16, height = 16) | ||
|  |             if 'n' in self['sticky']: | ||
|  |                 sticky = 'n' | ||
|  |             else: | ||
|  |                 sticky = '' | ||
|  |             if 's' in self['sticky']: | ||
|  |                 sticky = sticky + 's' | ||
|  | 	    self._arrowBtn.grid(column=3, row=2, sticky = sticky) | ||
|  | 	    self._arrowRelief = self._arrowBtn.cget('relief') | ||
|  | 
 | ||
|  | 	    # Create the label. | ||
|  | 	    self.createlabel(interior, childCols=2) | ||
|  | 
 | ||
|  | 	    # Create the dropdown window. | ||
|  | 	    self._popup = self.createcomponent('popup', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Toplevel, (interior,)) | ||
|  | 	    self._popup.withdraw() | ||
|  | 	    self._popup.overrideredirect(1) | ||
|  | 
 | ||
|  | 	    # Create the scrolled listbox inside the dropdown window. | ||
|  | 	    self._list = self.createcomponent('scrolledlist', | ||
|  | 		    (('listbox', 'scrolledlist_listbox'),), None, | ||
|  | 		    ScrolledListBox, (self._popup,), | ||
|  | 		    hull_borderwidth = 2, | ||
|  | 		    hull_relief = 'raised', | ||
|  | 		    hull_height = self['listheight'], | ||
|  | 		    usehullsize = 1, | ||
|  | 		    listbox_exportselection = 0) | ||
|  | 	    self._list.pack(expand=1, fill='both') | ||
|  | 	    self.__listbox = self._list.component('listbox') | ||
|  | 
 | ||
|  | 	    # Bind events to the arrow button. | ||
|  | 	    self._arrowBtn.bind('<1>', self._postList) | ||
|  | 	    self._arrowBtn.bind('<Configure>', self._drawArrow) | ||
|  | 	    self._arrowBtn.bind('<3>', self._next) | ||
|  | 	    self._arrowBtn.bind('<Shift-3>', self._previous) | ||
|  | 	    self._arrowBtn.bind('<Down>', self._next) | ||
|  | 	    self._arrowBtn.bind('<Up>', self._previous) | ||
|  | 	    self._arrowBtn.bind('<Control-n>', self._next) | ||
|  | 	    self._arrowBtn.bind('<Control-p>', self._previous) | ||
|  | 	    self._arrowBtn.bind('<Shift-Down>', self._postList) | ||
|  | 	    self._arrowBtn.bind('<Shift-Up>', self._postList) | ||
|  | 	    self._arrowBtn.bind('<F34>', self._postList) | ||
|  | 	    self._arrowBtn.bind('<F28>', self._postList) | ||
|  | 	    self._arrowBtn.bind('<space>', self._postList) | ||
|  | 
 | ||
|  | 	    # Bind events to the dropdown window. | ||
|  | 	    self._popup.bind('<Escape>', self._unpostList) | ||
|  | 	    self._popup.bind('<space>', self._selectUnpost) | ||
|  | 	    self._popup.bind('<Return>', self._selectUnpost) | ||
|  | 	    self._popup.bind('<ButtonRelease-1>', self._dropdownBtnRelease) | ||
|  | 	    self._popup.bind('<ButtonPress-1>', self._unpostOnNextRelease) | ||
|  | 
 | ||
|  | 	    # Bind events to the Tk listbox. | ||
|  | 	    self.__listbox.bind('<Enter>', self._unpostOnNextRelease) | ||
|  | 
 | ||
|  | 	    # Bind events to the Tk entry widget. | ||
|  | 	    self._entryWidget.bind('<Configure>', self._resizeArrow) | ||
|  | 	    self._entryWidget.bind('<Shift-Down>', self._postList) | ||
|  | 	    self._entryWidget.bind('<Shift-Up>', self._postList) | ||
|  | 	    self._entryWidget.bind('<F34>', self._postList) | ||
|  | 	    self._entryWidget.bind('<F28>', self._postList) | ||
|  | 
 | ||
|  |             # Need to unpost the popup if the entryfield is unmapped (eg:  | ||
|  |             # its toplevel window is withdrawn) while the popup list is | ||
|  |             # displayed. | ||
|  |             self._entryWidget.bind('<Unmap>', self._unpostList) | ||
|  | 
 | ||
|  | 	else: | ||
|  | 	    # Create the scrolled listbox below the entry field. | ||
|  | 	    self._list = self.createcomponent('scrolledlist', | ||
|  | 		    (('listbox', 'scrolledlist_listbox'),), None, | ||
|  | 		    ScrolledListBox, (interior,), | ||
|  |                     selectioncommand = self._selectCmd) | ||
|  | 	    self._list.grid(column=2, row=3, sticky='nsew') | ||
|  | 	    self.__listbox = self._list.component('listbox') | ||
|  | 
 | ||
|  | 	    # The scrolled listbox should expand vertically. | ||
|  | 	    interior.grid_rowconfigure(3, weight = 1) | ||
|  | 
 | ||
|  | 	    # Create the label. | ||
|  | 	    self.createlabel(interior, childRows=2) | ||
|  | 
 | ||
|  | 	self._entryWidget.bind('<Down>', self._next) | ||
|  | 	self._entryWidget.bind('<Up>', self._previous) | ||
|  | 	self._entryWidget.bind('<Control-n>', self._next) | ||
|  | 	self._entryWidget.bind('<Control-p>', self._previous) | ||
|  | 	self.__listbox.bind('<Control-n>', self._next) | ||
|  | 	self.__listbox.bind('<Control-p>', self._previous) | ||
|  | 
 | ||
|  | 	if self['history']: | ||
|  | 	    self._entryfield.configure(command=self._addHistory) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  | 	if self['dropdown'] and self._isPosted: | ||
|  |             popgrab(self._popup) | ||
|  |         MegaWidget.destroy(self) | ||
|  | 
 | ||
|  |     #====================================================================== | ||
|  | 
 | ||
|  |     # Public methods | ||
|  | 
 | ||
|  |     def get(self, first = None, last=None): | ||
|  | 	if first is None: | ||
|  | 	    return self._entryWidget.get() | ||
|  | 	else: | ||
|  | 	    return self._list.get(first, last) | ||
|  | 
 | ||
|  |     def invoke(self): | ||
|  | 	if self['dropdown']: | ||
|  | 	    self._postList() | ||
|  | 	else: | ||
|  | 	    return self._selectCmd() | ||
|  | 
 | ||
|  |     def selectitem(self, index, setentry=1): | ||
|  | 	if type(index) == types.StringType: | ||
|  | 	    text = index | ||
|  | 	    items = self._list.get(0, 'end') | ||
|  | 	    if text in items: | ||
|  | 		index = list(items).index(text) | ||
|  | 	    else: | ||
|  | 	    	raise IndexError, 'index "%s" not found' % text | ||
|  | 	elif setentry: | ||
|  | 	    text = self._list.get(0, 'end')[index] | ||
|  | 
 | ||
|  | 	self._list.select_clear(0, 'end') | ||
|  | 	self._list.select_set(index, index) | ||
|  | 	self._list.activate(index) | ||
|  | 	self.see(index) | ||
|  | 	if setentry: | ||
|  | 	    self._entryfield.setentry(text) | ||
|  | 
 | ||
|  |     # Need to explicitly forward this to override the stupid | ||
|  |     # (grid_)size method inherited from Tkinter.Frame.Grid. | ||
|  |     def size(self): | ||
|  | 	return self._list.size() | ||
|  | 
 | ||
|  |     # Need to explicitly forward this to override the stupid | ||
|  |     # (grid_)bbox method inherited from Tkinter.Frame.Grid. | ||
|  |     def bbox(self, index): | ||
|  | 	return self._list.bbox(index) | ||
|  | 
 | ||
|  |     def clear(self): | ||
|  | 	self._entryfield.clear() | ||
|  | 	self._list.clear() | ||
|  | 
 | ||
|  |     #====================================================================== | ||
|  | 
 | ||
|  |     # Private methods for both dropdown and simple comboboxes. | ||
|  | 
 | ||
|  |     def _addHistory(self): | ||
|  | 	input = self._entryWidget.get() | ||
|  | 
 | ||
|  | 	if input != '': | ||
|  | 	    index = None | ||
|  | 	    if self['unique']: | ||
|  | 		# If item is already in list, select it and return. | ||
|  | 		items = self._list.get(0, 'end') | ||
|  | 		if input in items: | ||
|  | 		    index = list(items).index(input) | ||
|  | 
 | ||
|  | 	    if index is None: | ||
|  | 		index = self._list.index('end') | ||
|  | 		self._list.insert('end', input) | ||
|  | 
 | ||
|  | 	    self.selectitem(index) | ||
|  | 	    if self['autoclear']: | ||
|  | 		self._entryWidget.delete(0, 'end') | ||
|  | 
 | ||
|  | 	    # Execute the selectioncommand on the new entry. | ||
|  | 	    self._selectCmd() | ||
|  | 
 | ||
|  |     def _next(self, event): | ||
|  | 	size = self.size() | ||
|  | 	if size <= 1: | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	cursels = self.curselection() | ||
|  | 
 | ||
|  | 	if len(cursels) == 0: | ||
|  | 	    index = 0 | ||
|  | 	else: | ||
|  | 	    index = string.atoi(cursels[0]) | ||
|  | 	    if index == size - 1: | ||
|  | 		index = 0 | ||
|  | 	    else: | ||
|  | 		index = index + 1 | ||
|  | 
 | ||
|  | 	self.selectitem(index) | ||
|  | 
 | ||
|  |     def _previous(self, event): | ||
|  | 	size = self.size() | ||
|  | 	if size <= 1: | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	cursels = self.curselection() | ||
|  | 
 | ||
|  | 	if len(cursels) == 0: | ||
|  | 	    index = size - 1 | ||
|  | 	else: | ||
|  | 	    index = string.atoi(cursels[0]) | ||
|  | 	    if index == 0: | ||
|  | 		index = size - 1 | ||
|  | 	    else: | ||
|  | 		index = index - 1 | ||
|  | 
 | ||
|  | 	self.selectitem(index) | ||
|  | 
 | ||
|  |     def _selectCmd(self, event=None): | ||
|  | 
 | ||
|  | 	sels = self.getcurselection() | ||
|  | 	if len(sels) == 0: | ||
|  | 	    item = None | ||
|  | 	else: | ||
|  | 	    item = sels[0] | ||
|  | 	    self._entryfield.setentry(item) | ||
|  | 
 | ||
|  | 	cmd = self['selectioncommand'] | ||
|  | 	if callable(cmd): | ||
|  |             if event is None: | ||
|  |                 # Return result of selectioncommand for invoke() method. | ||
|  |                 return cmd(item) | ||
|  |             else: | ||
|  |                 cmd(item) | ||
|  | 
 | ||
|  |     #====================================================================== | ||
|  | 
 | ||
|  |     # Private methods for dropdown combobox. | ||
|  | 
 | ||
|  |     def _drawArrow(self, event=None, sunken=0): | ||
|  |         arrow = self._arrowBtn | ||
|  | 	if sunken: | ||
|  | 	    self._arrowRelief = arrow.cget('relief') | ||
|  | 	    arrow.configure(relief = 'sunken') | ||
|  | 	else: | ||
|  | 	    arrow.configure(relief = self._arrowRelief) | ||
|  | 
 | ||
|  | 	if self._isPosted and self['fliparrow']: | ||
|  |             direction = 'up' | ||
|  |         else: | ||
|  |             direction = 'down' | ||
|  |         drawarrow(arrow, self['entry_foreground'], direction, 'arrow') | ||
|  | 
 | ||
|  |     def _postList(self, event = None): | ||
|  |         self._isPosted = 1 | ||
|  |         self._drawArrow(sunken=1) | ||
|  | 
 | ||
|  |         # Make sure that the arrow is displayed sunken. | ||
|  |         self.update_idletasks() | ||
|  | 
 | ||
|  |         x = self._entryfield.winfo_rootx() | ||
|  |         y = self._entryfield.winfo_rooty() + \ | ||
|  |             self._entryfield.winfo_height() | ||
|  |         w = self._entryfield.winfo_width() + self._arrowBtn.winfo_width() | ||
|  |         h =  self.__listbox.winfo_height() | ||
|  |         sh = self.winfo_screenheight() | ||
|  | 
 | ||
|  |         if y + h > sh and y > sh / 2: | ||
|  |             y = self._entryfield.winfo_rooty() - h | ||
|  | 
 | ||
|  |         self._list.configure(hull_width=w) | ||
|  | 
 | ||
|  |         setgeometryanddeiconify(self._popup, '+%d+%d' % (x, y)) | ||
|  | 
 | ||
|  |         # Grab the popup, so that all events are delivered to it, and | ||
|  |         # set focus to the listbox, to make keyboard navigation | ||
|  |         # easier. | ||
|  |         pushgrab(self._popup, 1, self._unpostList) | ||
|  |         self.__listbox.focus_set() | ||
|  | 
 | ||
|  |         self._drawArrow() | ||
|  | 
 | ||
|  |         # Ignore the first release of the mouse button after posting the | ||
|  |         # dropdown list, unless the mouse enters the dropdown list. | ||
|  |         self._ignoreRelease = 1 | ||
|  | 
 | ||
|  |     def _dropdownBtnRelease(self, event): | ||
|  | 	if (event.widget == self._list.component('vertscrollbar') or | ||
|  | 		event.widget == self._list.component('horizscrollbar')): | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	if self._ignoreRelease: | ||
|  | 	    self._unpostOnNextRelease() | ||
|  | 	    return | ||
|  | 
 | ||
|  |         self._unpostList() | ||
|  | 
 | ||
|  | 	if (event.x >= 0 and event.x < self.__listbox.winfo_width() and | ||
|  | 		event.y >= 0 and event.y < self.__listbox.winfo_height()): | ||
|  | 	    self._selectCmd() | ||
|  | 
 | ||
|  |     def _unpostOnNextRelease(self, event = None): | ||
|  | 	self._ignoreRelease = 0 | ||
|  | 
 | ||
|  |     def _resizeArrow(self, event): | ||
|  | 	bw = (string.atoi(self._arrowBtn['borderwidth']) +  | ||
|  | 		string.atoi(self._arrowBtn['highlightthickness'])) | ||
|  | 	newHeight = self._entryfield.winfo_reqheight() - 2 * bw | ||
|  | 	newWidth = int(newHeight * self['buttonaspect']) | ||
|  | 	self._arrowBtn.configure(width=newWidth, height=newHeight) | ||
|  | 	self._drawArrow() | ||
|  | 
 | ||
|  |     def _unpostList(self, event=None): | ||
|  | 	if not self._isPosted: | ||
|  |             # It is possible to get events on an unposted popup.  For | ||
|  |             # example, by repeatedly pressing the space key to post | ||
|  |             # and unpost the popup.  The <space> event may be | ||
|  |             # delivered to the popup window even though | ||
|  |             # popgrab() has set the focus away from the | ||
|  |             # popup window.  (Bug in Tk?) | ||
|  |             return | ||
|  | 
 | ||
|  |         # 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._popup) | ||
|  | 	self._popup.withdraw() | ||
|  | 
 | ||
|  | 	self._isPosted = 0 | ||
|  | 	self._drawArrow() | ||
|  | 
 | ||
|  |     def _selectUnpost(self, event): | ||
|  |         self._unpostList() | ||
|  | 	self._selectCmd() | ||
|  | 
 | ||
|  | forwardmethods(ComboBox, ScrolledListBox, '_list') | ||
|  | forwardmethods(ComboBox, EntryField, '_entryfield') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwComboBoxDialog.py | ||
|  | # Not Based on iwidgets version. | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | class ComboBoxDialog(Dialog): | ||
|  |     # Dialog window with simple combobox. | ||
|  |      | ||
|  |     # Dialog window displaying a list and entry field and requesting | ||
|  |     # the user to make a selection or enter a value | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('borderx',    10,              INITOPT), | ||
|  | 	    ('bordery',    10,              INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	Dialog.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 
 | ||
|  | 	aliases = ( | ||
|  | 	    ('listbox', 'combobox_listbox'), | ||
|  | 	    ('scrolledlist', 'combobox_scrolledlist'), | ||
|  | 	    ('entry', 'combobox_entry'), | ||
|  | 	    ('label', 'combobox_label'), | ||
|  | 	) | ||
|  | 	self._combobox = self.createcomponent('combobox', | ||
|  | 		aliases, None, | ||
|  | 		ComboBox, (interior,), | ||
|  | 		scrolledlist_dblclickcommand = self.invoke, | ||
|  | 		dropdown = 0, | ||
|  | 	) | ||
|  | 	self._combobox.pack(side='top', expand='true', fill='both', | ||
|  | 		padx = self['borderx'], pady = self['bordery']) | ||
|  | 
 | ||
|  |         if not kw.has_key('activatecommand'): | ||
|  |             # Whenever this dialog is activated, set the focus to the | ||
|  |             # ComboBox's listbox widget. | ||
|  |             listbox = self.component('listbox') | ||
|  |             self.configure(activatecommand = listbox.focus_set) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     # Need to explicitly forward this to override the stupid | ||
|  |     # (grid_)size method inherited from Tkinter.Toplevel.Grid. | ||
|  |     def size(self): | ||
|  | 	return self._combobox.size() | ||
|  | 
 | ||
|  |     # Need to explicitly forward this to override the stupid | ||
|  |     # (grid_)bbox method inherited from Tkinter.Toplevel.Grid. | ||
|  |     def bbox(self, index): | ||
|  | 	return self._combobox.bbox(index) | ||
|  | 
 | ||
|  | forwardmethods(ComboBoxDialog, ComboBox, '_combobox') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwCounter.py | ||
|  | import string | ||
|  | import sys | ||
|  | import types | ||
|  | import Tkinter | ||
|  | 
 | ||
|  | 
 | ||
|  | class Counter(MegaWidget): | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('autorepeat',     1,             None), | ||
|  | 	    ('buttonaspect',   1.0,           INITOPT), | ||
|  | 	    ('datatype',       'numeric',     self._datatype), | ||
|  | 	    ('increment',      1,             None), | ||
|  | 	    ('initwait',       300,           None), | ||
|  | 	    ('labelmargin',    0,             INITOPT), | ||
|  | 	    ('labelpos',       None,          INITOPT), | ||
|  | 	    ('orient',         'horizontal',  INITOPT), | ||
|  | 	    ('padx',           0,             INITOPT), | ||
|  | 	    ('pady',           0,             INITOPT), | ||
|  | 	    ('repeatrate',     50,            None), | ||
|  | 	    ('sticky',         'ew',          INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	MegaWidget.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Initialise instance variables. | ||
|  | 	self._timerId = None | ||
|  | 	self._normalRelief = None | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 
 | ||
|  | 	# If there is no label, put the arrows and the entry directly | ||
|  | 	# into the interior, otherwise create a frame for them.  In | ||
|  | 	# either case the border around the arrows and the entry will | ||
|  | 	# be raised (but not around the label). | ||
|  | 	if self['labelpos'] is None: | ||
|  | 	    frame = interior | ||
|  |             if not kw.has_key('hull_relief'): | ||
|  |                 frame.configure(relief = 'raised') | ||
|  |             if not kw.has_key('hull_borderwidth'): | ||
|  |                 frame.configure(borderwidth = 1) | ||
|  | 	else: | ||
|  | 	    frame = self.createcomponent('frame', | ||
|  | 		    (), None, | ||
|  | 		    Tkinter.Frame, (interior,), | ||
|  |                     relief = 'raised', borderwidth = 1) | ||
|  | 	    frame.grid(column=2, row=2, sticky=self['sticky']) | ||
|  | 	    interior.grid_columnconfigure(2, weight=1) | ||
|  | 	    interior.grid_rowconfigure(2, weight=1) | ||
|  | 
 | ||
|  | 	# Create the down arrow. | ||
|  | 	self._downArrowBtn = self.createcomponent('downarrow', | ||
|  | 		(), 'Arrow', | ||
|  | 		Tkinter.Canvas, (frame,), | ||
|  | 		width = 16, height = 16, relief = 'raised', borderwidth = 2) | ||
|  | 
 | ||
|  | 	# Create the entry field. | ||
|  | 	self._counterEntry = self.createcomponent('entryfield', | ||
|  | 		(('entry', 'entryfield_entry'),), None, | ||
|  | 		EntryField, (frame,)) | ||
|  | 
 | ||
|  | 	# Create the up arrow. | ||
|  | 	self._upArrowBtn = self.createcomponent('uparrow', | ||
|  | 		(), 'Arrow', | ||
|  | 		Tkinter.Canvas, (frame,), | ||
|  | 		width = 16, height = 16, relief = 'raised', borderwidth = 2) | ||
|  | 
 | ||
|  | 	padx = self['padx'] | ||
|  | 	pady = self['pady'] | ||
|  | 	orient = self['orient'] | ||
|  | 	if orient == 'horizontal': | ||
|  | 	    self._downArrowBtn.grid(column = 0, row = 0) | ||
|  | 	    self._counterEntry.grid(column = 1, row = 0, | ||
|  |                     sticky = self['sticky']) | ||
|  | 	    self._upArrowBtn.grid(column = 2, row = 0) | ||
|  | 	    frame.grid_columnconfigure(1, weight = 1) | ||
|  | 	    frame.grid_rowconfigure(0, weight = 1) | ||
|  | 	    if Tkinter.TkVersion >= 4.2: | ||
|  | 		frame.grid_columnconfigure(0, pad = padx) | ||
|  | 		frame.grid_columnconfigure(2, pad = padx) | ||
|  | 		frame.grid_rowconfigure(0, pad = pady) | ||
|  | 	elif orient == 'vertical': | ||
|  | 	    self._upArrowBtn.grid(column = 0, row = 0, sticky = 's') | ||
|  | 	    self._counterEntry.grid(column = 0, row = 1, | ||
|  |                     sticky = self['sticky']) | ||
|  | 	    self._downArrowBtn.grid(column = 0, row = 2, sticky = 'n') | ||
|  | 	    frame.grid_columnconfigure(0, weight = 1) | ||
|  | 	    frame.grid_rowconfigure(0, weight = 1) | ||
|  | 	    frame.grid_rowconfigure(2, weight = 1) | ||
|  | 	    if Tkinter.TkVersion >= 4.2: | ||
|  | 		frame.grid_rowconfigure(0, pad = pady) | ||
|  | 		frame.grid_rowconfigure(2, pad = pady) | ||
|  | 		frame.grid_columnconfigure(0, pad = padx) | ||
|  | 	else: | ||
|  | 	    raise ValueError, 'bad orient option ' + repr(orient) + \ | ||
|  | 		': must be either \'horizontal\' or \'vertical\'' | ||
|  | 
 | ||
|  | 	self.createlabel(interior) | ||
|  | 
 | ||
|  | 	self._upArrowBtn.bind('<Configure>', self._drawUpArrow) | ||
|  | 	self._upArrowBtn.bind('<1>', self._countUp) | ||
|  | 	self._upArrowBtn.bind('<Any-ButtonRelease-1>', self._stopCounting) | ||
|  | 	self._downArrowBtn.bind('<Configure>', self._drawDownArrow) | ||
|  | 	self._downArrowBtn.bind('<1>', self._countDown) | ||
|  | 	self._downArrowBtn.bind('<Any-ButtonRelease-1>', self._stopCounting) | ||
|  | 	self._counterEntry.bind('<Configure>', self._resizeArrow) | ||
|  | 	entry = self._counterEntry.component('entry') | ||
|  | 	entry.bind('<Down>', lambda event, s = self: s._key_decrement(event)) | ||
|  | 	entry.bind('<Up>', lambda event, s = self: s._key_increment(event)) | ||
|  | 
 | ||
|  | 	# Need to cancel the timer if an arrow button is unmapped (eg:  | ||
|  | 	# its toplevel window is withdrawn) while the mouse button is | ||
|  | 	# held down.  The canvas will not get the ButtonRelease event | ||
|  | 	# if it is not mapped, since the implicit grab is cancelled. | ||
|  | 	self._upArrowBtn.bind('<Unmap>', self._stopCounting) | ||
|  | 	self._downArrowBtn.bind('<Unmap>', self._stopCounting) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     def _resizeArrow(self, event): | ||
|  | 	for btn in (self._upArrowBtn, self._downArrowBtn): | ||
|  | 	    bw = (string.atoi(btn['borderwidth']) + | ||
|  | 		    string.atoi(btn['highlightthickness'])) | ||
|  | 	    newHeight = self._counterEntry.winfo_reqheight() - 2 * bw | ||
|  | 	    newWidth = int(newHeight * self['buttonaspect']) | ||
|  | 	    btn.configure(width=newWidth, height=newHeight) | ||
|  | 	    self._drawArrow(btn) | ||
|  | 
 | ||
|  |     def _drawUpArrow(self, event): | ||
|  | 	self._drawArrow(self._upArrowBtn) | ||
|  | 
 | ||
|  |     def _drawDownArrow(self, event): | ||
|  | 	self._drawArrow(self._downArrowBtn) | ||
|  | 
 | ||
|  |     def _drawArrow(self, arrow): | ||
|  |         if self['orient'] == 'vertical': | ||
|  |             if arrow == self._upArrowBtn: | ||
|  |                 direction = 'up' | ||
|  |             else: | ||
|  |                 direction = 'down' | ||
|  |         else: | ||
|  |             if arrow == self._upArrowBtn: | ||
|  |                 direction = 'right' | ||
|  |             else: | ||
|  |                 direction = 'left' | ||
|  |         drawarrow(arrow, self['entry_foreground'], direction, 'arrow') | ||
|  | 
 | ||
|  |     def _stopCounting(self, event = None): | ||
|  |         if self._timerId is not None: | ||
|  |             self.after_cancel(self._timerId) | ||
|  | 	    self._timerId = None | ||
|  | 	if self._normalRelief is not None: | ||
|  | 	    button, relief = self._normalRelief | ||
|  | 	    button.configure(relief=relief) | ||
|  | 	    self._normalRelief = None | ||
|  | 
 | ||
|  |     def _countUp(self, event): | ||
|  | 	self._normalRelief = (self._upArrowBtn, self._upArrowBtn.cget('relief')) | ||
|  | 	self._upArrowBtn.configure(relief='sunken') | ||
|  | 	# Force arrow down (it may come up immediately, if increment fails). | ||
|  | 	self._upArrowBtn.update_idletasks() | ||
|  | 	self._count(1, 1) | ||
|  | 
 | ||
|  |     def _countDown(self, event): | ||
|  | 	self._normalRelief = (self._downArrowBtn, self._downArrowBtn.cget('relief')) | ||
|  | 	self._downArrowBtn.configure(relief='sunken') | ||
|  | 	# Force arrow down (it may come up immediately, if increment fails). | ||
|  | 	self._downArrowBtn.update_idletasks() | ||
|  | 	self._count(-1, 1) | ||
|  | 
 | ||
|  |     def increment(self): | ||
|  | 	self._forceCount(1) | ||
|  | 
 | ||
|  |     def decrement(self): | ||
|  | 	self._forceCount(-1) | ||
|  | 
 | ||
|  |     def _key_increment(self, event): | ||
|  | 	self._forceCount(1) | ||
|  | 	self.update_idletasks() | ||
|  | 
 | ||
|  |     def _key_decrement(self, event): | ||
|  | 	self._forceCount(-1) | ||
|  | 	self.update_idletasks() | ||
|  | 
 | ||
|  |     def _datatype(self): | ||
|  | 	datatype = self['datatype'] | ||
|  | 
 | ||
|  | 	if type(datatype) is types.DictionaryType: | ||
|  | 	    self._counterArgs = datatype.copy() | ||
|  | 	    if self._counterArgs.has_key('counter'): | ||
|  | 		datatype = self._counterArgs['counter'] | ||
|  | 		del self._counterArgs['counter'] | ||
|  | 	    else: | ||
|  | 		datatype = 'numeric' | ||
|  | 	else: | ||
|  | 	    self._counterArgs = {} | ||
|  | 
 | ||
|  | 	if _counterCommands.has_key(datatype): | ||
|  | 	    self._counterCommand = _counterCommands[datatype] | ||
|  | 	elif callable(datatype): | ||
|  | 	    self._counterCommand = datatype | ||
|  | 	else: | ||
|  | 	    validValues = _counterCommands.keys() | ||
|  | 	    validValues.sort() | ||
|  | 	    raise ValueError, ('bad datatype value "%s":  must be a' + | ||
|  | 		    ' function or one of %s') % (datatype, validValues) | ||
|  | 
 | ||
|  |     def _forceCount(self, factor): | ||
|  | 	if not self.valid(): | ||
|  | 	    self.bell() | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	text = self._counterEntry.get() | ||
|  | 	try: | ||
|  | 	    value = apply(self._counterCommand, | ||
|  | 		    (text, factor, self['increment']), self._counterArgs) | ||
|  | 	except ValueError: | ||
|  | 	    self.bell() | ||
|  | 	    return | ||
|  | 
 | ||
|  |         previousICursor = self._counterEntry.index('insert') | ||
|  | 	if self._counterEntry.setentry(value) == OK: | ||
|  | 	    self._counterEntry.xview('end') | ||
|  | 	    self._counterEntry.icursor(previousICursor) | ||
|  | 
 | ||
|  |     def _count(self, factor, first): | ||
|  | 	if not self.valid(): | ||
|  | 	    self.bell() | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	self._timerId = None | ||
|  | 	origtext = self._counterEntry.get() | ||
|  | 	try: | ||
|  | 	    value = apply(self._counterCommand, | ||
|  | 		    (origtext, factor, self['increment']), self._counterArgs) | ||
|  | 	except ValueError: | ||
|  | 	    # If text is invalid, stop counting. | ||
|  | 	    self._stopCounting() | ||
|  | 	    self.bell() | ||
|  | 	    return | ||
|  | 
 | ||
|  | 	# If incrementing produces an invalid value, restore previous | ||
|  | 	# text and stop counting. | ||
|  |         previousICursor = self._counterEntry.index('insert') | ||
|  | 	valid = self._counterEntry.setentry(value) | ||
|  | 	if valid != OK: | ||
|  | 	    self._stopCounting() | ||
|  | 	    self._counterEntry.setentry(origtext) | ||
|  | 	    if valid == PARTIAL: | ||
|  | 		self.bell() | ||
|  | 	    return | ||
|  | 	self._counterEntry.xview('end') | ||
|  | 	self._counterEntry.icursor(previousICursor) | ||
|  | 
 | ||
|  | 	if self['autorepeat']: | ||
|  | 	    if first: | ||
|  | 		delay = self['initwait'] | ||
|  | 	    else: | ||
|  | 		delay = self['repeatrate'] | ||
|  | 	    self._timerId = self.after(delay, | ||
|  | 		    lambda self=self, factor=factor: self._count(factor, 0)) | ||
|  | 
 | ||
|  |     def destroy(self): | ||
|  | 	self._stopCounting() | ||
|  |         MegaWidget.destroy(self) | ||
|  | 
 | ||
|  | forwardmethods(Counter, EntryField, '_counterEntry') | ||
|  | 
 | ||
|  | def _changeNumber(text, factor, increment): | ||
|  |   value = string.atol(text) | ||
|  |   if factor > 0: | ||
|  |     value = (value / increment) * increment + increment | ||
|  |   else: | ||
|  |     value = ((value - 1) / increment) * increment | ||
|  | 
 | ||
|  |   # Get rid of the 'L' at the end of longs (in python up to 1.5.2). | ||
|  |   rtn = str(value) | ||
|  |   if rtn[-1] == 'L': | ||
|  |       return rtn[:-1] | ||
|  |   else: | ||
|  |       return rtn | ||
|  | 
 | ||
|  | def _changeReal(text, factor, increment, separator = '.'): | ||
|  |   value = stringtoreal(text, separator) | ||
|  |   div = value / increment | ||
|  | 
 | ||
|  |   # Compare reals using str() to avoid problems caused by binary | ||
|  |   # numbers being only approximations to decimal numbers. | ||
|  |   # For example, if value is -0.3 and increment is 0.1, then | ||
|  |   # int(value/increment) = -2, not -3 as one would expect. | ||
|  |   if str(div)[-2:] == '.0': | ||
|  |     # value is an even multiple of increment. | ||
|  |     div = round(div) + factor | ||
|  |   else: | ||
|  |     # value is not an even multiple of increment. | ||
|  |     div = int(div) * 1.0 | ||
|  |     if value < 0: | ||
|  |       div = div - 1 | ||
|  |     if factor > 0: | ||
|  |       div = (div + 1) | ||
|  | 
 | ||
|  |   value = div * increment | ||
|  | 
 | ||
|  |   text = str(value) | ||
|  |   if separator != '.': | ||
|  |       index = string.find(text, '.') | ||
|  |       if index >= 0: | ||
|  | 	text = text[:index] + separator + text[index + 1:] | ||
|  |   return text | ||
|  | 
 | ||
|  | def _changeDate(value, factor, increment, format = 'ymd', | ||
|  | 	separator = '/', yyyy = 0): | ||
|  | 
 | ||
|  |   jdn = datestringtojdn(value, format, separator) + factor * increment | ||
|  | 
 | ||
|  |   y, m, d = jdntoymd(jdn) | ||
|  |   result = '' | ||
|  |   for index in range(3): | ||
|  |     if index > 0: | ||
|  |       result = result + separator | ||
|  |     f = format[index] | ||
|  |     if f == 'y': | ||
|  |       if yyyy: | ||
|  |         result = result + '%02d' % y | ||
|  |       else: | ||
|  |         result = result + '%02d' % (y % 100) | ||
|  |     elif f == 'm': | ||
|  |       result = result + '%02d' % m | ||
|  |     elif f == 'd': | ||
|  |       result = result + '%02d' % d | ||
|  | 
 | ||
|  |   return result | ||
|  | 
 | ||
|  | _SECSPERDAY = 24 * 60 * 60 | ||
|  | def _changeTime(value, factor, increment, separator = ':', time24 = 0): | ||
|  |   unixTime = timestringtoseconds(value, separator) | ||
|  |   if factor > 0: | ||
|  |     chunks = unixTime / increment + 1 | ||
|  |   else: | ||
|  |     chunks = (unixTime - 1) / increment | ||
|  |   unixTime = chunks * increment | ||
|  |   if time24: | ||
|  |       while unixTime < 0: | ||
|  | 	  unixTime = unixTime + _SECSPERDAY | ||
|  |       while unixTime >= _SECSPERDAY: | ||
|  | 	  unixTime = unixTime - _SECSPERDAY | ||
|  |   if unixTime < 0: | ||
|  |     unixTime = -unixTime | ||
|  |     sign = '-' | ||
|  |   else: | ||
|  |     sign = '' | ||
|  |   secs = unixTime % 60 | ||
|  |   unixTime = unixTime / 60 | ||
|  |   mins = unixTime % 60 | ||
|  |   hours = unixTime / 60 | ||
|  |   return '%s%02d%s%02d%s%02d' % (sign, hours, separator, mins, separator, secs) | ||
|  | 
 | ||
|  | # hexadecimal, alphabetic, alphanumeric not implemented | ||
|  | _counterCommands = { | ||
|  |     'numeric'   : _changeNumber,      # } integer | ||
|  |     'integer'   : _changeNumber,      # } these two use the same function | ||
|  |     'real'      : _changeReal,        # real number | ||
|  |     'time'      : _changeTime, | ||
|  |     'date'      : _changeDate, | ||
|  | } | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwCounterDialog.py | ||
|  | 
 | ||
|  | 
 | ||
|  | # A Dialog with a counter | ||
|  | 
 | ||
|  | class CounterDialog(Dialog): | ||
|  | 
 | ||
|  |     def __init__(self, parent = None, **kw): | ||
|  | 
 | ||
|  | 	# Define the megawidget options. | ||
|  | 	 | ||
|  | 	optiondefs = ( | ||
|  | 	    ('borderx',    20,  INITOPT), | ||
|  | 	    ('bordery',    20,  INITOPT), | ||
|  | 	) | ||
|  | 	self.defineoptions(kw, optiondefs) | ||
|  | 
 | ||
|  | 	# Initialise the base class (after defining the options). | ||
|  | 	Dialog.__init__(self, parent) | ||
|  | 
 | ||
|  | 	# Create the components. | ||
|  | 	interior = self.interior() | ||
|  | 
 | ||
|  | 	# Create the counter. | ||
|  | 	aliases = ( | ||
|  | 	    ('entryfield', 'counter_entryfield'), | ||
|  | 	    ('entry', 'counter_entryfield_entry'), | ||
|  | 	    ('label', 'counter_label') | ||
|  | 	) | ||
|  | 	self._cdCounter = self.createcomponent('counter', | ||
|  | 		aliases, None, | ||
|  | 		Counter, (interior,)) | ||
|  | 	self._cdCounter.pack(fill='x', expand=1, | ||
|  | 		padx = self['borderx'], pady = self['bordery']) | ||
|  | 	 | ||
|  |         if not kw.has_key('activatecommand'): | ||
|  |             # Whenever this dialog is activated, set the focus to the | ||
|  |             # Counter's entry widget. | ||
|  |             tkentry = self.component('entry') | ||
|  |             self.configure(activatecommand = tkentry.focus_set) | ||
|  | 
 | ||
|  | 	# Check keywords and initialise options. | ||
|  | 	self.initialiseoptions() | ||
|  | 
 | ||
|  |     # Supply aliases to some of the entry component methods. | ||
|  |     def insertentry(self, index, text): | ||
|  | 	self._cdCounter.insert(index, text) | ||
|  | 
 | ||
|  |     def deleteentry(self, first, last=None): | ||
|  | 	self._cdCounter.delete(first, last) | ||
|  | 
 | ||
|  |     def indexentry(self, index): | ||
|  | 	return self._cdCounter.index(index) | ||
|  | 
 | ||
|  | forwardmethods(CounterDialog, Counter, '_cdCounter') | ||
|  | 
 | ||
|  | ###################################################################### | ||
|  | ### File: PmwLogicalFont.py | ||
|  | import os | ||
|  | import string | ||
|  | 
 | ||
|  | def _font_initialise(root, size=None, fontScheme = None): | ||
|  |     global _fontSize | ||
|  |     if size is not None: | ||
|  |         _fontSize = size | ||
|  | 
 | ||
|  |     if fontScheme in ('pmw1', 'pmw2'): | ||
|  |         if os.name == 'posix': | ||
|  |             defaultFont = logicalfont('Helvetica') | ||
|  |             menuFont = logicalfont('Helvetica', weight='bold', slant='italic') | ||
|  |             scaleFont = logicalfont('Helvetica', slant='italic') | ||
|  |             root.option_add('*Font',            defaultFont,  'userDefault') | ||
|  |             root.option_add('*Menu*Font',       menuFont,     'userDefault') | ||
|  |             root.option_add('*Menubutton*Font', menuFont,     'userDefault') | ||
|  |             root.option_add('*Scale.*Font',     scaleFont,    'userDefault') | ||
|  | 
 | ||
|  |             if fontScheme == 'pmw1': | ||
|  |                 balloonFont = logicalfont('Helvetica', -6, pixel = '12') | ||
|  |             else: # fontScheme == 'pmw2' | ||
|  |                 balloonFont = logicalfont('Helvetica', -2) | ||
|  |             root.option_add('*Balloon.*Font', balloonFont, 'userDefault') | ||
|  |         else: | ||
|  |             defaultFont = logicalfont('Helvetica') | ||
|  |             root.option_add('*Font', defaultFont,  'userDefault') | ||
|  |     elif fontScheme == 'default': | ||
|  |         defaultFont = ('Helvetica', '-%d' % (_fontSize,), 'bold') | ||
|  |         entryFont = ('Helvetica', '-%d' % (_fontSize,)) | ||
|  |         textFont = ('Courier', '-%d' % (_fontSize,)) | ||
|  |         root.option_add('*Font',            defaultFont,  'userDefault') | ||
|  |         root.option_add('*Entry*Font',      entryFont,    'userDefault') | ||
|  |         root.option_add('*Text*Font',       textFont,     'userDefault') | ||
|  | 
 | ||
|  | def logicalfont(name='Helvetica', sizeIncr = 0, **kw): | ||
|  |   if not _fontInfo.has_key(name): | ||
|  |     raise ValueError, 'font %s does not exist' % name | ||
|  | 
 | ||
|  |   rtn = [] | ||
|  |   for field in _fontFields: | ||
|  |     if kw.has_key(field): | ||
|  |       logicalValue = kw[field] | ||
|  |     elif _fontInfo[name].has_key(field): | ||
|  |       logicalValue = _fontInfo[name][field] | ||
|  |     else: | ||
|  |       logicalValue = '*' | ||
|  | 
 | ||
|  |     if _propertyAliases[name].has_key((field, logicalValue)): | ||
|  |       realValue = _propertyAliases[name][(field, logicalValue)] | ||
|  |     elif _propertyAliases[name].has_key((field, None)): | ||
|  |       realValue = _propertyAliases[name][(field, None)] | ||
|  |     elif _propertyAliases[None].has_key((field, logicalValue)): | ||
|  |       realValue = _propertyAliases[None][(field, logicalValue)] | ||
|  |     elif _propertyAliases[None].has_key((field, None)): | ||
|  |       realValue = _propertyAliases[None][(field, None)] | ||
|  |     else: | ||
|  |       realValue = logicalValue | ||
|  | 
 | ||
|  |     if field == 'size': | ||
|  |       if realValue == '*': | ||
|  | 	  realValue = _fontSize | ||
|  |       realValue = str((realValue + sizeIncr) * 10) | ||
|  | 
 | ||
|  |     rtn.append(realValue) | ||
|  | 
 | ||
|  |   return string.join(rtn, '-') | ||
|  | 
 | ||
|  | def logicalfontnames(): | ||
|  |   return _fontInfo.keys() | ||
|  | 
 | ||
|  | if os.name == 'nt': | ||
|  |     _fontSize = 16 | ||
|  | else: | ||
|  |     _fontSize = 14 | ||
|  | 
 | ||
|  | _fontFields = ( | ||
|  |   'registry', 'foundry', 'family', 'weight', 'slant', 'width', 'style', | ||
|  |   'pixel', 'size', 'xres', 'yres', 'spacing', 'avgwidth', 'charset', 'encoding') | ||
|  | 
 | ||
|  | # <_propertyAliases> defines other names for which property values may | ||
|  | # be known by.  This is required because italics in adobe-helvetica | ||
|  | # are specified by 'o', while other fonts use 'i'. | ||
|  | 
 | ||
|  | _propertyAliases = {} | ||
|  | 
 | ||
|  | _propertyAliases[None] = { | ||
|  |   ('slant', 'italic') : 'i', | ||
|  |   ('slant', 'normal') : 'r', | ||
|  |   ('weight', 'light') : 'normal', | ||
|  |   ('width', 'wide') : 'normal', | ||
|  |   ('width', 'condensed') : 'normal', | ||
|  | } | ||
|  | 
 | ||
|  | # <_fontInfo> describes a 'logical' font, giving the default values of | ||
|  | # some of its properties. | ||
|  | 
 | ||
|  | _fontInfo = {} | ||
|  | 
 | ||
|  | _fontInfo['Helvetica'] = { | ||
|  |   'foundry' : 'adobe', | ||
|  |   'family' : 'helvetica', | ||
|  |   'registry' : '', | ||
|  |   'charset' : 'iso8859', | ||
|  |   'encoding' : '1', | ||
|  |   'spacing' : 'p', | ||
|  |   'slant' : 'normal', | ||
|  |   'width' : 'normal', | ||
|  |   'weight' : 'normal', | ||
|  | } | ||
|  | 
 | ||
|  | _propertyAliases['Helvetica'] = { | ||
|  |   ('slant', 'italic') : 'o', | ||
|  |   ('weight', 'normal') : 'medium', | ||
|  |   ('weight', 'light') : 'medium', | ||
|  | } | ||
|  | 
 | ||
|  | _fontInfo['Times'] = { | ||
|  |   'foundry' : 'adobe', | ||
|  |   'family' : 'times', | ||
|  |   'registry' : '', | ||
|  |   'charset' : 'iso8859', | ||
|  |   'encoding' : '1', | ||
|  |   'spacing' : 'p', | ||
|  |   'slant' : 'normal', | ||
|  |   'width' : 'normal', | ||
|  |   'weight' : 'normal', | ||
|  | } | ||
|  | 
 | ||
|  | _propertyAliases['Times'] = { | ||
|  |   ('weight', 'normal') : 'medium', | ||
|  |   ('weight', 'light') : 'medium', | ||
|  | } | ||
|  | 
 | ||
|  | _fontInfo['Fixed'] = { | ||
|  |   'foundry' : 'misc', | ||
|  |   'family' : 'fixed', | ||
|  |   'registry' : '', | ||
|  |   'charset' : 'iso8859', | ||
|  |   'encoding' : '1', | ||
|  |   'spacing' : 'c', | ||
|  |   'slant' : 'normal', | ||
|  |   'width' : 'normal', | ||
|  |   'weight' : 'normal', | ||
|  | } | ||
|  | 
 | ||
|  | _propertyAliases['Fixed'] = { | ||
|  |   ('weight', 'normal') : 'medium', | ||
|  |   ('weight', 'light') : 'medium', | ||
|  |   ('style', None) : '', | ||
|  |   ('width', 'condensed') : 'semicondensed', | ||
|  | } | ||
|  | 
 | ||
|  | _fontInfo['Courier'] = { | ||
|  |   'foundry' : 'adobe', | ||
|  |   'family' : 'courier', | ||
|  |   'registry' : '', | ||
|  |   'charset' : 'iso8859', | ||
|  |   'encoding' : '1', | ||
|  |   'spacing' : 'm', | ||
|  |   'slant' : 'normal', | ||
|  |   'width' : 'normal', | ||
|  |   'weight' : 'normal', | ||
|  | } | ||
|  | 
 | ||
|  | _propertyAliases['Courier'] = { | ||
|  |   ('weight', 'normal') : 'medium', | ||
|  |   ('weight', 'light') : 'medium', | ||
|  |   ('style', None) : '', | ||
|  | } | ||
|  | 
 | ||
|  | _fontInfo['Typewriter'] = { | ||
|  |   'foundry' : 'b&h', | ||
|  |   'family' : 'lucidatypewriter', | ||
|  |   'registry' : '', | ||
|  |   'charset' : 'iso8859', | ||
|  |   'encoding' : '1', | ||
|  |   'spacing' : 'm', | ||
|  |   'slant' : 'normal', | ||
|  |   'width' : 'normal', | ||
|  |   'weight' : 'normal', | ||
|  | } | ||
|  | 
 | ||
|  | _propertyAliases['Typewriter'] = { | ||
|  |   ('weight', 'normal') : 'medium', | ||
|  |   ('weight', 'light') : 'medium', | ||
|  | } | ||
|  | 
 | ||
|  | if os.name == 'nt': | ||
|  |     # For some reason 'fixed' fonts on NT aren't. | ||
|  |     _fontInfo['Fixed'] = _fontInfo['Courier'] | ||
|  |     _propertyAliases['Fixed'] = _propertyAliases['Courier'] |