File: pyedit-products/unzipped/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, tkversion=(0, 0, 0)): # [4.0] add tkversion
"""
--------------------------------------------------------
pack frame in parent, wth menu, toolbar, part in middle
--------------------------------------------------------
"""
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(tkversion) # 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, tkversion):
"""
--------------------------------------------------------
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;
[4.0] multiple hacks to improve appearance in later Tks,
especially on macOS. These look lousy on earlier Tks,
so don't run unless tkversion (from PyEdit) warrants,
though exact Tk version for this is partly a guess:
8.6.10 hardcoded here is from the Py3.11 in Pydroid 3
7.X on Android and known to support button hacks (though
not emojis); 8.6.8 on macOS is known to not support hacks,
and 8.6.9 remains a soon-to-fade unknown. A wrong guess
just means 8.6.9's buttons will be larger than required.
--------------------------------------------------------
"""
# PyEdit [4.0] macOS buttons are gross in recent Tk/tkinter:
# tweak (after a cutoff), and shrink toolbar slightly on Linux
tktweakscutoff = [8, 6, 10] # right? - guessware
runningOnMacOS = sys.platform.startswith('darwin') # these should be gloals
runningOnLinux = sys.platform.startswith('linux') # includes Android!
runningOnAndroid = hasattr(sys, 'getandroidapilevel')
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
# [4.0] PUNT on Linux labels
if False and 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)
# [4.0] button default sucks in tk8.6.13+ - try manual alts;
# TK follows some dogma on macOS that makes most alts useless;
# also go custom on linux, and macos code helps on android too
# (toolbar just fits a Fold 6 with this + textConfig.py font);
if runningOnLinux:
but.pack(padx=0, pady=0) # outside widget
but.pack(ipadx=0, ipady=0) # inside widget
but.config(width=len(name)) # but 2 fails here
but.config(highlightthickness=0)
if ((runningOnMacOS or runningOnAndroid)
and # skip in older pys/tks
(tkversion >= tktweakscutoff)): # else buttons scrunched
# fails
#minbtnwidth = min(len(item[0]) for item in self.toolBar
# if not isinstance(item, str))
#but.config(width=minbtnwidth)
# fails
#but.pack(expand=NO, fill=NONE)
#but.pack(padx=0, pady=0) # outside widget
#but.pack(ipadx=0, ipady=0) # inside widget
but.config(width=1) # wth does 1|2 work?
#but.config(width=minbtnwidth)
but.config(highlightthickness=0) # odd macos shadow
#but.config(highlightbackground='white')
#----------------------------------
# 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') # includes Android!
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')
# PyEdit [4.0] no need to add an 'edit' here as in Frigcal:
# already has 'edit' and macOS auto adds emojis to it (yipee)
# 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()