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 not direct support for undo/redo (as yet at least) but the signals to implement the feature are already in place. The Gnome sourceView widget has support for undo/redo plus a whole host of code editing functionality, but gnocl::sourceView will be shipped with layer with the release of Gnocl 0.9.93. So, here's the script for undo/redo. One of the nice
#--------------- # 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 effecient 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 proceedures # # 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] # resposition 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] # resposition 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, gnocBind.tcl. Get it here:
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
enter categories here |
---|