File: pygadgets-products/unzipped/_PyToe/TicTacToe/PP4E/Gui/Tools/guimaker.py

"""
################################################################################
An extended Frame that makes window menus and toolbars automatically.
This is a general utility that can be used for any program's menu/tools.
Use GuiMakerFrameMenu for embedded components (makes frame-based menus).
Use GuiMakerWindowMenu for top-level windows (makes Tk8.0+ window menus).
Menus built here are top-of-screen on a Mac, and top-of-window elsewhere.
See the self-test code (and PyEdit) for an example layout tree format.

Extended Jan/Apr-2017 (post PP4E) with:

Menu acelerator keys
  Add menu accelerator keys (in addition to underline shortcut keys), via an
  optional and backward-compatible 4th item in menu command specs.  Accelerators
  matter: underline shortcuts don't work on a Mac, and don't work on Windows
  when using a frame-based menu in embedded mode.

  Accelerators require both a menu setting, and an event binding on a widget or
  window.  They use different control-key names on Mac and others by convention:
  '?-*-x' here means 'Control-Command-x' on Mac, and 'Alt-Control-x' elsewhere.
  This supports two control key replacements; menus may code others (e.g., Fn).

  For more docs and example usage, see this file's self-test below, and PyEdit.
  Note that accelerators take precedence over underline keys when they conflict,
  and accelerator keys have menu-global scope: they are not unique per pulldown.

Automatic help menu tweaks
  Use a 'self.appname' string if present for the Unix auto-help menu's text.
  Caveat: auto-help menu isn't very flexible, and doesn't support accelerators.

Underlines suppression in embedded mode
  Do not show Alt-key underlines in embedded mode.  Underlines don't function
  in this mode, only for top-level window menus (and never on Mac OS X).

Mac default-menus customization
  On Mac OS X, customize Tk's default "apple" (application) menu with the
  client's help (not Tk's); add an automatic Window menu that shows windows
  much like Dock; and catch and route Mac app-menu/Dock/shutdown Quits to
  the app, all per Tk's standard (yet arguably-convoluted) rules.

  For GuiMakerWindowMenu (top-level menu) clients, this is automatic at menu
  build time, and reuses the main/first window's help and quit callbacks.
  App-menu Help and Quit are app-global and shouldn't vary per window; WM
  quit (the upper-left red dot) and other menu items may still be per-window.

  For all other programs, a function is provided which should be called once
  per program with the program's root window's help and quit actions; the
  root menu will be inherited by all other windows, and any later guimaker
  client popups will reuse the program's help/quit.  This function can also
  be used by programs that make no menu (but get one "for free" on the Mac),
  and additional function fixes menu-inheritance issues for menuless dialogs.

  Without the new code, menus wind up with automatic Tk-propaganda items, and a
  Quit in the app menu or Dock silently closes the program (odd default, that!).
  Mac also adds things like window controls, and an emojis/symbols selector in 
  all Edit menus (useful in text-based apps, but largely pointless elsewhere).

Toolbar spacers and Linux labels
  Spacers between button groups can be added, and made to expand/shrink 
  proportionally with the window or not per an attribute on the subject 
  object.  Also, for Linux, use Labels instead of Buttons to avoid the 
  too-wide layout of Tk buttons on Linux (only).  See makeToolBar() below.
################################################################################
"""

import sys
from tkinter import *                     # widget classes
from tkinter.messagebox import showinfo


###############################################################################
# The main GUI border-builder class: a Frame with definable menu and toolbar
###############################################################################


class GuiMaker(Frame):
    menuBar    = []                       # class defaults
    toolBar    = []                       # change per instance in subclasses
    helpButton = True                     # set these in start() if need self


    def __init__(self, parent=None):
        Frame.__init__(self, parent)            # passed parent or implicit Tk()
        self.pack(expand=YES, fill=BOTH)        # make this frame stretchable
        self.start()                            # for subclass: set menu/toolBar
        self.accbind = self.accBindWidget()     # for subclass: if accelerators
        self.makeMenuBar()                      # done here: build menu bar
        self.makeToolBar()                      # done here: build toolbar
        self.makeWidgets()                      # for subclass: add middle part


    def makeMenuBar(self):
        """
        --------------------------------------------------------
        make menu bar at the top (see also Tk8.0 menus below);
        menubar uses expand=no, fill=x so same width on resize;
        --------------------------------------------------------
        """
        menubar = Frame(self, relief=RAISED, bd=2)
        menubar.pack(side=TOP, fill=X)

        # client-defined menus
        for (name, key, items) in self.menuBar:
            mbutton  = Menubutton(menubar, text=name, underline=key)
            mbutton.pack(side=LEFT)
            pulldown = Menu(mbutton)
            self.addMenuItems(pulldown, items)
            mbutton.config(menu=pulldown)

        # automatic help button (no accelerator)
        if self.helpButton:
            Button(menubar, text    = 'Help',
                            cursor  = 'gumby',   # hmm
                            relief  = FLAT,
                            command = self.onHelp).pack(side=RIGHT)


    def addMenuItems(self, menu, items):
        """
        --------------------------------------------------------
        scan nested items specs list, adding to menu;
        called recursively for any cascading submenus;
        Jan2017: add accelerators and their bindings;
        --------------------------------------------------------
        """
        for item in items:

            if type(item) == str:
                #---------------------------------------------------------------
                # string: separator line (e.g., '----')
                #---------------------------------------------------------------
                menu.add_separator()

            elif type(item) == list:
                #---------------------------------------------------------------
                # list: disabled item numbers list (e.g., [0, 2, 3])
                #---------------------------------------------------------------
                for num in item:
                    menu.entryconfig(num, state=DISABLED)

            elif type(item[2]) == list:
                #---------------------------------------------------------------
                # sublist: menu cascade =  (label, under, [items...])
                #---------------------------------------------------------------
                if not isinstance(self, GuiMakerWindowMenu):
                    underarg = {}                             # not if embedded
                else:
                    underarg = dict(underline = item[1])      # unders on Windows

                pullover = Menu(menu)                         # make and add submenu
                self.addMenuItems(pullover, item[2])          # recur for items
                menu.add_cascade(label = item[0],             # add cascade
                                 menu  = pullover,            # no accelerator
                                 **underarg)                  # alt underline?

            else:
                #---------------------------------------------------------------
                # callback: menu command = (label, under, cmd [, accel|None])
                #---------------------------------------------------------------
                if not isinstance(self, GuiMakerWindowMenu):
                    # don't show alt-unders on Windows if embedded
                    underarg = {}
                else:
                    underarg = dict(underline = item[1])        # Mac ignores
                
                if len(item) == 3 or (len(item) == 4 and item[3] == None):
                    # without accelerator (and b/w compat)
                    accelarg = {}
                else:
                    # with accelerator: per-platform keys
                    if sys.platform.startswith('darwin'):       # Mac
                        hotkey, altkey = ('Command', 'Control')
                    else:
                        hotkey, altkey = ('Control', 'Alt')     # Others
                    accstr = item[3].replace('*', hotkey).replace('?', altkey)

                    # reformat for Windows; Mac accepts and converts to icons
                    disstr = accstr.replace('-', '+').replace('Control', 'Ctrl')
                    accelarg = dict(accelerator = disstr)

                # make menu entry with possible shortcuts
                shortcutargs = underarg
                shortcutargs.update(accelarg)                    
                menu.add_command(label   = item[0],       # add command
                                 command = item[2],       # action=callable
                                 **shortcutargs)          # under? accel?

                # bind accelerator using tk event syntax
                if accelarg:
                    def callback(event, command=item[2]):
                        # item[2] saves loop's current value (else=last?);
                        # returns 'break' to disable standard tk bindings,
                        # else cmd/ctrl-v may wind up pasting text twice!
                        command()
                        return 'break'
                    tkspec = '<' + accstr +'>'
                    self.accbind.bind(tkspec, callback)


    def makeToolBar(self):
        """
        --------------------------------------------------------
        make button bar at bottom of window, if any;
        expand=no, fill=x (defaults_ so same width on resize;
        this could support images too, per Chapter 9: would
        require  prebuilt gifs or a PIL install for thumbnails;

        Mar2017: add spacers if item is a str, with fixed or
        expanding layout, system default or set font, where
        '>...' packs on the right, '....' packs on the left;
        Apr2017: use narrower Labels, not Buttons, on Linux;
        --------------------------------------------------------
        """
        if self.toolBar:
            toolbar = Frame(self, cursor='hand2', relief=SUNKEN, bd=2)
            toolbar.pack(side=BOTTOM, fill=X)
            for item in self.toolBar:

                if isinstance(item, str):
                    # spacer
                    side = RIGHT if item.startswith('>') else LEFT
                    if getattr(self, 'toolbarFixedLayout', False):
                        lab = Label(toolbar, text='   ')
                        lab.pack(side=side, expand=NO)
                    else:
                        lab = Label(toolbar, text='')
                        lab.pack(side=side, expand=YES)

                else:
                    # button with callback
                    (name, action, where) = item
                    if sys.platform.startswith('linux'):
                        but = Label(toolbar, text=name, bd=1, relief=RAISED)
                        but.bind('<Button-1>', lambda evt, act=action: act())
                    else:
                        but = Button(toolbar, text=name, command=action)
                    but.pack(where)
                    if getattr(self, 'toolbarFont', False):
                        but.config(font=self.toolbarFont)


    #----------------------------------
    # subclass protocol methods follow
    #----------------------------------

    def start(self): 
        """
        call 1: setup menu/toolbar structure;
        override me in subclass to use self;
        """
        pass

    def accBindWidget(self):
        """
        call 2: return bind widget for accelerators;
        override me in subclass if accelerators used;
        """
        return None

    def makeWidgets(self):
        """
        call 3: after menu/toolbar built here, make 'middle'
        part last, so menu/toolbar is always on top/bottom
        of window, and clipped last on all window resizes;
        override this default, pack middle part on any side;
        for grids: grid middle part in a dummy packed frame;
        """
        name = Label(self,
                     width=40, height=10,
                     relief=SUNKEN, bg='white',
                     text   = self.__class__.__name__,
                     cursor = 'crosshair')
        name.pack(expand=YES, fill=BOTH, side=TOP)

    def onHelp(self):
        "default: override me in subclass"
        showinfo('Help', 'Sorry, no help for ' + self.__class__.__name__)

    def onAbout(self):
        "default: override me in subclass"
        self.onHelp()  # default to Help if no About
        
    def onQuit(self):
        "default: override me in subclass"
        self.quit()    # default to Tk shutdow or class's Quit



###############################################################################
# Customize for Tk 8.0+ main window menu bar, instead of a frame
###############################################################################


# Use this variant for embedded component menus (Frames).
# On Mac, such windows should not change the main menu bar.
GuiMakerFrameMenu = GuiMaker


class GuiMakerWindowMenu(GuiMaker):
    """
    --------------------------------------------------------------
    Use this variant for top-level window menus in Tk 8.0+:
    at top of screen on Mac, at top of windows on Windows+Linux.
    Called only for top-level windows, not embedded Frame menus.
    
    Jan2017: On Mac OS X, customize apple (app) and help, add
    Windows, catch app and Dock Quit.  Unlike others, app and
    Window require 'name' keyword argument; don't use 'name'
    for help, else source gets auto 'Python'/appname entry too.

    In all windows built, Help and Quit are routed to those of
    the first, which is assumed to be the app Tk root.  This
    model assumes these are app-wide, not window-specific.  We
    need to customise Mac's default menus here, not by a later
    call to fixAppleMenuBar ahead: menus appear as laid out if
    they are rebuilt, and not inherited from the root window.

    Also route MAC's standard app menu Quit, also called for
    Quit in Dock and system shutdown.  This is app-wide quit and
    differs from the WM close button (the upper-left red circle)
    which may still be window-specific.  Not catching it exits
    the app sliently losing any changes in the process.  The
    Quit action is registered just once for first/root window.
    --------------------------------------------------------------
    """
    
    # class attrs, localized to this class's name
    __firstWindow = True    # e.g., first PyEdit Tk -or- embedding app's root
    __appName     = None    # use root's callbacks for all app windows
    __appHelp     = None    # per-window actions in non-default menus
    __appAbout    = None    # about = help by default, per GuiMaker super
    __appQuit     = None    # quit = per GuiMaker super or app call, by default
    
    __runningOnMac     = sys.platform.startswith('darwin')
    __runningOnWindows = sys.platform.startswith('win')
    __runningOnLinux   = sys.platform.startswith('linux')


    def makeMenuBar(self):
        """
        make menu bar at top of window (Windows, Linux) or display (Mac);
        on Mac, also customize app-wide defaults redrawn for each client
        window, and and set quit handler run on app-menu Quit and Dock;
        """
        if self.__firstWindow:
            #
            # Popups (non-Tks) should not be the firstWindow here;
            # if one is, it was opened by another program that did
            # not call fixAppleMenuBar; see that for more details.
            #
            if not isinstance(self.master, Tk):
                print("Warning: using a popup window's quit and help.")
                print('To avoid this, call guimaker.fixAppleMenuBar().')
            GuiMakerWindowMenu.__appName  = getattr(self, 'appname', '')
            GuiMakerWindowMenu.__appHelp  = self.onHelp
            GuiMakerWindowMenu.__appAbout = self.onAbout
            GuiMakerWindowMenu.__appQuit  = self.onQuit

        window = self.master
        assert isinstance(window, (Tk, Toplevel))
        menubar = Menu(window)

        # Mac: customize standard app menu
        if self.__runningOnMac:
            appmenu = Menu(menubar, name='apple')
            if self.__appName:
                menutext = 'About ' + self.__appName
            else:
                menutext = 'About'
            appmenu.add_command(label=menutext, command=self.__appAbout)
            menubar.add_cascade(menu=appmenu)

        # All: client-defined menus, via data structure (per-window)
        for (name, key, items) in self.menuBar:
            pulldown = Menu(menubar)
            self.addMenuItems(pulldown, items)
            menubar.add_cascade(menu=pulldown, label=name, underline=key)

        # Mac: add automatic windows (dock-ish) menu
        if self.__runningOnMac:
            winmenu = Menu(menubar, name='window')
            menubar.add_cascade(menu=winmenu, label='Window')

        # Automatic help menu last=rightmost (no accelerator, always on Mac)
        if self.helpButton or self.__runningOnMac:
            if self.__runningOnWindows:
                # Windows: just a button on menu bar
                menubar.add_command(label='Help', command=self.__appHelp)
            else:
                # Linux+Mac: need a real menu pulldown, Mac augments it
                assert self.__runningOnMac or self.__runningOnLinux
                if self.__appName:
                    menutext = self.__appName + ' Help'
                else:
                    menutext = 'About'
                helpmenu = Menu(menubar)  # omit name
                helpmenu.add_command(label=menutext, command=self.__appHelp)
                menubar.add_cascade(menu=helpmenu, label='Help')

        # attach to window last, so app/etc menus work on Mac
        window.config(menu=menubar)

        # Mac: catch std app-menu/Dock/shutdown Quit: this != WM close button
        # registers this once, uses .tk to get to _tkinter from Toplevel or Tk
        if self.__runningOnMac and self.__firstWindow:
            window.tk.createcommand('tk::mac::Quit', self.__appQuit)

        GuiMakerWindowMenu.__firstWindow = False   # for the next instance


    @staticmethod
    def setAppWideInfo(appname, helpaction, aboutaction, quitaction):
        """
        provide access to unmangled names from outside this class;
        a classmethod would work here too (self=class object);
        """
        GuiMakerWindowMenu.__firstWindow = False
        GuiMakerWindowMenu.__appName     = appname
        GuiMakerWindowMenu.__appHelp     = helpaction
        GuiMakerWindowMenu.__appAbout    = aboutaction 
        GuiMakerWindowMenu.__appQuit     = quitaction


    @staticmethod
    def getAppWideInfo():
        """
        provide access to unmangled names from outside this class;
        a classmethod would work here too (self=class object);
        """
        return (GuiMakerWindowMenu.__appName,
                GuiMakerWindowMenu.__appHelp,
                GuiMakerWindowMenu.__appAbout,
                GuiMakerWindowMenu.__appQuit)                     



###############################################################################
# For non-GuiMakerWindowMenu clients: customize default menus on Mac OS X
###############################################################################


def fixAppleMenuBar(window,            # a Tk or Toplevel window
                    appname,           # text added to menu labels
                    helpaction=None,   # Help menu callback, no args
                    aboutaction=None,  # About calback, default=Help
                    quitaction=None):  # Quit callback no args, else no-op

    """
    -----------------------------------------------------------------------
    Usage: this should be called on Mac OS for all programs that are not
    GuiMakerWindowMenu clients themselves, but create GuiMakerWindowMenu
    client popup windows: it picks up and saves the app's quit and help to
    apply to popups.  For other programs, this call is optional but useful
    for minimal menu and Dock config.  This is a no-op on Windows an Linux.

    Details: for Mac/Tk programs that are not GuiMakerWindowMenu clients,
    this function customizes the default Mac menus that always show up at
    top of screen even if the program builds no real menu.  Call this once
    per program, with the main window's app-wide help/quit actions; the
    customized menu with these actions will be inherited by other windows
    in the program that do not build a per-window menu of their own.
    
    Without this, Mac/Tk programs wind up with Tk-propaganda help and
    demos, and a Quit in the app menu or Dock silently closes the entire
    program - changes or not.  On Windows and Linux calling this is a no-op
    because no menubar appears unless one is built explicitly (unlike Mac).

    Note that program-defined menus can vary per window (e.g., "Cut"
    may apply to the current window's text) if a new menu is built for
    new windows (that's why GuiMakerWindowMenu repeats some code here),
    and WM quit can still vary per window.  By contrast, the settings
    made here apply to the entire app/program, not individual windows,
    and remain in force for all windows' menus.  In terms of use cases:
    
    - Programs that mix in GuiMakerWindowMenu for top-toplevel menus:
      do *not* call this, as menus are customized in the superclass.

    - Program that embed GuiMakerFrameMenu windows: call this once
      for the app's main window, not for windows with embedded menus.

    - Programs that build no program-specific menus of their own:
      call this once for the program's main window.

    Subtlety: this call is basically *required* of non-guimaker client
    programs run on Mac that make GuiMakerWindowMenu-client popups.  For
    example, programs that embed PyEdit as a library (e.g., PyMailGUI) may
    create both frame-based menus and standalone popup windows.  For the
    latter, this saves this app's help/quit info, to be applied to menus
    built for later PyEdit popups in GuiMakerWindowMenu.  In this use
    case, the 'first' window is the enclosing app, not a PyEdit Tk; its
    app-wide help and quit will be used in the PyEdit popups' menus too.
    
    Mac's always-present menu paradigm differs markedly from Windows, 
    and requires extra steps.  Modal dialogs may also disable menu 
    actions, and non-modal dialogs can either redraw menus minimally
    or allow them to remain active: see fixAppleMenuBarChild() ahead.
    -----------------------------------------------------------------------
    """

    # defaults
    helpaction  = helpaction  or (lambda: showinfo(appname, 'No help available'))
    aboutaction = aboutaction or helpaction
    quitaction  = quitaction  or (lambda: None)

    # save this app's info for use in any GuiMaker-based popups it creates
    GuiMakerWindowMenu.setAppWideInfo(appname, helpaction, aboutaction, quitaction)

    if sys.platform.startswith('darwin'):       # for Mac only
        menubar = Menu(window)                  # for this window

        # customize standard app menu on Mac
        menutext = 'About ' + appname
        appmenu = Menu(menubar, name='apple')
        appmenu.add_command(label=menutext, command=aboutaction)
        menubar.add_cascade(menu=appmenu)

        # add automatic windows (dock-ish) menu on Mac
        winmenu = Menu(menubar, name='window')
        menubar.add_cascade(menu=winmenu, label='Window')

        # automatic help menu last=rightmost (no accelerator)
        menutext = appname + ' Help'
        helpmenu = Menu(menubar)  # omit name 
        helpmenu.add_command(label=menutext, command=helpaction)
        menubar.add_cascade(menu=helpmenu, label='Help')

        # attach to window last, so app/etc menus work 
        window.config(menu=menubar)

        # catch std app-menu/Dock/shutdown Quit: this != WM close button
        # registers this once, uses .tk to get to _tkinter from any
        window.tk.createcommand('tk::mac::Quit', quitaction)



def fixAppleMenuBarChild(window):            # a Tk or Toplevel window
    """
    -----------------------------------------------------------------------
    Usage: on Mac OS X,

    1) Programs that are NOT a client of the GuiMakerWindowMenu class
    can call this to build default menus for the app on child windows
    that have no real menu, but no longer inherit one from the root due
    to other menu-ful windows.

    2) Programs that ARE GuiMakerWindowMenu clients may need to call
    this too, to build a minimal explicit menu for non-modal dialogs 
    without real menus of their own.
    
    Details: but wait - the Mac Tk menu story gets more convoluted!
    According to these:
        http://wiki.tcl.tk/12987#pagetoc6efbd677
        http://www.tcl.tk/software/mac/macFAQ.tml?sc_format=wider#Q5.3
    And as strongly suggested by this:
        http://www.tcl.tk/man/tcl8.6/TkCmd/menu.htm#M22,
    Toplevel windows that don't build an explicit menu are supposed
    to inherit the root Tk window's menu automatically.

    At least in ActiveState's Tk 8.5.18 on Mac OS X 10.11, they do -
    BUT ONLY UNTIL another Toplevel makes a menu of its own; at which
    point the Toplevels without an explicit menu pick up the _other_
    Toplevel's menu, and may wind up displaying an empty menu bar if
    they get focus when the other Toplevel is destroyed.  

    This cropped up for PyEdit popups in PyMailGUI: their PyEdit menus
    trash the inherited menu of PyMailGUI view windows.  Before this fix,
    this also happened for menuless nonmodal PyEdit dialogs like Change, 
    Grep, and Help that stay up and may be clicked any time: opening and
    closing another explicit-menu PyEdit window creates empty menu bars 
    for dialog windows if they regain focus first after the close.

    This is probably a bug in AS TK 8.5.16 (or Mac 10.11, or tkinter?),
    but as a workaround, this function builds an explict menu on Toplevels
    which is the same as that formerly built for their parent.  This
    fixes the PyMailGUI+PyEdit use case; PyEdit standalone edit windows
    don't need to care (each has its own menu), but all PyEdit nonmodal
    dialogs do; and in programs without real menus like frigcal and 
    mergeall, child Toplevels do inherit as they should.
    
    This workaround may or may not be required on Tk 8.6 available in
    Homebrew Python; TBD (a workaround is less painful than an install).
    -----------------------------------------------------------------------
    """

    # use the app root's info, from former window or call
    # ignore quitaction: assume the app-wide tk::mac::Quit already registered 
                    
    appname, helpaction, aboutaction, quitaction = (
        GuiMakerWindowMenu.getAppWideInfo())

    if sys.platform.startswith('darwin'):       # for Mac only
        menubar = Menu(window)                  # for this window

        # customize standard app menu on Mac
        menutext = 'About ' + appname
        appmenu = Menu(menubar, name='apple')
        appmenu.add_command(label=menutext, command=aboutaction)
        menubar.add_cascade(menu=appmenu)

        # add automatic windows (dock-ish) menu on Mac
        winmenu = Menu(menubar, name='window')
        menubar.add_cascade(menu=winmenu, label='Window')

        # automatic help menu last=rightmost (no accelerator)
        menutext = appname + ' Help'
        helpmenu = Menu(menubar)  # omit name 
        helpmenu.add_command(label=menutext, command=helpaction)
        menubar.add_cascade(menu=helpmenu, label='Help')

        # attach to window last, so app/etc menus work 
        window.config(menu=menubar)



###############################################################################
# Self-test when file run standalone: 'python guimaker.py'
###############################################################################


if __name__ == '__main__':
    import os
    sys.path.append(os.path.join('..', '..', '..'))            # testing this copy
    from guimixin import GuiMixin                              # mix in help method
                    
    menuBar = [
        ('File', 0,
            [('Open',  0, (lambda: print('open')),  '*-o'),    # lambda defers code
             ('Save',  0, (lambda: print('save')),  'F1'),     # function keys too
             ('Quit',  0, sys.exit,                 '?-q')]    # use sys: no self
        ),
        ('Edit', 0,
            [('Cut',   0, (lambda: print('cut')),   '?-*-c'),
             ('Copy',  2, (lambda: print('copy')),  None),     # no accel (or omit)
             '----',
             ('Paste', 0, (lambda: print('paste')), '*-v'),    # replace Text dflt
             ('Spam',  0, (lambda: print('spam')),  '?-s')]
        )
        ]
    toolBar = [('Quit', sys.exit, {'side': LEFT})]

    class TestMixin:
        appname = 'TestMixin'              # optional label for auto help menu
        def start(self):
            self.menuBar = menuBar
            self.toolBar = toolBar         # set menu/toolbar here if need self
        def accBindWidget(self):
            self.middle = Text(self)
            return self.middle             # need a real widget type for bind
        def makeWidgets(self):
            self.middle.insert(END, self.__class__.__name__)
            self.middle.pack()
        
    class TestAppFrameMenu(TestMixin, GuiMixin, GuiMakerFrameMenu):
        onHelp = GuiMixin.help
    
    class TestAppWindowMenu(TestMixin, GuiMixin, GuiMakerWindowMenu):
        onHelp = GuiMixin.help
    
    class TestAppWindowMenuPolite(TestMixin, GuiMakerWindowMenu):
        pass    # guimaker help, not guimixin
 
    root = Tk()
    TestAppWindowMenuPolite(root)    
    TestAppFrameMenu(Toplevel())
    TestAppWindowMenu(Toplevel())
    other = Toplevel()
    root.mainloop()



[Home page] Books Code Blog Python Author Train Find ©M.Lutz