don't let spaceAdd ever become negative and break the layout.
[kdelibs.git] / kde3support / kdeui / k3spell.cpp
bloba5de16dfc112882d23905c6cbab7823248355213
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.
21 #include "k3spell.h"
23 #include <config.h>
25 #include <stdio.h>
26 #include <sys/time.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29 #include <ctype.h>
30 #include <stdlib.h> // atoi
32 #ifdef HAVE_STRINGS_H
33 #include <strings.h>
34 #endif
37 #include <QtGui/QApplication>
38 #include <QtCore/QTextCodec>
39 #include <QtCore/QTimer>
41 #include <kmessagebox.h>
42 #include <kdebug.h>
43 #include <klocale.h>
44 #include "k3sconfig.h"
45 #include "k3spelldlg.h"
46 #include <kprocess.h>
47 #include <QTextStream>
49 #define MAXLINELENGTH 10000
50 #undef IGNORE //fix possible conflict
52 enum {
53 GOOD= 0,
54 IGNORE= 1,
55 REPLACE= 2,
56 MISTAKE= 3
59 enum checkMethod { Method1 = 0, Method2 };
61 struct BufferedWord
63 checkMethod method;
64 QString word;
65 bool useDialog;
66 bool suggest;
69 class K3Spell::K3SpellPrivate
71 public:
72 bool endOfResponse;
73 bool m_bIgnoreUpperWords;
74 bool m_bIgnoreTitleCase;
75 bool m_bNoMisspellingsEncountered;
76 SpellerType type;
77 K3Spell* suggestSpell;
78 bool checking;
79 QList<BufferedWord> unchecked;
80 QTimer *checkNextTimer;
81 bool aspellV6;
82 QTextCodec* m_codec;
83 QString convertQByteArray( const QByteArray& b )
85 QTextCodec* originalCodec = QTextCodec::codecForCStrings();
86 QTextCodec::setCodecForCStrings( m_codec );
87 QString s( b );
88 QTextCodec::setCodecForCStrings( originalCodec );
89 return s;
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 );
97 return b;
101 //TODO
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
142 return m_status;
145 void K3Spell::hide() { ksdlg->hide(); }
147 QStringList K3Spell::suggestions() const
149 return sugg;
152 int K3Spell::dlgResult () const
154 return dlgresult;
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()
168 QString result;
169 FILE *fs = popen("aspell -v", "r");
170 if (fs)
172 // Close textstream before we close fs
174 QTextStream ts(fs, QIODevice::ReadOnly);
175 result = ts.readAll().trimmed();
177 pclose(fs);
180 QRegExp rx("Aspell (\\d.\\d)");
181 if (rx.indexIn(result) != -1)
183 float version = rx.cap(1).toFloat();
184 return (version >= 0.6);
186 return false;
190 void
191 K3Spell::startIspell()
192 //trystart = {0,1,2}
194 if ((trystart == 0) && (ksconfig->client() == KS_CLIENT_ASPELL))
195 d->aspellV6 = determineASpellV6();
197 kDebug(750) << "Try #" << trystart;
199 if ( trystart > 0 ) {
200 proc->reset();
203 switch ( ksconfig->client() )
205 case KS_CLIENT_ISPELL:
206 *proc << "ispell";
207 kDebug(750) << "Using ispell";
208 break;
209 case KS_CLIENT_ASPELL:
210 *proc << "aspell";
211 kDebug(750) << "Using aspell";
212 break;
213 case KS_CLIENT_HSPELL:
214 *proc << "hspell";
215 kDebug(750) << "Using hspell";
216 break;
217 case KS_CLIENT_ZEMBEREK:
218 *proc << "zpspell";
219 kDebug(750) << "Using zemberek(zpspell)";
220 break;
223 if ( ksconfig->client() == KS_CLIENT_ISPELL || ksconfig->client() == KS_CLIENT_ASPELL )
225 *proc << "-a" << "-S";
227 switch ( d->type )
229 case HTML:
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.
234 *proc << "-H";
235 break;
236 case TeX:
237 //same for aspell and ispell
238 *proc << "-t";
239 break;
240 case Nroff:
241 //only ispell supports
242 if ( ksconfig->client() == KS_CLIENT_ISPELL )
243 *proc << "-n";
244 break;
245 case Text:
246 default:
247 //nothing
248 break;
250 if (ksconfig->noRootAffix())
252 *proc<<"-m";
254 if (ksconfig->runTogether())
256 *proc << "-B";
258 else
260 *proc << "-C";
264 if (trystart<2)
266 if (! ksconfig->dictionary().isEmpty())
268 kDebug(750) << "using dictionary [" << ksconfig->dictionary() << "]";
269 *proc << "-d";
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.
279 if ( trystart<1 ) {
280 switch ( ksconfig->encoding() )
282 case KS_E_LATIN1:
283 *proc << "-Tlatin1";
284 break;
285 case KS_E_LATIN2:
286 *proc << "-Tlatin2";
287 break;
288 case KS_E_LATIN3:
289 *proc << "-Tlatin3";
290 break;
292 // add the other charsets here
293 case KS_E_LATIN4:
294 case KS_E_LATIN5:
295 case KS_E_LATIN7:
296 case KS_E_LATIN8:
297 case KS_E_LATIN9:
298 case KS_E_LATIN13:
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;
301 break;
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.
310 *proc << "-Tlatin1";
312 else
313 kError(750) << "ISO-8859-15 not supported for aspell yet." << endl;
314 break;
315 case KS_E_UTF8:
316 *proc << "-Tutf8";
317 if (ksconfig->client() == KS_CLIENT_ASPELL)
318 *proc << "--encoding=utf-8";
319 break;
320 case KS_E_KOI8U:
321 *proc << "-w'"; // add ' as a word char
322 break;
323 default:
324 break;
328 // -a : pipe mode
329 // -S : sort suggestions by probable correctness
331 else // hspell and Zemberek(zpspell) doesn't need all the rest of the options
332 *proc << "-a";
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 );
345 OUTPUT(K3Spell2);
348 proc->start();
349 if ( !proc->waitForStarted() )
351 m_status = Error;
352 QTimer::singleShot( 0, this, SLOT(emitDeath()));
356 void
357 K3Spell::ispellErrors( )
359 // buffer[buflen-1] = '\0';
360 // kDebug(750) << "ispellErrors [" << buffer << "]\n";
363 void K3Spell::K3Spell2( )
366 QString line;
368 kDebug(750) << "K3Spell::K3Spell2";
370 trystart = maxtrystart; //We've officially started ispell and don't want
371 //to try again if it dies.
373 QByteArray data;
374 qint64 read = proc->readLine(data.data(),data.count());
375 if ( read == -1 )
377 QTimer::singleShot( 0, this, SLOT(emitDeath()) );
378 return;
380 line = d->convertQByteArray( data );
382 if ( !line.startsWith('@') ) //@ indicates that ispell is working fine
384 QTimer::singleShot( 0, this, SLOT(emitDeath()) );
385 return;
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()) );
393 return;
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()) );
401 return;
404 NOOUTPUT( K3Spell2 );
406 m_status = Running;
407 emit ready( this );
410 void
411 K3Spell::setUpDialog( bool reallyuseprogressbar )
413 if ( dialogsetup )
414 return;
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)) );
425 if ( modaldlg )
426 ksdlg->setFocus();
427 dialogsetup = true;
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_
436 return false;
438 qs.prepend( "*" );
439 personaldict = true;
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_
455 return false;
457 qs.prepend( "@" );
459 return proc->write( d->convertQString( qs ) );
462 bool
463 K3Spell::cleanFputsWord( const QString & s )
465 QString qs(s);
466 bool empty = true;
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() )
474 qs.remove(i,1);
475 i--;
476 } else {
477 if ( qs[i].isLetter() )
478 empty=false;
482 // don't check empty words, otherwise synchronization will lost
483 if (empty)
484 return false;
486 return proc->write( d->convertQString( QString('^'+qs+'\n') ) );
489 bool
490 K3Spell::cleanFputs( const QString & s )
492 QString qs(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 )
498 if( qs[i] == '$' )
499 qs[i] = ' ';
502 if ( l<MAXLINELENGTH )
504 if ( qs.isEmpty() )
505 qs="";
506 return proc->write( d->convertQString('^'+qs+'\n') );
508 else
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 );
520 return true;
522 d->checking = true;
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();
529 return false;
531 ///set the dialog signal handler
532 dialog3slot = SLOT(checkWord3());
534 usedialog = _usedialog;
535 setUpDialog( false );
536 if ( _usedialog )
538 emitProgress();
540 else
541 ksdlg->hide();
543 QByteArray data;
544 while (proc->readLine( data.data(), data.count() ) != -1 )
545 ; // eat spurious blanks
547 OUTPUT(checkWord2);
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
553 return true;
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 );
565 return true;
567 d->checking = true;
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();
574 return false;
577 ///set the dialog signal handler
578 if ( !suggest ) {
579 dialog3slot = SLOT(checkWord3());
580 usedialog = _usedialog;
581 setUpDialog( false );
582 if ( _usedialog )
584 emitProgress();
586 else
587 ksdlg->hide();
590 QByteArray data;
591 while (proc->readLine( data.data(), data.count() ) != -1 ); // eat spurious blanks
593 OUTPUT(checkWord2);
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
599 return true;
602 void K3Spell::checkWord2( )
604 QString word;
605 QString line;
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
610 the input line."
611 <sanders>
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
614 can get out of sync.
615 </sanders>
617 QByteArray data;
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 )
624 cwword = word;
625 dialog( word, sugg, SLOT(checkWord3()) );
626 d->checkNextTimer->setInterval(0);
627 d->checkNextTimer->setSingleShot(true);
628 d->checkNextTimer->start();
629 return;
631 else if( mistake )
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
647 d->checking = false;
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 );
654 else
655 checkWord( buf.word, buf.useDialog, buf.suggest );
659 void K3Spell::suggestWord()
661 QString word;
662 QString line;
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
667 the input line." */
668 QByteArray data;
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 )
676 cwword=word;
677 dialog( word, sugg, SLOT(checkWord3()) );
678 return;
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"
693 QString qs;
694 for( int i=0; i<word.size(); i++ )
696 if (word [i]=='+')
697 continue;
698 if (word [i]=='-')
700 QString shorty;
701 int j, k;
703 for( j = i+1; j < word.size() && word[j] != '+' && word[j] != '-'; j++ )
704 shorty += word[j];
706 i = j-1;
708 if ( !( k = qs.lastIndexOf(shorty) ) || k != -1 )
709 qs.remove( k, shorty.length() );
710 else
712 qs += '-';
713 qs += shorty; //it was a hyphen, not a '-' from ispell
716 else
717 qs += word[i];
720 return qs;
724 int K3Spell::parseOneResponse( const QString &buffer, QString &word, QStringList & sugg )
725 // buffer is checked, word and sugg are filled in
726 // returns
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
732 word = "";
733 posinline=0;
735 sugg.clear();
737 if ( buffer[0] == '*' || buffer[0] == '+' || buffer[0] == '-' )
739 return GOOD;
742 if ( buffer[0] == '&' || buffer[0] == '?' || buffer[0] == '#' )
744 int i,j;
747 word = buffer.mid( 2, buffer.indexOf( ' ', 3 ) -2 );
748 //check() needs this
749 orig=word;
751 if( d->m_bIgnoreTitleCase && word == word.toUpper() )
752 return IGNORE;
754 if( d->m_bIgnoreUpperWords && word[0] == word[0].toUpper() )
756 QString text = word[0] + word.right( word.length()-1 ).toLower();
757 if( text == word )
758 return IGNORE;
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 )
766 return IGNORE;
768 //// Position in line ///
769 QString qs2;
771 if ( buffer.indexOf( ':' ) != -1 )
772 qs2 = buffer.left( buffer.indexOf(':') );
773 else
774 qs2 = buffer;
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
784 ++it;
785 word = *it; // Replace it with the next entry
786 return REPLACE;
790 /////// Suggestions //////
791 if ( buffer[0] != '#' )
793 QString qs = buffer.mid( buffer.indexOf(':')+2, buffer.length() );
794 qs += ',';
795 sugg.clear();
796 i = j = 0;
798 while( i < qs.length() )
800 QString temp = qs.mid( i, (j=qs.indexOf(',',i)) - i );
801 sugg.append( funnyWord(temp) );
803 i=j+2;
807 if ( (sugg.count()==1) && (sugg.first() == word) )
808 return GOOD;
810 return MISTAKE;
813 if ( buffer.isEmpty() ) {
814 kDebug(750) << "Got an empty response: ignoring";
815 return GOOD;
818 kError(750) << "HERE?: [" << buffer << "]" << endl;
819 kError(750) << "Please report this to zack@kde.org" << endl;
820 kError(750) << "Thank you!" << endl;
822 emit done( false );
823 emit done( K3Spell::origbuffer );
824 return MISTAKE;
827 bool K3Spell::checkList (QStringList *_wordlist, bool _usedialog)
828 // prepare check of string list
830 wordlist=_wordlist;
831 if ((totalpos=wordlist->count())==0)
832 return false;
833 wlIt = wordlist->begin();
834 usedialog=_usedialog;
836 // prepare the dialog
837 setUpDialog();
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()
845 lastpos = -1;
846 checkList2();
848 // when checked, KProcess calls checkList3a
849 OUTPUT(checkList3a);
851 return true;
854 void K3Spell::checkList2 ()
855 // send one word from the list to KProcess
856 // invoked first time by checkList, later by checkListReplaceCurrent and checkList4
858 // send next word
859 if (wlIt != wordlist->end())
861 kDebug(750) << "KS::cklist2 " << lastpos << ": " << *wlIt;
863 d->endOfResponse = false;
864 bool put;
865 lastpos++; offset=0;
866 put = cleanFputsWord (*wlIt);
867 ++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
872 if (!put) {
873 checkList2();
876 else
877 // end of word list
879 NOOUTPUT(checkList3a);
880 ksdlg->hide();
881 emit done(true);
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
892 if ( dlgon ) {
893 //kDebug(750) << "dlgon: don't read more data";
894 return;
897 int e;
898 qint64 tempe;
900 QString word;
901 QString line;
905 QByteArray 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 );
911 if ( tempe == 0 ) {
912 d->endOfResponse = true;
913 //kDebug(750) << "checkList3a: end of resp";
914 } else if ( tempe>0 ) {
915 if ( (e=parseOneResponse( line, word, sugg ) ) == MISTAKE ||
916 e==REPLACE )
918 dlgresult=-1;
920 if ( e == REPLACE )
922 QString old = *(--wlIt); ++wlIt;
923 dlgreplacement = word;
924 checkListReplaceCurrent();
925 // inform application
926 emit corrected( old, *(--wlIt), lastpos ); ++wlIt;
928 else if( usedialog )
930 cwword = word;
931 dlgon = true;
932 // show the dialog
933 dialog( word, sugg, SLOT(checkList4()) );
934 return;
936 else
938 d->m_bNoMisspellingsEncountered = false;
939 emit misspelling( word, sugg, lastpos );
944 emitProgress (); //maybe
946 // stop when empty line or no more data
947 } while (tempe > 0);
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";
955 checkList2();
959 void K3Spell::checkListReplaceCurrent()
962 // go back to misspelled word
963 wlIt--;
965 QString s = *wlIt;
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
977 dlgon=false;
978 QString old;
980 disconnect (this, SIGNAL (dialog3()), this, SLOT (checkList4()));
982 //others should have been processed by dialog() already
983 switch (dlgresult)
985 case KS_REPLACE:
986 case KS_REPLACEALL:
987 kDebug(750) << "KS: cklist4: lastpos: " << lastpos;
988 old = *(--wlIt);
989 ++wlIt;
990 // replace word
991 checkListReplaceCurrent();
992 emit corrected( old, *(--wlIt), lastpos );
993 ++wlIt;
994 break;
995 case KS_CANCEL:
996 ksdlg->hide();
997 emit done( false );
998 return;
999 case KS_STOP:
1000 ksdlg->hide();
1001 emit done( true );
1002 return;
1003 case KS_CONFIG:
1004 ksdlg->hide();
1005 emit done( false );
1006 //check( origbuffer.mid( lastpos ), true );
1007 //trystart = 0;
1008 //proc->disconnect();
1009 //proc->kill();
1010 //delete proc;
1011 //proc = new KProcess( codec );
1012 //startIspell();
1013 return;
1016 // read more if there is more, otherwise send next word
1017 if (!d->endOfResponse) {
1018 //kDebug(750) << "checkList4: read more from response";
1019 checkList3a();
1023 bool K3Spell::check( const QString &_buffer, bool _usedialog )
1025 QString qs;
1027 usedialog = _usedialog;
1028 setUpDialog();
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 );
1037 return false;
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')
1047 origbuffer+='\n';
1048 origbuffer+='\n'; //shouldn't these be removed at some point?
1050 else
1051 origbuffer+='\n';
1054 newbuffer = origbuffer;
1056 // KProcess calls check2 when read from ispell
1057 OUTPUT( check2 );
1058 proc->write( QByteArray( "!" ) );
1060 //lastpos is a position in newbuffer (it has offset in it)
1061 offset = lastlastline = lastpos = lastline = 0;
1063 emitProgress();
1065 // send first buffer line
1066 int i = origbuffer.indexOf( '\n', 0 ) + 1;
1067 qs = origbuffer.mid( 0, i );
1068 cleanFputs( qs );
1070 lastline=i; //the character position, not a line number
1072 if ( usedialog )
1074 emitProgress();
1076 else
1077 ksdlg->hide();
1079 return true;
1082 int K3Spell::lastPosition() const
1084 return lastpos;
1088 void K3Spell::check2()
1089 // invoked by KProcess when read from ispell
1091 int e;
1092 qint64 tempe;
1093 QString word;
1094 QString line;
1095 static bool recursive = false;
1096 if (recursive &&
1097 !ksdlg )
1099 return;
1101 recursive = true;
1105 QByteArray data;
1106 tempe = proc->readLine( data.data(), data.count() ); //get ispell's response
1107 line = d->convertQByteArray( data );
1108 //kDebug(750) << "K3Spell::check2 (" << tempe << "b)";
1110 if ( tempe>0 )
1112 if ( ( e=parseOneResponse (line, word, sugg) )==MISTAKE ||
1113 e==REPLACE)
1115 dlgresult=-1;
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()
1135 if (e==REPLACE)
1137 dlgreplacement=word;
1138 emit corrected( orig, replacement(), lastpos );
1139 offset += replacement().length()-orig.length();
1140 newbuffer.replace( lastpos, orig.length(), word );
1142 else //MISTAKE
1144 cwword = word;
1145 //kDebug(750) << "(Before dialog) word=[" << word << "] cwword =[" << cwword << "]\n";
1146 if ( usedialog ) {
1147 // show the word in the dialog
1148 dialog( word, sugg, SLOT(check3()) );
1149 } else {
1150 // No dialog, just emit misspelling and continue
1151 d->m_bNoMisspellingsEncountered = false;
1152 emit misspelling( word, sugg, lastpos );
1153 dlgresult = KS_IGNORE;
1154 check3();
1156 recursive = false;
1157 return;
1163 emitProgress(); //maybe
1165 } while( tempe>0 );
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.
1170 NOOUTPUT( check2 );
1171 // proc->enableReadSignals(true);
1172 OUTPUT( check2 );
1173 recursive = false;
1174 return;
1177 // proc->ackRead();
1179 //If there is more to check, then send another line to ISpell.
1180 if ( lastline < origbuffer.length() )
1182 int i;
1183 QString qs;
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 );
1190 cleanFputs( qs );
1191 lastline = i;
1192 recursive = false;
1193 return;
1195 else
1196 //This is the end of it all
1198 ksdlg->hide();
1199 // kDebug(750) << "check2() done";
1200 newbuffer.truncate( newbuffer.length()-2 );
1201 emitProgress();
1202 emit done( newbuffer );
1204 recursive = false;
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
1214 switch (dlgresult)
1216 case KS_REPLACE:
1217 case KS_REPLACEALL:
1218 offset+=replacement().length()-cwword.length();
1219 newbuffer.replace (lastpos, cwword.length(),
1220 replacement());
1221 emit corrected (dlgorigword, replacement(), lastpos);
1222 break;
1223 case KS_CANCEL:
1224 // kDebug(750) << "canceled\n";
1225 ksdlg->hide();
1226 emit done( origbuffer );
1227 return;
1228 case KS_CONFIG:
1229 ksdlg->hide();
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 );
1233 return;
1234 case KS_STOP:
1235 ksdlg->hide();
1236 //buffer=newbuffer);
1237 emitProgress();
1238 emit done (newbuffer);
1239 return;
1242 // proc->ackRead();
1245 void
1246 K3Spell::slotStopCancel (int result)
1248 if (dialogwillprocess)
1249 return;
1251 kDebug(750) << "K3Spell::slotStopCancel [" << result << "]";
1253 if (result==KS_STOP || result==KS_CANCEL)
1254 if (!dialog3slot.isEmpty())
1256 dlgresult=result;
1257 connect (this, SIGNAL (dialog3()), this, dialog3slot.toAscii().constData());
1258 emit dialog3();
1263 void K3Spell::dialog( const QString & word, QStringList & sugg, const char *_slot )
1265 dlgorigword = word;
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("&lt;") );
1280 context.replace( '>', QLatin1String("&gt;") );
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 );
1288 emitProgress();
1289 ksdlg->show();
1292 QString K3Spell::replacement () const
1294 return dlgreplacement;
1297 void K3Spell::dialog2( int result )
1299 QString qs;
1301 disconnect( ksdlg, SIGNAL(command(int)), this, SLOT(dialog2(int)) );
1302 dialogwillprocess = false;
1303 dlgresult = result;
1304 ksdlg->standby();
1306 dlgreplacement = ksdlg->replacement();
1308 //process result here
1309 switch ( dlgresult )
1311 case KS_IGNORE:
1312 emit ignoreword( dlgorigword );
1313 break;
1314 case KS_IGNOREALL:
1315 // would be better to lower case only words with beginning cap
1316 ignorelist.prepend( dlgorigword.toLower() );
1317 emit ignoreall( dlgorigword );
1318 break;
1319 case KS_ADD:
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() );
1325 break;
1326 case KS_REPLACEALL:
1328 replacelist.append( dlgorigword );
1329 QString _replacement = replacement();
1330 replacelist.append( _replacement );
1331 emit replaceall( dlgorigword , _replacement );
1333 break;
1334 case KS_SUGGEST:
1335 checkWord( ksdlg->replacement(), false, true );
1336 return;
1337 break;
1340 connect( this, SIGNAL(dialog3()), this, dialog3slot.toAscii().constData() );
1341 emit dialog3();
1345 K3Spell::~K3Spell()
1347 delete proc;
1348 delete ksconfig;
1349 delete ksdlg;
1350 delete d->checkNextTimer;
1351 delete d;
1355 K3SpellConfig K3Spell::ksConfig() const
1357 ksconfig->setIgnoreList(ignorelist);
1358 ksconfig->setReplaceAllList(replacelist);
1359 return *ksconfig;
1362 void K3Spell::cleanUp()
1364 if ( m_status == Cleaning )
1365 return; // Ignore
1367 if ( m_status == Running )
1369 if ( personaldict )
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) )
1387 trystart++;
1388 startIspell();
1389 return;
1392 if ( m_status == Starting )
1393 m_status = Error;
1394 else if (m_status == Cleaning)
1395 m_status = d->m_bNoMisspellingsEncountered ? FinishedNoMisspellingsEncountered : Finished;
1396 else if ( m_status == Running )
1397 m_status = Crashed;
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
1407 // K3Spell object.
1408 void K3Spell::emitDeath()
1410 bool deleteMe = autoDelete; // Can't access object after next call!
1411 emit death();
1412 if ( deleteMe )
1413 deleteLater();
1416 void K3Spell::setProgressResolution (unsigned int res)
1418 progres=res;
1421 void K3Spell::emitProgress ()
1423 uint nextprog = (uint) (100.*lastpos/(double)totalpos);
1425 if ( nextprog >= curprog )
1427 curprog = nextprog;
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 )
1464 modalreturn = 0;
1465 modaltext = text;
1467 K3Spell* spell = new K3Spell( 0L, i18n("Spell Checker"), 0 ,
1468 0, _kcs, true, true );
1470 while (spell->status()!=Finished)
1471 qApp->processEvents();
1473 text = modaltext;
1475 delete spell;
1476 return modalreturn;
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( ) ) );
1497 check( modaltext );
1500 void K3Spell::slotModalDone( const QString &/*_buffer*/ )
1502 //kDebug(750) << "MODAL DONE " << _buffer;
1503 //modaltext = _buffer;
1504 cleanUp();
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;
1527 d->type = type;
1528 d->checking = false;
1529 d->aspellV6 = false;
1530 d->checkNextTimer = new QTimer( this );
1531 connect( d->checkNextTimer, SIGNAL( timeout() ),
1532 this, SLOT( checkNext() ));
1533 autoDelete = false;
1534 modaldlg = _modal;
1535 progressbar = _progressbar;
1537 proc = 0;
1538 ksconfig = 0;
1539 ksdlg = 0;
1540 lastpos = 0;
1542 //won't be using the dialog in ksconfig, just the option values
1543 if ( _ksc )
1544 ksconfig = new K3SpellConfig( *_ksc );
1545 else
1546 ksconfig = new K3SpellConfig;
1548 d->m_codec = 0;
1549 switch ( ksconfig->encoding() )
1551 case KS_E_LATIN1:
1552 d->m_codec = QTextCodec::codecForName("ISO 8859-1");
1553 break;
1554 case KS_E_LATIN2:
1555 d->m_codec = QTextCodec::codecForName("ISO 8859-2");
1556 break;
1557 case KS_E_LATIN3:
1558 d->m_codec = QTextCodec::codecForName("ISO 8859-3");
1559 break;
1560 case KS_E_LATIN4:
1561 d->m_codec = QTextCodec::codecForName("ISO 8859-4");
1562 break;
1563 case KS_E_LATIN5:
1564 d->m_codec = QTextCodec::codecForName("ISO 8859-5");
1565 break;
1566 case KS_E_LATIN7:
1567 d->m_codec = QTextCodec::codecForName("ISO 8859-7");
1568 break;
1569 case KS_E_LATIN8:
1570 d->m_codec = QTextCodec::codecForName("ISO 8859-8-i");
1571 break;
1572 case KS_E_LATIN9:
1573 d->m_codec = QTextCodec::codecForName("ISO 8859-9");
1574 break;
1575 case KS_E_LATIN13:
1576 d->m_codec = QTextCodec::codecForName("ISO 8859-13");
1577 break;
1578 case KS_E_LATIN15:
1579 d->m_codec = QTextCodec::codecForName("ISO 8859-15");
1580 break;
1581 case KS_E_UTF8:
1582 d->m_codec = QTextCodec::codecForName("UTF-8");
1583 break;
1584 case KS_E_KOI8R:
1585 d->m_codec = QTextCodec::codecForName("KOI8-R");
1586 break;
1587 case KS_E_KOI8U:
1588 d->m_codec = QTextCodec::codecForName("KOI8-U");
1589 break;
1590 case KS_E_CP1251:
1591 d->m_codec = QTextCodec::codecForName("CP1251");
1592 break;
1593 case KS_E_CP1255:
1594 d->m_codec = QTextCodec::codecForName("CP1255");
1595 break;
1596 default:
1597 break;
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;
1609 progres=10;
1610 curprog=0;
1612 dialogwillprocess = false;
1613 dialog3slot.clear();
1615 personaldict = false;
1616 dlgresult = -1;
1618 caption = _caption;
1620 parent = _parent;
1622 trystart = 0;
1623 maxtrystart = 2;
1625 if ( obj && slot )
1626 // caller wants to know when k3spell is ready
1627 connect( this, SIGNAL(ready(K3Spell *)), obj, slot);
1628 else
1629 // Hack for modal spell checking
1630 connect( this, SIGNAL(ready(K3Spell *)), this, SLOT(slotModalReady()) );
1632 proc = new KProcess();
1634 startIspell();
1637 QString K3Spell::modaltext;
1638 int K3Spell::modalreturn = 0;
1639 QWidget* K3Spell::modalWidgetHack = 0;
1641 #include "k3spell.moc"