home *** CD-ROM | disk | FTP | other *** search
/ PCNET 2006 September - Disc 1 / PCNET_CD_2006_09.iso / linux / puppy-barebones-2.01r2.iso / pup_201.sfs / usr / lib / ml / supertext.tcl < prev   
Encoding:
Text File  |  2004-05-06  |  10.6 KB  |  387 lines

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