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