391 lines
13 KiB
Tcl
391 lines
13 KiB
Tcl
|
# supertext.tcl v1.01
|
||
|
#
|
||
|
# Copyright (c) 1998 Bryan Oakley
|
||
|
# All Rights Reserved
|
||
|
#
|
||
|
# this code is freely distributable, but is provided as-is with
|
||
|
# no waranty expressed or implied.
|
||
|
|
||
|
# send comments to oakley@channelpoint.com
|
||
|
|
||
|
# What is this?
|
||
|
#
|
||
|
# This is a replacement for (or superset of , or subclass of, ...)
|
||
|
# the tk text widget. Its big feature is that it supports unlimited
|
||
|
# undo. It also has two poorly documented options: -preproc and
|
||
|
# -postproc.
|
||
|
|
||
|
# The entry point to this widget is supertext::text; it takes all of
|
||
|
# the same arguments as the standard text widget and exhibits all of
|
||
|
# the same behaviors. The proc supertext::overrideTextCommand may be
|
||
|
# called to have the supertext widget be used whenever the command
|
||
|
# "text" is used (ie: it imports supertext::text as the command "text").
|
||
|
# Use at your own risk...
|
||
|
|
||
|
# To access the undo feature, use ".widget undo". It will undo the
|
||
|
# most recent insertion or deletion. On windows and the mac
|
||
|
# this command is bound to <Control-z>; on unix it is bound to
|
||
|
# <Control-_>
|
||
|
|
||
|
# if you are lucky, you might find documentation here:
|
||
|
# http://www1.clearlight.com/~oakley/tcl/supertext.html
|
||
|
|
||
|
package provide supertext 1.01
|
||
|
|
||
|
namespace eval supertext {
|
||
|
variable undo
|
||
|
variable undoIndex
|
||
|
variable text "::text"
|
||
|
variable preProc
|
||
|
variable postProc
|
||
|
namespace export text
|
||
|
}
|
||
|
|
||
|
# this proc is probably attempting to be more clever than it should...
|
||
|
# When called, it will (*gasp*) rename the tk command "text" to "_text_",
|
||
|
# then import our text command into the global scope.
|
||
|
#
|
||
|
# Use at your own risk!
|
||
|
|
||
|
proc supertext::overrideTextCommand {} {
|
||
|
variable text
|
||
|
set text "::_text_"
|
||
|
rename ::text $text
|
||
|
uplevel #0 namespace import supertext::text
|
||
|
}
|
||
|
|
||
|
proc supertext::text {w args} {
|
||
|
variable text
|
||
|
variable undo
|
||
|
variable undoIndex
|
||
|
variable preProc
|
||
|
variable postProc
|
||
|
|
||
|
# this is what we will rename our widget proc to...
|
||
|
set original __$w
|
||
|
|
||
|
# do we have any of our custom options? If so, process them and
|
||
|
# strip them out before sending them to the real text command
|
||
|
if {[set i [lsearch -exact $args "-preproc"]] >= 0} {
|
||
|
set j [expr $i + 1]
|
||
|
set preProc($original) [lindex $args $j]
|
||
|
set args [lreplace $args $i $j]
|
||
|
} else {
|
||
|
set preProc($original) {}
|
||
|
}
|
||
|
|
||
|
if {[set i [lsearch -exact $args "-postproc"]] >= 0} {
|
||
|
set j [expr $i + 1]
|
||
|
set postProc($original) [lindex $args $j]
|
||
|
set args [lreplace $args $i $j]
|
||
|
} else {
|
||
|
set postProc($original) {}
|
||
|
}
|
||
|
|
||
|
# let the text command create the widget...
|
||
|
eval $text $w $args
|
||
|
|
||
|
# now, rename the resultant widget proc so we can create our own
|
||
|
rename ::$w $original
|
||
|
|
||
|
# here's where we create our own widget proc.
|
||
|
proc ::$w {command args} \
|
||
|
"namespace eval supertext widgetproc $w $original \$command \$args"
|
||
|
|
||
|
# set up platform-specific binding for undo; the only one I'm
|
||
|
# really sure about is winders; the rest will stay the same for
|
||
|
# now until someone has a better suggestion...
|
||
|
switch $::tcl_platform(platform) {
|
||
|
unix {
|
||
|
event add <<Undo>> <Control-z>
|
||
|
event add <<Undo>> <Control-Z>
|
||
|
}
|
||
|
windows {
|
||
|
event add <<Undo>> <Control-z>
|
||
|
event add <<Undo>> <Control-Z>
|
||
|
}
|
||
|
macintosh {
|
||
|
event add <<Undo>> <Control-z>
|
||
|
event add <<Undo>> <Control-Z>
|
||
|
}
|
||
|
}
|
||
|
bind $w <<Undo>> "$w undo"
|
||
|
|
||
|
set undo($original) {}
|
||
|
set undoIndex($original) -1
|
||
|
set clones($original) {}
|
||
|
|
||
|
return $w
|
||
|
}
|
||
|
|
||
|
# this is the command that we associate with a supertext widget.
|
||
|
proc supertext::widgetproc {this w command args} {
|
||
|
|
||
|
variable undo
|
||
|
variable undoIndex
|
||
|
variable preProc
|
||
|
variable postProc
|
||
|
|
||
|
# these will be the arguments to the pre and post procs
|
||
|
set originalCommand $command
|
||
|
set originalArgs $args
|
||
|
|
||
|
# is there a pre-proc? If so, run it. If there is a problem,
|
||
|
# die. This is potentially bad, because once there is a problem
|
||
|
# in a preproc the user must fix the preproc -- there is no
|
||
|
# way to unconfigure the preproc. Oh well. The other choice
|
||
|
# is to ignore errors, but then how will the caller know if
|
||
|
# the proc fails?
|
||
|
if {[info exists preProc($w)] && $preProc($w) != ""} {
|
||
|
if {[catch "$preProc($w) command args" error]} {
|
||
|
return -code error "error during processing of -preproc: $error"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
# if the command is "undo", we need to morph it into the appropriate
|
||
|
# command for undoing the last item on the stack
|
||
|
if {$command == "undo"} {
|
||
|
|
||
|
if {$undoIndex($w) == ""} {
|
||
|
# ie: last command was anything _but_ an undo...
|
||
|
set undoIndex($w) [expr [llength $undo($w)] -1]
|
||
|
}
|
||
|
|
||
|
# if the index is pointing to a valid list element,
|
||
|
# lets undo it...
|
||
|
if {$undoIndex($w) < 0} {
|
||
|
# nothing to undo...
|
||
|
bell
|
||
|
|
||
|
} else {
|
||
|
|
||
|
# data is a list comprised of a command token
|
||
|
# (i=insert, d=delete) and parameters related
|
||
|
# to that token
|
||
|
set data [lindex $undo($w) $undoIndex($w)]
|
||
|
|
||
|
if {[lindex $data 0] == "d"} {
|
||
|
set command "delete"
|
||
|
} else {
|
||
|
set command "insert"
|
||
|
}
|
||
|
set args [lrange $data 1 end]
|
||
|
|
||
|
# adjust the index
|
||
|
incr undoIndex($w) -1
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# now, process the command (either the original one, or the morphed
|
||
|
# undo command
|
||
|
switch $command {
|
||
|
|
||
|
reset_undo {
|
||
|
set undo($w) ""
|
||
|
set undoIndex($w) ""
|
||
|
set result {}
|
||
|
}
|
||
|
|
||
|
configure {
|
||
|
# we have to deal with configure specially, since the
|
||
|
# user could try to configure the -preproc or -postproc
|
||
|
# options...
|
||
|
|
||
|
if {[llength $args] == 0} {
|
||
|
# first, the case where they just type "configure"; lets
|
||
|
# get it out of the way
|
||
|
set list [$w configure]
|
||
|
lappend list [list -preproc preproc Preproc {} $preProc($w)]
|
||
|
lappend list [list -postproc postproc Postproc {} $postProc($w)]
|
||
|
set result $list
|
||
|
|
||
|
|
||
|
} elseif {[llength $args] == 1} {
|
||
|
# this means they are wanting specific configuration
|
||
|
# information
|
||
|
set option [lindex $args 0]
|
||
|
if {$option == "-preproc"} {
|
||
|
set result [list -preproc preproc Preproc {} $preProc($w)]
|
||
|
|
||
|
} elseif {$option == "-postproc"} {
|
||
|
set result [list -postproc postproc Postproc {} $postProc($w)]
|
||
|
|
||
|
} else {
|
||
|
if {[catch "$w $command $args" result]} {
|
||
|
regsub $w $result $this result
|
||
|
return -code error $result
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
# ok, the user is actually configuring something...
|
||
|
# we'll deal with our special options first
|
||
|
if {[set i [lsearch -exact $args "-preproc"]] >= 0} {
|
||
|
set j [expr $i + 1]
|
||
|
set preProc($w) [lindex $args $j]
|
||
|
set args [lreplace $args $i $j]
|
||
|
set result {}
|
||
|
}
|
||
|
|
||
|
if {[set i [lsearch -exact $args "-postproc"]] >= 0} {
|
||
|
set j [expr $i + 1]
|
||
|
set postProc($w) [lindex $args $j]
|
||
|
set args [lreplace $args $i $j]
|
||
|
set result {}
|
||
|
}
|
||
|
|
||
|
# now, process any remaining args
|
||
|
if {[llength $args] > 0} {
|
||
|
if {[catch "$w $command $args" result]} {
|
||
|
regsub $w $result $this result
|
||
|
return -code error $result
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
undo {
|
||
|
# if an undo command makes it to here, that means there
|
||
|
# wasn't anything to undo; this effectively becomes a
|
||
|
# no-op
|
||
|
set result {}
|
||
|
}
|
||
|
|
||
|
insert {
|
||
|
|
||
|
if {[catch {set index [text_index $w [lindex $args 0]]}]} {
|
||
|
set index [lindex $args 0]
|
||
|
}
|
||
|
|
||
|
# since the insert command can have an arbitrary number
|
||
|
# of strings and possibly tags, we need to ferret that out
|
||
|
# now... what a pain!
|
||
|
set myargs [lrange $args 1 end]
|
||
|
set length 0
|
||
|
while {[llength $myargs] > 0} {
|
||
|
incr length [string length [lindex $myargs 0]]
|
||
|
if {[llength $myargs] > 1} {
|
||
|
# we have a tag...
|
||
|
set myargs [lrange $myargs 2 end]
|
||
|
} else {
|
||
|
set myargs [lrange $myargs 1 end]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# now, let the real widget command do the dirty work
|
||
|
# of inserting the text. If we fail, do some munging
|
||
|
# of the error message so the right widget name appears...
|
||
|
|
||
|
if {[catch "$w $command $args" result]} {
|
||
|
regsub $w $result $this result
|
||
|
return -code error $result
|
||
|
}
|
||
|
|
||
|
# we need this for the undo stack; index2 couldn't be
|
||
|
# computed until after we inserted the data...
|
||
|
set index2 [text_index $w "$index + $length chars"]
|
||
|
|
||
|
if {$originalCommand == "undo"} {
|
||
|
# let's do a "see" so what we just did is visible;
|
||
|
# also, we'll move the insertion cursor to the end
|
||
|
# of what we just did...
|
||
|
$w see $index2
|
||
|
$w mark set insert $index2
|
||
|
|
||
|
} else {
|
||
|
# since the original command wasn't undo, we need
|
||
|
# to reset the undoIndex. This means that the next
|
||
|
# time an undo is called for we'll start at the
|
||
|
# end of the stack
|
||
|
set undoIndex($w) ""
|
||
|
}
|
||
|
|
||
|
# add a delete command on the undo stack.
|
||
|
lappend undo($w) "d $index $index2"
|
||
|
|
||
|
}
|
||
|
|
||
|
delete {
|
||
|
|
||
|
# this converts the insertion index into an absolute address
|
||
|
set index [text_index $w [lindex $args 0]]
|
||
|
|
||
|
# lets get the data we are about to delete; we'll need
|
||
|
# it to be able to undo it (obviously. Duh.)
|
||
|
set data [eval $w get $args]
|
||
|
|
||
|
# add an insert on the undo stack
|
||
|
lappend undo($w) [list "i" $index $data]
|
||
|
|
||
|
if {$originalCommand == "undo"} {
|
||
|
# let's do a "see" so what we just did is visible;
|
||
|
# also, we'll move the insertion cursor to a suitable
|
||
|
# spot
|
||
|
$w see $index
|
||
|
$w mark set insert $index
|
||
|
|
||
|
} else {
|
||
|
# since the original command wasn't undo, we need
|
||
|
# to reset the undoIndex. This means that the next
|
||
|
# time an undo is called for we'll start at the
|
||
|
# end of the stack
|
||
|
set undoIndex($w) ""
|
||
|
}
|
||
|
|
||
|
# let the real widget command do the actual deletion. If
|
||
|
# we fail, do some munging of the error message so the right
|
||
|
# widget name appears...
|
||
|
if {[catch "$w $command $args" result]} {
|
||
|
regsub $w $result $this result
|
||
|
return -code error $result
|
||
|
}
|
||
|
}
|
||
|
|
||
|
default {
|
||
|
# if the command wasn't one of the special commands above,
|
||
|
# just pass it on to the real widget command as-is. If
|
||
|
# we fail, do some munging of the error message so the right
|
||
|
# widget name appears...
|
||
|
if {[catch "$w $command $args" result]} {
|
||
|
regsub $w $result $this result
|
||
|
return -code error $result
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# is there a post-proc? If so, run it.
|
||
|
if {[info exists postProc($w)] && $postProc($w) != ""} {
|
||
|
if {[catch "$postProc($w) originalCommand originalArgs" error]} {
|
||
|
return -code error "error during processing of -postproc: $error"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
# we're outta here! (I think this is faster than a
|
||
|
# return, though I'm not 100% sure on this...)
|
||
|
set result $result
|
||
|
}
|
||
|
|
||
|
# this returns a normalized index (ie: line.column), with special
|
||
|
# handling for the index "end"; to undo something we pretty much
|
||
|
# _have_ to have a precise row and column number.
|
||
|
proc supertext::text_index {w i} {
|
||
|
if {$i == "end"} {
|
||
|
set index [$w index "end-1c"]
|
||
|
} else {
|
||
|
set index [$w index $i]
|
||
|
}
|
||
|
|
||
|
return $index
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|