proc comment text {} proc dbg args { tk_messageBox -message "$args" ; return 1} comment { Undoing events. Almost all modern programs use an undoing mechanism - so here is one for Tk. Use the Undo - Redo or Control-z, Control-y to undo/redo the events you perform on the widgets in the interface created. Press any button, menu item, enter text in the text widget, use the spinbox move the sliders with either left drag or right-click - all these options are undoable. Each implementation of a widget may require special undo operations - for example if we have 30 objects, all red or yellow then a button changes the yellow objects to red then the undo for this button is NOT change all the red objects (back to) yellow, but change back all the objects which were previously changed. The implementation below provides an '-undocommand' option for each undoable widget; this must be created by the user to provide an exact undo mechanism. Typically each widget would be extended by the user to include a history of the effects of the widget, and then when its 'undo' command is called the last history event would be undone. } proc testop {s S P V} { ;# this is the validation routine for entry e2 puts "testop validating $s $S -> $P type $V" return 1 ;# or 0 if not OK! } proc undomenu {menubtn menu} { puts "Undoing $menubtn $menu [$menubtn cget -text]" } proc scalevalue {w v} { puts "scale $w set to value $v" } proc testUndo {} { ;# create an interface using "undoable" Tk items. console show ;# debug and check infomration appears here via puts commands. pack [frame .fr] pack [button .fr.undo -text Undo -command "undolast"] -side left pack [button .fr.redo -text Redo -command "redolast"] -side left bind . "undolast" bind . "redolast" pack [button .fr.undos -text "Show Undos" -command "showundos"] -side left pack [button .fr.redos -text "Show Redos" -command "showredos"] -side left pack [button .fr.exit -text Exit -command "exit 1"] -side left set testfr [frame .tests] pack $testfr # default type is an entry. pack [undoable $testfr.e1 -validate focus] -side top pack [undoable $testfr.e2 -kind entry -vcmd "testop %s %S %P %V" -validate all] -side top pack [undoable $testfr.sp2 -kind spinbox -from 1 -to 12 \ -vcmd "testop %s %S %P %V" -validate all] -side top pack [undoable $testfr.b1 -kind menubutton -bg red -menu $testfr.b1.menu1 -textvariable menval -bd 2 -relief raised] -side top undoable $testfr.b1.menu1 -kind menu -tearoff 0 -undocommand "undomenu $testfr.b1 $testfr.b1.menu1" foreach num {One Two Three} { $testfr.b1.menu1 add command -label $num -command "puts \"menu item $num chosen\"; set ::menval $num"} $testfr.b1.menu1 add cascade -label "Roman" -menu $testfr.b1.menu1.cascade undoable $testfr.b1.menu1.cascade -kind menu -tearoff 0 -undocommand "puts \"Undoing $testfr.b1.menu1.cascade\"" foreach num {I II III} { $testfr.b1.menu1.cascade add command -label $num -command "puts \"menu item $num chosen\"; set ::menval $num"} set ::menval Number pack [undoable $testfr.bmen1 -kind menubutton -menu $testfr.bmen1.menu1 -text "Dropdown Menu" -bd 2 -relief raised] -side top undoable $testfr.bmen1.menu1 -kind menu -tearoff 0 -undocommand "undomenu $testfr.bmen1 $testfr.bmen1.menu1" foreach num {One Two Three} { $testfr.bmen1.menu1 add command -label $num -command "puts \"menu item $num chosen\""} $testfr.bmen1.menu1 add cascade -label "Roman" -menu $testfr.bmen1.menu1.cascade undoable $testfr.bmen1.menu1.cascade -kind menu -tearoff 0 -undocommand "puts \"Undoing $testfr.bmen1.menu1.cascade\"" foreach num {I II III} { $testfr.bmen1.menu1.cascade add command -label $num -command "puts \"menu item $num chosen\""} pack [undoable $testfr.b2 -kind button -text "Press Me" -command "puts {Button pressed}" \ -undocommand "puts {Button press undone}" ] -side top pack [undoable $testfr.b3 -kind checkbutton -text "Check Me" -command "puts {checkButton pressed}" \ -undocommand "puts {checkButton press undone}" ] -side top pack [undoable $testfr.b4 -kind radiobutton -text "Check Me" -command "puts {radioButton pressed}" \ -undocommand "puts {radioButton press undone}" ] -side top pack [undoable $testfr.e3 -kind entry \ -vcmd "puts {validating %s %S -> %P type %V}; return 1" \ -validate all] -side top pack [undoable $testfr.s1 -kind scale -from 100 -to 200 \ -command "scalevalue $testfr.s1" -orient horiz] -side top pack [undoable $testfr.s2 -kind scale -from 0 -to 120 \ -command "scalevalue $testfr.s2" -orient horiz] -side top pack [undoable $testfr.lb2 -kind listbox -command "puts {Listbox Selected}" \ -undocommand "puts {Listbox undone}" ] -side top foreach fruit [list "Apple" "Peach" "Pear" "Banana" "Strawberry" \ "Lingonberry" "Blackberry" "Damson" "Plum"] { $testfr.lb2 insert end $fruit } pack [undoable $testfr.tx1 -kind text -undo true ] -side top } proc undolast {} { ;# undoes last undoable operation. global undoings if {[llength $undoings]>0} { set undothis [lindex [lindex $undoings end] 0] set widget [lindex $undothis 0] eval $widget undo $undothis global redoings lappend redoings [lindex $undoings end] set undoings [lrange $undoings 0 end-1] } else { puts "No more undoable events" } } comment { This is the important part - a list of undoable events, and a redo list (in case you undid an event and realised it should not have been undone). Each undo event has two parts (it is a list) - the record of old values needing to be undone, and the redo event (a copy of the original event). The class undoable is a [Polymorphic] or Template class which records the actions of any Tk widget and can 'undo' these actions. Records events in all undoable widgets as a list, then you can undo the list (and possibly redo). All Tk widgets (except canvas?) can be used as undoables. "Polymorphism" means that the undoable widget can inherit from any of the Tk widgets. It should also be able to represent an Iwidget or BWidget. Usually 'entry' 'menu' and 'scale' widgets won't need an undocommand as they call the return the value of the widget to its previous value, which calls the standard 'item changed command' for the widget (which should cause all changes to be reset as if the menu/entry/scale had been set manually). undoableCmd is the place where the undo events are coded and interpreted. } global undoings ;# list of undoable things - each is a 2 part list # first the arguments to undo the operation; # then the arguments to redo the operation. set undoings {} global redoings ;# list of redoable things - copy of those undoings which have been undone. set redoings {} proc showundos {} { ;# display list of undo operations. global undoings foreach un $undoings { puts "Undo:: [lindex $un 0] ::redo:: [lindex $un 1]"} } proc showredos {} { ;# display list of redo operations. global redoings foreach un $redoings { puts "Redo:: [lindex $un 0] ::undo:: [lindex $un 1]"} } proc redolast {} { global redoings if {[llength $redoings]>0} { set redothis [lindex $redoings end] set redocommand [lindex $redothis 1] set widget [lindex $redocommand 0] eval $widget undo $widget [lrange $redocommand 1 end] update idletasks set redoings [lrange $redoings 0 end-1] global undoings lappend undoings $redothis } } proc undoable {w args} { ;# an undoable is a widget # and allows new smooth shaped buttons. global $w.props ;# an array of options specific to the undoable 'class' # set by .this -