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