1
##############################################
2
# ::SpellCheck => Spell Checker for aMSN #
3
# ====================================== #
4
# SpellCheck requires aspell to be installed #
5
# ------------------------------------------ #
7
# yadgor02@hotmail.com #
8
##############################################
11
############################################
12
#I am not a Tcl programmer, so lots of this#
13
#code has been copied and modified slightly#
14
# for this plugin. Many methods have been #
15
#taken from the Nudge plugin. #
16
# ---------------------------------------- #
17
# The spell checker is based on a small #
18
# script by Richard Suchenwirth #
19
# http://wiki.tcl.tk/88 #
20
############################################
21
namespace eval ::SpellCheck {
26
######################################
27
# ::SpellCheck::InitPlug (dir) #
28
#------------------------------------#
29
#Register events and initialize #
30
######################################
31
proc InitPlugin { dir } {
32
::plugins::RegisterPlugin SpellCheck
33
::plugins::RegisterEvent SpellCheck chatwindowbutton checkspellbutton
34
::plugins::RegisterEvent SpellCheck chat_msg_send AutoCheck
35
::plugins::RegisterEvent SpellCheck Load pluginDidLoad
37
::SpellCheck::LoadLangFiles $dir
42
array set ::SpellCheck::config {
43
location {Path to aspell}
54
set ::SpellCheck::configlist [list \
55
[list str "[trans aspell_location]" location] \
56
[list bool "[trans auto_check]" autocheck] \
57
[list bool "[trans check_as_typing]" astyping] \
58
[list frame ::SpellCheck::languageFrame ""] \
59
[list frame ::SpellCheck::tagFrame ""] \
60
[list ext "[trans removecols]" {resetCols}] \
62
::SpellCheck::LoadPixmaps $dir
63
set ::SpellCheck::sendMsg 0
69
######################################
70
# ::SpellCheck::LoadLangFiles (dir) #
71
#------------------------------------#
72
# Load the language files #
73
######################################
74
proc LoadLangFiles { dir } {
75
set langdir [file join $dir "lang"]
76
set lang [::config::getGlobalKey language]
78
load_lang $lang $langdir
82
###########################################
83
# ::SpellCheck::pluginDidLoad event evpar #
84
#-----------------------------------------#
85
# If SpellCheck gets loaded, check for #
87
###########################################
88
proc pluginDidLoad {event evpar} {
90
if {$newvar(name)=="SpellCheck"} {
91
::SpellCheck::checkForAspell
97
######################################
98
# ::SpellCheck::checkForAspell #
99
#------------------------------------#
100
# Check some paths for aspell #
101
######################################
102
proc checkForAspell { } {
103
if {![catch {exec $::SpellCheck::config(location)}]} {
105
} elseif {![catch {exec aspell -v}]} {
106
set ::SpellCheck::config(location) "aspell"
107
} elseif {![catch {exec "c:/program files/aspell/bin/aspell.exe" -v}] && [OnWin]} {
108
set ::SpellCheck::config(location) "c:/program files/aspell/bin/aspell.exe"
109
} elseif {![catch {exec /opt/local/bin/aspell -v}]} {
110
set ::SpellCheck::config(location) "/opt/local/bin/aspell"
111
} elseif {![catch {exec /usr/bin/aspell -v}]} {
112
set ::SpellCheck::config(location) "/usr/bin/aspell"
113
} elseif {![catch {exec /usr/local/bin/aspell -v}]} {
114
set ::SpellCheck::config(location) "/usr/local/bin/aspell"
115
} elseif {![catch {exec /sw/bin/aspell -v}]} {
116
set ::SpellCheck::config(location) "/sw/bin/aspell"
118
::SpellCheck::aspellNotFound
123
######################################
124
# ::SpellCheck::languageFrame win #
125
#------------------------------------#
126
#Drop down menu for choosing language#
127
######################################
128
proc languageFrame { win } {
130
label $win.lang.label -text "[trans lang_select]"
131
combobox::combobox $win.lang.accounts -editable false -highlightthickness 0 -bg #FFFFFF -font splainf -textvariable ::SpellCheck::config(language)
132
if {![catch {set dicts [exec $::SpellCheck::config(location) dicts]}]} {
133
foreach aLang [split $dicts \n] {
134
$win.lang.accounts list insert end $aLang
137
$win.lang.accounts list insert end "[trans not_found]"
140
pack $win.lang.label -side left -anchor w
141
pack $win.lang.accounts -side left -anchor w
142
pack $win.lang -anchor w -padx 20
146
######################################
147
# ::SpellCheck::tagFrame win #
148
#------------------------------------#
149
#set up the frame for configuring the#
150
#incorrect spelling tag #
151
######################################
152
proc tagFrame { win } {
155
label $win.tag.label -text "[trans showincorrect]"
157
combobox::combobox $win.tag.options -editable false -highlightthickness 0 -bg #FFFFFF -font splainf -command ::SpellCheck::setTagOption
160
$win.tag.options list insert end "[trans background]"
161
$win.tag.options list insert end "[trans foreground]"
162
$win.tag.options list insert end "[trans fontstyle]"
164
#show what the incorect tag will look like
165
label $win.tag.value -text "[trans example]" -highlightthickness 0 -font [regsub -all {\s} "[lindex [::config::getKey mychatfont] 0]" "\\ "]
167
#if no background colour has been set, use skin defined one
168
if {$::SpellCheck::config(background) != {} } {
169
$win.tag.value configure -background $::SpellCheck::config(background)
171
$win.tag.value configure -background [::skin::getKey chat_input_back_color]
174
#if no foreground colour has been set, use the users font colour
175
if {$::SpellCheck::config(foreground) != {} } {
176
$win.tag.value configure -foreground $::SpellCheck::config(foreground)
178
$win.tag.value configure -foreground "#[lindex [::config::getKey mychatfont] 2]"
180
if { $::SpellCheck::config(fontstyle) != {} } {
181
$win.tag.value configure -font $::SpellCheck::config(fontstyle)
184
combobox::combobox $win.tag.font -editable false -highlightthickness 0 -bg #FFFFFF -font splainf -command ::SpellCheck::updateFontStyle
185
$win.tag.font configure -value ""
187
pack $win.tag.label -side left -anchor w
188
pack $win.tag.options -side left -anchor w
189
pack $win.tag.font -side left -anchor w
190
pack $win.tag.value -side left -anchor w
192
pack $win.tag -anchor w -padx 20
197
proc updateFontStyle { widget value } {
198
set w [string replace $widget end-4 end ""]
200
if { $value != {} } {
201
set ::SpellCheck::config(fontstyle) $value
202
$w.value configure -font $::SpellCheck::config(fontstyle)
207
###########################################
208
# ::SpellCheck::setTagOption widget value #
209
#-----------------------------------------#
210
# if the user selects background or #
211
# foreground, show the colour picker. Show#
212
# a list of they select fontstyle #
213
###########################################
214
proc setTagOption { widget value } {
215
set w [string replace $widget end-7 end ""]
220
#hide other list if it is shown
221
$w.font list delete 0 end
222
$w.font configure -value ""
223
#if a colour is already set, use it as the initial colour
224
if {$::SpellCheck::config(background) != {} } {
225
set theCol [tk_chooseColor -initialcolor $::SpellCheck::config(background)]
227
set theCol [tk_chooseColor]
230
#if a new colour is chosen, set the config.
231
if { $theCol != "" } {
232
set ::SpellCheck::config(background) $theCol
233
$w.value configure -bg $::SpellCheck::config(background)
238
$w.font list delete 0 end
239
$w.font configure -value ""
240
if {$::SpellCheck::config(foreground) != {} } {
241
set theCol [tk_chooseColor -initialcolor $::SpellCheck::config(foreground)]
243
set theCol [tk_chooseColor]
245
if { $theCol != "" } {
246
set ::SpellCheck::config(foreground) $theCol
247
$w.value configure -fg $::SpellCheck::config(foreground)
249
#pack $w.value -side left -anchor w
252
if { [$w.font list get 0] == {} } {
253
foreach aFont [split [font names] " "] {
254
$w.font list insert end $aFont
257
if { $::SpellCheck::config(fontstyle) != {} } {
258
$w.font configure -value $::SpellCheck::config(fontstyle)
268
######################################
269
# ::SpellCheck::resetCols #
270
#------------------------------------#
271
# Removes all the set colours so that#
272
# the incorrect tags will use the #
273
# skin background and users font. #
274
######################################
276
set ::SpellCheck::config(background) {}
277
set ::SpellCheck::config(foreground) {}
278
set ::SpellCheck::config(fontstyle) {}
280
#not sure how to get this value yet...
281
set widget ".plugin_selector.winconf_SpellCheck.area.5.tag.value"
283
$widget configure -background [::skin::getKey chat_input_back_color]
284
$widget configure -foreground "#[lindex [::config::getKey mychatfont] 2]"
285
$widget configure -font [regsub -all {\s} "[lindex [::config::getKey mychatfont] 0]" "\\ "]
286
tk_messageBox -message "[trans tagwarning]" -type ok -icon warning
290
###########################################
291
# ::SpellCheck::languageFrame event evpar #
292
#-----------------------------------------#
293
#Stop the message being sent if there are #
294
#errors in the text. If you send it again #
295
#before the delay is up, it will send with#
297
###########################################
298
proc AutoCheck {event evpar} {
300
upvar 2 win_name win_name
301
if { $::SpellCheck::config(autocheck) } {
302
if {$::SpellCheck::sendMsg==1} {
303
set ::SpellCheck::sendMsg 0
304
[::ChatWindow::GetInputText $win_name] delete 0.0 end
307
set theInput [::ChatWindow::GetInputText $win_name]
308
catch {menu $win_name.aMenu}
309
::SpellCheck::parseText $theInput $win_name.aMenu $win_name $msg
310
if {$::SpellCheck::numOfErrors==0} {
311
[::ChatWindow::GetInputText $win_name] delete 0.0 end
314
set ::SpellCheck::sendMsg 1
315
set theTime [expr {int($::SpellCheck::config(delay)*1000)}]
316
after $theTime set ::SpellCheck::sendMsg 0
326
######################################
327
# ::SpellCheck::LoadPixmaps (dir) #
328
#------------------------------------#
329
#Load the button images #
330
######################################
331
proc LoadPixmaps {dir} {
332
::skin::setPixmap spellCheck checkSpelling.png pixmaps [file join $dir pixmaps]
333
::skin::setPixmap spellCheck_Hover checkSpelling_hover.png pixmaps [file join $dir pixmaps]
338
########################################
339
# ::SpellCheck::changeLang x y winName #
340
#--------------------------------------#
341
#Allow the user to select a custom #
342
#language for certain users. If none #
343
#has been set for the user, the config #
345
########################################
346
proc changeLang { x y winName} {
347
set theUser [::ChatWindow::Name $winName]
348
catch {menu .changLangPopup}
349
.changLangPopup delete 0 end
351
if {![catch {set dicts [exec $::SpellCheck::config(location) dicts]}]} {
352
foreach aLang [split $dicts \n] {
353
.changLangPopup add command -label $aLang -command "set ::SpellCheck::config($theUser) $aLang"
356
.changLangPopup add command -label [trans not_found]
358
tk_popup .changLangPopup $x $y
363
##############################################
364
# ::SpellCheck::checkspellbutton event evpar #
365
#--------------------------------------------#
366
#Taken straight from the Nudge plugin. #
367
#Adds a button to the ChatWindow #
368
##############################################
369
proc checkspellbutton { event evpar } {
370
upvar 2 $evpar newvar
371
set winName $newvar(window_name)
372
set spellCheckButton $newvar(bottom).checkSpelling
373
set theInput [::ChatWindow::GetInputText $newvar(window_name)]
374
menu $newvar(window_name).aMenu
377
label $spellCheckButton -image [::skin::loadPixmap spellCheck] -relief flat -padx 0 \
378
-background [::skin::getKey buttonbarbg] -highlightthickness 0 -borderwidth 0 \
379
-highlightbackground [::skin::getKey buttonbarbg] -activebackground [::skin::getKey buttonbarbg] \
381
bind $spellCheckButton <<Button1>> "::SpellCheck::parseText $theInput $newvar(window_name).aMenu $newvar(window_name) zzButtonWasPressed"
382
bind $spellCheckButton <<Button3>> "::SpellCheck::changeLang %X %Y $winName"
383
bind $spellCheckButton <Enter> "$spellCheckButton configure -image [::skin::loadPixmap spellCheck_Hover]"
384
bind $spellCheckButton <Leave> "$spellCheckButton configure -image [::skin::loadPixmap spellCheck]"
386
set_balloon $spellCheckButton "[trans ballon_text]"
388
pack $spellCheckButton -side right
390
#add the binds to check as the user is typing
391
#after 0 so that the pressed key will be entered before the check happens
392
if {$::SpellCheck::config(astyping)} {
393
bind $theInput <Key-space> "after 0 ::SpellCheck::parseText $theInput $newvar(window_name).aMenu $newvar(window_name) zzButtonWasPressed"
394
bind $theInput <.> "after 0 ::SpellCheck::parseText $theInput $newvar(window_name).aMenu $newvar(window_name) zzButtonWasPressed"
395
bind $theInput <?> "after 0 ::SpellCheck::parseText $theInput $newvar(window_name).aMenu $newvar(window_name) zzButtonWasPressed"
396
bind $theInput <!> "after 0 ::SpellCheck::parseText $theInput $newvar(window_name).aMenu $newvar(window_name) zzButtonWasPressed"
397
bind $theInput <(> "after 0 ::SpellCheck::parseText $theInput $newvar(window_name).aMenu $newvar(window_name) zzButtonWasPressed"
398
bind $theInput <)> "after 0 ::SpellCheck::parseText $theInput $newvar(window_name).aMenu $newvar(window_name) zzButtonWasPressed"
404
###################################################
405
# ::SpellCheck::parseText textField aMenu win msg #
406
#-------------------------------------------------#
407
# Based on Suchenwirth's spell checker. #
408
#-------------------------------------------------#
409
# Takes the text from the input, splits it into #
410
# lines and then runs is through aspell. Then #
411
# spiltsthe input into words and highlights any #
412
# that aspell finds to be incorrect #
413
###################################################
414
proc parseText { textField aMenu win msg } {
415
#if autochecking, put the text back in the textfield
416
if {$msg!="zzButtonWasPressed"} {
417
$textField insert 0.0 $msg
419
set ::SpellCheck::numOfErrors 0
422
#remove any existing tags
423
foreach aTag [$textField tag name] {$textField tag delete $aTag}
425
#make sure invalid tag is not invisible
426
if { $::SpellCheck::config(foreground)=={} && \
427
$::SpellCheck::config(background)=={} && \
428
$::SpellCheck::config(fontstyle)=={} } {
430
tk_messageBox -message "[trans tagwarning]" -type ok -icon warning
434
#set up the highlight tag
435
if { $::SpellCheck::config(background) != {} } {
436
$textField tag configure spelling -background $::SpellCheck::config(background)
438
$textField tag configure spelling -background [::skin::getKey chat_input_back_color]
441
if { $::SpellCheck::config(foreground) != {} } {
442
$textField tag configure spelling -foreground $::SpellCheck::config(foreground)
444
$textField tag configure spelling -foreground "#[lindex [::config::getKey mychatfont] 2]"
446
if { $::SpellCheck::config(fontstyle) != {} } {
447
$textField tag configure spelling -font $::SpellCheck::config(fontstyle)
449
$textField tag configure spelling -font {}
453
foreach line [split [$textField get 1.0 end-1c] \n] {
454
set theCheck [runAspell $line $win]
456
#start the counter at 1 to ignore first aspell output
459
foreach {from to} [getWordIndexes $line] {
460
set word [string range $line $from [expr $to-1]]
462
if { [lindex $theCheck $counter]=="#" || [lindex $theCheck $counter]=="?" || [lindex $theCheck $counter]=="&"} {
463
incr ::SpellCheck::numOfErrors
465
#when you click on a word, set up and show the suggestions
466
$textField tag bind $word <<Button1>> "::SpellCheck::setUpMenu $textField $word $aMenu %X %Y $win"
468
$textField tag add spelling $lineno.$from $lineno.$to
471
$textField tag add $word $lineno.$from $lineno.$to
483
#####################################################
484
# ::SpellCheck::addWord textField theWord win aMenu #
485
#---------------------------------------------------#
486
# adds the clicked word to the personal dictionary #
487
# for aspell. Language specific, so words will be #
488
# added to a custom en dictionary for english, fr #
490
#####################################################
491
proc addWord { textField theWord win aMenu} {
495
set theUser [::ChatWindow::Name $win]
496
if {[catch {list $::SpellCheck::config($theUser)}]} {
497
set theLang $::SpellCheck::config(language)
499
set theLang $::SpellCheck::config($theUser)
503
set loc $::SpellCheck::config(location)
504
set loc [regsub -all {\s} $loc "\\\\ "]
505
if {[catch {set pipe [open "| $loc -a" WRONLY]}]} {
506
::SpellCheck::aspellNotFound
509
fconfigure $pipe -buffering line -blocking 0
514
set theWord "*$theWord\n#"
515
if {[catch {exec echo $theWord | $::SpellCheck::config(location) -a --lang=$theLang}]} {
516
::SpellCheck::aspellNotFound
520
$textField tag remove $theWord 1.0 end
521
after 200 ::SpellCheck::parseText $textField $aMenu $win zzButtonWasPressed
526
####################################
527
# ::SpellCheck::aspellNotFound #
528
#----------------------------------#
529
# Alert if aspell is not found #
530
####################################
531
proc aspellNotFound { } {
532
set answer [tk_messageBox -type yesno -default yes -icon question -message [trans aspell_not_found]]
537
#catch for amsn v0.95
538
if {![catch {set ::plugins::selection SpellCheck}]} {
539
::plugins::GUI_Config
547
############################################
548
# ::SpellCheck::runAspell theWords win #
549
#------------------------------------------#
550
#Send the line of text to aspell and return#
551
# a single character for each word #
552
#------------------------------------------#
553
# * means word correct #
554
# ? or # means word incorrect #
555
############################################
556
proc runAspell { theWords win } {
558
set formattedResults {}
560
set theUser [::ChatWindow::Name $win]
561
#does the user have a custom language set?
562
if {[catch {list $::SpellCheck::config($theUser)}]} {
563
set theLang $::SpellCheck::config(language)
565
set theLang $::SpellCheck::config($theUser)
568
#aspell removes all numbers from the input
569
#replace with "a" so they always return correct
570
set theWords [regsub -all {[0-9_]+} $theWords " "]
572
set theWords [regsub -all {[(\<\>\@\|\*\#\&)]} $theWords "\\\\&"]
576
if {[catch {set aspellResult [exec cmd /c echo $theWords | $::SpellCheck::config(location) -a --lang=$theLang]}]} {
577
::SpellCheck::aspellNotFound
581
if {[catch {set aspellResult [exec echo $theWords | $::SpellCheck::config(location) -a --lang=$theLang]}]} {
582
::SpellCheck::aspellNotFound
586
#fix a bug that displays 2 results on the same line occasionally
587
set aspellResult [regsub -all {(\w)([\*\?\#])(\n)} $aspellResult {\1\3\2\3}]
588
foreach line [split $aspellResult \n] {
589
lappend formattedResults [string range $line 0 0]
592
return $formattedResults
597
##########################################
598
# ::SpellCheck::getWordIndexes s #
599
#----------------------------------------#
600
# Based on Suchenwirth's spell checker #
601
#----------------------------------------#
602
# Returns the start and end index of the #
603
# words, doesn't count non-alphanumeric #
604
# characters to be consistent with aspell#
605
##########################################
606
proc getWordIndexes s {
609
set s [string tolower $s]
611
#replace all ' so the right words get highlighted
612
set s [regsub -all {'} $s "a"]
613
set s [regsub -all {[0-9_]} $s " "]
614
foreach c [split $s ""] {
615
if {$c ne " " && $i eq [string wordstart $s $i] } {
617
#tcl counts punctuation as words and aspell doesnt,
618
#so only count alphanumeric characters as words
619
if {![regexp {[^a-z\s]} $c]} {
620
lappend res $i [string wordend $s $i]
631
#################################################
632
# ::SpellCheck::setUpMenu w aWord aMenu x y win #
633
#-----------------------------------------------#
634
#Called when a user clicks on a highlighted word#
635
#It gets a list of suggestions from aspell, #
636
#create a popup menu with the items. When the #
637
#user selects a word, delete the incorrect word #
638
#and replace it with the new one. #
639
#################################################
640
proc setUpMenu {input aWord aMenu x y win} {
642
#delete old popup menu items
645
set tempListOfWords {}
647
set theUser [::ChatWindow::Name $win]
649
#use the user's language if set
650
if {[catch {list $::SpellCheck::config($theUser)}]} {
651
set theLang $::SpellCheck::config(language)
653
set theLang $::SpellCheck::config($theUser)
657
if {[catch {set suggestions [exec cmd /c echo $aWord | $::SpellCheck::config(location) --lang=$theLang -a]}]} {
658
::SpellCheck::aspellNotFound
662
if {[catch {set suggestions [exec echo $aWord | $::SpellCheck::config(location) --lang=$theLang -a]}]} {
663
::SpellCheck::aspellNotFound
667
foreach line [split $suggestions \n] {
668
lappend tempListOfWords $line
671
#discard first information line
672
foreach word [split [lindex $tempListOfWords 1] ", "] {
674
lappend theList $word
678
#remove the incorrect word and unneeded values
679
set theList [lreplace $theList 0 3]
681
foreach changeWord $theList {
682
$aMenu add command -label $changeWord -command "::SpellCheck::replaceWord $input $changeWord $aWord"
684
$aMenu add command -label "[trans addword]" -command "::SpellCheck::addWord $input $aWord $win $aMenu"
685
tk_popup $aMenu $x $y
690
#########################################################
691
# ::SpellCheck::replaceWord w replaceWith wordToReplace #
692
#-------------------------------------------------------#
693
#When the user chooses a word from the menu, replace the#
694
#incorrect word with the new one #
695
#########################################################
696
proc replaceWord { w replaceWith wordToReplace } {
697
foreach {start end} [$w tag ranges $wordToReplace] {
698
$w delete $start $end
700
#amsn was doing some weird random selecting so:
701
$w tag remove sel 0.0 end
703
#reposition the insert marker
704
$w mark set insert $start
705
$w insert insert $replaceWith
707
#decrease the number of incorrect words
708
incr ::SpellCheck::numOfErrors -1
710
#only do one occurrence of the word because it wont replace the
711
#right spot if the chosen word is a different length