File: textundo.tcl

package info (click to toggle)
tkabber 0.9.9-2
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k
  • size: 5,028 kB
  • ctags: 1,798
  • sloc: tcl: 36,852; xml: 3,704; sh: 1,386; makefile: 67
file content (241 lines) | stat: -rw-r--r-- 7,279 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
### -------------------------------------------------------------------------------------------------------
#
# PROCEDURE
#   textUndoable
#
# DESCRIPTION
#   Creates a text widget with the enhancement of a history of (update) actions performed on the widget.
#   Can also modify an existing text widget to add a history
#
# ARGUMENTS
#   As for the ordinary text command, plus:
#     -historycommand   This is new widget option which specifies a procedure to call whenever the history
#                       state changes. This can be retrieved/updated in the usual manner.
#
# ADDED WIDGET COMMANDS
#   hclear              clears the history
#   hget                gets info from the histlist
#   undo                undoes the last update operation on the histlist
#   redo                redoes the last undone operation on the histlist
#   revert              keeps undoing until the histlist is empty (only one call of callback though)
#
# WIDGET COMMAND FORMAT
#   configure -historycommand proc_name
#        proc_name should take 3 args (widget path, size of undo list, size of redo list)
#   hclear    ?-redo?
#   hget      index ?-redo?
#   undo
#   redo
#   revert
#
# RETURN VALUE
#   That of the text operation (probably)
#
# ERRORS
#   none
#
# GLOBALS
#   textUndoable(callback,widgetname)
#   textUndoable(undo,widgetname)
#   textUndoable(redo,widgetname)
#   textUndoable(realwidget,widgetname)
#
# (SEMI-)PUBLIC ROUTINES
#   textUndoable_cmdproc   Parses/handles the changes to the text widget commands
#   textUndoable_callback  Performs the assigned undo callback if it is set
#
# PROBLEMS/BUGS
#   Makes no attempt to preserve the tags (probably a good idea... :)
#   Bound to be some others... :)
#
# TO DO
#

option add "*Text.historyCommand" {} widgetDefault

proc textUndoable {w args} {
    global textUndoable

    # First we find a free name for the widget to be moved to
    for {set name "_$w"} {[string length [info commands $name]]} {set name "_$name"} {
	# Dummy
    }
    # Now we create the widget if needed
    set wanted(-historycommand) 2
    if ![winfo exists $w] {
	set got(-historycommand) " "
	set retval [uplevel text $w [parseargs wanted got $args]]
	if {" " == $got(-historycommand)} {
	    set got(-historycommand) [option get $w historyCommand HistoryCommand]
	}
    } else {
	set got(-historycommand) [option get $w historyCommand HistoryCommand]
	parseargs wanted got $args
	set retval $w
    }
    # Move that widget
    rename $w $name
    # Install our replacement command
    ;proc $w {opt args} "uplevel textUndoable_cmdproc $w \$opt \$args"
    # Initialise the globals
    set textUndoable(callback,$w)   $got(-historycommand)
    set textUndoable(undo,$w)       {}
    set textUndoable(redo,$w)       {}
    set textUndoable(realwidget,$w) $name
    # Return the correct result...
    return $retval
}

;proc textUndoable_callback w {
    upvar #0 textUndoable(callback,$w) callback	\
	    textUndoable(undo,$w) undolist		\
	    textUndoable(redo,$w) redolist
    if [llength [info proc [lindex $callback 0]]] {
	$callback $w [llength $undolist] [llength $redolist]
    }
}

;proc textUndoable_cmdproc {w opt args} {
    upvar #0 textUndoable(callback,$w) callback	\
	    textUndoable(undo,$w) undolist		\
	    textUndoable(redo,$w) redolist		\
	    textUndoable(realwidget,$w) wdgt

    switch -- $opt {
	configure {
	    set wanted(-historycommand) 2
	    set got(-historycommand) " "
	    set nargs [parseargs wanted got $args]
	    if {" " != $got(-historycommand)} {
		set callback $got(-historycommand)
		textUndoable_callback $w
	    }
	    if [llength $nargs] {
		return [uplevel $wdgt configure $nargs]
	    }
	    return 
	}
	cget {
	    if {"-historycommand" == $args} {
		return $callback
	    }
	}
	hclear {
	    if {[llength $args] && [string compare $args "-redo"]} {
		error "Usage: $w hclear ?-redo?"
	    }
	    set redolist {}
	    if ![llength $args] {
		set undolist {}
	    }
	    textUndoable_callback $w
	    return
	}
	hget {
	    if {[set ll [llength $args]] != 1 && ($ll != 2 || [string compare [lindex $args 1] "-redo"])} {
		error "Usage: $w hget index ?-redo?"
	    }
	    if {$ll - 1} {
		return [lindex $redolist [lindex $args 0]]
	    } else {
		return [lindex $undolist [lindex $args 0]]
	    }
	}
	undo {
	    if [llength $undolist] {
		set cmd [lindex $undolist end]
		if {"d" == [lindex $cmd 0]} {
		    lappend redolist [list \
			    i [lindex $cmd 1] [lindex $cmd 2] [$wdgt get [lindex $cmd 1] [lindex $cmd 2]]]
		    $wdgt delete [lindex $cmd 1] [lindex $cmd 2]
		    $wdgt mark set insert [lindex $cmd 1]
		} else {
		    lappend redolist [list \
			    d [lindex $cmd 1] [lindex $cmd 2]]
		    $wdgt insert [lindex $cmd 1] [lindex $cmd 3]
		    $wdgt mark set insert [lindex $cmd 2]
		}
		set undolist [lreplace $undolist end end]
	    }
	    textUndoable_callback $w
	    return
	}
	redo {
	    if [llength $redolist] {
		set cmd [lindex $redolist end]
		if {"d" == [lindex $cmd 0]} {
		    lappend undolist [list \
			    i [lindex $cmd 1] [lindex $cmd 2] [$wdgt get [lindex $cmd 1] [lindex $cmd 2]]]
		    $wdgt delete [lindex $cmd 1] [lindex $cmd 2]
		    $wdgt mark set insert [lindex $cmd 1]
		} else {
		    lappend undolist [list \
			    d [lindex $cmd 1] [lindex $cmd 2]]
		    $wdgt insert [lindex $cmd 1] [lindex $cmd 3]
		    $wdgt mark set insert [lindex $cmd 2]
		}
		set redolist [lreplace $redolist end end]
	    }
	    textUndoable_callback $w
	    return
	}
	revert {
	    while {[llength $undolist]} {
		set cmd [lindex $undolist end]
		if {"d" == [lindex $cmd 0]} {
		    lappend redolist [list \
			    i [lindex $cmd 1] [lindex $cmd 2] [$wdgt get [lindex $cmd 1] [lindex $cmd 2]]]
		    $wdgt delete [lindex $cmd 1] [lindex $cmd 2]
		} else {
		    lappend redolist [list \
			    d [lindex $cmd 1] [lindex $cmd 2]]
		    $wdgt insert [lindex $cmd 1] [lindex $cmd 2] [lindex $cmd 3]
		}
		set undolist [lreplace $undolist end end]
	    }
	    textUndoable_callback $w
	    return
	}
	insert {
	    if {[llength $args] < 2} {
		lappend args wibble
		return -code error "wrong # args: should be \".t insert index chars ?tagList chars tagList ...?\""
	    }
	    set index1 [$wdgt index [lindex $args 0]]
	    uplevel $wdgt $opt $args
	    for {set posn 1} {$posn < [llength $args]} {incr posn 2} {
		if {"\n" == [$wdgt get $index1]} {
		    set index1 [$wdgt index "$index1 -1c"]
		}
		set length [string length [lindex $args $posn]]
		set index2 "$index1 + $length chars"
		# Must put the undo info on the undo list in reverse order!
		lappend undoing [list d $index1 $index2]
		set index1 $index2
	    }
	    foreach action $undoing {
		lappend undolist $action
	    }
	    set redolist {}
	    textUndoable_callback $w
	    return
	}
	delete {
	    if {![llength $args] || [llength $args] > 2} {
		error "wrong # args: should be \".t delete index1 ?index2?\""
	    }
	    set index1 [$wdgt index [lindex $args 0]]
	    if {[llength $args] > 1} {
		set index2 [$wdgt index [lindex $args 1]]
	    } else {
		set index2 "$index1 +1c"
	    }
	    lappend undolist [list i $index1 $index2 [$wdgt get $index1 $index2]]
	    set redolist {}
	    uplevel $wdgt $opt $args
	    textUndoable_callback $w
	    return	    
	}
    }
    uplevel $wdgt $opt $args
}