Easy User Configurable Menus

this code creates a set of menus - a toolbar - from a file that can be easily edited by a user. the menus are specified in the format:


 set menu(window) {
    command "Command 1" {puts "command 1"}
    separator
    menu "Submenu 1"
        radiobutton "Radiobutton 1" rbutton1 {}
        radiobutton "Radiobutton 2" rbutton1 {::rbutton select}
    end
    checkbox "Checkbox 1" options(cbox1) {}
    tcl {
        # this is for inline tcl whose output is the syntax seen in the rest of the menu
        set r ""
        foreach x $somevar {
            lappend r [list command $x "::docommand $x"]
        }
        return [join $r]
    }
 }

# here is the code for parsing the above format

 proc makemenu {i types} {
    global prefs menu
    foreach type $types {
        if {[info exists menu($type)]} {
            menubutton $i.top.$type -text [string totitle $type] -menu $i.top.$type.menu -underline 0 -font $prefs(font,menu)
            menu $i.top.$type.menu -tearoff 0 -font $prefs(font,menu)
            menuparse $type $i.top.$type.menu $menu($type)
            pack $i.top.$type -side left
        }
    }
 }


 proc menuparse {type menu list} {
    global prefs options
    set sub -1
    set num -1
    set window .[lindex [split $menu .] 1]
    set end [llength $list]
    while {[set list [lreplace $list 0 $num]] != ""} {
        set num 0
        switch -exact -- [lindex $list 0] {
            command {
                $menu add command -label [lindex $list 1] -command [lindex $list 2]
                incr num 2
            }
            separator {
                $menu add separator
            }
            menu {
                incr sub
                $menu add cascade -label [subst [lindex $list 1]] -menu $menu.$sub
                append menu .$sub
                menu $menu -tearoff 0 -font $prefs(font,menu)
                $menu delete 0 end
                incr num
            }
            end {
                set menu [string range $menu 0 [expr [string last . $menu] - 1]]
            }
            checkbutton {
                $menu add checkbutton -label [lindex $list 1] -variable [subst [lindex $list 2]] -command [subst [lindex $list 3]]
                incr num 3
            }
            radiobutton {
                $menu add radiobutton -label [lindex $list 1] -variable [subst [lindex $list 2]] -command [subst [lindex $list 3]]
                incr num 3
            }
            tcl {
                menuparse $type $menu [menutcl $window $type [lindex $list 1]]
                incr num
            }
            default {
                Echo .0 "\[ error \] Unknown menu option \"[lindex $list 0]\" in $type menu" error default
                return
            }
        }
    }
 }


 proc menutcl {window type tcl} {
    global prefs menu options
    if {[catch {eval $tcl} err] == 1} {
        Echo .0 "\[ error \] Error in $type menu tcl command: $err" error default
        return {}
    }
    return $err
 }

this can all then be called as

 makemenu .toplevel.menubar [array names menu]

where menubar is an existing frame

and if you want to let the user change the menubar while the program is running

 foreach child [winfo children .toplevel.menubar] {
     destroy $child
 }
 makemenu .toplevel.menubar [array names menu]

which will not affect the application

This works very well with the configuration file bit from the bag of algorithms


Arts and crafts of Tcl-Tk programming