# menus.py - create menus for a Tkinter application, given a menu outline # copyright 2008, Martin Rinehart # started 20080326 # version 1.0 20080326 # version 2 should add keyboard shortcut support, but I'm temporarily unable # do this as I'm busy catching up on all the stuff I pushed aside for this hack. # author: Martin Rinehart """ Let's the Tkinter programmer type a multiline string outlining the menus, instead of writing menu code. From the outline, this module creates the otherwise tedious menu-creation code. Example: ----------------------------------------- menus = ''' File New Open - Save Save As ... - Print /dis - eXit Edit Undo /dis Redo /dis Special Windows /to Cascade Tile Vertically Tile Horizontally Help About Help ''' app = Tk() makeMenus( app, menus ) ----------------------------------------- At this point, 'app' will be the topmost application window with menus as per the outline. Outline syntax points: 1) Indent is four spaces (tab==error). 2) Options: ' /dis' = disabled, ' /to' = tearoff 3) Callbacks: call hierarchy, concatenated: FileNew(), FileOpen(), ... 4) Callback details: trailing ellipses dropped, spaces converted to underscores. Example: FileSave_As() 5) No following suite? Treated as menu item (in above, Special() ). 6) '-' = menu separator You need to define your worker routines (e.g., FileNew() ) or you can't compile. See the test code for a way to do this painlessly. (No, this does not code your application for you. Just the menus. Sorry.) Operation is via creation of the code in a text string, then exec the string. The string can be returned (if you want to customize the code) by calling codeMenus( menus ). Structure of this module: import makeMenus() codeMenus() support routines, alphabetically test code """ from Tkinter import * def makeMenus( tk, menus ): """ Creates and assigns to tk the menus outlined in 'menus', a multiline string. """ menu = Menu( tk ) code = codeMenus( menus ) exec code tk.config( menu=menu ) def codeMenus( menus ): """ Prepares Python code to instantiate menus outlined in 'menus'. """ menus = menus.split( '\n' ) code = [] last_main = None has_suite = False old_main_line = '' for line in menus: if len( line ) == 0: continue if line.startswith( ' ' ): if last_main == None: raise Error( line, ' has no preceding level 1 entry.' ) line = line[4:len(line)] if line.startswith( ' ' ): raise Error( ' ' + line + ' is indented more than 4 spaces.' ) has_suite = True code.append( suite_line(last_main, line) ) else: # new main menu entry if last_main != None: finish_suite( last_main, has_suite, code, old_main_line ) has_suite = False code.append( main_line(line) ) last_main = keyword( line ) old_main_line = line # end of for loop finish_suite( last_main, has_suite, code, old_main_line ) return '\n'.join( code ) # end of codeMenus() def finish_suite( last_main, has_suite, code, old_main_line ): """ Complete cascade or correctly add a command. """ text, key, opt = split( old_main_line ) line = '' if not has_suite: code.pop() # removes the 'xxx = Menu( ...' line line = 'menu.add_command( label="' + text + \ '", command=' + key else: # a suite ended line += 'menu.add_cascade( label="' + text + \ '", menu=' + last_main + 'Menu' if opt == '/dis': line += ', state=DISABLED' line += ' )' code.append( line ) code.append( '' ) def keyword( line ): """ Returns just keyword from split( line ). """ text, key, opt = split( line ) return key def main_line( line ): """ Write line from main menu entry. """ text, key, opt = split( line ) prefix = key + 'Menu' ret = prefix + ' = Menu( menu, tearoff=' if opt == '/to': ret += 'True' else: ret += 'False' ret += ' )' return ret def make_key( line ): """ Removes trailing elipses, replaces blanks w/underscores. """ if line.endswith( "..." ): line = line[ 0 : len(line) - 3 ].strip() return line.replace( ' ', '_' ) def split( line ): """ Returns text for menu label, key for name prefix and option, if any. """ line = line.strip() line, option = strip_option( line ) key = make_key( line ) return line, key, option def strip_option( line ): """ Picks option, if any, off end of line. """ if line.endswith( ' /to' ): return line[ 0:len(line)-4 ].strip(), '/to' elif line.endswith( ' /dis' ): return line[ 0:len(line)-5 ].strip(), '/dis' else: return line, '' def suite_line( last_main, line ): """ Prepare and return line of suite code. """ text, key, opt = split( line ) prefix = last_main + 'Menu' command = last_main + key if line != '-': ret = prefix + '.add_command( label="' \ + text + '", command=' + command if opt == '/dis': ret += ', state=DISABLED' ret += ' )' else: ret = prefix + '.add_separator()' return ret # test code ---------------------------------------------------------------- if __name__ == '__main__': def _(): pass FileNew = FileOpen = FileSave = FileSave_As = FilePrint = FileeXit = _ EditUndo = EditRedo = Special = _ WindowsCascade = WindowsTile_Vertically = WindowsTile_Horizontally = _ HelpAbout = HelpHelp = _ menus = ''' File New Open - Save Save As ... - Print /dis - eXit Edit Undo /dis Redo /dis Special Windows /to Cascade Tile Vertically Tile Horizontally Help About Help ''' # print codeMenus( menus ) # uncomment this to see the work saved! app = Tk() makeMenus( app, menus ) app.geometry( '400x300+400+100' ) app.mainloop() # end of menus.py