WJG (25/11/08) What use is text widget without an undo/redo? For some reasons known only to the Gtk core development team, there's no direct support for undo/redo (as yet at least) but the signals to implement the feature are already in place. Perhaps one reason for this is the introduction of the Gnome sourceView widget which has full support for undo/redo plus a whole host of code editing functionality (gnocl::sourceView will be shipped with the release of Gnocl 0.9.93). So, here's the script for undo/redo.
#--------------- # gnoclTextUndoRedo.tcl #--------------- # Created by WIlliam J Giddings # 03/08/2008 #--------------- # Description: # Provide undo/redo functionality for the gnocl text widget. #--------------- # Notes: # #--------------- # use arrays to create stacks to contain details of the events # this is more efficient than using lists # see Welch, Jones & Hobbs(2003, pp101) proc push {stack value} { upvar $stack S if { ! [info exists S(top)] } { set S(top) 0 } set S($S(top)) $value incr S(top) } proc pop { stack } { upvar $stack S if { ![info exists S(top)] } { return {} } if {$S(top) == 0 } { return {} } else { incr S(top) -1 set x $S($S(top)) unset S($S(top)) return $x } } # # UNDO / REDO procedures # # Notes: # ----- # Create undo/redo buffers unique to the widget. proc on_undo { w } { global ${w}.UNDO global ${w}.REDO if { [array size ${w}.UNDO] == 0 } { return } set action [pop ${w}.UNDO] switch [lindex $action 0 ] { "insert-text" { # determine the end of range to delete from length of text inserted set col [expr [lindex [lindex $action 1] 1] +[lindex $action 3] ] set row [lindex [lindex $action 1] 0] $w erase [lindex $action 1] [list $row $col] # resposition the cursor $w setCursor [list $row $col] } "delete-range" { # strip leading and trailing braces from the string $w insert [lindex $action 1] [string trim [lindex $action 3] \{\}] # resposition the cursor to the end of the inserted text $w setCursor [lindex $action 1] $w setCursor cursor+[string length [lindex $action 2]] } } # display the changes $w scrollToPosition cursor $w configure -hasFocus 1 push ${w}.REDO $action } proc on_redo { w } { global ${w}.UNDO global ${w}.REDO if { [array size ${w}.REDO] == 0 } { return } set action [pop ${w}.REDO] switch [lindex $action 0 ] { "insert-text" { # determine the end of range to delete from length of text inserted # The text is returned as a list, so if there is purely whitespace this # will re mis-read as a sub-list by the interpreter. # So, get the text as the first item to prevent this. $w insert [lindex $action 1] [lindex [lindex $action 2] 0] # reposition the cursor to the end of the inserted text $w setCursor [lindex $action 1] $w setCursor cursor+[string length [lindex $action 2]] } "delete-range" { # determine the end of range to delete from length of text inserted set col [expr [lindex [lindex $action 1] 1] +[lindex $action 3] ] set row [lindex [lindex $action 1] 0] $w erase [lindex $action 1] [list $row $col] # reposition the cursor $w setCursor [list $row $col] } } # display the changes $w scrollToPosition cursor $w configure -hasFocus 1 push ${w}.UNDO $action }
Note, to run the demo, it'll be necessary to load a further module, gnoclBind.tcl. Get it here: [L1 ]
So, at last, here's the demo script:
#--------------- # UndoRedoDemo.tcl #--------------- # Created by WIlliam J Giddings # March, 2008 #--------------- # Description: # Demonstrates simple textBuffer undo/redo functionality. #--------------- # Notes: # cd /Desktop/GnoclEdit_2/undoer/wjgstuff # # This version has a single buffer to show edit history # #--------------- # basic Tcl/Gnocl Script #! /bin/sh/ #\ exec tclsh "$0" "$@" package require Gnocl source gnoclTextUndoRedo.tcl source gnoclBind.tcl set text [gnocl::text] set box1 [gnocl::box -orientation vertical] set box2 [gnocl::box -orientation horizontal ] set undo [gnocl::button -text "%#Undo" -onClicked { on_undo $text } ] set redo [gnocl::button -text "%#Redo" -onClicked { on_redo $text } ] $box2 add [list $undo $redo] $box1 add $box2 $box1 add $text -fill {1 1} -expand 1 gnocl::window -title "Text" -child $box1 set userAction 1 #---------------- # UNDO/REDO STUFF #---------------- # respond to text insert signals $text configure -onInsertText { if { $userAction } { # add the event details to the undo stack push ${text}.UNDO "insert-text \{%r %c\} \{%t\} \{%l\}" # clear the redo stack catch { unset ${text}.REDO } } } # respond to text insert signals $text configure -onDeleteRange { if { $userAction } { push ${text}.UNDO "delete-range \{%r %c\} \{%l %o\} \{%t\}" } } # respond to text action signals $text configure -onBeginUserAction { set userAction 1 } $text configure -onEndUserAction { set userAction 0 } # there are no default Undo/Redo bindings, so set your own # (GtkSourceView widget defaults to Ctrl-z & Ctrl-Z gnocl::bind $text <Ctrl-Key-z> "on_undo $text" gnocl::bind $text <Ctrl-Key-y> "on_redo $text" $text insert end "Ctrl-z undo\nCtrl-y redo" #----------------- gnocl::mainLoop