Gnocl Cookbook‎ > ‎

Implementing Entry Undo/Redo

For some reason the basic Gtk+ text and entry widgets have no inbuilt undo/redo functionality. The gnocl::text widget does have undo/redo functionality but this does not yet extend to the application of tags. Because the GtkEntry widget is aimed at holding only small amounts of text, its possible to be build a simple set of Tcl procs to wrap undo/redo functionality around the category of widget.

The way in which it works is that an array of lists is created containing the undo/redo stack for each widget. As text is modified the entire content of the widget is grabbed. If it needs to be undone, the undo stack is stepped through until the desired string is found. Adding new text following an undo will be placed at the end of the list and so editing can continue. When the widget is destroyed, the array elements containing the undo stack and its matching pointer will be unset. Ctrl-Z and Ctrl-Y will cause an edit to be undone or redone as required.  Refer to the following script for more information.


#---------------
# entryUndoRedo.tcl
#---------------
# Created by William J Giddings
# October, 2012
#---------------
# Description:
# Simple entry undo($ent)/redo functionality.
#---------------
# Notes:
#
#
#---------------

# basic Tcl/Gnocl Script
#! /bin/sh/
#\
exec tclsh "$0" "$@"

package require Gnocl

#---------------
# Clear the undo/redo stack and reset the index.
#---------------
# Args:
#    ent    id of entry widget.
#
proc entry:resetUndo {ent } {
    global undo idx
    set idx($ent) -1
    set undo($ent) {\"\"}
}

#---------------
# Undo last operation(s).
#---------------
# Args:
#    ent    id of entry widget.
#
proc entry:undo {ent} {
    global idx undo

    incr idx($ent) -1

    if { $idx($ent) < 0} {
        $ent configure -value {} -cursorPos end
        set idx($ent) -1
        }

    $ent configure -value [lindex $undo($ent) $idx($ent)] -cursorPos end
       
 
}


#---------------
# Redo previous undo operation(s).
#---------------
# Args:
#    ent    id of entry widget.
#
proc entry:redo {ent} {

    global idx undo
    incr idx($ent)
    if { $idx($ent) >= [llength $undo($ent)] } { incr idx($ent) -1 }
    $ent configure -value [lindex $undo($ent) $idx($ent)] -cursorPos end
}

#---------------
#  Wrap undo/redo functionality around existing entry widget.
#---------------
# Args:
#    ent    id of entry widget.
#
proc entry:addUndo {ent} {

   
    global undo idx
   
    set undo($ent) ""
    set idx($ent) -1
    set undo(stop) 0
   
    $ent configure -onKeyPress {
        if { "%s" == 20 && "%a" == "y" } { entry:redo %w ; set undo(stop) 1 }
        if { "%s" == 20 && "%a" == "z" } { entry:undo %w ; set undo(stop) 1 }
        }

    $ent configure -onChanged {
        if { $undo(stop) == 0 } {
            lappend undo(%w) [%w get]
            incr idx(%w)
            }
        }

    $ent configure -onDestroy {
        array unset undo %w
        array unset idx %w
        }

}

#---------------
# Dump contents of undo stack and pointer.
#---------------
# Args:
#    ent    id of entry widget.
#
proc entry:undoDump {ent} {
    global undo idx
    parray undo
    parray idx
}

#---------------
# Demo proc.
#---------------
#
proc entryUndoRedoDemo {} {

    set ent [gnocl::entry]
    set b(1) [gnocl::button -text Undo -onClicked "entry:undo $ent" ]
    set b(2) [gnocl::button -text Redo -onClicked "entry:redo $ent" ]
    set b(3) [gnocl::button -text Reset -onClicked "entry:resetUndo $ent" ]
    set b(4) [gnocl::button -text Dump -onClicked "entry:undoDump $ent" ]
    set box [gnocl::box -orientation vertical]
    $box add $b(1)
    $box add $b(2)
    $box add $b(3)
    $box add $b(4)
    $box add $ent
    gnocl::window -child $box
    entry:addUndo $ent

}

entryUndoRedoDemo

Comments