source: tol/tolbase/lib/toltk/supertext.tcl @ 1b5b24e

devel
Last change on this file since 1b5b24e was 6d7779d, checked in by Jorge Pérez <josp.jorge@…>, 18 years ago

initial import

git-svn-id: ​https://www.tol-project.org/svn/tolp/trunk@1 be4846ae-f051-483a-831f-b64e7d08527f

  • Property mode set to 100644
File size: 11.2 KB
Line 
1#/////////////////////////////////////////////////////////////////////////////
2# FILE    : supertext.tcl
3# PURPOSE : This file
4#/////////////////////////////////////////////////////////////////////////////
5
6# Supertext.tcl v1.0b1
7#
8# Copyright (c) 1998 Bryan Oakley
9# All Rights Reserved
10#
11# this code is freely distributable, but is provided as-is with
12# no waranty expressed or implied.
13
14# send comments to boakley@austin.rr.com
15
16# What is this?
17#
18# This is a replacement for (or superset of , or subclass of, ...)
19# the tk text widget. Its big feature is that it supports unlimited
20# undo. It also has two poorly documented options: -preproc and
21# -postproc.
22
23# The entry point to this widget is Supertext::text; it takes all of
24# the same arguments as the standard text widget and exhibits all of
25# the same behaviors.  The proc Supertext::overrideTextCommand may be
26# called to have the supertext widget be used whenever the command
27# "text" is used (ie: it imports Supertext::text as the command "text").
28# Use at your own risk...
29
30# To access the undo feature, use ".widget undo". It will undo the
31# most recent insertion or deletion. On windows and the mac
32# this command is bound to <Control-z>; on unix it is bound to
33# <Control-_>
34
35# if you are lucky, you might find documentation here:
36# http://www1.clearlight.com/~oakley/tcl/supertext.html
37
38package provide Supertext 1.0
39
40namespace eval Supertext { 
41  variable undo
42  variable undoIndex
43  variable text "::text"
44  variable preProc
45  variable postProc
46   
47  namespace export text
48}
49
50# this proc is probably attempting to be more clever than it should...
51# When called, it will (*gasp*) rename the tk command "text" to "_text_",
52# then import our text command into the global scope.
53#
54# Use at your own risk!
55
56proc Supertext::overrideTextCommand {} {
57  variable text
58   
59  set text "::_text_"
60  rename ::text $text
61  uplevel #0 namespace import Supertext::text
62}
63
64proc Supertext::text {w args} {
65  variable text
66  variable undo
67  variable undoIndex
68  variable preProc
69  variable postProc
70   
71  # this is what we will rename our widget proc to...
72  set original __$w
73   
74  # do we have any of our custom options? If so, process them and
75  # strip them out before sending them to the real text command
76  if {[set i [lsearch -exact $args "-preproc"]] >= 0} {
77    set j [expr $i + 1]
78    set preProc($original) [lindex $args $j]
79    set args [lreplace $args $i $j]
80  } else {
81    set preProc($original) {}
82  }
83   
84  if {[set i [lsearch -exact $args "-postproc"]] >= 0} {
85    set j [expr $i + 1]
86    set postProc($original) [lindex $args $j]
87    set args [lreplace $args $i $j]
88  } else {
89    set postProc($original) {}
90  }
91   
92  # let the text command create the widget...
93  eval $text $w $args
94   
95  # now, rename the resultant widget proc so we can create our own
96  rename ::$w $original
97   
98  # here's where we create our own widget proc.
99  proc ::$w {command args} \
100        "namespace eval Supertext widgetproc $w $original \$command \$args"
101   
102  # set up platform-specific binding for undo; the only one I'm
103  # really sure about is winders; what should the mac be? On unix
104  # I just picked what I'm used to in emacs :-)
105  switch $::tcl_platform(platform) {
106    unix         {event add <<Undo>> <Control-z>}
107    windows      {event add <<Undo>> <Control-z>}
108    macintosh    {event add <<Undo>> <Control-z>}
109  }
110  bind $w <<Undo>> "$w undo"
111   
112  set undo($original)    {}
113  set undoIndex($original) -1
114  set clones($original) {}
115   
116  return $w
117}
118
119# this is the command that we associate with a supertext widget.
120proc Supertext::widgetproc {this w command args} {
121  variable undo
122  variable undoIndex
123  variable preProc
124  variable postProc
125   
126  # these will be the arguments to the pre and post procs
127  set originalCommand $command
128  set originalArgs $args
129   
130  # is there a pre-proc? If so, run it. If there is a problem,
131  # die. This is potentially bad, because once there is a problem
132  # in a preproc the user must fix the preproc -- there is no
133  # way to unconfigure the preproc. Oh well. The other choice
134  # is to ignore errors, but then how will the caller know if
135  # the proc fails?
136  if {[info exists preProc($w)] && $preProc($w) != ""} {
137    if {[catch "$preProc($w) $command $args" error]} {
138      return -code error "error during processing of -preproc: $error"
139    }
140  }
141   
142  # if the command is "undo", we need to morph it into the appropriate
143  # command for undoing the last item on the stack
144  if {$command == "undo"} {
145    if {$undoIndex($w) == ""} {
146      # ie: last command was anything _but_ an undo...
147      set undoIndex($w) [expr [llength $undo($w)] -1]
148    }
149    # if the index is pointing to a valid list element,
150    # lets undo it...
151    if {$undoIndex($w) < 0} {
152      # nothing to undo...
153      bell
154    } else {
155      # data is a list comprised of a command token
156      # (i=insert, d=delete) and parameters related
157      # to that token
158      set data [lindex $undo($w) $undoIndex($w)]
159         
160      if {[lindex $data 0] == "d"} {
161        set command "delete"
162      } else {
163        set command "insert"
164      }
165      set args [lrange $data 1 end]
166      # adjust the index
167      incr undoIndex($w) -1           
168    }
169  }
170   
171  # now, process the command (either the original one, or the morphed
172  # undo command
173  switch $command {     
174    configure {
175      # we have to deal with configure specially, since the
176      # user could try to configure the -preproc or -postproc
177      # options...       
178      if {[llength $args] == 0} {
179        # first, the case where they just type "configure"; lets
180        # get it out of the way
181        set list [$w configure]
182        lappend list [list -preproc preproc Preproc {} $preProc($w)]
183        lappend list [list -postproc postproc Postproc {} $postProc($w)]
184        set result $list         
185               
186      } elseif {[llength $args] == 1} {
187        # this means they are wanting specific configuration
188        # information
189        set option [lindex $args 0]
190        if {$option == "-preproc"} {
191          set result [list -preproc preproc Preproc {} $preProc($w)]
192           
193        } elseif {$option == "-postproc"} {
194          set result [list -postproc postproc Postproc {} $postProc($w)]
195           
196        } else {
197          if {[catch "$w $command $args" result]} {
198            regsub $w $result $this result
199            return -code error $result
200          }
201        }
202      } else {
203        # ok, the user is actually configuring something...
204        # we'll deal with our special options first
205        if {[set i [lsearch -exact $args "-preproc"]] >= 0} {
206          set j [expr $i + 1]
207          set preProc($w) [lindex $args $j]
208          set args [lreplace $args $i $j]
209          set result {}
210        }
211         
212        if {[set i [lsearch -exact $args "-postproc"]] >= 0} {
213          set j [expr $i + 1]
214          set postProc($w) [lindex $args $j]
215          set args [lreplace $args $i $j]
216          set result {}
217        }
218         
219        # now, process any remaining args
220        if {[llength $args] > 0} {
221          if {[catch "$w $command $args" result]} {
222            regsub $w $result $this result
223            return -code error $result
224          }
225        }
226      }
227    }
228       
229    undo {
230      # if an undo command makes it to here, that means there
231      # wasn't anything to undo; this effectively becomes a
232      # no-op
233      set result {}
234    }
235       
236    insert {
237       
238      if {[catch {set index  [text_index $w [lindex $args 0]]}]} {
239        set index [lindex $args 0]
240      }
241       
242      # since the insert command can have an arbitrary number
243      # of strings and possibly tags, we need to ferret that out
244      # now... what a pain!
245      set myargs [lrange $args 1 end]
246      set length 0
247      while {[llength $myargs] > 0} {
248        incr length [string length [lindex $myargs 0]]
249        if {[llength $myargs] > 1} {
250          # we have a tag...
251          set myargs [lrange $myargs 2 end]
252        } else {
253          set myargs [lrange $myargs 1 end]
254        }
255      }
256           
257      # now, let the real widget command do the dirty work
258      # of inserting the text. If we fail, do some munging
259      # of the error message so the right widget name appears...
260           
261      if {[catch "$w $command $args" result]} {
262        regsub $w $result $this result
263        return -code error $result
264      }
265
266      # we need this for the undo stack; index2 couldn't be
267      # computed until after we inserted the data...
268      set index2 [text_index $w "$index + $length chars"]
269           
270      if {$originalCommand == "undo"} {
271        # let's do a "see" so what we just did is visible;
272        # also, we'll move the insertion cursor to the end
273        # of what we just did...
274        $w see $index2
275        $w mark set insert $index2
276      } else {
277        # since the original command wasn't undo, we need
278        # to reset the undoIndex. This means that the next
279        # time an undo is called for we'll start at the
280        # end of the stack
281        set undoIndex($w) ""
282      }
283           
284      # add a delete command on the undo stack.
285      lappend undo($w) "d $index $index2"       
286    }
287       
288    delete {
289           
290      # this converts the insertion index into an absolute address
291      set index [text_index $w [lindex $args 0]]
292           
293      # lets get the data we are about to delete; we'll need
294      # it to be able to undo it (obviously. Duh.)
295      set data [eval $w get $args]
296           
297      # add an insert on the undo stack
298      lappend undo($w) [list "i" $index $data]
299           
300      if {$originalCommand == "undo"} {
301        # let's do a "see" so what we just did is visible;
302        # also, we'll move the insertion cursor to a suitable
303        # spot
304        $w see $index
305        $w mark set insert $index           
306      } else {
307        # since the original command wasn't undo, we need
308        # to reset the undoIndex. This means that the next
309        # time an undo is called for we'll start at the
310        # end of the stack
311        set undoIndex($w) ""
312      }
313           
314      # let the real widget command do the actual deletion. If
315      # we fail, do some munging of the error message so the right
316      # widget name appears...
317      if {[catch "$w $command $args" result]} {
318        regsub $w $result $this result
319        return -code error $result
320      }
321    }
322       
323    default {
324      # if the command wasn't one of the special commands above,
325      # just pass it on to the real widget command as-is. If
326      # we fail, do some munging of the error message so the right
327      # widget name appears...
328      if {[catch "$w $command $args" result]} {
329        regsub $w $result $this result
330        return -code error $result
331      }
332    }
333  }
334   
335  # is there a post-proc? If so, run it.
336  if {[info exists postProc($w)] && $postProc($w) != ""} {
337    if {[catch "$postProc($w) $originalCommand $originalArgs" error]} {
338      return -code error "error during processing of -postproc: $error"
339    }
340  }
341       
342  # we're outta here!
343  return $result
344}
345
346# this returns a normalized index (ie: line.column), with special
347# handling for the index "end"; to undo something we pretty much
348# _have_ to have a precise row and column number.
349proc Supertext::text_index {w i} {
350  if {$i == "end"} {
351    set index [$w index "end-1c"]
352  } else {
353    set index [$w index $i]
354  } 
355  return $index
356}
357
358
Note: See TracBrowser for help on using the repository browser.