MC, 14 May 2003: Along the lines of Visual menus, Menus made easy, Menus Even Easier and Menus Even Easier Redux, pdmenus is a small package to describe Tk pulldown menus in XML (using tDOM). I've attempted to keep this page reapable with wish-reaper. The package is also available via CVS[L1 ].
First, here is what the example menu from Menus made easy looks like translated into our pdmenus XML dialect:
set xml { <menu name=".m" parent="."> <menu name=".m.file" label="*File" tearoff="0"> <command label="*Open"> <code>openFile</code> </command> <command label="*Save"> <code>saveAs known</code> </command> <command label="Save *As ..."> <code>saveAs</code> </command> <separator /> <command label="*Quit"> <code>exit</code> </command> </menu> <menu name=".m.edit" label="*Edit" tearoff="0"> <command label="Cut"> <code>doCut</code> </command> <menu name=".m.edit.copy" label="Copy" tearoff="0"> <command label="foo"> <code>puts foo</code> </command> <command label="bar"> <code>puts bar</code> </command> <command label="grill"> <code>puts grill</code> </command> </menu> <command label="*Paste"> <code>doPaste</code> </command> </menu> </menu> }
Here is the DTD describing our markup language for pull down menus:
set DTD { <!DOCTYPE menu [ <!ELEMENT menu (menu | command | separator | radio)*> <!ELEMENT command (binding*, code?, test?)> <!ELEMENT radio (binding*, code?, test?)> <!ELEMENT separator EMPTY> <!ELEMENT binding (#PCDATA)> <!ELEMENT code (#PCDATA)> <!ELEMENT test (#PCDATA)> <!ATTLIST menu name NMTOKEN #REQUIRED label CDATA #IMPLIED parent CDATA #IMPLIED tearoff CDATA #IMPLIED varray NMTOKEN #IMPLIED> <!ATTLIST command label CDATA #REQUIRED accelerator CDATA #IMPLIED> <!ATTLIST separator> <!ATTLIST radio label CDATA #REQUIRED var NMTOKEN #REQUIRED value CDATA #REQUIRED default NMTOKEN #IMPLIED> <!ATTLIST binding> <!ATTLIST code> <!ATTLIST test> ]> }
And the actual code that makes everything happen:
#------------------------------------------------------------------------------- # # PULL DOWN MENUS via XML # # PURPOSE: # # Implement pull down menus, based on an XML definition. # # METHODS: # # $menu VerifyStates # Private method called when a menu is posted to run through the menu's # tests to see which items should be in a clickable state. # # $menu CalcUnderline # Private method that returns the position of an asterisk in a label # and then the label without the asterisk. # # $menu loadFromXML xml # Create a menu based on an XML description. # #------------------------------------------------------------------------------- namespace eval ::pdmenu { package require Tcl 8.3 package require Tk 8.3 package require tdom 0.7.5 } proc ::pdmenu::CalcUnderline label { return [list [string first * $label] [string map {* {}} $label]] } proc ::pdmenu::VerifyStates handle { variable $handle upvar 0 $handle menu set counter 0 foreach test $menu(tests) { if {[string length $test]} { if {[expr $test] == 1} { $menu(hull) entryconfigure $counter -state normal } else { $menu(hull) entryconfigure $counter -state disabled } } incr counter } } proc ::pdmenu::loadFromXML xml { set doc [dom parse $xml] set root [$doc documentElement] set handle [$root getAttribute name] variable $handle upvar 0 $handle menu upvar 0 menu(tests) tests set menu(hull) $handle if {[llength [info commands $handle]] == 0} { menu $menu(hull) -postcommand [list ::pdmenu::VerifyStates $handle] \ -tearoff [$root getAttribute tearoff 0] } set tests [list] set varray [$root getAttribute varray ""] if {[$root hasAttribute parent]} { [$root getAttribute parent] configure -menu $menu(hull) } foreach item [$root selectNodes *] { set type [$item nodeName] set node [$item selectNodes code/text()] if {$node != ""} { set code [$node data] foreach node [$item selectNodes binding/text()] { bind . <[$node data]> $code } } else { set code {} } set node [$item selectNodes test/text()] if {$node != ""} { lappend tests [$node data] } else { lappend tests {} } set accel [$item getAttribute accelerator ""] set label [$item getAttribute label ""] foreach {pos label} [CalcUnderline $label] break switch -exact -- $type { menu { set name [$item getAttribute name] $menu(hull) add cascade -label $label \ -menu $name \ -underline $pos ::pdmenu::loadFromXML [$item asXML] } command { $menu(hull) add command -label $label \ -command $code \ -accel $accel \ -underline $pos } radio { set var [string trim ${varray}([$item getAttribute var]) ()] set value [$item getAttribute value] if {[$item hasAttribute default] && [string is true -strict [$item getAttribute default]]} { set ::$var $value } $menu(hull) add radiobutton -label $label \ -command $code \ -value $value \ -accelerator $accel \ -variable $var } separator { $menu(hull) add separator } default { error "Unknown menu entry type \"$type\"" } } } $doc delete } #------------------------------------------------------------------------------- # Initilization package provide pdmenus 1.0
And now, to make our example menu:
pdmenu::loadFromXML $xml
TODO: add checkbuttons.