#################################################################################
# TCL Interactive Spell Checker Version 1.01 
# Developed for use in EXMH by John McLaughlin (johnmcl@sr.hp.com) 6/7/97
# 
# This new spell checking code for EXMH was developed out of
# frustration with the current spell checking EXMH code
# mostly I could not get it to work the way I wanted it to.
# Because I couldn't get the spell checker to work as well
# as I would like, I found myself spending inordinate amounts
# of time when writing e-mail, constantly fretting over
# the spelling.  This little piece of code is the result
# and seems to work pretty well in my environment.  I have
# tested this under both linux (redhat 4.0) HPUX 9.05
# 
# This software operates in one of two different
# modes, it can either spell check as you type, marking words
# in a variety of ways (underline, bold, italic, etc) that are
# not spelled correctly.  Also it can put a button in the sedit
# window to allow in place spell checking. (The button option
# requires changes to your .exmh-defaults file)
#
# This code depends on the excellent 'ispell' program and most
# of the variables & procedure's get their name from it.
# This code was developed under ispell v3.1.20
#
#######################################################################
#             INSTALLATION
# to Use: 3 easy steps (Note: EXMH should not be running when you do this)
#
# Note 2: This may be incorporated into a 'core' EXMH release in 
# the future so below may not be required.....
#
# 1) add to the file ~/.tk/exmh/user.tcl in the function 'User_Init'
#    a call to ispell_Preferences.  If you don't have a user.tcl
#    get one, Usually it can be found in '/usr/local/exmh-<version>'
#    where <version> is the version of exmh you are running
# 
#    if you are really desperate for a user.tcl the following
#    should work (just make a file called user.tcl with the
#    following line.....
#    proc User_Init {} { ispell_Preferences }
#
# 
# 2) add the following to your .exmh-defaults at the TOP of the file
# 
# *Sedit.Menubar.ubuttonlist: ispell
# *Sedit.Menubar.ispell.text: Ispell	    
# *Sedit.Menubar.ispell.command: Ispell_CheckEntireWindow $t
#
# 3) in your ~/.tk/exmh directory type 'wish' then type 
#         auto_mkindex . *.tcl
#    then type 'exit'
# 
#  
# That should be it!  There should be a preferences menu for 'I-Spell' now to allow
# control of various parts of the ispell package... Also the 'Sedit' window should
# have a 'ispell' button to check the entire document...
# if a word is marked misspelled right click on it to add to dictionary or
# select an alternate version
#
############## Trouble Shooting ######################
#
# Did you make sure that....
# Ispell was turned on? (from preferences/I-spell  menu)
# A 'Miss-Spelled word style' is selected? (from preferences/I-spell)
# the User Library directory is ~/.tk/exmh (preferences/Hacking Support)
# the changes above were made with EXMH NOT running?
#
# You are using the correct version of ispell?  it works against ispell
# version 3.0 & 3.1, version 4.0 (which seems to be a sort of 'non product'
# is reported NOT to work.  most un*x users seem to have 3.1 installed..
#
############## Performance ##########################
#
# This spell checker seems to operate without any 
# obvious performance drag when typing in sedit
# with enough ram, most modern workstations should 
# be able to use this without any obvious performance hit
# I find running it under Linux with a 40mb P90
# quite comfortable
# as a few benchmarks, this software can spell check
# a 2700 word letter in 12 seconds on a P90 running
# linux (redhat 4.0) with 40mb of ram.  other 
# timing results indicate a user can expect 
# a 1.3ms to 3.0ms additional delay for
# correctly spelled words, and up to 30ms
# for words not spelled correctly.  As always
# ram helps, ram starved machines will 
# not fair as well
######################################################
#
# Enjoy, if you find it useful or have any comments
# please let me know (johnmcl@sr.hp.com) also if you 
# make any improvements please send them to me
#
# John McLaughlin, HP Santa Rosa, January 1997 (johnmcl@sr.hp.com)
######################################################

#########################################################
# ispell_init is called to start the entire process off
#########################################################
proc Ispell_Init { } { 
    global ispellVars

    if {! [ info exists ispellVars(currentLanguage) ] } {
	set ispellVars(currentLanguage) ""
    }

    if { $ispellVars(currentLanguage) == $ispellVars(language) } return ; 

    set ispellVars(currentLanguage) $ispellVars(language)  ; # mark current language


    # These things are now specified by the 'Preferences' menu
    #    a good choice f0or the spell command is "ispell -a"
    #   
    set ispellVars(last_word) "dummy" 
    # this variable are the alternate spellings of the misspelled word...
    set ispellVars(choices) "" 

    # how to view, see the text.n man page for other ideas
    # options include -background <color> -foreground <color> 
    # -font <font> etc..
    #    set ispellVars(viewStyle) "-underline t"

    if { [ info exists ispellVars(spell_buffer) ] } {
	if { [catch {
	    close $ispellVars(spell_buffer)
	} reason] } {
	     Exmh_Debug Ispell Buffer Closed:$reason
	}
    }

    if {!$ispellVars(on)} {
	set ispellVars(currentLanguage) "disabled"
    }
    if {$ispellVars(currentLanguage) == "disabled"} {
	catch {unset ispellVars(spell_buffer) }
	return ;
    }

    if {[info exists ispellVars(command)]} {
	set cmd $ispellVars(command)
    } else {
	set cmd $ispellVars(defaultCommand)
    }
    if [catch {open "|$cmd" r+} ispellVars(spell_buffer)] {
	set ispellVars(on) 0	;# triggers trace
	return
    }

    # Poke the process because:
    # 4.0 doesn't respond with a full line, so gets hangs
    # 2.0 doesn't output a version number.
    # We only like 3.*

    puts $ispellVars(spell_buffer) "?"
    flush $ispellVars(spell_buffer)
    gets $ispellVars(spell_buffer) line
    set version {"unknown"}
    if {([string compare $line *] == 0) ||
	([regexp {[Vv]ersion ([0-9])\.} $line x version] &&
	    $version != 3)} {
	# Oh No!  Why are you not running version 3.X???
	# specifically version 4.0 doesn't work!!!
	Exmh_Status "Ignoring ispell version $version (3.* required)"
	catch { close $ispellVars(spell_buffer) } reason
	set ispellVars(on) 0
	return
    }
    #
    # Since this is the right version, we need to read the (blank) reply to
    # the "?" we sent...
    #
    gets $ispellVars(spell_buffer) line

    IspellWriteSpellBuffer "!" ; # enter terse mode
}

######################
# preferences
######################

proc Ispell_Preferences {} { 

    Preferences_Add "I-Spell" \
	    "This is a module to allow interactive spell checking  within 
a sedit window.
It's many fine features include suggested correction and the ability
to add new words to a session or to your personal dictionary.
For words that are either not correct or not generated 
by a combination of roots or compounds, the word is marked as not
spelled correctly.

Please note that the I-Spell module is only active if you select 
the option \"interactive\" as Spell program (Simple Editor preferences).
" { 

	{ ispellVars(ReCheckAfterAdd) ispellRecheckAfterAdd ON {Re-Verify after Adds?} 
	"Check this box if you want to re spell check words 
	currently marked as misspelled after you add to the dictionary 
	or session.  In general this is a good idea except for 
	extremely long documents where a small delay may be noticed 
	after you add words to your personal dictionary.
	Additionally the right mouse button can be used to
	accept suggested words." }

	{ ispellVars(textOnly) ispellTextOnly OFF {Spell Check 'Text' only?} 
	"Check this box if you want to only perform spell checking 
of the text marked as 'text'. This should avoid spell checking 
To:, CC: & X-Face: lines. It comes at a small time penalty, turn it off
if you want to see a small improvement in response time.
Note that turning this option on will also not spell check 'attachments'
unless they are marked as Content-Type: text/enriched or text/plain.
If you find that spell checking STOPS working in a section of the document you
may want to turn this off." }

	{ ispellVars(defaultCommand) ispellCommand "ispell -a -S" {Default speller invocation}
	"This is the program used to actually do the real work
'ispell -a' is probably a good choice.  if you want to 
use an alternate dictionary, 'ispell -a -d <dictionary-file>' may be 
appropriate. You may find that -S sorts the list of possible
words better, see the ispell(1) man page for more details." } 
	{ ispellVars(otherCommands) ispellOthers " German  { ispell -a -d deutsch } \
		French { ispell -a -d francais } English { ispell -a -d english } " { Other Invocations }
	"Alternate Invocations of the 'Ispell' programs, mostly intended for 
our friends in Europe who have to work in a variety of languages. This 
entry must be in label/invocation pairs." } 

	
	{ ispellVars(popupBinding) ispellPopupBinding "ButtonPress-3" {Menu popup bound to:}
	" A popup window is used for interactive correction of the word 
under the mouse pointer. This option controls how the popup window is invoked. 

Any TK bind() event specification that involves the mouse is allowed. Some examples:
ButtonPress-3 
ButtonPress-2
ButtonPress-1
Shift-3
Control-3
Meta-2
Alt-1" }

	{ ispellVars(viewStyle) ispellStyle {CHOICE underline italic bold bgcolor fgcolor other } {Misspelled word style}
	    "This is how to display misspelled words.
Use the built in types or create your own using 'other'. 
Colors are given as X11 color names or #RGB.

Some examples for the 'other' style (see the TK docs for more info):
	-underline t
	-background red
	-foreground Bisque
	-font <font>
 -font *italic*
 -font *bold*
 -relief <relief>
	-fgstipple <bitmap>
	-bgstipple <bitmap>

Bitmaps can be many things, 'gray50' and 'gray25' are popular.
Effects can also be combined:
	
	-underline t -foreground red
 -bgstipple gray25 -color red" } 
	{ ispellVars(viewStyle-Color) ispellStyleColor red {color:} 
	"color for fgcolor and bgcolor" }
	{ ispellVars(viewStyle-Other) ispellStyleOther {-underline t -foreground red}  {other:}
	"Style if 'other' is selected" }
    }
	    
    global ispellVars
    set ispellVars(language) default
    trace variable ispellVars(on) w IspellOnOff    
}

proc IspellOnOff {args} {
    global ispellVars
    catch {unset ispellVars(currentLanguage)}
    Ispell_Init
}



# a safe procedure to write to the ispell buffer
# this procedure dumps the variable 'word' to the spell buffer
# if the buffer has died, it will restart it

proc IspellWriteSpellBuffer { word } {
    global ispellVars
    
    if {$ispellVars(currentLanguage) == "disabled"} { return * } ;

    puts $ispellVars(spell_buffer) $word
    if { [ catch { flush $ispellVars(spell_buffer) } ] } {
	Exmh_Debug "Ispell process terminated!!!!!, temp disabling"
	set ispellVars(language) disabled
	Ispell_Init
	return "*" ; # return if we had to restart
    }
}

# This procedure kills the ispell buffer
# 
proc Ispell_Kill {} { 
    global ispellVars
    close $ispellVars(spell_buffer)
    set ispellVars(on) 0
}

##########################################
# this is the proc that does the 
# actual spell checking, it will return a 
# '*' if everything is cool, otherwise
# it returns a list of possible miss-spelled
# words.  See ispell(1) for more details
proc IspellWords line { 
    global ispellVars

    regsub -all { +} $line { } line		;# compress out extra spaces
    set count [llength [split $line { }]]	;# Count space separated words
    set result ""

    if { $ispellVars(currentLanguage) == "disabled" } { return "*" } ;

    # clear out the fileevent
    if { [ catch {fileevent $ispellVars(spell_buffer) readable {} } ] } {
	Ispell_Init
	return "*"
    }
    # so the puts stuff doesn't freak out
    # CRITCAL prepend a '^' to keep the buffer from freaking
    puts $ispellVars(spell_buffer) "^$line"
    # we have to put the ^ in front of the line so ispell works correctly
    # see ispell(1) for more details
    if { [ catch { flush $ispellVars(spell_buffer) } ] } {
	Exmh_Debug "Ispell process terminated!!!!!, restarting"
	Ispell_Init
	return "*" ; # return if we had to restart
    }

    # loop through list of words, usually there is just 1
    for { set i 0 } { $i <= $count } {  incr i } { 
	gets $ispellVars(spell_buffer) var
	if {$var == {} } then {
	    lappend result "*";
	    break;
	}
	lappend result $var
    }
    # invoke a fileevent to help flush out the data so wer are always in sync
    fileevent $ispellVars(spell_buffer) readable {
	global ispellVars
	gets $ispellVars(spell_buffer) dummy 
    }
    return $result
}


# this proc spell checks the word under the current cursor
# marking it with a 'MissSpelled' tag if it is in fact incorrect
# text is the text window
# This version runs about 300us slower than the previous
# version using tk's built in 'wordstart' and 'wordend'
# (1.3ms vs 1.7ms)

proc IspellTextWindow { text } { 

#################
# WARNING
# CHANGES FOR SUPPORT OF EUROPEAN CHARACTERS!!
##########################
    set start [ $text get "insert linestart" insert ]
    
    set end   [ $text get insert "insert lineend" ] 
    
    set e1 ""
    set s1 ""
    
    regexp "\[^\t \]*" $end e1
    set e1 [ string trim $e1 ] 
    regexp "\[^\t \]+$" $start s1
    
    set startIndex "insert - [string length $s1] chars"
    set stopIndex "insert + [string length $e1] chars "

    set word "[ string trim $s1$e1 "\"\{\}\[\] \t" ] "

    IspellMarkWord $text $startIndex $stopIndex $word 
}

# this Proc is to spell check words that with 'inserts'
# i.e. after 'space', 'tab' etc.... This version
# runs at exactly the same speed as the tk built
# in version (1.3ms) so I feel pretty comfortable
# that this shouldn't effect speed too much
# all test times were gotten via 'time' 
# and thus may have some errors (especially 
# with regexps, I think the system compiles
# them).  In this version I can't use
# tk's built in 'word' functions because
# they don't allow for european characters....

proc IspellTextWindowInsert { text } { 
#################
# WARNING
# CHANGES FOR SUPPORT OF EUROPEAN CHARACTERS!!
##########################
    set start [ $text get "insert linestart" insert ]
    
    set s1 ""
    
    # now let's pick off the last word
    regexp "\[^\t ]+$" $start s1


    set startIndex "insert - [string length $s1] chars"
    set stopIndex "insert" 

    set word " [ string trim $s1 "\"\{\}\[\]  \t" ] "

    IspellMarkWord $text $startIndex $stopIndex $word 

}

####################################################
# proc to mark words in the text window, with the given 
# indexes, the 'word' is the word in question
####################################################
proc IspellMarkWord {text startIndex stopIndex word} {

    global ispellVars ;

    #first let's not mark the word bad if we aren't in a section marked 'type=text*"

    if {$ispellVars(textOnly)} {
	if { ! [ string match "*type=text*" [ $text tag names insert ] ] } { return * } ;
    }

    set result [ IspellWords $word ];
    #    * means fine, + means a root?, - means compount controlled by -C  option of ispell
    if { ! [regexp {^[*+-]} $result ] } {
	$text tag add MissSpelled $startIndex $stopIndex
	set prompt "Suggested for $word: [ lreplace [ lindex $result 0 ] 0 3 ]"
	# EXMH Specific 
	SeditMsg $text $prompt
	
    } else {
	$text tag remove MissSpelled $startIndex "$stopIndex +1c"
    }
    
    set ispellVars(last_word) $word ; # store word so we don't re-check next
    # time
    return $result
}


##############################################################
# Proedure to call to mark words after the dictionary has been
# modified, called from within the 'add' menus.....
# 
##############################################################

proc IspellReCheckBuffer { window startIndex stopIndex word } { 
    global ispellVars;
    
    # first let's make sure it's a real word....
    if { $word == "" } return ;

    IspellMarkWord $window $startIndex $stopIndex $word; 

    # check word requested
    if { [ info exists ispellVars(ReCheckAfterAdd) ] }  {
	
	if { $ispellVars(ReCheckAfterAdd) } { 
	    IspellReCheckWords $window ; 
	    # re-check buffer if requested..
	}   
    }
}

##########################################################
# This proc will take the word currently under the mouse pointer
# spell check it, and pop up a menu with suggestions or allowing
# additions to the ispell-dictionary
# 'text' is the text window, x,y are the co-ordinates relative to the
# window, X,Y are the co-ordinates relative to the root window
##########################################################

proc IspellPostMenuChoices { text x y   X Y } { 

    global ispellVars;

    set adjustment {} 
    set oldInsert [ $text index insert ] 
    $text mark set insert "@$x,$y"

    set start [ $text get "insert linestart" insert ]
    
    set end   [ $text get insert "insert lineend" ] 
    
    set e1 ""
    set s1 ""
    
    regexp "\[^\t \]*" $end e1
    set e1 [ string trim $e1 ] 
    regexp "\[^\t \]+$" $start s1

    set startIndex "insert - [string length $s1] chars"
    set stopIndex "insert + [string length $e1] chars "
    set word $s1$e1

    set word [ string trim $word  "\]\[\.\,\<\>\/\?\!\@\#\%\*0123456789\&\@\(\)\:\;\$ \{\}\"\\ \'\~\`\_\-\+\t\n\r\b\a\f\v\n "]   
    set word [ string trim $word ]

#    set stopIndex [ $text index "@$x,$y wordend"  ]
#    set startIndex [ $text index "$stopIndex  - 1 chars wordstart" ]
#    set word  " [ string trim [ $text get $startIndex "$stopIndex wordend" ] \
#	    "\@\(\)\:\;\$ \{\}\"\\ \t\n\r\b\a\f\v\n "] " ; # "
#    set word [ string trim $word ] ; # get rid of white space

    # if there is no word to mention, don't even post a menu...

    if { $word == "" } return ; 

    set result [ IspellMarkWord $text $startIndex $stopIndex $word ]

    $text mark set insert $oldInsert ; # get it back where it belongs
    # create a meanu
    set menu "$text.m"
    catch { 
	destroy $menu
    }
    menu $menu -tearoff f

    # remember the menu name so we can unpost it later.
    set ispellVars(PopupMenu) $menu
    
    # first let's label the menu with the current language
    $menu add command -label $ispellVars(language) -state disabled

    # now if spell checking is disabled, let's mark menus as such
    set disFlag normal
    if { $ispellVars(currentLanguage) == "disabled" } {
	set disFlag "disabled" 
    }

    $menu add separator 

    $menu add command -label "Add '$word' to Dictionary" -command  \
	    "IspellWriteSpellBuffer \"*$word\";\
	    IspellWriteSpellBuffer \#;\
	    IspellReCheckBuffer $text \"$startIndex\" \"$stopIndex\" $word;" -state $disFlag
    # add word to dictionary, save dictionary, recheck word
    
    $menu add command -label "Accept '$word' for this session" -command \
	    "IspellWriteSpellBuffer \"@$word\";\
	    IspellReCheckBuffer $text \"$startIndex\"  \"$stopIndex\" $word;" -state $disFlag
    # add word for this session, recheck word

    $menu add separator
    foreach i   [ split [ lreplace [ lindex $result 0 ] 0 3 ] "," ]   {
	set choice [ string trim $i ", " ]
	$menu add command -label $choice -command "IspellReplaceWordInText $text $x $y \"$choice\" " 
    }
    $menu add separator

    menu $menu.sub -tearoff f

    $menu.sub add radiobutton -label "disabled" \
	    -command "set ispellVars(language) disabled ;
    set ispellVars(command) \"\";
    Ispell_Init" -variable ispellVars(language) -value "disabled"


    $menu.sub add radiobutton -label "default" \
	-command "set ispellVars(language) default ;
	    set ispellVars(command) \"$ispellVars(defaultCommand)\";
	    Ispell_Init" \
	-variable ispellVars(language) -value "default"

    set count [ llength $ispellVars(otherCommands) ] 
    for { set i 0 } { $i < $count } { incr i 2 } {
	set lab  [ lindex $ispellVars(otherCommands) $i ]
	set command  [ lindex $ispellVars(otherCommands) [ expr $i +1 ]  ]
	$menu.sub add radiobutton -label "$lab " \
	    -command  " set ispellVars(language)  \"$lab\" ; 
		    set ispellVars(command) \"$command\"; 
		    Ispell_Init"  \
	    -variable ispellVars(language) -value "$lab"                       
    }
    
    $menu add cascade -label "Alternate..." -menu $menu.sub

    tk_popup $menu $X $Y 
}

#######################################################
#
# Procedure called to Unpost 
# the menu
#######################################################
proc IspellUnPostMenuChoices {window } { 
    global ispellVars

    catch {
	tkMenuUnpost $ispellVars(PopupMenu)
    }
}

#########################################################
# This proc will replace whatever word is listed at x,y
# with 'word'  It goes to some lengths to keep surrouning
# punctuation.
#########################################################
proc IspellReplaceWordInText { text x y word } { 

    set oldInsert [ $text index insert ] 

    $text mark set insert "@$x,$y"
    
    set start [ $text get "insert linestart" insert ]
    
    set end   [ $text get insert "insert lineend" ] 
    
    set e1 ""
    set s1 ""
    
    regexp "\[^\t \]*" $end e1
    set e1 [ string trim $e1 ] 
    regexp "\[^\t \]+$" $start s1

    # If we are being asked to replace a word, first remove the tag
    # so that whatever highlighting is there will be gone.
    $text tag remove MissSpelled "insert - [string length $s1] chars" "insert + [string length $e1] chars "

    # Now let's clean up that string a bit..... remove punctuation & stuff
 
    set e1 [ string trim [ string trim $e1 ] "\]\[\.\,\<\>\/\?\!\@\#\%\*0123456789\&\@\(\)\:\;\$ \{\}\"\\ \'\~\`\_\-\+\t\n\r\b\a\f\v\n "]   

    set s1 [ string trim [ string trim $s1 ] "\]\[\.\,\<\>\/\?\!\@\#\%\*0123456789\&\@\(\)\:\;\$ \{\}\"\\ \'\~\`\_\-\+\t\n\r\b\a\f\v\n "]   


    # now let's remove the old word & insert the new word.

    set startIndex "insert - [string length $s1] chars"
    set stopIndex "insert + [string length $e1] chars "
    set startInsert [ $text index $startIndex ] 

    $text delete $startIndex $stopIndex 
    $text insert $startInsert $word
    $text mark set insert $oldInsert ; # get it back where it belongs
}

##########################################################
# EXMH Specific procedure to bind the window in question 
# note that this has to be in the current process
# it won't automagically be sucked in
# a call to 'IspellPreferences' should do the trick...
##########################################################
proc Hook_SeditInit_TagMissSpelled { file window } {
    global ispellVars sedit
    # only configure the window for ispell support if it is
    # actually needed, and if the appropriate variables exist
    set id [SeditId $file]
    set b .sedit${id}.but
    if {$sedit(spell) == "interactive"} {
	set ispellVars(on) 1
	$b.more.m entryconfigure Spell* -state disabled
	$b.more.m entryconfigure I-Spell* -state normal
    } else { 
	set ispellVars(on) 0
	$b.more.m entryconfigure Spell* -state normal
	$b.more.m entryconfigure I-Spell* -state disabled
	return
    }
    
    # bind the window.....
    # use default style of underline
    set style "-underline t"

    if { [ catch {    
	switch -exact -- $ispellVars(viewStyle) \
		underline  { set style "-underline t"} \
		italic     { set style "-font *italic*" } \
		bold       { set style "-font *bold*"}  \
		other      { set style "$ispellVars(viewStyle-Other)" } \
		bgcolor    { set style "-background $ispellVars(viewStyle-Color)" } \
		fgcolor    { set style "-foreground $ispellVars(viewStyle-Color)" }
	
	eval  $window tag configure MissSpelled $style
    } result ] } { 	
	tk_dialog .window "Bad Style" \
		"Invalid I-Spell style: '$result' changing to underline" \
		{} 0 ok
	eval $window tag configure MissSpelled -underline t
    }
    
    # Only bind the window if 'ispell' is turned on...
    # and if so, disable external ispell (and vice-versa)

	if { $ispellVars(on) == 1 } { 
	    set ispellVars($window,effect) 1
	    Ispellbind $window 
    }

    set ispellVars(command) $ispellVars(defaultCommand)

    set ispellVars(language) "default" 

    # Only init the spell checker if it had already not been previously init'd
    if { ! [ info exists ispellVars(spell_buffer) ] } { 
	Ispell_Init ; # init if the spell buffer is undefine
    }
} 

# this procedure re-checks the entire buffer in the
# window specified by 'window'
proc Ispell_CheckEntireWindow { text } { 
    global ispellVars


    set oldInsert [ $text index insert ] 

    set count 0
    # First things first, because this function COULD be called without
    # using any of the other ispell stuff, first ensure that the ispell 
    # process is running...
    # Only init the spell checker if it had already not been previously init'd

    if { ! [ info exists ispellVars(spell_buffer) ] } { 
	Ispell_Init ; # init if the spell buffer is undefine
    }

    # Pop up a little window to allow spell checking to be turned off......
    #
    set ispellVars(label) "Stop Spell Checking"
    catch { destroy .ispellStopWindow }
    set top [ toplevel .ispellStopWindow ] 
    wm group .ispellStopWindow .
    button $top.b  -textvariable ispellVars(label) -command { 
	set ispellVars(label) "" 
    }
    label $top.l1 -bitmap warning
    label $top.l2 -bitmap warning
    
    pack $top.l1 -side left
    pack $top.b -side left
    pack $top.l2 -side left
    
    set endOfDoc [ $text index end ] ; # get the last index mark
    set current 1.0

    # here is the actual code to spell check the document
    while { [ expr $current < $endOfDoc ] }  {

	if { $ispellVars(label) == "" } { break}
	$text mark set insert "$current"
	set start [ $text get "insert linestart" insert ]
	
	set end   [ $text get insert "insert lineend" ] 
	set e1 ""
	set s1 ""
	
	regexp "\[^\t \]*" $end e1
#	set e1 [ string trim $e1 ] 
	regexp "\[^\t \]+$" $start s1
	
	set startIndex "insert - [string length $s1] chars"
	set stopIndex "insert + [string length $e1] chars "
	
	set word "[ string trim $s1$e1 "\"\{\}\[\]" ] "
	
	set current [ $text index "$stopIndex + 2 chars" ]

	if {[string length $word] == 1} continue; # speed up process for small words....

	incr count
	update

	if { $count > 120 } {
	    
	    $text see $current
	    set count 0
	}

	IspellMarkWord $text $startIndex $stopIndex $word 
    }
    
    destroy $top
    # now let's redisplay the screen at the insert point....
    $text mark set insert "$oldInsert"
    $text see insert
}


########################################################
# procedure to re-check bound words to reconfirm they
# are still missspelled
# note that if quite a few words are missSpelled this could
# take quite a while.... Also note that this should probably
# only be called AFTER the dictionary has changed/updated
########################################################

proc IspellReCheckWords {window} { 
    global ispellVars
    

    set ranges [ $window tag ranges MissSpelled ] 
    set ispellVars(label) "Stop Spell Checking"
    set wcount 0

    if { [ expr [ llength $ranges ] > 100 ] } { 
	# Only pop up a window if
	# 100 or so need to be re-checked.
	catch {
	    destroy .ispellStop
	}

	toplevel .ispellStop
	button .ispellStop.b  -textvariable ispellVars(label) -command { 
	    set ispellVars(label) "" 
	}
	label .ispellStop.l1 -bitmap warning
	label .ispellStop.l2 -bitmap warning
	
	pack .ispellStop.l1 -side left
	pack .ispellStop.b -side left
	pack .ispellStop.l2 -side left
    }

    # loop through all the current words marked as misspelled
    #
    for { set i 0 } { $i < [ expr [llength $ranges] / 2 ] } { incr i } {
	set startIndex [ lindex $ranges [ expr $i*2 ] ] 
	set stopIndex  [ lindex $ranges [ expr $i*2+1 ] ]
	
	if { [ $window compare "$startIndex + 1 chars" == "$stopIndex" ] } {
	    $window tag remove MissSpelled $startIndex "$stopIndex +1c"
	}
	set word  " [ string trim [ $window get $startIndex $stopIndex ] \
		 " \t\"\{\}\[\]"] " ; # "
	if { $ispellVars(label) == "" } { break }

	incr wcount
	if { $wcount > 20 } {
	    $window see $startIndex
	    set wcount 0
	}

	update
	set result [ IspellMarkWord $window $startIndex $stopIndex $word ]

    }

    # destroy the toplevel window

    catch {
	destroy .ispellStop
    }

    # put the window back under the insert cursor

    $window see insert
}


# Call this procedure with the text window path to bind the spell command

proc Ispellbind { text } { 

    global ispellVars;

    set command {IspellTextWindow %W} ; 
    

    bind $text <Key-space> "IspellTextWindowInsert %W"
    bind $text <Key-Tab> "IspellTextWindowInsert %W" 
    
    bind $text <Key-Right> "$command"   
    bind $text <Key-Left>  "$command"    
    bind $text <Key-Down>  "$command"
    bind $text <Key-Up>    "$command"
    
    bind $text <Key-Return> " $command "
    
    # How do we correct words? Normally button-3!
    bind $text <$ispellVars(popupBinding)> { 
	IspellPostMenuChoices %W %x %y %X %Y 
    } ;

    # no user configurable way to select the unpost...
    # tk_popup should unpost the menu for us automatically
#    bind $text <Any-ButtonRelease> { IspellUnPostMenuChoices %W } 
}


    
    







