1933 lines
65 KiB
Python
1933 lines
65 KiB
Python
# Pmw megawidget base classes.
|
|
|
|
# This module provides a foundation for building megawidgets. It
|
|
# contains the MegaArchetype class which manages component widgets and
|
|
# configuration options. Also provided are the MegaToplevel and
|
|
# MegaWidget classes, derived from the MegaArchetype class. The
|
|
# MegaToplevel class contains a Tkinter Toplevel widget to act as the
|
|
# container of the megawidget. This is used as the base class of all
|
|
# megawidgets that are contained in their own top level window, such
|
|
# as a Dialog window. The MegaWidget class contains a Tkinter Frame
|
|
# to act as the container of the megawidget. This is used as the base
|
|
# class of all other megawidgets, such as a ComboBox or ButtonBox.
|
|
#
|
|
# Megawidgets are built by creating a class that inherits from either
|
|
# the MegaToplevel or MegaWidget class.
|
|
|
|
import os
|
|
import string
|
|
import sys
|
|
import traceback
|
|
import types
|
|
import Tkinter
|
|
|
|
# Special values used in index() methods of several megawidgets.
|
|
END = ['end']
|
|
SELECT = ['select']
|
|
DEFAULT = ['default']
|
|
|
|
# Constant used to indicate that an option can only be set by a call
|
|
# to the constructor.
|
|
INITOPT = ['initopt']
|
|
_DEFAULT_OPTION_VALUE = ['default_option_value']
|
|
_useTkOptionDb = 0
|
|
|
|
# Symbolic constants for the indexes into an optionInfo list.
|
|
_OPT_DEFAULT = 0
|
|
_OPT_VALUE = 1
|
|
_OPT_FUNCTION = 2
|
|
|
|
# Stacks
|
|
|
|
_busyStack = []
|
|
# Stack which tracks nested calls to show/hidebusycursor (called
|
|
# either directly or from activate()/deactivate()). Each element
|
|
# is a dictionary containing:
|
|
# 'newBusyWindows' : List of windows which had busy_hold called
|
|
# on them during a call to showbusycursor().
|
|
# The corresponding call to hidebusycursor()
|
|
# will call busy_release on these windows.
|
|
# 'busyFocus' : The blt _Busy window which showbusycursor()
|
|
# set the focus to.
|
|
# 'previousFocus' : The focus as it was when showbusycursor()
|
|
# was called. The corresponding call to
|
|
# hidebusycursor() will restore this focus if
|
|
# the focus has not been changed from busyFocus.
|
|
|
|
_grabStack = []
|
|
# Stack of grabbed windows. It tracks calls to push/popgrab()
|
|
# (called either directly or from activate()/deactivate()). The
|
|
# window on the top of the stack is the window currently with the
|
|
# grab. Each element is a dictionary containing:
|
|
# 'grabWindow' : The window grabbed by pushgrab(). The
|
|
# corresponding call to popgrab() will release
|
|
# the grab on this window and restore the grab
|
|
# on the next window in the stack (if there is one).
|
|
# 'globalMode' : True if the grabWindow was grabbed with a
|
|
# global grab, false if the grab was local
|
|
# and 'nograb' if no grab was performed.
|
|
# 'previousFocus' : The focus as it was when pushgrab()
|
|
# was called. The corresponding call to
|
|
# popgrab() will restore this focus.
|
|
# 'deactivateFunction' :
|
|
# The function to call (usually grabWindow.deactivate) if
|
|
# popgrab() is called (usually from a deactivate() method)
|
|
# on a window which is not at the top of the stack (that is,
|
|
# does not have the grab or focus). For example, if a modal
|
|
# dialog is deleted by the window manager or deactivated by
|
|
# a timer. In this case, all dialogs above and including
|
|
# this one are deactivated, starting at the top of the
|
|
# stack.
|
|
|
|
# Note that when dealing with focus windows, the name of the Tk
|
|
# widget is used, since it may be the '_Busy' window, which has no
|
|
# python instance associated with it.
|
|
|
|
#=============================================================================
|
|
|
|
# Functions used to forward methods from a class to a component.
|
|
|
|
# Fill in a flattened method resolution dictionary for a class (attributes are
|
|
# filtered out). Flattening honours the MI method resolution rules
|
|
# (depth-first search of bases in order). The dictionary has method names
|
|
# for keys and functions for values.
|
|
def __methodDict(cls, dict):
|
|
|
|
# the strategy is to traverse the class in the _reverse_ of the normal
|
|
# order, and overwrite any duplicates.
|
|
baseList = list(cls.__bases__)
|
|
baseList.reverse()
|
|
|
|
# do bases in reverse order, so first base overrides last base
|
|
for super in baseList:
|
|
__methodDict(super, dict)
|
|
|
|
# do my methods last to override base classes
|
|
for key, value in cls.__dict__.items():
|
|
# ignore class attributes
|
|
if type(value) == types.FunctionType:
|
|
dict[key] = value
|
|
|
|
def __methods(cls):
|
|
# Return all method names for a class.
|
|
|
|
# Return all method names for a class (attributes are filtered
|
|
# out). Base classes are searched recursively.
|
|
|
|
dict = {}
|
|
__methodDict(cls, dict)
|
|
return dict.keys()
|
|
|
|
# Function body to resolve a forwarding given the target method name and the
|
|
# attribute name. The resulting lambda requires only self, but will forward
|
|
# any other parameters.
|
|
__stringBody = (
|
|
'def %(method)s(this, *args, **kw): return ' +
|
|
'apply(this.%(attribute)s.%(method)s, args, kw)')
|
|
|
|
# Get a unique id
|
|
__counter = 0
|
|
def __unique():
|
|
global __counter
|
|
__counter = __counter + 1
|
|
return str(__counter)
|
|
|
|
# Function body to resolve a forwarding given the target method name and the
|
|
# index of the resolution function. The resulting lambda requires only self,
|
|
# but will forward any other parameters. The target instance is identified
|
|
# by invoking the resolution function.
|
|
__funcBody = (
|
|
'def %(method)s(this, *args, **kw): return ' +
|
|
'apply(this.%(forwardFunc)s().%(method)s, args, kw)')
|
|
|
|
def forwardmethods(fromClass, toClass, toPart, exclude = ()):
|
|
# Forward all methods from one class to another.
|
|
|
|
# Forwarders will be created in fromClass to forward method
|
|
# invocations to toClass. The methods to be forwarded are
|
|
# identified by flattening the interface of toClass, and excluding
|
|
# methods identified in the exclude list. Methods already defined
|
|
# in fromClass, or special methods with one or more leading or
|
|
# trailing underscores will not be forwarded.
|
|
|
|
# For a given object of class fromClass, the corresponding toClass
|
|
# object is identified using toPart. This can either be a String
|
|
# denoting an attribute of fromClass objects, or a function taking
|
|
# a fromClass object and returning a toClass object.
|
|
|
|
# Example:
|
|
# class MyClass:
|
|
# ...
|
|
# def __init__(self):
|
|
# ...
|
|
# self.__target = TargetClass()
|
|
# ...
|
|
# def findtarget(self):
|
|
# return self.__target
|
|
# forwardmethods(MyClass, TargetClass, '__target', ['dangerous1', 'dangerous2'])
|
|
# # ...or...
|
|
# forwardmethods(MyClass, TargetClass, MyClass.findtarget,
|
|
# ['dangerous1', 'dangerous2'])
|
|
|
|
# In both cases, all TargetClass methods will be forwarded from
|
|
# MyClass except for dangerous1, dangerous2, special methods like
|
|
# __str__, and pre-existing methods like findtarget.
|
|
|
|
|
|
# Allow an attribute name (String) or a function to determine the instance
|
|
if type(toPart) != types.StringType:
|
|
|
|
# check that it is something like a function
|
|
if callable(toPart):
|
|
|
|
# If a method is passed, use the function within it
|
|
if hasattr(toPart, 'im_func'):
|
|
toPart = toPart.im_func
|
|
|
|
# After this is set up, forwarders in this class will use
|
|
# the forwarding function. The forwarding function name is
|
|
# guaranteed to be unique, so that it can't be hidden by subclasses
|
|
forwardName = '__fwdfunc__' + __unique()
|
|
fromClass.__dict__[forwardName] = toPart
|
|
|
|
# It's not a valid type
|
|
else:
|
|
raise TypeError, 'toPart must be attribute name, function or method'
|
|
|
|
# get the full set of candidate methods
|
|
dict = {}
|
|
__methodDict(toClass, dict)
|
|
|
|
# discard special methods
|
|
for ex in dict.keys():
|
|
if ex[:1] == '_' or ex[-1:] == '_':
|
|
del dict[ex]
|
|
# discard dangerous methods supplied by the caller
|
|
for ex in exclude:
|
|
if dict.has_key(ex):
|
|
del dict[ex]
|
|
# discard methods already defined in fromClass
|
|
for ex in __methods(fromClass):
|
|
if dict.has_key(ex):
|
|
del dict[ex]
|
|
|
|
for method, func in dict.items():
|
|
d = {'method': method, 'func': func}
|
|
if type(toPart) == types.StringType:
|
|
execString = \
|
|
__stringBody % {'method' : method, 'attribute' : toPart}
|
|
else:
|
|
execString = \
|
|
__funcBody % {'forwardFunc' : forwardName, 'method' : method}
|
|
|
|
exec execString in d
|
|
|
|
# this creates a method
|
|
fromClass.__dict__[method] = d[method]
|
|
|
|
#=============================================================================
|
|
|
|
def setgeometryanddeiconify(window, geom):
|
|
# To avoid flashes on X and to position the window correctly on NT
|
|
# (caused by Tk bugs).
|
|
|
|
if os.name == 'nt' or \
|
|
(os.name == 'posix' and sys.platform[:6] == 'cygwin'):
|
|
# Require overrideredirect trick to stop window frame
|
|
# appearing momentarily.
|
|
redirect = window.overrideredirect()
|
|
if not redirect:
|
|
window.overrideredirect(1)
|
|
window.deiconify()
|
|
if geom is not None:
|
|
window.geometry(geom)
|
|
# Call update_idletasks to ensure NT moves the window to the
|
|
# correct position it is raised.
|
|
window.update_idletasks()
|
|
window.tkraise()
|
|
if not redirect:
|
|
window.overrideredirect(0)
|
|
else:
|
|
if geom is not None:
|
|
window.geometry(geom)
|
|
|
|
# Problem!? Which way around should the following two calls
|
|
# go? If deiconify() is called first then I get complaints
|
|
# from people using the enlightenment or sawfish window
|
|
# managers that when a dialog is activated it takes about 2
|
|
# seconds for the contents of the window to appear. But if
|
|
# tkraise() is called first then I get complaints from people
|
|
# using the twm window manager that when a dialog is activated
|
|
# it appears in the top right corner of the screen and also
|
|
# takes about 2 seconds to appear.
|
|
|
|
#window.tkraise()
|
|
# Call update_idletasks to ensure certain window managers (eg:
|
|
# enlightenment and sawfish) do not cause Tk to delay for
|
|
# about two seconds before displaying window.
|
|
#window.update_idletasks()
|
|
#window.deiconify()
|
|
|
|
window.deiconify()
|
|
if window.overrideredirect():
|
|
# The window is not under the control of the window manager
|
|
# and so we need to raise it ourselves.
|
|
window.tkraise()
|
|
|
|
#=============================================================================
|
|
|
|
class MegaArchetype:
|
|
# Megawidget abstract root class.
|
|
|
|
# This class provides methods which are inherited by classes
|
|
# implementing useful bases (this class doesn't provide a
|
|
# container widget inside which the megawidget can be built).
|
|
|
|
def __init__(self, parent = None, hullClass = None):
|
|
|
|
# Mapping from each megawidget option to a list of information
|
|
# about the option
|
|
# - default value
|
|
# - current value
|
|
# - function to call when the option is initialised in the
|
|
# call to initialiseoptions() in the constructor or
|
|
# modified via configure(). If this is INITOPT, the
|
|
# option is an initialisation option (an option that can
|
|
# be set by the call to the constructor but can not be
|
|
# used with configure).
|
|
# This mapping is not initialised here, but in the call to
|
|
# defineoptions() which precedes construction of this base class.
|
|
#
|
|
# self._optionInfo = {}
|
|
|
|
# Mapping from each component name to a tuple of information
|
|
# about the component.
|
|
# - component widget instance
|
|
# - configure function of widget instance
|
|
# - the class of the widget (Frame, EntryField, etc)
|
|
# - cget function of widget instance
|
|
# - the name of the component group of this component, if any
|
|
self.__componentInfo = {}
|
|
|
|
# Mapping from alias names to the names of components or
|
|
# sub-components.
|
|
self.__componentAliases = {}
|
|
|
|
# Contains information about the keywords provided to the
|
|
# constructor. It is a mapping from the keyword to a tuple
|
|
# containing:
|
|
# - value of keyword
|
|
# - a boolean indicating if the keyword has been used.
|
|
# A keyword is used if, during the construction of a megawidget,
|
|
# - it is defined in a call to defineoptions() or addoptions(), or
|
|
# - it references, by name, a component of the megawidget, or
|
|
# - it references, by group, at least one component
|
|
# At the end of megawidget construction, a call is made to
|
|
# initialiseoptions() which reports an error if there are
|
|
# unused options given to the constructor.
|
|
#
|
|
# After megawidget construction, the dictionary contains
|
|
# keywords which refer to a dynamic component group, so that
|
|
# these components can be created after megawidget
|
|
# construction and still use the group options given to the
|
|
# constructor.
|
|
#
|
|
# self._constructorKeywords = {}
|
|
|
|
# List of dynamic component groups. If a group is included in
|
|
# this list, then it not an error if a keyword argument for
|
|
# the group is given to the constructor or to configure(), but
|
|
# no components with this group have been created.
|
|
# self._dynamicGroups = ()
|
|
|
|
if hullClass is None:
|
|
self._hull = None
|
|
else:
|
|
if parent is None:
|
|
parent = Tkinter._default_root
|
|
|
|
# Create the hull.
|
|
self._hull = self.createcomponent('hull',
|
|
(), None,
|
|
hullClass, (parent,))
|
|
_hullToMegaWidget[self._hull] = self
|
|
|
|
if _useTkOptionDb:
|
|
# Now that a widget has been created, query the Tk
|
|
# option database to get the default values for the
|
|
# options which have not been set in the call to the
|
|
# constructor. This assumes that defineoptions() is
|
|
# called before the __init__().
|
|
option_get = self.option_get
|
|
_VALUE = _OPT_VALUE
|
|
_DEFAULT = _OPT_DEFAULT
|
|
for name, info in self._optionInfo.items():
|
|
value = info[_VALUE]
|
|
if value is _DEFAULT_OPTION_VALUE:
|
|
resourceClass = string.upper(name[0]) + name[1:]
|
|
value = option_get(name, resourceClass)
|
|
if value != '':
|
|
try:
|
|
# Convert the string to int/float/tuple, etc
|
|
value = eval(value, {'__builtins__': {}})
|
|
except:
|
|
pass
|
|
info[_VALUE] = value
|
|
else:
|
|
info[_VALUE] = info[_DEFAULT]
|
|
|
|
def destroy(self):
|
|
# Clean up optionInfo in case it contains circular references
|
|
# in the function field, such as self._settitle in class
|
|
# MegaToplevel.
|
|
|
|
self._optionInfo = {}
|
|
if self._hull is not None:
|
|
del _hullToMegaWidget[self._hull]
|
|
self._hull.destroy()
|
|
|
|
#======================================================================
|
|
# Methods used (mainly) during the construction of the megawidget.
|
|
|
|
def defineoptions(self, keywords, optionDefs, dynamicGroups = ()):
|
|
# Create options, providing the default value and the method
|
|
# to call when the value is changed. If any option created by
|
|
# base classes has the same name as one in <optionDefs>, the
|
|
# base class's value and function will be overriden.
|
|
|
|
# This should be called before the constructor of the base
|
|
# class, so that default values defined in the derived class
|
|
# override those in the base class.
|
|
|
|
if not hasattr(self, '_constructorKeywords'):
|
|
# First time defineoptions has been called.
|
|
tmp = {}
|
|
for option, value in keywords.items():
|
|
tmp[option] = [value, 0]
|
|
self._constructorKeywords = tmp
|
|
self._optionInfo = {}
|
|
self._initialiseoptions_counter = 0
|
|
self._initialiseoptions_counter = self._initialiseoptions_counter + 1
|
|
|
|
if not hasattr(self, '_dynamicGroups'):
|
|
self._dynamicGroups = ()
|
|
self._dynamicGroups = self._dynamicGroups + tuple(dynamicGroups)
|
|
self.addoptions(optionDefs)
|
|
|
|
def addoptions(self, optionDefs):
|
|
# Add additional options, providing the default value and the
|
|
# method to call when the value is changed. See
|
|
# "defineoptions" for more details
|
|
|
|
# optimisations:
|
|
optionInfo = self._optionInfo
|
|
optionInfo_has_key = optionInfo.has_key
|
|
keywords = self._constructorKeywords
|
|
keywords_has_key = keywords.has_key
|
|
FUNCTION = _OPT_FUNCTION
|
|
|
|
for name, default, function in optionDefs:
|
|
if '_' not in name:
|
|
# The option will already exist if it has been defined
|
|
# in a derived class. In this case, do not override the
|
|
# default value of the option or the callback function
|
|
# if it is not None.
|
|
if not optionInfo_has_key(name):
|
|
if keywords_has_key(name):
|
|
value = keywords[name][0]
|
|
optionInfo[name] = [default, value, function]
|
|
del keywords[name]
|
|
else:
|
|
if _useTkOptionDb:
|
|
optionInfo[name] = \
|
|
[default, _DEFAULT_OPTION_VALUE, function]
|
|
else:
|
|
optionInfo[name] = [default, default, function]
|
|
elif optionInfo[name][FUNCTION] is None:
|
|
optionInfo[name][FUNCTION] = function
|
|
else:
|
|
# This option is of the form "component_option". If this is
|
|
# not already defined in self._constructorKeywords add it.
|
|
# This allows a derived class to override the default value
|
|
# of an option of a component of a base class.
|
|
if not keywords_has_key(name):
|
|
keywords[name] = [default, 0]
|
|
|
|
def createcomponent(self, componentName, componentAliases,
|
|
componentGroup, widgetClass, *widgetArgs, **kw):
|
|
# Create a component (during construction or later).
|
|
|
|
if self.__componentInfo.has_key(componentName):
|
|
raise ValueError, 'Component "%s" already exists' % componentName
|
|
|
|
if '_' in componentName:
|
|
raise ValueError, \
|
|
'Component name "%s" must not contain "_"' % componentName
|
|
|
|
if hasattr(self, '_constructorKeywords'):
|
|
keywords = self._constructorKeywords
|
|
else:
|
|
keywords = {}
|
|
for alias, component in componentAliases:
|
|
# Create aliases to the component and its sub-components.
|
|
index = string.find(component, '_')
|
|
if index < 0:
|
|
self.__componentAliases[alias] = (component, None)
|
|
else:
|
|
mainComponent = component[:index]
|
|
subComponent = component[(index + 1):]
|
|
self.__componentAliases[alias] = (mainComponent, subComponent)
|
|
|
|
# Remove aliases from the constructor keyword arguments by
|
|
# replacing any keyword arguments that begin with *alias*
|
|
# with corresponding keys beginning with *component*.
|
|
|
|
alias = alias + '_'
|
|
aliasLen = len(alias)
|
|
for option in keywords.keys():
|
|
if len(option) > aliasLen and option[:aliasLen] == alias:
|
|
newkey = component + '_' + option[aliasLen:]
|
|
keywords[newkey] = keywords[option]
|
|
del keywords[option]
|
|
|
|
componentPrefix = componentName + '_'
|
|
nameLen = len(componentPrefix)
|
|
for option in keywords.keys():
|
|
if len(option) > nameLen and option[:nameLen] == componentPrefix:
|
|
# The keyword argument refers to this component, so add
|
|
# this to the options to use when constructing the widget.
|
|
kw[option[nameLen:]] = keywords[option][0]
|
|
del keywords[option]
|
|
else:
|
|
# Check if this keyword argument refers to the group
|
|
# of this component. If so, add this to the options
|
|
# to use when constructing the widget. Mark the
|
|
# keyword argument as being used, but do not remove it
|
|
# since it may be required when creating another
|
|
# component.
|
|
index = string.find(option, '_')
|
|
if index >= 0 and componentGroup == option[:index]:
|
|
rest = option[(index + 1):]
|
|
kw[rest] = keywords[option][0]
|
|
keywords[option][1] = 1
|
|
|
|
if kw.has_key('pyclass'):
|
|
widgetClass = kw['pyclass']
|
|
del kw['pyclass']
|
|
if widgetClass is None:
|
|
return None
|
|
if len(widgetArgs) == 1 and type(widgetArgs[0]) == types.TupleType:
|
|
# Arguments to the constructor can be specified as either
|
|
# multiple trailing arguments to createcomponent() or as a
|
|
# single tuple argument.
|
|
widgetArgs = widgetArgs[0]
|
|
widget = apply(widgetClass, widgetArgs, kw)
|
|
componentClass = widget.__class__.__name__
|
|
self.__componentInfo[componentName] = (widget, widget.configure,
|
|
componentClass, widget.cget, componentGroup)
|
|
|
|
return widget
|
|
|
|
def destroycomponent(self, name):
|
|
# Remove a megawidget component.
|
|
|
|
# This command is for use by megawidget designers to destroy a
|
|
# megawidget component.
|
|
|
|
self.__componentInfo[name][0].destroy()
|
|
del self.__componentInfo[name]
|
|
|
|
def createlabel(self, parent, childCols = 1, childRows = 1):
|
|
|
|
labelpos = self['labelpos']
|
|
labelmargin = self['labelmargin']
|
|
if labelpos is None:
|
|
return
|
|
|
|
label = self.createcomponent('label',
|
|
(), None,
|
|
Tkinter.Label, (parent,))
|
|
|
|
if labelpos[0] in 'ns':
|
|
# vertical layout
|
|
if labelpos[0] == 'n':
|
|
row = 0
|
|
margin = 1
|
|
else:
|
|
row = childRows + 3
|
|
margin = row - 1
|
|
label.grid(column=2, row=row, columnspan=childCols, sticky=labelpos)
|
|
parent.grid_rowconfigure(margin, minsize=labelmargin)
|
|
else:
|
|
# horizontal layout
|
|
if labelpos[0] == 'w':
|
|
col = 0
|
|
margin = 1
|
|
else:
|
|
col = childCols + 3
|
|
margin = col - 1
|
|
label.grid(column=col, row=2, rowspan=childRows, sticky=labelpos)
|
|
parent.grid_columnconfigure(margin, minsize=labelmargin)
|
|
|
|
def initialiseoptions(self, dummy = None):
|
|
self._initialiseoptions_counter = self._initialiseoptions_counter - 1
|
|
if self._initialiseoptions_counter == 0:
|
|
unusedOptions = []
|
|
keywords = self._constructorKeywords
|
|
for name in keywords.keys():
|
|
used = keywords[name][1]
|
|
if not used:
|
|
# This keyword argument has not been used. If it
|
|
# does not refer to a dynamic group, mark it as
|
|
# unused.
|
|
index = string.find(name, '_')
|
|
if index < 0 or name[:index] not in self._dynamicGroups:
|
|
unusedOptions.append(name)
|
|
if len(unusedOptions) > 0:
|
|
if len(unusedOptions) == 1:
|
|
text = 'Unknown option "'
|
|
else:
|
|
text = 'Unknown options "'
|
|
raise KeyError, text + string.join(unusedOptions, ', ') + \
|
|
'" for ' + self.__class__.__name__
|
|
|
|
# Call the configuration callback function for every option.
|
|
FUNCTION = _OPT_FUNCTION
|
|
for info in self._optionInfo.values():
|
|
func = info[FUNCTION]
|
|
if func is not None and func is not INITOPT:
|
|
func()
|
|
|
|
#======================================================================
|
|
# Method used to configure the megawidget.
|
|
|
|
def configure(self, option=None, **kw):
|
|
# Query or configure the megawidget options.
|
|
#
|
|
# If not empty, *kw* is a dictionary giving new
|
|
# values for some of the options of this megawidget or its
|
|
# components. For options defined for this megawidget, set
|
|
# the value of the option to the new value and call the
|
|
# configuration callback function, if any. For options of the
|
|
# form <component>_<option>, where <component> is a component
|
|
# of this megawidget, call the configure method of the
|
|
# component giving it the new value of the option. The
|
|
# <component> part may be an alias or a component group name.
|
|
#
|
|
# If *option* is None, return all megawidget configuration
|
|
# options and settings. Options are returned as standard 5
|
|
# element tuples
|
|
#
|
|
# If *option* is a string, return the 5 element tuple for the
|
|
# given configuration option.
|
|
|
|
# First, deal with the option queries.
|
|
if len(kw) == 0:
|
|
# This configure call is querying the values of one or all options.
|
|
# Return 5-tuples:
|
|
# (optionName, resourceName, resourceClass, default, value)
|
|
if option is None:
|
|
rtn = {}
|
|
for option, config in self._optionInfo.items():
|
|
resourceClass = string.upper(option[0]) + option[1:]
|
|
rtn[option] = (option, option, resourceClass,
|
|
config[_OPT_DEFAULT], config[_OPT_VALUE])
|
|
return rtn
|
|
else:
|
|
config = self._optionInfo[option]
|
|
resourceClass = string.upper(option[0]) + option[1:]
|
|
return (option, option, resourceClass, config[_OPT_DEFAULT],
|
|
config[_OPT_VALUE])
|
|
|
|
# optimisations:
|
|
optionInfo = self._optionInfo
|
|
optionInfo_has_key = optionInfo.has_key
|
|
componentInfo = self.__componentInfo
|
|
componentInfo_has_key = componentInfo.has_key
|
|
componentAliases = self.__componentAliases
|
|
componentAliases_has_key = componentAliases.has_key
|
|
VALUE = _OPT_VALUE
|
|
FUNCTION = _OPT_FUNCTION
|
|
|
|
# This will contain a list of options in *kw* which
|
|
# are known to this megawidget.
|
|
directOptions = []
|
|
|
|
# This will contain information about the options in
|
|
# *kw* of the form <component>_<option>, where
|
|
# <component> is a component of this megawidget. It is a
|
|
# dictionary whose keys are the configure method of each
|
|
# component and whose values are a dictionary of options and
|
|
# values for the component.
|
|
indirectOptions = {}
|
|
indirectOptions_has_key = indirectOptions.has_key
|
|
|
|
for option, value in kw.items():
|
|
if optionInfo_has_key(option):
|
|
# This is one of the options of this megawidget.
|
|
# Make sure it is not an initialisation option.
|
|
if optionInfo[option][FUNCTION] is INITOPT:
|
|
raise KeyError, \
|
|
'Cannot configure initialisation option "' \
|
|
+ option + '" for ' + self.__class__.__name__
|
|
optionInfo[option][VALUE] = value
|
|
directOptions.append(option)
|
|
else:
|
|
index = string.find(option, '_')
|
|
if index >= 0:
|
|
# This option may be of the form <component>_<option>.
|
|
component = option[:index]
|
|
componentOption = option[(index + 1):]
|
|
|
|
# Expand component alias
|
|
if componentAliases_has_key(component):
|
|
component, subComponent = componentAliases[component]
|
|
if subComponent is not None:
|
|
componentOption = subComponent + '_' \
|
|
+ componentOption
|
|
|
|
# Expand option string to write on error
|
|
option = component + '_' + componentOption
|
|
|
|
if componentInfo_has_key(component):
|
|
# Configure the named component
|
|
componentConfigFuncs = [componentInfo[component][1]]
|
|
else:
|
|
# Check if this is a group name and configure all
|
|
# components in the group.
|
|
componentConfigFuncs = []
|
|
for info in componentInfo.values():
|
|
if info[4] == component:
|
|
componentConfigFuncs.append(info[1])
|
|
|
|
if len(componentConfigFuncs) == 0 and \
|
|
component not in self._dynamicGroups:
|
|
raise KeyError, 'Unknown option "' + option + \
|
|
'" for ' + self.__class__.__name__
|
|
|
|
# Add the configure method(s) (may be more than
|
|
# one if this is configuring a component group)
|
|
# and option/value to dictionary.
|
|
for componentConfigFunc in componentConfigFuncs:
|
|
if not indirectOptions_has_key(componentConfigFunc):
|
|
indirectOptions[componentConfigFunc] = {}
|
|
indirectOptions[componentConfigFunc][componentOption] \
|
|
= value
|
|
else:
|
|
raise KeyError, 'Unknown option "' + option + \
|
|
'" for ' + self.__class__.__name__
|
|
|
|
# Call the configure methods for any components.
|
|
map(apply, indirectOptions.keys(),
|
|
((),) * len(indirectOptions), indirectOptions.values())
|
|
|
|
# Call the configuration callback function for each option.
|
|
for option in directOptions:
|
|
info = optionInfo[option]
|
|
func = info[_OPT_FUNCTION]
|
|
if func is not None:
|
|
func()
|
|
|
|
def __setitem__(self, key, value):
|
|
apply(self.configure, (), {key: value})
|
|
|
|
#======================================================================
|
|
# Methods used to query the megawidget.
|
|
|
|
def component(self, name):
|
|
# Return a component widget of the megawidget given the
|
|
# component's name
|
|
# This allows the user of a megawidget to access and configure
|
|
# widget components directly.
|
|
|
|
# Find the main component and any subcomponents
|
|
index = string.find(name, '_')
|
|
if index < 0:
|
|
component = name
|
|
remainingComponents = None
|
|
else:
|
|
component = name[:index]
|
|
remainingComponents = name[(index + 1):]
|
|
|
|
# Expand component alias
|
|
if self.__componentAliases.has_key(component):
|
|
component, subComponent = self.__componentAliases[component]
|
|
if subComponent is not None:
|
|
if remainingComponents is None:
|
|
remainingComponents = subComponent
|
|
else:
|
|
remainingComponents = subComponent + '_' \
|
|
+ remainingComponents
|
|
|
|
widget = self.__componentInfo[component][0]
|
|
if remainingComponents is None:
|
|
return widget
|
|
else:
|
|
return widget.component(remainingComponents)
|
|
|
|
def interior(self):
|
|
return self._hull
|
|
|
|
def hulldestroyed(self):
|
|
return not _hullToMegaWidget.has_key(self._hull)
|
|
|
|
def __str__(self):
|
|
return str(self._hull)
|
|
|
|
def cget(self, option):
|
|
# Get current configuration setting.
|
|
|
|
# Return the value of an option, for example myWidget['font'].
|
|
|
|
if self._optionInfo.has_key(option):
|
|
return self._optionInfo[option][_OPT_VALUE]
|
|
else:
|
|
index = string.find(option, '_')
|
|
if index >= 0:
|
|
component = option[:index]
|
|
componentOption = option[(index + 1):]
|
|
|
|
# Expand component alias
|
|
if self.__componentAliases.has_key(component):
|
|
component, subComponent = self.__componentAliases[component]
|
|
if subComponent is not None:
|
|
componentOption = subComponent + '_' + componentOption
|
|
|
|
# Expand option string to write on error
|
|
option = component + '_' + componentOption
|
|
|
|
if self.__componentInfo.has_key(component):
|
|
# Call cget on the component.
|
|
componentCget = self.__componentInfo[component][3]
|
|
return componentCget(componentOption)
|
|
else:
|
|
# If this is a group name, call cget for one of
|
|
# the components in the group.
|
|
for info in self.__componentInfo.values():
|
|
if info[4] == component:
|
|
componentCget = info[3]
|
|
return componentCget(componentOption)
|
|
|
|
raise KeyError, 'Unknown option "' + option + \
|
|
'" for ' + self.__class__.__name__
|
|
|
|
__getitem__ = cget
|
|
|
|
def isinitoption(self, option):
|
|
return self._optionInfo[option][_OPT_FUNCTION] is INITOPT
|
|
|
|
def options(self):
|
|
options = []
|
|
if hasattr(self, '_optionInfo'):
|
|
for option, info in self._optionInfo.items():
|
|
isinit = info[_OPT_FUNCTION] is INITOPT
|
|
default = info[_OPT_DEFAULT]
|
|
options.append((option, default, isinit))
|
|
options.sort()
|
|
return options
|
|
|
|
def components(self):
|
|
# Return a list of all components.
|
|
|
|
# This list includes the 'hull' component and all widget subcomponents
|
|
|
|
names = self.__componentInfo.keys()
|
|
names.sort()
|
|
return names
|
|
|
|
def componentaliases(self):
|
|
# Return a list of all component aliases.
|
|
|
|
componentAliases = self.__componentAliases
|
|
|
|
names = componentAliases.keys()
|
|
names.sort()
|
|
rtn = []
|
|
for alias in names:
|
|
(mainComponent, subComponent) = componentAliases[alias]
|
|
if subComponent is None:
|
|
rtn.append((alias, mainComponent))
|
|
else:
|
|
rtn.append((alias, mainComponent + '_' + subComponent))
|
|
|
|
return rtn
|
|
|
|
def componentgroup(self, name):
|
|
return self.__componentInfo[name][4]
|
|
|
|
#=============================================================================
|
|
|
|
# The grab functions are mainly called by the activate() and
|
|
# deactivate() methods.
|
|
#
|
|
# Use pushgrab() to add a new window to the grab stack. This
|
|
# releases the grab by the window currently on top of the stack (if
|
|
# there is one) and gives the grab and focus to the new widget.
|
|
#
|
|
# To remove the grab from the window on top of the grab stack, call
|
|
# popgrab().
|
|
#
|
|
# Use releasegrabs() to release the grab and clear the grab stack.
|
|
|
|
def pushgrab(grabWindow, globalMode, deactivateFunction):
|
|
prevFocus = grabWindow.tk.call('focus')
|
|
grabInfo = {
|
|
'grabWindow' : grabWindow,
|
|
'globalMode' : globalMode,
|
|
'previousFocus' : prevFocus,
|
|
'deactivateFunction' : deactivateFunction,
|
|
}
|
|
_grabStack.append(grabInfo)
|
|
_grabtop()
|
|
grabWindow.focus_set()
|
|
|
|
def popgrab(window):
|
|
# Return the grab to the next window in the grab stack, if any.
|
|
|
|
# If this window is not at the top of the grab stack, then it has
|
|
# just been deleted by the window manager or deactivated by a
|
|
# timer. Call the deactivate method for the modal dialog above
|
|
# this one on the stack.
|
|
if _grabStack[-1]['grabWindow'] != window:
|
|
for index in range(len(_grabStack)):
|
|
if _grabStack[index]['grabWindow'] == window:
|
|
_grabStack[index + 1]['deactivateFunction']()
|
|
break
|
|
|
|
grabInfo = _grabStack[-1]
|
|
del _grabStack[-1]
|
|
|
|
topWidget = grabInfo['grabWindow']
|
|
prevFocus = grabInfo['previousFocus']
|
|
globalMode = grabInfo['globalMode']
|
|
|
|
if globalMode != 'nograb':
|
|
topWidget.grab_release()
|
|
|
|
if len(_grabStack) > 0:
|
|
_grabtop()
|
|
if prevFocus != '':
|
|
try:
|
|
topWidget.tk.call('focus', prevFocus)
|
|
except Tkinter.TclError:
|
|
# Previous focus widget has been deleted. Set focus
|
|
# to root window.
|
|
Tkinter._default_root.focus_set()
|
|
else:
|
|
# Make sure that focus does not remain on the released widget.
|
|
if len(_grabStack) > 0:
|
|
topWidget = _grabStack[-1]['grabWindow']
|
|
topWidget.focus_set()
|
|
else:
|
|
Tkinter._default_root.focus_set()
|
|
|
|
def grabstacktopwindow():
|
|
if len(_grabStack) == 0:
|
|
return None
|
|
else:
|
|
return _grabStack[-1]['grabWindow']
|
|
|
|
def releasegrabs():
|
|
# Release grab and clear the grab stack.
|
|
|
|
current = Tkinter._default_root.grab_current()
|
|
if current is not None:
|
|
current.grab_release()
|
|
_grabStack[:] = []
|
|
|
|
def _grabtop():
|
|
grabInfo = _grabStack[-1]
|
|
topWidget = grabInfo['grabWindow']
|
|
globalMode = grabInfo['globalMode']
|
|
|
|
if globalMode == 'nograb':
|
|
return
|
|
|
|
while 1:
|
|
try:
|
|
if globalMode:
|
|
topWidget.grab_set_global()
|
|
else:
|
|
topWidget.grab_set()
|
|
break
|
|
except Tkinter.TclError:
|
|
# Another application has grab. Keep trying until
|
|
# grab can succeed.
|
|
topWidget.after(100)
|
|
|
|
#=============================================================================
|
|
|
|
class MegaToplevel(MegaArchetype):
|
|
|
|
def __init__(self, parent = None, **kw):
|
|
# Define the options for this megawidget.
|
|
optiondefs = (
|
|
('activatecommand', None, None),
|
|
('deactivatecommand', None, None),
|
|
('master', None, None),
|
|
('title', None, self._settitle),
|
|
('hull_class', self.__class__.__name__, None),
|
|
)
|
|
self.defineoptions(kw, optiondefs)
|
|
|
|
# Initialise the base class (after defining the options).
|
|
MegaArchetype.__init__(self, parent, Tkinter.Toplevel)
|
|
|
|
# Initialise instance.
|
|
|
|
# Set WM_DELETE_WINDOW protocol, deleting any old callback, so
|
|
# memory does not leak.
|
|
if hasattr(self._hull, '_Pmw_WM_DELETE_name'):
|
|
self._hull.tk.deletecommand(self._hull._Pmw_WM_DELETE_name)
|
|
self._hull._Pmw_WM_DELETE_name = \
|
|
self.register(self._userDeleteWindow, needcleanup = 0)
|
|
self.protocol('WM_DELETE_WINDOW', self._hull._Pmw_WM_DELETE_name)
|
|
|
|
# Initialise instance variables.
|
|
|
|
self._firstShowing = 1
|
|
# Used by show() to ensure window retains previous position on screen.
|
|
|
|
# The IntVar() variable to wait on during a modal dialog.
|
|
self._wait = None
|
|
|
|
self._active = 0
|
|
self._userDeleteFunc = self.destroy
|
|
self._userModalDeleteFunc = self.deactivate
|
|
|
|
# Check keywords and initialise options.
|
|
self.initialiseoptions()
|
|
|
|
def _settitle(self):
|
|
title = self['title']
|
|
if title is not None:
|
|
self.title(title)
|
|
|
|
def userdeletefunc(self, func=None):
|
|
if func:
|
|
self._userDeleteFunc = func
|
|
else:
|
|
return self._userDeleteFunc
|
|
|
|
def usermodaldeletefunc(self, func=None):
|
|
if func:
|
|
self._userModalDeleteFunc = func
|
|
else:
|
|
return self._userModalDeleteFunc
|
|
|
|
def _userDeleteWindow(self):
|
|
if self.active():
|
|
self._userModalDeleteFunc()
|
|
else:
|
|
self._userDeleteFunc()
|
|
|
|
def destroy(self):
|
|
# Allow this to be called more than once.
|
|
if _hullToMegaWidget.has_key(self._hull):
|
|
self.deactivate()
|
|
|
|
# Remove circular references, so that object can get cleaned up.
|
|
del self._userDeleteFunc
|
|
del self._userModalDeleteFunc
|
|
|
|
MegaArchetype.destroy(self)
|
|
|
|
def show(self, master = None):
|
|
if self.state() != 'normal':
|
|
if self._firstShowing:
|
|
# Just let the window manager determine the window
|
|
# position for the first time.
|
|
geom = None
|
|
else:
|
|
# Position the window at the same place it was last time.
|
|
geom = self._sameposition()
|
|
setgeometryanddeiconify(self, geom)
|
|
|
|
if self._firstShowing:
|
|
self._firstShowing = 0
|
|
else:
|
|
if self.transient() == '':
|
|
self.tkraise()
|
|
|
|
# Do this last, otherwise get flashing on NT:
|
|
if master is not None:
|
|
if master == 'parent':
|
|
parent = self.winfo_parent()
|
|
# winfo_parent() should return the parent widget, but the
|
|
# the current version of Tkinter returns a string.
|
|
if type(parent) == types.StringType:
|
|
parent = self._hull._nametowidget(parent)
|
|
master = parent.winfo_toplevel()
|
|
self.transient(master)
|
|
|
|
self.focus()
|
|
|
|
def _centreonscreen(self):
|
|
# Centre the window on the screen. (Actually halfway across
|
|
# and one third down.)
|
|
|
|
parent = self.winfo_parent()
|
|
if type(parent) == types.StringType:
|
|
parent = self._hull._nametowidget(parent)
|
|
|
|
# Find size of window.
|
|
self.update_idletasks()
|
|
width = self.winfo_width()
|
|
height = self.winfo_height()
|
|
if width == 1 and height == 1:
|
|
# If the window has not yet been displayed, its size is
|
|
# reported as 1x1, so use requested size.
|
|
width = self.winfo_reqwidth()
|
|
height = self.winfo_reqheight()
|
|
|
|
# Place in centre of screen:
|
|
x = (self.winfo_screenwidth() - width) / 2 - parent.winfo_vrootx()
|
|
y = (self.winfo_screenheight() - height) / 3 - parent.winfo_vrooty()
|
|
if x < 0:
|
|
x = 0
|
|
if y < 0:
|
|
y = 0
|
|
return '+%d+%d' % (x, y)
|
|
|
|
def _sameposition(self):
|
|
# Position the window at the same place it was last time.
|
|
|
|
geometry = self.geometry()
|
|
index = string.find(geometry, '+')
|
|
if index >= 0:
|
|
return geometry[index:]
|
|
else:
|
|
return None
|
|
|
|
def activate(self, globalMode = 0, geometry = 'centerscreenfirst'):
|
|
if self._active:
|
|
raise ValueError, 'Window is already active'
|
|
if self.state() == 'normal':
|
|
self.withdraw()
|
|
|
|
self._active = 1
|
|
|
|
showbusycursor()
|
|
|
|
if self._wait is None:
|
|
self._wait = Tkinter.IntVar()
|
|
self._wait.set(0)
|
|
|
|
if geometry == 'centerscreenalways':
|
|
geom = self._centreonscreen()
|
|
elif geometry == 'centerscreenfirst':
|
|
if self._firstShowing:
|
|
# Centre the window the first time it is displayed.
|
|
geom = self._centreonscreen()
|
|
else:
|
|
# Position the window at the same place it was last time.
|
|
geom = self._sameposition()
|
|
elif geometry[:5] == 'first':
|
|
if self._firstShowing:
|
|
geom = geometry[5:]
|
|
else:
|
|
# Position the window at the same place it was last time.
|
|
geom = self._sameposition()
|
|
else:
|
|
geom = geometry
|
|
|
|
self._firstShowing = 0
|
|
|
|
setgeometryanddeiconify(self, geom)
|
|
|
|
# Do this last, otherwise get flashing on NT:
|
|
master = self['master']
|
|
if master is not None:
|
|
if master == 'parent':
|
|
parent = self.winfo_parent()
|
|
# winfo_parent() should return the parent widget, but the
|
|
# the current version of Tkinter returns a string.
|
|
if type(parent) == types.StringType:
|
|
parent = self._hull._nametowidget(parent)
|
|
master = parent.winfo_toplevel()
|
|
self.transient(master)
|
|
|
|
pushgrab(self._hull, globalMode, self.deactivate)
|
|
command = self['activatecommand']
|
|
if callable(command):
|
|
command()
|
|
self.wait_variable(self._wait)
|
|
|
|
return self._result
|
|
|
|
def deactivate(self, result=None):
|
|
if not self._active:
|
|
return
|
|
self._active = 0
|
|
|
|
# Restore the focus before withdrawing the window, since
|
|
# otherwise the window manager may take the focus away so we
|
|
# can't redirect it. Also, return the grab to the next active
|
|
# window in the stack, if any.
|
|
popgrab(self._hull)
|
|
|
|
command = self['deactivatecommand']
|
|
if callable(command):
|
|
command()
|
|
|
|
self.withdraw()
|
|
hidebusycursor(forceFocusRestore = 1)
|
|
|
|
self._result = result
|
|
self._wait.set(1)
|
|
|
|
def active(self):
|
|
return self._active
|
|
|
|
forwardmethods(MegaToplevel, Tkinter.Toplevel, '_hull')
|
|
|
|
#=============================================================================
|
|
|
|
class MegaWidget(MegaArchetype):
|
|
def __init__(self, parent = None, **kw):
|
|
# Define the options for this megawidget.
|
|
optiondefs = (
|
|
('hull_class', self.__class__.__name__, None),
|
|
)
|
|
self.defineoptions(kw, optiondefs)
|
|
|
|
# Initialise the base class (after defining the options).
|
|
MegaArchetype.__init__(self, parent, Tkinter.Frame)
|
|
|
|
# Check keywords and initialise options.
|
|
self.initialiseoptions()
|
|
|
|
forwardmethods(MegaWidget, Tkinter.Frame, '_hull')
|
|
|
|
#=============================================================================
|
|
|
|
# Public functions
|
|
#-----------------
|
|
|
|
_traceTk = 0
|
|
def tracetk(root = None, on = 1, withStackTrace = 0, file=None):
|
|
global _withStackTrace
|
|
global _traceTkFile
|
|
global _traceTk
|
|
|
|
if root is None:
|
|
root = Tkinter._default_root
|
|
|
|
_withStackTrace = withStackTrace
|
|
_traceTk = on
|
|
if on:
|
|
if hasattr(root.tk, '__class__'):
|
|
# Tracing already on
|
|
return
|
|
if file is None:
|
|
_traceTkFile = sys.stderr
|
|
else:
|
|
_traceTkFile = file
|
|
tk = _TraceTk(root.tk)
|
|
else:
|
|
if not hasattr(root.tk, '__class__'):
|
|
# Tracing already off
|
|
return
|
|
tk = root.tk.getTclInterp()
|
|
_setTkInterps(root, tk)
|
|
|
|
def showbusycursor():
|
|
|
|
_addRootToToplevelBusyInfo()
|
|
root = Tkinter._default_root
|
|
|
|
busyInfo = {
|
|
'newBusyWindows' : [],
|
|
'previousFocus' : None,
|
|
'busyFocus' : None,
|
|
}
|
|
_busyStack.append(busyInfo)
|
|
|
|
if _disableKeyboardWhileBusy:
|
|
# Remember the focus as it is now, before it is changed.
|
|
busyInfo['previousFocus'] = root.tk.call('focus')
|
|
|
|
if not _havebltbusy(root):
|
|
# No busy command, so don't call busy hold on any windows.
|
|
return
|
|
|
|
for (window, winInfo) in _toplevelBusyInfo.items():
|
|
if (window.state() != 'withdrawn' and not winInfo['isBusy']
|
|
and not winInfo['excludeFromBusy']):
|
|
busyInfo['newBusyWindows'].append(window)
|
|
winInfo['isBusy'] = 1
|
|
_busy_hold(window, winInfo['busyCursorName'])
|
|
|
|
# Make sure that no events for the busy window get
|
|
# through to Tkinter, otherwise it will crash in
|
|
# _nametowidget with a 'KeyError: _Busy' if there is
|
|
# a binding on the toplevel window.
|
|
window.tk.call('bindtags', winInfo['busyWindow'], 'Pmw_Dummy_Tag')
|
|
|
|
if _disableKeyboardWhileBusy:
|
|
# Remember previous focus widget for this toplevel window
|
|
# and set focus to the busy window, which will ignore all
|
|
# keyboard events.
|
|
winInfo['windowFocus'] = \
|
|
window.tk.call('focus', '-lastfor', window._w)
|
|
window.tk.call('focus', winInfo['busyWindow'])
|
|
busyInfo['busyFocus'] = winInfo['busyWindow']
|
|
|
|
if len(busyInfo['newBusyWindows']) > 0:
|
|
if os.name == 'nt':
|
|
# NT needs an "update" before it will change the cursor.
|
|
window.update()
|
|
else:
|
|
window.update_idletasks()
|
|
|
|
def hidebusycursor(forceFocusRestore = 0):
|
|
|
|
# Remember the focus as it is now, before it is changed.
|
|
root = Tkinter._default_root
|
|
if _disableKeyboardWhileBusy:
|
|
currentFocus = root.tk.call('focus')
|
|
|
|
# Pop the busy info off the stack.
|
|
busyInfo = _busyStack[-1]
|
|
del _busyStack[-1]
|
|
|
|
for window in busyInfo['newBusyWindows']:
|
|
# If this window has not been deleted, release the busy cursor.
|
|
if _toplevelBusyInfo.has_key(window):
|
|
winInfo = _toplevelBusyInfo[window]
|
|
winInfo['isBusy'] = 0
|
|
_busy_release(window)
|
|
|
|
if _disableKeyboardWhileBusy:
|
|
# Restore previous focus window for this toplevel window,
|
|
# but only if is still set to the busy window (it may have
|
|
# been changed).
|
|
windowFocusNow = window.tk.call('focus', '-lastfor', window._w)
|
|
if windowFocusNow == winInfo['busyWindow']:
|
|
try:
|
|
window.tk.call('focus', winInfo['windowFocus'])
|
|
except Tkinter.TclError:
|
|
# Previous focus widget has been deleted. Set focus
|
|
# to toplevel window instead (can't leave focus on
|
|
# busy window).
|
|
window.focus_set()
|
|
|
|
if _disableKeyboardWhileBusy:
|
|
# Restore the focus, depending on whether the focus had changed
|
|
# between the calls to showbusycursor and hidebusycursor.
|
|
if forceFocusRestore or busyInfo['busyFocus'] == currentFocus:
|
|
# The focus had not changed, so restore it to as it was before
|
|
# the call to showbusycursor,
|
|
previousFocus = busyInfo['previousFocus']
|
|
if previousFocus is not None:
|
|
try:
|
|
root.tk.call('focus', previousFocus)
|
|
except Tkinter.TclError:
|
|
# Previous focus widget has been deleted; forget it.
|
|
pass
|
|
else:
|
|
# The focus had changed, so restore it to what it had been
|
|
# changed to before the call to hidebusycursor.
|
|
root.tk.call('focus', currentFocus)
|
|
|
|
def clearbusycursor():
|
|
while len(_busyStack) > 0:
|
|
hidebusycursor()
|
|
|
|
def setbusycursorattributes(window, **kw):
|
|
_addRootToToplevelBusyInfo()
|
|
for name, value in kw.items():
|
|
if name == 'exclude':
|
|
_toplevelBusyInfo[window]['excludeFromBusy'] = value
|
|
elif name == 'cursorName':
|
|
_toplevelBusyInfo[window]['busyCursorName'] = value
|
|
else:
|
|
raise KeyError, 'Unknown busycursor attribute "' + name + '"'
|
|
|
|
def _addRootToToplevelBusyInfo():
|
|
# Include the Tk root window in the list of toplevels. This must
|
|
# not be called before Tkinter has had a chance to be initialised by
|
|
# the application.
|
|
|
|
root = Tkinter._default_root
|
|
if root == None:
|
|
root = Tkinter.Tk()
|
|
if not _toplevelBusyInfo.has_key(root):
|
|
_addToplevelBusyInfo(root)
|
|
|
|
def busycallback(command, updateFunction = None):
|
|
if not callable(command):
|
|
raise ValueError, \
|
|
'cannot register non-command busy callback %s %s' % \
|
|
(repr(command), type(command))
|
|
wrapper = _BusyWrapper(command, updateFunction)
|
|
return wrapper.callback
|
|
|
|
_errorReportFile = None
|
|
_errorWindow = None
|
|
|
|
def reporterrorstofile(file = None):
|
|
global _errorReportFile
|
|
_errorReportFile = file
|
|
|
|
def displayerror(text):
|
|
global _errorWindow
|
|
|
|
if _errorReportFile is not None:
|
|
_errorReportFile.write(text + '\n')
|
|
else:
|
|
# Print error on standard error as well as to error window.
|
|
# Useful if error window fails to be displayed, for example
|
|
# when exception is triggered in a <Destroy> binding for root
|
|
# window.
|
|
sys.stderr.write(text + '\n')
|
|
|
|
if _errorWindow is None:
|
|
# The error window has not yet been created.
|
|
_errorWindow = _ErrorWindow()
|
|
|
|
_errorWindow.showerror(text)
|
|
|
|
_root = None
|
|
_disableKeyboardWhileBusy = 1
|
|
|
|
def initialise(
|
|
root = None,
|
|
size = None,
|
|
fontScheme = None,
|
|
useTkOptionDb = 0,
|
|
noBltBusy = 0,
|
|
disableKeyboardWhileBusy = None,
|
|
):
|
|
# Remember if show/hidebusycursor should ignore keyboard events.
|
|
global _disableKeyboardWhileBusy
|
|
if disableKeyboardWhileBusy is not None:
|
|
_disableKeyboardWhileBusy = disableKeyboardWhileBusy
|
|
|
|
# Do not use blt busy command if noBltBusy is set. Otherwise,
|
|
# use blt busy if it is available.
|
|
global _haveBltBusy
|
|
if noBltBusy:
|
|
_haveBltBusy = 0
|
|
|
|
# Save flag specifying whether the Tk option database should be
|
|
# queried when setting megawidget option default values.
|
|
global _useTkOptionDb
|
|
_useTkOptionDb = useTkOptionDb
|
|
|
|
# If we haven't been given a root window, use the default or
|
|
# create one.
|
|
if root is None:
|
|
if Tkinter._default_root is None:
|
|
root = Tkinter.Tk()
|
|
else:
|
|
root = Tkinter._default_root
|
|
|
|
# If this call is initialising a different Tk interpreter than the
|
|
# last call, then re-initialise all global variables. Assume the
|
|
# last interpreter has been destroyed - ie: Pmw does not (yet)
|
|
# support multiple simultaneous interpreters.
|
|
global _root
|
|
if _root is not None and _root != root:
|
|
global _busyStack
|
|
global _errorWindow
|
|
global _grabStack
|
|
global _hullToMegaWidget
|
|
global _toplevelBusyInfo
|
|
_busyStack = []
|
|
_errorWindow = None
|
|
_grabStack = []
|
|
_hullToMegaWidget = {}
|
|
_toplevelBusyInfo = {}
|
|
_root = root
|
|
|
|
# Trap Tkinter Toplevel constructors so that a list of Toplevels
|
|
# can be maintained.
|
|
Tkinter.Toplevel.title = __TkinterToplevelTitle
|
|
|
|
# Trap Tkinter widget destruction so that megawidgets can be
|
|
# destroyed when their hull widget is destoyed and the list of
|
|
# Toplevels can be pruned.
|
|
Tkinter.Toplevel.destroy = __TkinterToplevelDestroy
|
|
Tkinter.Widget.destroy = __TkinterWidgetDestroy
|
|
|
|
# Modify Tkinter's CallWrapper class to improve the display of
|
|
# errors which occur in callbacks.
|
|
Tkinter.CallWrapper = __TkinterCallWrapper
|
|
|
|
# Make sure we get to know when the window manager deletes the
|
|
# root window. Only do this if the protocol has not yet been set.
|
|
# This is required if there is a modal dialog displayed and the
|
|
# window manager deletes the root window. Otherwise the
|
|
# application will not exit, even though there are no windows.
|
|
if root.protocol('WM_DELETE_WINDOW') == '':
|
|
root.protocol('WM_DELETE_WINDOW', root.destroy)
|
|
|
|
# Set the base font size for the application and set the
|
|
# Tk option database font resources.
|
|
import PmwLogicalFont
|
|
PmwLogicalFont._font_initialise(root, size, fontScheme)
|
|
|
|
return root
|
|
|
|
def alignlabels(widgets, sticky = None):
|
|
if len(widgets) == 0:
|
|
return
|
|
|
|
widgets[0].update_idletasks()
|
|
|
|
# Determine the size of the maximum length label string.
|
|
maxLabelWidth = 0
|
|
for iwid in widgets:
|
|
labelWidth = iwid.grid_bbox(0, 1)[2]
|
|
if labelWidth > maxLabelWidth:
|
|
maxLabelWidth = labelWidth
|
|
|
|
# Adjust the margins for the labels such that the child sites and
|
|
# labels line up.
|
|
for iwid in widgets:
|
|
if sticky is not None:
|
|
iwid.component('label').grid(sticky=sticky)
|
|
iwid.grid_columnconfigure(0, minsize = maxLabelWidth)
|
|
#=============================================================================
|
|
|
|
# Private routines
|
|
#-----------------
|
|
_callToTkReturned = 1
|
|
_recursionCounter = 1
|
|
|
|
class _TraceTk:
|
|
def __init__(self, tclInterp):
|
|
self.tclInterp = tclInterp
|
|
|
|
def getTclInterp(self):
|
|
return self.tclInterp
|
|
|
|
# Calling from python into Tk.
|
|
def call(self, *args, **kw):
|
|
global _callToTkReturned
|
|
global _recursionCounter
|
|
|
|
_callToTkReturned = 0
|
|
if len(args) == 1 and type(args[0]) == types.TupleType:
|
|
argStr = str(args[0])
|
|
else:
|
|
argStr = str(args)
|
|
_traceTkFile.write('CALL TK> %d:%s%s' %
|
|
(_recursionCounter, ' ' * _recursionCounter, argStr))
|
|
_recursionCounter = _recursionCounter + 1
|
|
try:
|
|
result = apply(self.tclInterp.call, args, kw)
|
|
except Tkinter.TclError, errorString:
|
|
_callToTkReturned = 1
|
|
_recursionCounter = _recursionCounter - 1
|
|
_traceTkFile.write('\nTK ERROR> %d:%s-> %s\n' %
|
|
(_recursionCounter, ' ' * _recursionCounter,
|
|
repr(errorString)))
|
|
if _withStackTrace:
|
|
_traceTkFile.write('CALL TK> stack:\n')
|
|
traceback.print_stack()
|
|
raise Tkinter.TclError, errorString
|
|
|
|
_recursionCounter = _recursionCounter - 1
|
|
if _callToTkReturned:
|
|
_traceTkFile.write('CALL RTN> %d:%s-> %s' %
|
|
(_recursionCounter, ' ' * _recursionCounter, repr(result)))
|
|
else:
|
|
_callToTkReturned = 1
|
|
if result:
|
|
_traceTkFile.write(' -> %s' % repr(result))
|
|
_traceTkFile.write('\n')
|
|
if _withStackTrace:
|
|
_traceTkFile.write('CALL TK> stack:\n')
|
|
traceback.print_stack()
|
|
|
|
_traceTkFile.flush()
|
|
return result
|
|
|
|
def __getattr__(self, key):
|
|
return getattr(self.tclInterp, key)
|
|
|
|
def _setTkInterps(window, tk):
|
|
window.tk = tk
|
|
for child in window.children.values():
|
|
_setTkInterps(child, tk)
|
|
|
|
#=============================================================================
|
|
|
|
# Functions to display a busy cursor. Keep a list of all toplevels
|
|
# and display the busy cursor over them. The list will contain the Tk
|
|
# root toplevel window as well as all other toplevel windows.
|
|
# Also keep a list of the widget which last had focus for each
|
|
# toplevel.
|
|
|
|
# Map from toplevel windows to
|
|
# {'isBusy', 'windowFocus', 'busyWindow',
|
|
# 'excludeFromBusy', 'busyCursorName'}
|
|
_toplevelBusyInfo = {}
|
|
|
|
# Pmw needs to know all toplevel windows, so that it can call blt busy
|
|
# on them. This is a hack so we get notified when a Tk topevel is
|
|
# created. Ideally, the __init__ 'method' should be overridden, but
|
|
# it is a 'read-only special attribute'. Luckily, title() is always
|
|
# called from the Tkinter Toplevel constructor.
|
|
|
|
def _addToplevelBusyInfo(window):
|
|
if window._w == '.':
|
|
busyWindow = '._Busy'
|
|
else:
|
|
busyWindow = window._w + '._Busy'
|
|
|
|
_toplevelBusyInfo[window] = {
|
|
'isBusy' : 0,
|
|
'windowFocus' : None,
|
|
'busyWindow' : busyWindow,
|
|
'excludeFromBusy' : 0,
|
|
'busyCursorName' : None,
|
|
}
|
|
|
|
def __TkinterToplevelTitle(self, *args):
|
|
# If this is being called from the constructor, include this
|
|
# Toplevel in the list of toplevels and set the initial
|
|
# WM_DELETE_WINDOW protocol to destroy() so that we get to know
|
|
# about it.
|
|
if not _toplevelBusyInfo.has_key(self):
|
|
_addToplevelBusyInfo(self)
|
|
self._Pmw_WM_DELETE_name = self.register(self.destroy, None, 0)
|
|
self.protocol('WM_DELETE_WINDOW', self._Pmw_WM_DELETE_name)
|
|
|
|
return apply(Tkinter.Wm.title, (self,) + args)
|
|
|
|
_haveBltBusy = None
|
|
def _havebltbusy(window):
|
|
global _busy_hold, _busy_release, _haveBltBusy
|
|
if _haveBltBusy is None:
|
|
import PmwBlt
|
|
_haveBltBusy = PmwBlt.havebltbusy(window)
|
|
_busy_hold = PmwBlt.busy_hold
|
|
if os.name == 'nt':
|
|
# There is a bug in Blt 2.4i on NT where the busy window
|
|
# does not follow changes in the children of a window.
|
|
# Using forget works around the problem.
|
|
_busy_release = PmwBlt.busy_forget
|
|
else:
|
|
_busy_release = PmwBlt.busy_release
|
|
return _haveBltBusy
|
|
|
|
class _BusyWrapper:
|
|
def __init__(self, command, updateFunction):
|
|
self._command = command
|
|
self._updateFunction = updateFunction
|
|
|
|
def callback(self, *args):
|
|
showbusycursor()
|
|
rtn = apply(self._command, args)
|
|
|
|
# Call update before hiding the busy windows to clear any
|
|
# events that may have occurred over the busy windows.
|
|
if callable(self._updateFunction):
|
|
self._updateFunction()
|
|
|
|
hidebusycursor()
|
|
return rtn
|
|
|
|
#=============================================================================
|
|
|
|
def drawarrow(canvas, color, direction, tag, baseOffset = 0.25, edgeOffset = 0.15):
|
|
canvas.delete(tag)
|
|
|
|
bw = (string.atoi(canvas['borderwidth']) +
|
|
string.atoi(canvas['highlightthickness']))
|
|
width = string.atoi(canvas['width'])
|
|
height = string.atoi(canvas['height'])
|
|
|
|
if direction in ('up', 'down'):
|
|
majorDimension = height
|
|
minorDimension = width
|
|
else:
|
|
majorDimension = width
|
|
minorDimension = height
|
|
|
|
offset = round(baseOffset * majorDimension)
|
|
if direction in ('down', 'right'):
|
|
base = bw + offset
|
|
apex = bw + majorDimension - offset
|
|
else:
|
|
base = bw + majorDimension - offset
|
|
apex = bw + offset
|
|
|
|
if minorDimension > 3 and minorDimension % 2 == 0:
|
|
minorDimension = minorDimension - 1
|
|
half = int(minorDimension * (1 - 2 * edgeOffset)) / 2
|
|
low = round(bw + edgeOffset * minorDimension)
|
|
middle = low + half
|
|
high = low + 2 * half
|
|
|
|
if direction in ('up', 'down'):
|
|
coords = (low, base, high, base, middle, apex)
|
|
else:
|
|
coords = (base, low, base, high, apex, middle)
|
|
kw = {'fill' : color, 'outline' : color, 'tag' : tag}
|
|
apply(canvas.create_polygon, coords, kw)
|
|
|
|
#=============================================================================
|
|
|
|
# Modify the Tkinter destroy methods so that it notifies us when a Tk
|
|
# toplevel or frame is destroyed.
|
|
|
|
# A map from the 'hull' component of a megawidget to the megawidget.
|
|
# This is used to clean up a megawidget when its hull is destroyed.
|
|
_hullToMegaWidget = {}
|
|
|
|
def __TkinterToplevelDestroy(tkWidget):
|
|
if _hullToMegaWidget.has_key(tkWidget):
|
|
mega = _hullToMegaWidget[tkWidget]
|
|
try:
|
|
mega.destroy()
|
|
except:
|
|
_reporterror(mega.destroy, ())
|
|
else:
|
|
# Delete the busy info structure for this toplevel (if the
|
|
# window was created before Pmw.initialise() was called, it
|
|
# will not have any.
|
|
if _toplevelBusyInfo.has_key(tkWidget):
|
|
del _toplevelBusyInfo[tkWidget]
|
|
if hasattr(tkWidget, '_Pmw_WM_DELETE_name'):
|
|
tkWidget.tk.deletecommand(tkWidget._Pmw_WM_DELETE_name)
|
|
del tkWidget._Pmw_WM_DELETE_name
|
|
Tkinter.BaseWidget.destroy(tkWidget)
|
|
|
|
def __TkinterWidgetDestroy(tkWidget):
|
|
if _hullToMegaWidget.has_key(tkWidget):
|
|
mega = _hullToMegaWidget[tkWidget]
|
|
try:
|
|
mega.destroy()
|
|
except:
|
|
_reporterror(mega.destroy, ())
|
|
else:
|
|
Tkinter.BaseWidget.destroy(tkWidget)
|
|
|
|
#=============================================================================
|
|
|
|
# Add code to Tkinter to improve the display of errors which occur in
|
|
# callbacks.
|
|
|
|
class __TkinterCallWrapper:
|
|
def __init__(self, func, subst, widget):
|
|
self.func = func
|
|
self.subst = subst
|
|
self.widget = widget
|
|
|
|
# Calling back from Tk into python.
|
|
def __call__(self, *args):
|
|
try:
|
|
if self.subst:
|
|
args = apply(self.subst, args)
|
|
if _traceTk:
|
|
if not _callToTkReturned:
|
|
_traceTkFile.write('\n')
|
|
if hasattr(self.func, 'im_class'):
|
|
name = self.func.im_class.__name__ + '.' + \
|
|
self.func.__name__
|
|
else:
|
|
name = self.func.__name__
|
|
if len(args) == 1 and hasattr(args[0], 'type'):
|
|
# The argument to the callback is an event.
|
|
eventName = _eventTypeToName[string.atoi(args[0].type)]
|
|
if eventName in ('KeyPress', 'KeyRelease',):
|
|
argStr = '(%s %s Event: %s)' % \
|
|
(eventName, args[0].keysym, args[0].widget)
|
|
else:
|
|
argStr = '(%s Event, %s)' % (eventName, args[0].widget)
|
|
else:
|
|
argStr = str(args)
|
|
_traceTkFile.write('CALLBACK> %d:%s%s%s\n' %
|
|
(_recursionCounter, ' ' * _recursionCounter, name, argStr))
|
|
_traceTkFile.flush()
|
|
return apply(self.func, args)
|
|
except SystemExit, msg:
|
|
raise SystemExit, msg
|
|
except:
|
|
_reporterror(self.func, args)
|
|
|
|
_eventTypeToName = {
|
|
2 : 'KeyPress', 15 : 'VisibilityNotify', 28 : 'PropertyNotify',
|
|
3 : 'KeyRelease', 16 : 'CreateNotify', 29 : 'SelectionClear',
|
|
4 : 'ButtonPress', 17 : 'DestroyNotify', 30 : 'SelectionRequest',
|
|
5 : 'ButtonRelease', 18 : 'UnmapNotify', 31 : 'SelectionNotify',
|
|
6 : 'MotionNotify', 19 : 'MapNotify', 32 : 'ColormapNotify',
|
|
7 : 'EnterNotify', 20 : 'MapRequest', 33 : 'ClientMessage',
|
|
8 : 'LeaveNotify', 21 : 'ReparentNotify', 34 : 'MappingNotify',
|
|
9 : 'FocusIn', 22 : 'ConfigureNotify', 35 : 'VirtualEvents',
|
|
10 : 'FocusOut', 23 : 'ConfigureRequest', 36 : 'ActivateNotify',
|
|
11 : 'KeymapNotify', 24 : 'GravityNotify', 37 : 'DeactivateNotify',
|
|
12 : 'Expose', 25 : 'ResizeRequest', 38 : 'MouseWheelEvent',
|
|
13 : 'GraphicsExpose', 26 : 'CirculateNotify',
|
|
14 : 'NoExpose', 27 : 'CirculateRequest',
|
|
}
|
|
|
|
def _reporterror(func, args):
|
|
# Fetch current exception values.
|
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
|
|
# Give basic information about the callback exception.
|
|
if type(exc_type) == types.ClassType:
|
|
# Handle python 1.5 class exceptions.
|
|
exc_type = exc_type.__name__
|
|
msg = str(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')
|