1 /* This file is part of the KDE libraries
2 Copyright (C) 1997 David Sweet <dsweet@kde.org>
3 Copyright (C) 2000-2001 Wolfram Diestel <wolfram@steloj.de>
4 Copyright (C) 2003 Zack Rusin <zack@kde.org>
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License version 2 as published by the Free Software Foundation.
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
27 #include <sys/types.h>
30 #include <stdlib.h> // atoi
37 #include <QtGui/QApplication>
38 #include <QtCore/QTextCodec>
39 #include <QtCore/QTimer>
41 #include <kmessagebox.h>
44 #include "k3sconfig.h"
45 #include "k3spelldlg.h"
47 #include <QTextStream>
49 #define MAXLINELENGTH 10000
50 #undef IGNORE //fix possible conflict
59 enum checkMethod
{ Method1
= 0, Method2
};
69 class K3Spell::K3SpellPrivate
73 bool m_bIgnoreUpperWords
;
74 bool m_bIgnoreTitleCase
;
75 bool m_bNoMisspellingsEncountered
;
77 K3Spell
* suggestSpell
;
79 QList
<BufferedWord
> unchecked
;
80 QTimer
*checkNextTimer
;
83 QString
convertQByteArray( const QByteArray
& b
)
85 QTextCodec
* originalCodec
= QTextCodec::codecForCStrings();
86 QTextCodec::setCodecForCStrings( m_codec
);
88 QTextCodec::setCodecForCStrings( originalCodec
);
91 QByteArray
convertQString( const QString
& s
)
93 QTextCodec
* originalCodec
= QTextCodec::codecForCStrings();
94 QTextCodec::setCodecForCStrings( m_codec
);
95 QByteArray b
= s
.toAscii();
96 QTextCodec::setCodecForCStrings( originalCodec
);
102 //Parse stderr output
103 //e.g. -- invalid dictionary name
106 Things to put in K3SpellConfigDlg:
107 make root/affix combinations that aren't in the dictionary (-m)
108 don't generate any affix/root combinations (-P)
109 Report run-together words with missing blanks as spelling errors. (-B)
110 default dictionary (-d [dictionary])
111 personal dictionary (-p [dictionary])
112 path to ispell -- NO: ispell should be in $PATH
116 // Connects a slot to KProcess's output signal
117 #define OUTPUT(x) (connect (proc, SIGNAL (readyReadStandardOutput()), this, SLOT (x())))
119 // Disconnect a slot from...
120 #define NOOUTPUT(x) (disconnect (proc, SIGNAL (readyReadStandardOutput()), this, SLOT (x())))
124 K3Spell::K3Spell( QWidget
*_parent
, const QString
&_caption
,
125 QObject
*obj
, const char *slot
, K3SpellConfig
*_ksc
,
126 bool _progressbar
, bool _modal
)
128 initialize( _parent
, _caption
, obj
, slot
, _ksc
,
129 _progressbar
, _modal
, Text
);
132 K3Spell::K3Spell( QWidget
*_parent
, const QString
&_caption
,
133 QObject
*obj
, const char *slot
, K3SpellConfig
*_ksc
,
134 bool _progressbar
, bool _modal
, SpellerType type
)
136 initialize( _parent
, _caption
, obj
, slot
, _ksc
,
137 _progressbar
, _modal
, type
);
140 K3Spell::spellStatus
K3Spell::status() const
145 void K3Spell::hide() { ksdlg
->hide(); }
147 QStringList
K3Spell::suggestions() const
152 int K3Spell::dlgResult () const
157 int K3Spell::heightDlg() const { return ksdlg
->height(); }
158 int K3Spell::widthDlg() const { return ksdlg
->width(); }
160 QString
K3Spell::intermediateBuffer() const
162 return K3Spell::newbuffer
;
165 // Check if aspell is at least version 0.6
166 static bool determineASpellV6()
169 FILE *fs
= popen("aspell -v", "r");
172 // Close textstream before we close fs
174 QTextStream
ts(fs
, QIODevice::ReadOnly
);
175 result
= ts
.readAll().trimmed();
180 QRegExp
rx("Aspell (\\d.\\d)");
181 if (rx
.indexIn(result
) != -1)
183 float version
= rx
.cap(1).toFloat();
184 return (version
>= 0.6);
191 K3Spell::startIspell()
194 if ((trystart
== 0) && (ksconfig
->client() == KS_CLIENT_ASPELL
))
195 d
->aspellV6
= determineASpellV6();
197 kDebug(750) << "Try #" << trystart
;
199 if ( trystart
> 0 ) {
203 switch ( ksconfig
->client() )
205 case KS_CLIENT_ISPELL
:
207 kDebug(750) << "Using ispell";
209 case KS_CLIENT_ASPELL
:
211 kDebug(750) << "Using aspell";
213 case KS_CLIENT_HSPELL
:
215 kDebug(750) << "Using hspell";
217 case KS_CLIENT_ZEMBEREK
:
219 kDebug(750) << "Using zemberek(zpspell)";
223 if ( ksconfig
->client() == KS_CLIENT_ISPELL
|| ksconfig
->client() == KS_CLIENT_ASPELL
)
225 *proc
<< "-a" << "-S";
230 //Debian uses an ispell version that has the -h option instead.
231 //Not sure what they did, but the preferred spell checker
232 //on that platform is aspell anyway, so use -H untill I'll come
233 //up with something better.
237 //same for aspell and ispell
241 //only ispell supports
242 if ( ksconfig
->client() == KS_CLIENT_ISPELL
)
250 if (ksconfig
->noRootAffix())
254 if (ksconfig
->runTogether())
266 if (! ksconfig
->dictionary().isEmpty())
268 kDebug(750) << "using dictionary [" << ksconfig
->dictionary() << "]";
270 *proc
<< ksconfig
->dictionary();
274 //Note to potential debuggers: -Tlatin2 _is_ being added on the
275 // _first_ try. But, some versions of ispell will fail with this
276 // option, so k3spell tries again without it. That's why as 'ps -ax'
277 // shows "ispell -a -S ..." withou the "-Tlatin2" option.
280 switch ( ksconfig
->encoding() )
292 // add the other charsets here
299 // will work, if this is the default charset in the dictionary
300 kError(750) << "charsets ISO-8859-4, -5, -7, -8, -9 and -13 not supported yet" << endl
;
302 case KS_E_LATIN15
: // ISO-8859-15 (Latin 9)
303 if (ksconfig
->client() == KS_CLIENT_ISPELL
)
306 * As far as I know, there are no ispell dictionary using ISO-8859-15
307 * but users have the tendency to select this encoding instead of ISO-8859-1
308 * So put ispell in ISO-8859-1 (Latin 1) mode.
313 kError(750) << "ISO-8859-15 not supported for aspell yet." << endl
;
317 if (ksconfig
->client() == KS_CLIENT_ASPELL
)
318 *proc
<< "--encoding=utf-8";
321 *proc
<< "-w'"; // add ' as a word char
329 // -S : sort suggestions by probable correctness
331 else // hspell and Zemberek(zpspell) doesn't need all the rest of the options
334 if (trystart
== 0) //don't connect these multiple times
336 connect( proc
, SIGNAL(readyReadStandardError()),
337 this, SLOT(ispellErrors()) );
339 connect( proc
, SIGNAL(finished(int, QProcess::ExitStatus
)),
340 this, SLOT(ispellExit ()) );
342 proc
->setOutputChannelMode( KProcess::SeparateChannels
);
343 proc
->setNextOpenMode( QIODevice::ReadWrite
| QIODevice::Text
);
349 if ( !proc
->waitForStarted() )
352 QTimer::singleShot( 0, this, SLOT(emitDeath()));
357 K3Spell::ispellErrors( )
359 // buffer[buflen-1] = '\0';
360 // kDebug(750) << "ispellErrors [" << buffer << "]\n";
363 void K3Spell::K3Spell2( )
368 kDebug(750) << "K3Spell::K3Spell2";
370 trystart
= maxtrystart
; //We've officially started ispell and don't want
371 //to try again if it dies.
374 qint64 read
= proc
->readLine(data
.data(),data
.count());
377 QTimer::singleShot( 0, this, SLOT(emitDeath()) );
380 line
= d
->convertQByteArray( data
);
382 if ( !line
.startsWith('@') ) //@ indicates that ispell is working fine
384 QTimer::singleShot( 0, this, SLOT(emitDeath()) );
388 //We want to recognize KDE in any text!
389 if ( !ignore("kde") )
391 kDebug(750) << "@KDE was false";
392 QTimer::singleShot( 0, this, SLOT(emitDeath()) );
396 //We want to recognize linux in any text!
397 if ( !ignore("linux") )
399 kDebug(750) << "@Linux was false";
400 QTimer::singleShot( 0, this, SLOT(emitDeath()) );
404 NOOUTPUT( K3Spell2
);
411 K3Spell::setUpDialog( bool reallyuseprogressbar
)
416 //Set up the dialog box
417 ksdlg
= new K3SpellDlg( parent
, progressbar
&& reallyuseprogressbar
, modaldlg
);
418 ksdlg
->setCaption( caption
);
420 connect( ksdlg
, SIGNAL(command(int)),
421 this, SLOT(slotStopCancel(int)) );
422 connect( this, SIGNAL(progress(unsigned int)),
423 ksdlg
, SLOT(slotProgress(unsigned int)) );
430 bool K3Spell::addPersonal( const QString
& word
)
432 QString qs
= word
.simplified();
434 //we'll let ispell do the work here b/c we can
435 if ( qs
.indexOf(' ') != -1 || qs
.isEmpty() ) // make sure it's a _word_
441 return proc
->write( d
->convertQString( qs
) );
444 bool K3Spell::writePersonalDictionary()
446 return proc
->write( QByteArray( "#" ) );
449 bool K3Spell::ignore( const QString
& word
)
451 QString qs
= word
.simplified();
453 //we'll let ispell do the work here b/c we can
454 if ( qs
.indexOf (' ') != -1 || qs
.isEmpty() ) // make sure it's a _word_
459 return proc
->write( d
->convertQString( qs
) );
463 K3Spell::cleanFputsWord( const QString
& s
)
468 for( int i
= 0; i
< qs
.length(); i
++ )
470 //we need some punctuation for ornaments
471 if ( (qs
[i
] != '\'' && qs
[i
] != '\"' && qs
[i
] != '-'
472 && qs
[i
].isPunct()) || qs
[i
].isSpace() )
477 if ( qs
[i
].isLetter() )
482 // don't check empty words, otherwise synchronization will lost
486 return proc
->write( d
->convertQString( QString('^'+qs
+'\n') ) );
490 K3Spell::cleanFputs( const QString
& s
)
493 unsigned l
= qs
.length();
495 // some uses of '$' (e.g. "$0") cause ispell to skip all following text
496 for( unsigned int i
= 0; i
< l
; ++i
)
502 if ( l
<MAXLINELENGTH
)
506 return proc
->write( d
->convertQString('^'+qs
+'\n') );
509 return proc
->write( d
->convertQString( "^\n" ) );
512 bool K3Spell::checkWord( const QString
& buffer
, bool _usedialog
)
514 if (d
->checking
) { // don't check multiple words simultaneously
515 BufferedWord bufferedWord
;
516 bufferedWord
.method
= Method1
;
517 bufferedWord
.word
= buffer
;
518 bufferedWord
.useDialog
= _usedialog
;
519 d
->unchecked
.append( bufferedWord
);
523 QString qs
= buffer
.simplified();
525 if ( qs
.indexOf (' ') != -1 || qs
.isEmpty() ) { // make sure it's a _word_
526 d
->checkNextTimer
->setInterval(0);
527 d
->checkNextTimer
->setSingleShot(true);
528 d
->checkNextTimer
->start();
531 ///set the dialog signal handler
532 dialog3slot
= SLOT(checkWord3());
534 usedialog
= _usedialog
;
535 setUpDialog( false );
544 while (proc
->readLine( data
.data(), data
.count() ) != -1 )
545 ; // eat spurious blanks
548 // connect (this, SIGNAL (dialog3()), this, SLOT (checkWord3()));
550 proc
->write( d
->convertQString( QString( "%" ) ) ); // turn off terse mode
551 proc
->write( d
->convertQString( buffer
) ); // send the word to ispell
556 bool K3Spell::checkWord( const QString
& buffer
, bool _usedialog
, bool suggest
)
558 if (d
->checking
) { // don't check multiple words simultaneously
559 BufferedWord bufferedWord
;
560 bufferedWord
.method
= Method2
;
561 bufferedWord
.word
= buffer
;
562 bufferedWord
.useDialog
= _usedialog
;
563 bufferedWord
.suggest
= suggest
;
564 d
->unchecked
.append( bufferedWord
);
568 QString qs
= buffer
.simplified();
570 if ( qs
.indexOf (' ') != -1 || qs
.isEmpty() ) { // make sure it's a _word_
571 d
->checkNextTimer
->setInterval(0);
572 d
->checkNextTimer
->setSingleShot(true);
573 d
->checkNextTimer
->start();
577 ///set the dialog signal handler
579 dialog3slot
= SLOT(checkWord3());
580 usedialog
= _usedialog
;
581 setUpDialog( false );
591 while (proc
->readLine( data
.data(), data
.count() ) != -1 ); // eat spurious blanks
594 // connect (this, SIGNAL (dialog3()), this, SLOT (checkWord3()));
596 proc
->write( d
->convertQString( QString( "%" ) ) ); // turn off terse mode
597 proc
->write( d
->convertQString( buffer
) ); // send the word to ispell
602 void K3Spell::checkWord2( )
606 line
= d
->convertQByteArray( proc
->readLine() ); //get ispell's response
608 /* ispell man page: "Each sentence of text input is terminated with an
609 additional blank line, indicating that ispell has completed processing
612 But there can be multiple lines returned in the case of an error,
613 in this case we should consume all the output given otherwise spell checking
618 while (proc
->readLine( data
.data(), data
.count() ) != -1 ); // eat spurious blanks
619 NOOUTPUT(checkWord2
);
621 bool mistake
= ( parseOneResponse(line
, word
, sugg
) == MISTAKE
);
622 if ( mistake
&& usedialog
)
625 dialog( word
, sugg
, SLOT(checkWord3()) );
626 d
->checkNextTimer
->setInterval(0);
627 d
->checkNextTimer
->setSingleShot(true);
628 d
->checkNextTimer
->start();
633 emit
misspelling( word
, sugg
, lastpos
);
636 //emits a "corrected" signal _even_ if no change was made
637 //so that the calling program knows when the check is complete
638 emit
corrected( word
, word
, 0L );
639 d
->checkNextTimer
->setInterval(0);
640 d
->checkNextTimer
->setSingleShot(true);
641 d
->checkNextTimer
->start();
644 void K3Spell::checkNext()
646 // Queue words to prevent kspell from turning into a fork bomb
648 if (!d
->unchecked
.empty()) {
649 BufferedWord buf
= d
->unchecked
.front();
650 d
->unchecked
.pop_front();
652 if (buf
.method
== Method1
)
653 checkWord( buf
.word
, buf
.useDialog
);
655 checkWord( buf
.word
, buf
.useDialog
, buf
.suggest
);
659 void K3Spell::suggestWord()
663 line
= d
->convertQByteArray( proc
->readLine() ); //get ispell's response
665 /* ispell man page: "Each sentence of text input is terminated with an
666 additional blank line, indicating that ispell has completed processing
669 while (proc
->readLine( data
.data(), data
.count() ) != -1 ); // eat spurious blanks
671 NOOUTPUT(checkWord2
);
673 bool mistake
= ( parseOneResponse(line
, word
, sugg
) == MISTAKE
);
674 if ( mistake
&& usedialog
)
677 dialog( word
, sugg
, SLOT(checkWord3()) );
682 void K3Spell::checkWord3()
684 disconnect( this, SIGNAL(dialog3()), this, SLOT(checkWord3()) );
686 emit
corrected( cwword
, replacement(), 0L );
689 QString
K3Spell::funnyWord( const QString
& word
)
690 // composes a guess from ispell to a readable word
691 // e.g. "re+fry-y+ies" -> "refries"
694 for( int i
=0; i
<word
.size(); i
++ )
703 for( j
= i
+1; j
< word
.size() && word
[j
] != '+' && word
[j
] != '-'; j
++ )
708 if ( !( k
= qs
.lastIndexOf(shorty
) ) || k
!= -1 )
709 qs
.remove( k
, shorty
.length() );
713 qs
+= shorty
; //it was a hyphen, not a '-' from ispell
724 int K3Spell::parseOneResponse( const QString
&buffer
, QString
&word
, QStringList
& sugg
)
725 // buffer is checked, word and sugg are filled in
727 // GOOD if word is fine
728 // IGNORE if word is in ignorelist
729 // REPLACE if word is in replacelist
730 // MISTAKE if word is misspelled
737 if ( buffer
[0] == '*' || buffer
[0] == '+' || buffer
[0] == '-' )
742 if ( buffer
[0] == '&' || buffer
[0] == '?' || buffer
[0] == '#' )
747 word
= buffer
.mid( 2, buffer
.indexOf( ' ', 3 ) -2 );
751 if( d
->m_bIgnoreTitleCase
&& word
== word
.toUpper() )
754 if( d
->m_bIgnoreUpperWords
&& word
[0] == word
[0].toUpper() )
756 QString text
= word
[0] + word
.right( word
.length()-1 ).toLower();
761 /////// Ignore-list stuff //////////
762 //We don't take advantage of ispell's ignore function because
763 //we can't interrupt ispell's output (when checking a large
764 //buffer) to add a word to _it's_ ignore-list.
765 if ( ignorelist
.indexOf( word
.toLower() ) != -1 )
768 //// Position in line ///
771 if ( buffer
.indexOf( ':' ) != -1 )
772 qs2
= buffer
.left( buffer
.indexOf(':') );
776 posinline
= qs2
.right( qs2
.length()-qs2
.lastIndexOf(' ') ).toInt()-1;
778 ///// Replace-list stuff ////
779 QStringList::Iterator it
= replacelist
.begin();
780 for( ;it
!= replacelist
.end(); ++it
, ++it
) // Skip two entries at a time.
782 if ( word
== *it
) // Word matches
785 word
= *it
; // Replace it with the next entry
790 /////// Suggestions //////
791 if ( buffer
[0] != '#' )
793 QString qs
= buffer
.mid( buffer
.indexOf(':')+2, buffer
.length() );
798 while( i
< qs
.length() )
800 QString temp
= qs
.mid( i
, (j
=qs
.indexOf(',',i
)) - i
);
801 sugg
.append( funnyWord(temp
) );
807 if ( (sugg
.count()==1) && (sugg
.first() == word
) )
813 if ( buffer
.isEmpty() ) {
814 kDebug(750) << "Got an empty response: ignoring";
818 kError(750) << "HERE?: [" << buffer
<< "]" << endl
;
819 kError(750) << "Please report this to zack@kde.org" << endl
;
820 kError(750) << "Thank you!" << endl
;
823 emit
done( K3Spell::origbuffer
);
827 bool K3Spell::checkList (QStringList
*_wordlist
, bool _usedialog
)
828 // prepare check of string list
831 if ((totalpos
=wordlist
->count())==0)
833 wlIt
= wordlist
->begin();
834 usedialog
=_usedialog
;
836 // prepare the dialog
839 //set the dialog signal handler
840 dialog3slot
= SLOT (checkList4 ());
842 proc
->write(QByteArray( '%' ) ); // turn off terse mode & check one word at a time
844 //lastpos now counts which *word number* we are at in checkListReplaceCurrent()
848 // when checked, KProcess calls checkList3a
854 void K3Spell::checkList2 ()
855 // send one word from the list to KProcess
856 // invoked first time by checkList, later by checkListReplaceCurrent and checkList4
859 if (wlIt
!= wordlist
->end())
861 kDebug(750) << "KS::cklist2 " << lastpos
<< ": " << *wlIt
;
863 d
->endOfResponse
= false;
866 put
= cleanFputsWord (*wlIt
);
869 // when cleanFPutsWord failed (e.g. on empty word)
870 // try next word; may be this is not good for other
871 // problems, because this will make read the list up to the end
879 NOOUTPUT(checkList3a
);
885 void K3Spell::checkList3a ()
886 // invoked by KProcess, when data from ispell are read
888 //kDebug(750) << "start of checkList3a";
890 // don't read more data, when dialog is waiting
891 // for user interaction
893 //kDebug(750) << "dlgon: don't read more data";
906 tempe
= proc
->readLine( data
.data(), data
.count() ); //get ispell's response
908 //kDebug(750) << "checkList3a: read bytes [" << tempe << "]";
909 line
= d
->convertQByteArray( data
);
912 d
->endOfResponse
= true;
913 //kDebug(750) << "checkList3a: end of resp";
914 } else if ( tempe
>0 ) {
915 if ( (e
=parseOneResponse( line
, word
, sugg
) ) == MISTAKE
||
922 QString old
= *(--wlIt
); ++wlIt
;
923 dlgreplacement
= word
;
924 checkListReplaceCurrent();
925 // inform application
926 emit
corrected( old
, *(--wlIt
), lastpos
); ++wlIt
;
933 dialog( word
, sugg
, SLOT(checkList4()) );
938 d
->m_bNoMisspellingsEncountered
= false;
939 emit
misspelling( word
, sugg
, lastpos
);
944 emitProgress (); //maybe
946 // stop when empty line or no more data
949 //kDebug(750) << "checkList3a: exit loop with [" << tempe << "]";
951 // if we got an empty line, t.e. end of ispell/aspell response
952 // and the dialog isn't waiting for user interaction, send next word
953 if (d
->endOfResponse
&& !dlgon
) {
954 //kDebug(750) << "checkList3a: send next word";
959 void K3Spell::checkListReplaceCurrent()
962 // go back to misspelled word
966 s
.replace(posinline
+offset
,orig
.length(),replacement());
967 offset
+= replacement().length()-orig
.length();
968 wordlist
->insert (wlIt
, s
);
969 wlIt
= wordlist
->erase (wlIt
);
970 // wlIt now points to the word after the repalced one
974 void K3Spell::checkList4 ()
975 // evaluate dialog return, when a button was pressed there
980 disconnect (this, SIGNAL (dialog3()), this, SLOT (checkList4()));
982 //others should have been processed by dialog() already
987 kDebug(750) << "KS: cklist4: lastpos: " << lastpos
;
991 checkListReplaceCurrent();
992 emit
corrected( old
, *(--wlIt
), lastpos
);
1006 //check( origbuffer.mid( lastpos ), true );
1008 //proc->disconnect();
1011 //proc = new KProcess( codec );
1016 // read more if there is more, otherwise send next word
1017 if (!d
->endOfResponse
) {
1018 //kDebug(750) << "checkList4: read more from response";
1023 bool K3Spell::check( const QString
&_buffer
, bool _usedialog
)
1027 usedialog
= _usedialog
;
1029 //set the dialog signal handler
1030 dialog3slot
= SLOT(check3());
1032 kDebug(750) << "KS: check";
1033 origbuffer
= _buffer
;
1034 if ( ( totalpos
= origbuffer
.length() ) == 0 )
1036 emit
done( origbuffer
);
1041 // Torben: I corrected the \n\n problem directly in the
1042 // origbuffer since I got errors otherwise
1043 if ( !origbuffer
.endsWith("\n\n" ) )
1045 if (origbuffer
.at(origbuffer
.length()-1)!='\n')
1048 origbuffer
+='\n'; //shouldn't these be removed at some point?
1054 newbuffer
= origbuffer
;
1056 // KProcess calls check2 when read from ispell
1058 proc
->write( QByteArray( "!" ) );
1060 //lastpos is a position in newbuffer (it has offset in it)
1061 offset
= lastlastline
= lastpos
= lastline
= 0;
1065 // send first buffer line
1066 int i
= origbuffer
.indexOf( '\n', 0 ) + 1;
1067 qs
= origbuffer
.mid( 0, i
);
1070 lastline
=i
; //the character position, not a line number
1082 int K3Spell::lastPosition() const
1088 void K3Spell::check2()
1089 // invoked by KProcess when read from ispell
1095 static bool recursive
= false;
1106 tempe
= proc
->readLine( data
.data(), data
.count() ); //get ispell's response
1107 line
= d
->convertQByteArray( data
);
1108 //kDebug(750) << "K3Spell::check2 (" << tempe << "b)";
1112 if ( ( e
=parseOneResponse (line
, word
, sugg
) )==MISTAKE
||
1117 // for multibyte encoding posinline needs correction
1118 if ((ksconfig
->encoding() == KS_E_UTF8
) && !d
->aspellV6
) {
1119 // kDebug(750) << "line: " << origbuffer.mid(lastlastline,
1120 // lastline-lastlastline) << endl;
1121 // kDebug(750) << "posinline uncorr: " << posinline;
1123 // convert line to UTF-8, cut at pos, convert back to UCS-2
1124 // and get string length
1125 posinline
= (QString::fromUtf8(
1126 origbuffer
.mid(lastlastline
,lastline
-lastlastline
).toUtf8(),
1127 posinline
)).length();
1128 // kDebug(750) << "posinline corr: " << posinline;
1131 lastpos
= posinline
+lastlastline
+offset
;
1133 //orig is set by parseOneResponse()
1137 dlgreplacement
=word
;
1138 emit
corrected( orig
, replacement(), lastpos
);
1139 offset
+= replacement().length()-orig
.length();
1140 newbuffer
.replace( lastpos
, orig
.length(), word
);
1145 //kDebug(750) << "(Before dialog) word=[" << word << "] cwword =[" << cwword << "]\n";
1147 // show the word in the dialog
1148 dialog( word
, sugg
, SLOT(check3()) );
1150 // No dialog, just emit misspelling and continue
1151 d
->m_bNoMisspellingsEncountered
= false;
1152 emit
misspelling( word
, sugg
, lastpos
);
1153 dlgresult
= KS_IGNORE
;
1163 emitProgress(); //maybe
1167 if ( tempe
== -1 ) { //we were called, but no data seems to be ready...
1168 // Make sure we don't get called directly again and make sure we do get
1169 // called when new data arrives.
1171 // proc->enableReadSignals(true);
1179 //If there is more to check, then send another line to ISpell.
1180 if ( lastline
< origbuffer
.length() )
1185 //kDebug(750) << "[EOL](" << tempe << ")[" << temp << "]";
1187 lastpos
= (lastlastline
=lastline
) + offset
; //do we really want this?
1188 i
= origbuffer
.indexOf('\n', lastline
) + 1;
1189 qs
= origbuffer
.mid( lastline
, i
-lastline
);
1196 //This is the end of it all
1199 // kDebug(750) << "check2() done";
1200 newbuffer
.truncate( newbuffer
.length()-2 );
1202 emit
done( newbuffer
);
1207 void K3Spell::check3 ()
1208 // evaluates the return value of the dialog
1210 disconnect (this, SIGNAL (dialog3()), this, SLOT (check3()));
1211 kDebug(750) << "check3 [" << cwword
<< "] [" << replacement() << "] " << dlgresult
;
1213 //others should have been processed by dialog() already
1218 offset
+=replacement().length()-cwword
.length();
1219 newbuffer
.replace (lastpos
, cwword
.length(),
1221 emit
corrected (dlgorigword
, replacement(), lastpos
);
1224 // kDebug(750) << "canceled\n";
1226 emit
done( origbuffer
);
1230 emit
done( origbuffer
);
1231 KMessageBox::information( 0, i18n("You have to restart the dialog for changes to take effect") );
1232 //check( origbuffer.mid( lastpos ), true );
1236 //buffer=newbuffer);
1238 emit
done (newbuffer
);
1246 K3Spell::slotStopCancel (int result
)
1248 if (dialogwillprocess
)
1251 kDebug(750) << "K3Spell::slotStopCancel [" << result
<< "]";
1253 if (result
==KS_STOP
|| result
==KS_CANCEL
)
1254 if (!dialog3slot
.isEmpty())
1257 connect (this, SIGNAL (dialog3()), this, dialog3slot
.toAscii().constData());
1263 void K3Spell::dialog( const QString
& word
, QStringList
& sugg
, const char *_slot
)
1267 dialog3slot
= _slot
;
1268 dialogwillprocess
= true;
1269 connect( ksdlg
, SIGNAL(command(int)), this, SLOT(dialog2(int)) );
1270 QString tmpBuf
= newbuffer
;
1271 kDebug(750)<<" position = "<<lastpos
;
1273 // extract a context string, replace all characters which might confuse
1274 // the RichText display and highlight the possibly wrong word
1275 QString
marker( "_MARKER_" );
1276 tmpBuf
.replace( lastpos
, word
.length(), marker
);
1277 QString context
= tmpBuf
.mid(qMax(lastpos
-18,0), 2*18+marker
.length());
1278 context
.replace( '\n',QLatin1String(" "));
1279 context
.replace( '<', QLatin1String("<") );
1280 context
.replace( '>', QLatin1String(">") );
1281 context
.replace( marker
, QString::fromLatin1("<b>%1</b>").arg( word
) );
1282 context
= "<qt>" + context
+ "</qt>";
1284 ksdlg
->init( word
, &sugg
, context
);
1285 d
->m_bNoMisspellingsEncountered
= false;
1286 emit
misspelling( word
, sugg
, lastpos
);
1292 QString
K3Spell::replacement () const
1294 return dlgreplacement
;
1297 void K3Spell::dialog2( int result
)
1301 disconnect( ksdlg
, SIGNAL(command(int)), this, SLOT(dialog2(int)) );
1302 dialogwillprocess
= false;
1306 dlgreplacement
= ksdlg
->replacement();
1308 //process result here
1309 switch ( dlgresult
)
1312 emit
ignoreword( dlgorigword
);
1315 // would be better to lower case only words with beginning cap
1316 ignorelist
.prepend( dlgorigword
.toLower() );
1317 emit
ignoreall( dlgorigword
);
1320 addPersonal( dlgorigword
);
1321 personaldict
= true;
1322 emit
addword( dlgorigword
);
1323 // adding to pesonal dict takes effect at the next line, not the current
1324 ignorelist
.prepend( dlgorigword
.toLower() );
1328 replacelist
.append( dlgorigword
);
1329 QString _replacement
= replacement();
1330 replacelist
.append( _replacement
);
1331 emit
replaceall( dlgorigword
, _replacement
);
1335 checkWord( ksdlg
->replacement(), false, true );
1340 connect( this, SIGNAL(dialog3()), this, dialog3slot
.toAscii().constData() );
1350 delete d
->checkNextTimer
;
1355 K3SpellConfig
K3Spell::ksConfig() const
1357 ksconfig
->setIgnoreList(ignorelist
);
1358 ksconfig
->setReplaceAllList(replacelist
);
1362 void K3Spell::cleanUp()
1364 if ( m_status
== Cleaning
)
1367 if ( m_status
== Running
)
1370 writePersonalDictionary();
1371 m_status
= Cleaning
;
1373 proc
->closeWriteChannel();
1376 void K3Spell::setAutoDelete(bool _autoDelete
)
1378 autoDelete
= _autoDelete
;
1381 void K3Spell::ispellExit()
1383 kDebug() << "K3Spell::ispellExit() " << m_status
;
1385 if ( (m_status
== Starting
) && (trystart
< maxtrystart
) )
1392 if ( m_status
== Starting
)
1394 else if (m_status
== Cleaning
)
1395 m_status
= d
->m_bNoMisspellingsEncountered
? FinishedNoMisspellingsEncountered
: Finished
;
1396 else if ( m_status
== Running
)
1398 else // Error, Finished, Crashed
1399 return; // Dead already
1401 kDebug(750) << "Death";
1402 QTimer::singleShot( 0, this, SLOT(emitDeath()) );
1405 // This is always called from the event loop to make
1406 // sure that the receiver can safely delete the
1408 void K3Spell::emitDeath()
1410 bool deleteMe
= autoDelete
; // Can't access object after next call!
1416 void K3Spell::setProgressResolution (unsigned int res
)
1421 void K3Spell::emitProgress ()
1423 uint nextprog
= (uint
) (100.*lastpos
/(double)totalpos
);
1425 if ( nextprog
>= curprog
)
1428 emit
progress( curprog
);
1432 void K3Spell::moveDlg( int x
, int y
)
1434 QPoint
pt( x
,y
), pt2
;
1435 pt2
= parent
->mapToGlobal( pt
);
1436 ksdlg
->move( pt2
.x(),pt2
.y() );
1439 void K3Spell::setIgnoreUpperWords(bool _ignore
)
1441 d
->m_bIgnoreUpperWords
=_ignore
;
1444 void K3Spell::setIgnoreTitleCase(bool _ignore
)
1446 d
->m_bIgnoreTitleCase
=_ignore
;
1448 // --------------------------------------------------
1449 // Stuff for modal (blocking) spell checking
1451 // Written by Torben Weis <weis@kde.org>. So please
1452 // send bug reports regarding the modal stuff to me.
1453 // --------------------------------------------------
1456 K3Spell::modalCheck( QString
& text
)
1458 return modalCheck( text
,0 );
1462 K3Spell::modalCheck( QString
& text
, K3SpellConfig
* _kcs
)
1467 K3Spell
* spell
= new K3Spell( 0L, i18n("Spell Checker"), 0 ,
1468 0, _kcs
, true, true );
1470 while (spell
->status()!=Finished
)
1471 qApp
->processEvents();
1479 void K3Spell::slotSpellCheckerCorrected( const QString
& oldText
, const QString
& newText
, unsigned int pos
)
1481 modaltext
=modaltext
.replace(pos
,oldText
.length(),newText
);
1485 void K3Spell::slotModalReady()
1487 //kDebug() << qApp->loopLevel();
1488 //kDebug(750) << "MODAL READY------------------";
1490 Q_ASSERT( m_status
== Running
);
1491 connect( this, SIGNAL( done( const QString
& ) ),
1492 this, SLOT( slotModalDone( const QString
& ) ) );
1493 QObject::connect( this, SIGNAL( corrected( const QString
&, const QString
&, unsigned int ) ),
1494 this, SLOT( slotSpellCheckerCorrected( const QString
&, const QString
&, unsigned int ) ) );
1495 QObject::connect( this, SIGNAL( death() ),
1496 this, SLOT( slotModalSpellCheckerFinished( ) ) );
1500 void K3Spell::slotModalDone( const QString
&/*_buffer*/ )
1502 //kDebug(750) << "MODAL DONE " << _buffer;
1503 //modaltext = _buffer;
1506 //kDebug() << "ABOUT TO EXIT LOOP";
1507 //qApp->exit_loop();
1509 //modalWidgetHack->close(true);
1510 slotModalSpellCheckerFinished();
1513 void K3Spell::slotModalSpellCheckerFinished( )
1515 modalreturn
=(int)this->status();
1518 void K3Spell::initialize( QWidget
*_parent
, const QString
&_caption
,
1519 QObject
*obj
, const char *slot
, K3SpellConfig
*_ksc
,
1520 bool _progressbar
, bool _modal
, SpellerType type
)
1522 d
= new K3SpellPrivate
;
1524 d
->m_bIgnoreUpperWords
=false;
1525 d
->m_bIgnoreTitleCase
=false;
1526 d
->m_bNoMisspellingsEncountered
= true;
1528 d
->checking
= false;
1529 d
->aspellV6
= false;
1530 d
->checkNextTimer
= new QTimer( this );
1531 connect( d
->checkNextTimer
, SIGNAL( timeout() ),
1532 this, SLOT( checkNext() ));
1535 progressbar
= _progressbar
;
1542 //won't be using the dialog in ksconfig, just the option values
1544 ksconfig
= new K3SpellConfig( *_ksc
);
1546 ksconfig
= new K3SpellConfig
;
1549 switch ( ksconfig
->encoding() )
1552 d
->m_codec
= QTextCodec::codecForName("ISO 8859-1");
1555 d
->m_codec
= QTextCodec::codecForName("ISO 8859-2");
1558 d
->m_codec
= QTextCodec::codecForName("ISO 8859-3");
1561 d
->m_codec
= QTextCodec::codecForName("ISO 8859-4");
1564 d
->m_codec
= QTextCodec::codecForName("ISO 8859-5");
1567 d
->m_codec
= QTextCodec::codecForName("ISO 8859-7");
1570 d
->m_codec
= QTextCodec::codecForName("ISO 8859-8-i");
1573 d
->m_codec
= QTextCodec::codecForName("ISO 8859-9");
1576 d
->m_codec
= QTextCodec::codecForName("ISO 8859-13");
1579 d
->m_codec
= QTextCodec::codecForName("ISO 8859-15");
1582 d
->m_codec
= QTextCodec::codecForName("UTF-8");
1585 d
->m_codec
= QTextCodec::codecForName("KOI8-R");
1588 d
->m_codec
= QTextCodec::codecForName("KOI8-U");
1591 d
->m_codec
= QTextCodec::codecForName("CP1251");
1594 d
->m_codec
= QTextCodec::codecForName("CP1255");
1600 kDebug(750) << __FILE__
<< ":" << __LINE__
<< " Codec = " << (d
->m_codec
? d
->m_codec
->name() : "<default>");
1602 // copy ignore list from ksconfig
1603 ignorelist
+= ksconfig
->ignoreList();
1605 replacelist
+= ksconfig
->replaceAllList();
1606 texmode
=dlgon
=false;
1607 m_status
= Starting
;
1608 dialogsetup
= false;
1612 dialogwillprocess
= false;
1613 dialog3slot
.clear();
1615 personaldict
= false;
1626 // caller wants to know when k3spell is ready
1627 connect( this, SIGNAL(ready(K3Spell
*)), obj
, slot
);
1629 // Hack for modal spell checking
1630 connect( this, SIGNAL(ready(K3Spell
*)), this, SLOT(slotModalReady()) );
1632 proc
= new KProcess();
1637 QString
K3Spell::modaltext
;
1638 int K3Spell::modalreturn
= 0;
1639 QWidget
* K3Spell::modalWidgetHack
= 0;
1641 #include "k3spell.moc"