00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #ifdef HAVE_CONFIG_H
00022 #include <config.h>
00023 #endif
00024
00025 #include <stdio.h>
00026 #include <sys/time.h>
00027 #include <sys/types.h>
00028 #include <unistd.h>
00029 #include <ctype.h>
00030 #include <stdlib.h>
00031
00032 #ifdef HAVE_STRINGS_H
00033 #include <strings.h>
00034 #endif
00035
00036 #include <qtextcodec.h>
00037 #include <qtimer.h>
00038 #include <kapplication.h>
00039 #include <kmessagebox.h>
00040 #include <kdebug.h>
00041 #include <klocale.h>
00042 #include "kspell.h"
00043 #include "kspelldlg.h"
00044 #include <kwin.h>
00045 #include <kprocio.h>
00046
00047 #define MAXLINELENGTH 10000
00048
00049 enum {
00050 GOOD= 0,
00051 IGNORE= 1,
00052 REPLACE= 2,
00053 MISTAKE= 3
00054 };
00055
00056 enum checkMethod { Method1 = 0, Method2 };
00057
00058 struct BufferedWord
00059 {
00060 checkMethod method;
00061 QString word;
00062 bool useDialog;
00063 bool suggest;
00064 };
00065
00066 class KSpell::KSpellPrivate
00067 {
00068 public:
00069 bool endOfResponse;
00070 bool m_bIgnoreUpperWords;
00071 bool m_bIgnoreTitleCase;
00072 bool m_bNoMisspellingsEncountered;
00073 SpellerType type;
00074 KSpell* suggestSpell;
00075 bool checking;
00076 QValueList<BufferedWord> unchecked;
00077 QTimer *checkNextTimer;
00078 };
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096 #define OUTPUT(x) (connect (proc, SIGNAL (readReady(KProcIO *)), this, SLOT (x(KProcIO *))))
00097
00098
00099 #define NOOUTPUT(x) (disconnect (proc, SIGNAL (readReady(KProcIO *)), this, SLOT (x(KProcIO *))))
00100
00101
00102
00103 KSpell::KSpell( QWidget *_parent, const QString &_caption,
00104 QObject *obj, const char *slot, KSpellConfig *_ksc,
00105 bool _progressbar, bool _modal )
00106 {
00107 initialize( _parent, _caption, obj, slot, _ksc,
00108 _progressbar, _modal, Text );
00109 }
00110
00111 KSpell::KSpell( QWidget *_parent, const QString &_caption,
00112 QObject *obj, const char *slot, KSpellConfig *_ksc,
00113 bool _progressbar, bool _modal, SpellerType type )
00114 {
00115 initialize( _parent, _caption, obj, slot, _ksc,
00116 _progressbar, _modal, type );
00117 }
00118
00119 void KSpell::hide() { ksdlg->hide(); }
00120
00121 int KSpell::heightDlg() const { return ksdlg->height(); }
00122 int KSpell::widthDlg() const { return ksdlg->width(); }
00123
00124
00125 void
00126 KSpell::startIspell()
00127
00128 {
00129
00130 kdDebug(750) << "Try #" << trystart << endl;
00131
00132 if ( trystart > 0 ) {
00133 proc->resetAll();
00134 }
00135
00136 switch ( ksconfig->client() )
00137 {
00138 case KS_CLIENT_ISPELL:
00139 *proc << "ispell";
00140 kdDebug(750) << "Using ispell" << endl;
00141 break;
00142 case KS_CLIENT_ASPELL:
00143 *proc << "aspell";
00144 kdDebug(750) << "Using aspell" << endl;
00145 break;
00146 case KS_CLIENT_HSPELL:
00147 *proc << "hspell";
00148 kdDebug(750) << "Using hspell" << endl;
00149 break;
00150 }
00151
00152 if ( ksconfig->client() == KS_CLIENT_ISPELL || ksconfig->client() == KS_CLIENT_ASPELL )
00153 {
00154 *proc << "-a" << "-S";
00155
00156 switch ( d->type )
00157 {
00158 case HTML:
00159
00160
00161
00162
00163 *proc << "-H";
00164 break;
00165 case TeX:
00166
00167 *proc << "-t";
00168 break;
00169 case Nroff:
00170
00171 if ( ksconfig->client() == KS_CLIENT_ISPELL )
00172 *proc << "-n";
00173 break;
00174 case Text:
00175 default:
00176
00177 break;
00178 }
00179 if (ksconfig->noRootAffix())
00180 {
00181 *proc<<"-m";
00182 }
00183 if (ksconfig->runTogether())
00184 {
00185 *proc << "-B";
00186 }
00187 else
00188 {
00189 *proc << "-C";
00190 }
00191
00192
00193 if (trystart<2)
00194 {
00195 if (! ksconfig->dictionary().isEmpty())
00196 {
00197 kdDebug(750) << "using dictionary [" << ksconfig->dictionary() << "]" << endl;
00198 *proc << "-d";
00199 *proc << ksconfig->dictionary();
00200 }
00201 }
00202
00203
00204
00205
00206
00207
00208 if ( trystart<1 ) {
00209 switch ( ksconfig->encoding() )
00210 {
00211 case KS_E_LATIN1:
00212 *proc << "-Tlatin1";
00213 break;
00214 case KS_E_LATIN2:
00215 *proc << "-Tlatin2";
00216 break;
00217 case KS_E_LATIN3:
00218 *proc << "-Tlatin3";
00219 break;
00220
00221
00222 case KS_E_LATIN4:
00223 case KS_E_LATIN5:
00224 case KS_E_LATIN7:
00225 case KS_E_LATIN8:
00226 case KS_E_LATIN9:
00227 case KS_E_LATIN13:
00228 case KS_E_LATIN15:
00229
00230 kdError(750) << "charsets iso-8859-4 .. iso-8859-15 not supported yet" << endl;
00231 break;
00232 case KS_E_UTF8:
00233 *proc << "-Tutf8";
00234 break;
00235 case KS_E_KOI8U:
00236 *proc << "-w'";
00237 break;
00238 }
00239 }
00240
00241
00242
00243 }
00244 else
00245 *proc << "-a";
00246
00247 if (trystart==0)
00248 {
00249 connect( proc, SIGNAL(receivedStderr(KProcess *, char *, int)),
00250 this, SLOT(ispellErrors(KProcess *, char *, int)) );
00251
00252 connect( proc, SIGNAL(processExited(KProcess *)),
00253 this, SLOT(ispellExit (KProcess *)) );
00254
00255 OUTPUT(KSpell2);
00256 }
00257
00258 if ( proc->start() == false )
00259 {
00260 m_status = Error;
00261 QTimer::singleShot( 0, this, SLOT(emitDeath()));
00262 }
00263 }
00264
00265 void
00266 KSpell::ispellErrors( KProcess *, char *buffer, int buflen )
00267 {
00268 buffer[buflen-1] = '\0';
00269
00270 }
00271
00272 void KSpell::KSpell2( KProcIO * )
00273
00274 {
00275 QString line;
00276
00277 kdDebug(750) << "KSpell::KSpell2" << endl;
00278
00279 trystart = maxtrystart;
00280
00281
00282 if ( proc->readln( line, true ) == -1 )
00283 {
00284 QTimer::singleShot( 0, this, SLOT(emitDeath()) );
00285 return;
00286 }
00287
00288
00289 if ( line[0] != '@' )
00290 {
00291 QTimer::singleShot( 0, this, SLOT(emitDeath()) );
00292 return;
00293 }
00294
00295
00296 if ( ignore("kde") == false)
00297 {
00298 kdDebug(750) << "@KDE was false" << endl;
00299 QTimer::singleShot( 0, this, SLOT(emitDeath()) );
00300 return;
00301 }
00302
00303
00304 if ( ignore("linux") == false )
00305 {
00306 kdDebug(750) << "@Linux was false" << endl;
00307 QTimer::singleShot( 0, this, SLOT(emitDeath()) );
00308 return;
00309 }
00310
00311 NOOUTPUT( KSpell2 );
00312
00313 m_status = Running;
00314 emit ready( this );
00315 }
00316
00317 void
00318 KSpell::setUpDialog( bool reallyuseprogressbar )
00319 {
00320 if ( dialogsetup )
00321 return;
00322
00323
00324 ksdlg = new KSpellDlg( parent, "dialog",
00325 progressbar && reallyuseprogressbar, modaldlg );
00326 ksdlg->setCaption( caption );
00327
00328 connect( ksdlg, SIGNAL(command(int)),
00329 this, SLOT(slotStopCancel(int)) );
00330 connect( this, SIGNAL(progress(unsigned int)),
00331 ksdlg, SLOT(slotProgress(unsigned int)) );
00332
00333 #ifdef Q_WS_X11 // FIXME(E): Implement for Qt/Embedded
00334 KWin::setIcons( ksdlg->winId(), kapp->icon(), kapp->miniIcon() );
00335 #endif
00336 if ( modaldlg )
00337 ksdlg->setFocus();
00338 dialogsetup = true;
00339 }
00340
00341 bool KSpell::addPersonal( const QString & word )
00342 {
00343 QString qs = word.simplifyWhiteSpace();
00344
00345
00346 if ( qs.find(' ') != -1 || qs.isEmpty() )
00347 return false;
00348
00349 qs.prepend( "*" );
00350 personaldict = true;
00351
00352 return proc->writeStdin( qs );
00353 }
00354
00355 bool KSpell::writePersonalDictionary()
00356 {
00357 return proc->writeStdin("#");
00358 }
00359
00360 bool KSpell::ignore( const QString & word )
00361 {
00362 QString qs = word.simplifyWhiteSpace();
00363
00364
00365 if ( qs.find (' ') != -1 || qs.isEmpty() )
00366 return false;
00367
00368 qs.prepend( "@" );
return proc->writeStdin( qs );
}
bool
KSpell::cleanFputsWord( const QString & s, bool appendCR )
{
QString qs(s);
bool empty = true;
for( unsigned int i = 0; i < qs.length(); i++ )
{
//we need some punctuation for ornaments
if ( qs[i] != '\'' && qs[i] != '\"' && qs[i] != '-'
00369 && qs[i].isPunct() || qs[i].isSpace() )
00370 {
00371 qs.remove(i,1);
00372 i--;
00373 } else {
00374 if ( qs[i].isLetter() )
00375 empty=false;
00376 }
00377 }
00378
00379 // don't check empty words, otherwise synchronization will lost
00380 if (empty)
00381 return false;
00382
00383 return proc->writeStdin( "^"+qs, appendCR );
00384 }
00385
00386 bool
00387 KSpell::cleanFputs( const QString & s, bool appendCR )
00388 {
00389 QString qs(s);
00390 unsigned l = qs.length();
00391
00392 // some uses of '$' (e.g. "$0") cause ispell to skip all following text
00393 for( unsigned int i = 0; i < l; ++i )
00394 {
00395 if( qs[i] == '$' )
00396 qs[i] = ' ';
00397 }
00398
00399 if ( l<MAXLINELENGTH )
00400 {
00401 if ( qs.isEmpty() )
00402 qs="";
00403 return proc->writeStdin( "^"+qs, appendCR );
00404 }
00405 else
00406 return proc->writeStdin( QString::fromAscii( "^\n" ),appendCR );
00407 }
00408
00409 bool KSpell::checkWord( const QString & buffer, bool _usedialog )
00410 {
00411 if (d->checking) { // don't check multiple words simultaneously
00412 BufferedWord bufferedWord;
00413 bufferedWord.method = Method1;
00414 bufferedWord.word = buffer;
00415 bufferedWord.useDialog = _usedialog;
00416 d->unchecked.append( bufferedWord );
00417 return true;
00418 }
00419 d->checking = true;
00420 QString qs = buffer.simplifyWhiteSpace();
00421
00422 if ( qs.find (' ') != -1 || qs.isEmpty() ) { // make sure it's a _word_
00423 d->checkNextTimer->start( 0, true );
00424 return false;
00425 }
00427 dialog3slot = SLOT(checkWord3());
00428
00429 usedialog = _usedialog;
00430 setUpDialog( false );
00431 if ( _usedialog )
00432 {
00433 emitProgress();
00434 }
00435 else
00436 ksdlg->hide();
00437
00438 QString blank_line;
00439 while (proc->readln( blank_line, true ) != -1); // eat spurious blanks
00440
00441 OUTPUT(checkWord2);
00442 // connect (this, SIGNAL (dialog3()), this, SLOT (checkWord3()));
00443
00444 proc->writeStdin( "%" ); // turn off terse mode
00445 proc->writeStdin( buffer ); // send the word to ispell
00446
00447 return true;
00448 }
00449
00450 bool KSpell::checkWord( const QString & buffer, bool _usedialog, bool suggest )
00451 {
00452 if (d->checking) { // don't check multiple words simultaneously
00453 BufferedWord bufferedWord;
00454 bufferedWord.method = Method2;
00455 bufferedWord.word = buffer;
00456 bufferedWord.useDialog = _usedialog;
00457 bufferedWord.suggest = suggest;
00458 d->unchecked.append( bufferedWord );
00459 return true;
00460 }
00461 d->checking = true;
00462 QString qs = buffer.simplifyWhiteSpace();
00463
00464 if ( qs.find (' ') != -1 || qs.isEmpty() ) { // make sure it's a _word_
00465 d->checkNextTimer->start( 0, true );
00466 return false;
00467 }
00468
00470 if ( !suggest ) {
00471 dialog3slot = SLOT(checkWord3());
00472 usedialog = _usedialog;
00473 setUpDialog( false );
00474 if ( _usedialog )
00475 {
00476 emitProgress();
00477 }
00478 else
00479 ksdlg->hide();
00480 }
00481
00482 QString blank_line;
00483 while (proc->readln( blank_line, true ) != -1); // eat spurious blanks
00484
00485 OUTPUT(checkWord2);
00486 // connect (this, SIGNAL (dialog3()), this, SLOT (checkWord3()));
00487
00488 proc->writeStdin( "%" ); // turn off terse mode
00489 proc->writeStdin( buffer ); // send the word to ispell
00490
00491 return true;
00492 }
00493
00494 void KSpell::checkWord2( KProcIO* )
00495 {
00496 QString word;
00497 QString line;
00498 proc->readln( line, true ); //get ispell's response
00499
00500 /* ispell man page: "Each sentence of text input is terminated with an
00501 additional blank line, indicating that ispell has completed processing
00502 the input line."
00503 <sanders>
00504 But there can be multiple lines returned in the case of an error,
00505 in this case we should consume all the output given otherwise spell checking
00506 can get out of sync.
00507 </sanders>
00508 */
00509 QString blank_line;
00510 while (proc->readln( blank_line, true ) != -1); // eat the blank line
00511 NOOUTPUT(checkWord2);
00512
00513 bool mistake = ( parseOneResponse(line, word, sugg) == MISTAKE );
00514 if ( mistake && usedialog )
00515 {
00516 cwword = word;
00517 dialog( word, sugg, SLOT(checkWord3()) );
00518 d->checkNextTimer->start( 0, true );
00519 return;
00520 }
00521 else if( mistake )
00522 {
00523 emit misspelling( word, sugg, lastpos );
00524 }
00525
00526 //emits a "corrected" signal _even_ if no change was made
00527 //so that the calling program knows when the check is complete
00528 emit corrected( word, word, 0L );
00529 d->checkNextTimer->start( 0, true );
00530 }
00531
00532 void KSpell::checkNext()
00533 {
00534 // Queue words to prevent kspell from turning into a fork bomb
00535 d->checking = false;
00536 if (!d->unchecked.empty()) {
00537 BufferedWord buf = d->unchecked.front();
00538 d->unchecked.pop_front();
00539
00540 if (buf.method == Method1)
00541 checkWord( buf.word, buf.useDialog );
00542 else
00543 checkWord( buf.word, buf.useDialog, buf.suggest );
00544 }
00545 }
00546
00547 void KSpell::suggestWord( KProcIO * )
00548 {
00549 QString word;
00550 QString line;
00551 proc->readln( line, true ); //get ispell's response
00552
00553 /* ispell man page: "Each sentence of text input is terminated with an
00554 additional blank line, indicating that ispell has completed processing
00555 the input line." */
00556 QString blank_line;
00557 proc->readln( blank_line, true ); // eat the blank line
00558
00559 NOOUTPUT(checkWord2);
00560
00561 bool mistake = ( parseOneResponse(line, word, sugg) == MISTAKE );
00562 if ( mistake && usedialog )
00563 {
00564 cwword=word;
00565 dialog( word, sugg, SLOT(checkWord3()) );
00566 return;
00567 }
00568 }
00569
00570 void KSpell::checkWord3()
00571 {
00572 disconnect( this, SIGNAL(dialog3()), this, SLOT(checkWord3()) );
00573
00574 emit corrected( cwword, replacement(), 0L );
00575 }
00576
00577 QString KSpell::funnyWord( const QString & word )
00578 // composes a guess from ispell to a readable word
00579 // e.g. "re+fry-y+ies" -> "refries"
00580 {
00581 QString qs;
00582 unsigned int i=0;
00583
00584 for( i=0; word [i]!='\0';i++ )
00585 {
00586 if (word [i]=='+')
00587 continue;
00588 if (word [i]=='-')
00589 {
00590 QString shorty;
00591 unsigned int j;
00592 int k;
00593
00594 for( j = i+1; word[j] != '\0' && word[j] != '+' && word[j] != '-'; j++ )
00595 shorty += word[j];
00596
00597 i = j-1;
00598
00599 if ( ( k = qs.findRev(shorty) ) == 0 || k != -1 )
00600 qs.remove( k, shorty.length() );
00601 else
00602 {
00603 qs += '-';
00604 qs += shorty; //it was a hyphen, not a '-' from ispell
00605 }
00606 }
00607 else
00608 qs += word[i];
00609 }
00610
00611 return qs;
00612 }
00613
00614
00615 int KSpell::parseOneResponse( const QString &buffer, QString &word, QStringList & sugg )
00616 // buffer is checked, word and sugg are filled in
00617 // returns
00618 // GOOD if word is fine
00619 // IGNORE if word is in ignorelist
00620 // REPLACE if word is in replacelist
00621 // MISTAKE if word is misspelled
00622 {
00623 word = "";
00624 posinline=0;
00625
00626 sugg.clear();
00627
00628 if ( buffer[0] == '*' || buffer[0] == '+' || buffer[0] == '-' )
00629 {
00630 return GOOD;
00631 }
00632
00633 if ( buffer[0] == '&' || buffer[0] == '?' || buffer[0] == '#' )
00634 {
00635 int i,j;
00636
00637
00638 word = buffer.mid( 2, buffer.find( ' ', 3 ) -2 );
00639 //check() needs this
00640 orig=word;
00641
00642 if( d->m_bIgnoreTitleCase && word == word.upper() )
00643 return IGNORE;
00644
00645 if( d->m_bIgnoreUpperWords && word[0] == word[0].upper() )
00646 {
00647 QString text = word[0] + word.right( word.length()-1 ).lower();
00648 if( text == word )
00649 return IGNORE;
00650 }
00651
00653 //We don't take advantage of ispell's ignore function because
00654 //we can't interrupt ispell's output (when checking a large
00655 //buffer) to add a word to _it's_ ignore-list.
00656 if ( ignorelist.findIndex( word.lower() ) != -1 )
00657 return IGNORE;
00658
00660 QString qs2;
00661
00662 if ( buffer.find( ':' ) != -1 )
00663 qs2 = buffer.left( buffer.find(':') );
00664 else
00665 qs2 = buffer;
00666
00667 posinline = qs2.right( qs2.length()-qs2.findRev(' ') ).toInt()-1;
00668
00670 QStringList::Iterator it = replacelist.begin();
00671 for( ;it != replacelist.end(); ++it, ++it ) // Skip two entries at a time.
00672 {
00673 if ( word == *it ) // Word matches
00674 {
00675 ++it;
00676 word = *it; // Replace it with the next entry
00677 return REPLACE;
00678 }
00679 }
00680
00682 if ( buffer[0] != '#' )
00683 {
00684 QString qs = buffer.mid( buffer.find(':')+2, buffer.length() );
00685 qs += ',';
00686 sugg.clear();
00687 i = j = 0;
00688
00689 while( (unsigned int)i < qs.length() )
00690 {
00691 QString temp = qs.mid( i, (j=qs.find (',',i)) - i );
00692 sugg.append( funnyWord(temp) );
00693
00694 i=j+2;
00695 }
00696 }
00697
00698 if ( (sugg.count()==1) && (sugg.first() == word) )
00699 return GOOD;
00700
00701 return MISTAKE;
00702 }
00703
00704 if ( buffer.isEmpty() ) {
00705 kdDebug(750) << "Got an empty response: ignoring"<<endl;
00706 return GOOD;
00707 }
00708
00709 kdError(750) << "HERE?: [" << buffer << "]" << endl;
00710 kdError(750) << "Please report this to zack@kde.org" << endl;
00711 kdError(750) << "Thank you!" << endl;
00712
00713 emit done( false );
00714 emit done( KSpell::origbuffer );
00715 return MISTAKE;
00716 }
00717
00718 bool KSpell::checkList (QStringList *_wordlist, bool _usedialog)
00719 // prepare check of string list
00720 {
00721 wordlist=_wordlist;
00722 if ((totalpos=wordlist->count())==0)
00723 return false;
00724 wlIt = wordlist->begin();
00725 usedialog=_usedialog;
00726
00727 // prepare the dialog
00728 setUpDialog();
00729
00730 //set the dialog signal handler
00731 dialog3slot = SLOT (checkList4 ());
00732
00733 proc->writeStdin ("%"); // turn off terse mode & check one word at a time
00734
00735 //lastpos now counts which *word number* we are at in checkListReplaceCurrent()
00736 lastpos = -1;
00737 checkList2();
00738
00739 // when checked, KProcIO calls checkList3a
00740 OUTPUT(checkList3a);
00741
00742 return true;
00743 }
00744
00745 void KSpell::checkList2 ()
00746 // send one word from the list to KProcIO
00747 // invoked first time by checkList, later by checkListReplaceCurrent and checkList4
00748 {
00749 // send next word
00750 if (wlIt != wordlist->end())
00751 {
00752 kdDebug(750) << "KS::cklist2 " << lastpos << ": " << *wlIt << endl;
00753
00754 d->endOfResponse = false;
00755 bool put;
00756 lastpos++; offset=0;
00757 put = cleanFputsWord (*wlIt);
00758 ++wlIt;
00759
00760 // when cleanFPutsWord failed (e.g. on empty word)
00761 // try next word; may be this is not good for other
00762 // problems, because this will make read the list up to the end
00763 if (!put) {
00764 checkList2();
00765 }
00766 }
00767 else
00768 // end of word list
00769 {
00770 NOOUTPUT(checkList3a);
00771 ksdlg->hide();
00772 emit done(true);
00773 }
00774 }
00775
00776 void KSpell::checkList3a (KProcIO *)
00777 // invoked by KProcIO, when data from ispell are read
00778 {
00779 //kdDebug(750) << "start of checkList3a" << endl;
00780
00781 // don't read more data, when dialog is waiting
00782 // for user interaction
00783 if ( dlgon ) {
00784 //kdDebug(750) << "dlgon: don't read more data" << endl;
00785 return;
00786 }
00787
00788 int e, tempe;
00789
00790 QString word;
00791 QString line;
00792
00793 do
00794 {
00795 tempe=proc->readln( line, true ); //get ispell's response
00796
00797 //kdDebug(750) << "checkList3a: read bytes [" << tempe << "]" << endl;
00798
00799
00800 if ( tempe == 0 ) {
00801 d->endOfResponse = true;
00802 //kdDebug(750) << "checkList3a: end of resp" << endl;
00803 } else if ( tempe>0 ) {
00804 if ( (e=parseOneResponse( line, word, sugg ) ) == MISTAKE ||
00805 e==REPLACE )
00806 {
00807 dlgresult=-1;
00808
00809 if ( e == REPLACE )
00810 {
00811 QString old = *(--wlIt); ++wlIt;
00812 dlgreplacement = word;
00813 checkListReplaceCurrent();
00814 // inform application
00815 emit corrected( old, *(--wlIt), lastpos ); ++wlIt;
00816 }
00817 else if( usedialog )
00818 {
00819 cwword = word;
00820 dlgon = true;
00821 // show the dialog
00822 dialog( word, sugg, SLOT(checkList4()) );
00823 return;
00824 }
00825 else
00826 {
00827 d->m_bNoMisspellingsEncountered = false;
00828 emit misspelling( word, sugg, lastpos );
00829 }
00830 }
00831
00832 }
00833 emitProgress (); //maybe
00834
00835 // stop when empty line or no more data
00836 } while (tempe > 0);
00837
00838 //kdDebug(750) << "checkList3a: exit loop with [" << tempe << "]" << endl;
00839
00840 // if we got an empty line, t.e. end of ispell/aspell response
00841 // and the dialog isn't waiting for user interaction, send next word
00842 if (d->endOfResponse && !dlgon) {
00843 //kdDebug(750) << "checkList3a: send next word" << endl;
00844 checkList2();
00845 }
00846 }
00847
00848 void KSpell::checkListReplaceCurrent()
00849 {
00850
00851 // go back to misspelled word
00852 wlIt--;
00853
00854 QString s = *wlIt;
00855 s.replace(posinline+offset,orig.length(),replacement());
00856 offset += replacement().length()-orig.length();
00857 wordlist->insert (wlIt, s);
00858 wlIt = wordlist->remove (wlIt);
00859 // wlIt now points to the word after the repalced one
00860
00861 }
00862
00863 void KSpell::checkList4 ()
00864 // evaluate dialog return, when a button was pressed there
00865 {
00866 dlgon=false;
00867 QString old;
00868
00869 disconnect (this, SIGNAL (dialog3()), this, SLOT (checkList4()));
00870
00871 //others should have been processed by dialog() already
00872 switch (dlgresult)
00873 {
00874 case KS_REPLACE:
00875 case KS_REPLACEALL:
00876 kdDebug(750) << "KS: cklist4: lastpos: " << lastpos << endl;
00877 old = *(--wlIt);
00878 ++wlIt;
00879 // replace word
00880 checkListReplaceCurrent();
00881 emit corrected( old, *(--wlIt), lastpos );
00882 ++wlIt;
00883 break;
00884 case KS_CANCEL:
00885 ksdlg->hide();
00886 emit done( false );
00887 return;
00888 case KS_STOP:
00889 ksdlg->hide();
00890 emit done( true );
00891 return;
00892 case KS_CONFIG:
00893 ksdlg->hide();
00894 emit done( false );
00895 //check( origbuffer.mid( lastpos ), true );
00896 //trystart = 0;
00897 //proc->disconnect();
00898 //proc->kill();
00899 //delete proc;
00900 //proc = new KProcIO( codec );
00901 //startIspell();
00902 return;
00903 };
00904
00905 // read more if there is more, otherwise send next word
00906 if (!d->endOfResponse) {
00907 //kdDebug(750) << "checkList4: read more from response" << endl;
00908 checkList3a(NULL);
00909 }
00910 }
00911
00912 bool KSpell::check( const QString &_buffer, bool _usedialog )
00913 {
00914 QString qs;
00915
00916 usedialog = _usedialog;
00917 setUpDialog();
00918 //set the dialog signal handler
00919 dialog3slot = SLOT(check3());
00920
00921 kdDebug(750) << "KS: check" << endl;
00922 origbuffer = _buffer;
00923 if ( ( totalpos = origbuffer.length() ) == 0 )
00924 {
00925 emit done( origbuffer );
00926 return false;
00927 }
00928
00929
00930 // Torben: I corrected the \n\n problem directly in the
00931 // origbuffer since I got errors otherwise
00932 if ( !origbuffer.endsWith("\n\n" ) )
00933 {
00934 if (origbuffer.at(origbuffer.length()-1)!='\n')
00935 {
00936 origbuffer+='\n';
00937 origbuffer+='\n'; //shouldn't these be removed at some point?
00938 }
00939 else
00940 origbuffer+='\n';
00941 }
00942
00943 newbuffer = origbuffer;
00944
00945 // KProcIO calls check2 when read from ispell
00946 OUTPUT( check2 );
00947 proc->writeStdin( "!" );
00948
00949 //lastpos is a position in newbuffer (it has offset in it)
00950 offset = lastlastline = lastpos = lastline = 0;
00951
00952 emitProgress();
00953
00954 // send first buffer line
00955 int i = origbuffer.find( '\n', 0 ) + 1;
00956 qs = origbuffer.mid( 0, i );
00957 cleanFputs( qs, false );
00958
00959 lastline=i; //the character position, not a line number
00960
00961 if ( usedialog )
00962 {
00963 emitProgress();
00964 }
00965 else
00966 ksdlg->hide();
00967
00968 return true;
00969 }
00970
00971
00972 void KSpell::check2( KProcIO * )
00973 // invoked by KProcIO when read from ispell
00974 {
00975 int e, tempe;
00976 QString word;
00977 QString line;
00978 static bool recursive = false;
00979 if (recursive &&
00980 !ksdlg )
00981 {
00982 return;
00983 }
00984 recursive = true;
00985
00986 do
00987 {
00988 tempe = proc->readln( line, false ); //get ispell's response
00989 //kdDebug(750) << "KSpell::check2 (" << tempe << "b)" << endl;
00990
00991 if ( tempe>0 )
00992 {
00993 if ( ( e=parseOneResponse (line, word, sugg) )==MISTAKE ||
00994 e==REPLACE)
00995 {
00996 dlgresult=-1;
00997
00998 // for multibyte encoding posinline needs correction
00999 if (ksconfig->encoding() == KS_E_UTF8) {
01000 // kdDebug(750) << "line: " << origbuffer.mid(lastlastline,
01001 // lastline-lastlastline) << endl;
01002 // kdDebug(750) << "posinline uncorr: " << posinline << endl;
01003
01004 // convert line to UTF-8, cut at pos, convert back to UCS-2
01005 // and get string length
01006 posinline = (QString::fromUtf8(
01007 origbuffer.mid(lastlastline,lastline-lastlastline).utf8(),
01008 posinline)).length();
01009 // kdDebug(750) << "posinline corr: " << posinline << endl;
01010 }
01011
01012 lastpos = posinline+lastlastline+offset;
01013
01014 //orig is set by parseOneResponse()
01015
01016 if (e==REPLACE)
01017 {
01018 dlgreplacement=word;
01019 emit corrected( orig, replacement(), lastpos );
01020 offset += replacement().length()-orig.length();
01021 newbuffer.replace( lastpos, orig.length(), word );
01022 }
01023 else //MISTAKE
01024 {
01025 cwword = word;
01026 //kdDebug(750) << "(Before dialog) word=[" << word << "] cwword =[" << cwword << "]\n" << endl;
01027 if ( usedialog ) {
01028 // show the word in the dialog
01029 dialog( word, sugg, SLOT(check3()) );
01030 } else {
01031 // No dialog, just emit misspelling and continue
01032 d->m_bNoMisspellingsEncountered = false;
01033 emit misspelling( word, sugg, lastpos );
01034 dlgresult = KS_IGNORE;
01035 check3();
01036 }
01037 recursive = false;
01038 return;
01039 }
01040 }
01041
01042 }
01043
01044 emitProgress(); //maybe
01045
01046 } while( tempe>0 );
01047
01048 proc->ackRead();
01049
01050
01051 if ( tempe == -1 ) { //we were called, but no data seems to be ready...
01052 recursive = false;
01053 return;
01054 }
01055
01056 //If there is more to check, then send another line to ISpell.
01057 if ( (unsigned int)lastline < origbuffer.length() )
01058 {
01059 int i;
01060 QString qs;
01061
01062 //kdDebug(750) << "[EOL](" << tempe << ")[" << temp << "]" << endl;
01063
01064 lastpos = (lastlastline=lastline) + offset; //do we really want this?
01065 i = origbuffer.find('\n', lastline) + 1;
01066 qs = origbuffer.mid( lastline, i-lastline );
01067 cleanFputs( qs, false );
01068 lastline = i;
01069 recursive = false;
01070 return;
01071 }
01072 else
01073 //This is the end of it all
01074 {
01075 ksdlg->hide();
01076 // kdDebug(750) << "check2() done" << endl;
01077 newbuffer.truncate( newbuffer.length()-2 );
01078 emitProgress();
01079 emit done( newbuffer );
01080 }
01081 recursive = false;
01082 }
01083
01084 void KSpell::check3 ()
01085 // evaluates the return value of the dialog
01086 {
01087 disconnect (this, SIGNAL (dialog3()), this, SLOT (check3()));
01088 kdDebug(750) << "check3 [" << cwword << "] [" << replacement() << "] " << dlgresult << endl;
01089
01090 //others should have been processed by dialog() already
01091 switch (dlgresult)
01092 {
01093 case KS_REPLACE:
01094 case KS_REPLACEALL:
01095 offset+=replacement().length()-cwword.length();
01096 newbuffer.replace (lastpos, cwword.length(),
01097 replacement());
01098 emit corrected (dlgorigword, replacement(), lastpos);
01099 break;
01100 case KS_CANCEL:
01101 // kdDebug(750) << "canceled\n" << endl;
01102 ksdlg->hide();
01103 emit done( origbuffer );
01104 return;
01105 case KS_CONFIG:
01106 ksdlg->hide();
01107 emit done( origbuffer );
01108 KMessageBox::information( 0, i18n("You have to restart the dialog for changes to take effect") );
01109 //check( origbuffer.mid( lastpos ), true );
01110 return;
01111 case KS_STOP:
01112 ksdlg->hide();
01113 //buffer=newbuffer);
01114 emitProgress();
01115 emit done (newbuffer);
01116 return;
01117 };
01118
01119 proc->ackRead();
01120 }
01121
01122 void
01123 KSpell::slotStopCancel (int result)
01124 {
01125 if (dialogwillprocess)
01126 return;
01127
01128 kdDebug(750) << "KSpell::slotStopCancel [" << result << "]" << endl;
01129
01130 if (result==KS_STOP || result==KS_CANCEL)
01131 if (!dialog3slot.isEmpty())
01132 {
01133 dlgresult=result;
01134 connect (this, SIGNAL (dialog3()), this, dialog3slot.ascii());
01135 emit dialog3();
01136 }
01137 }
01138
01139
01140 void KSpell::dialog( const QString & word, QStringList & sugg, const char *_slot )
01141 {
01142 dlgorigword = word;
01143
01144 dialog3slot = _slot;
01145 dialogwillprocess = true;
01146 connect( ksdlg, SIGNAL(command(int)), this, SLOT(dialog2(int)) );
01147 QString tmpBuf = newbuffer;
01148 kdDebug(750)<<" position = "<<lastpos<<endl;
01149
01150 // extract a context string, replace all characters which might confuse
01151 // the RichText display and highlight the possibly wrong word
01152 QString marker( "_MARKER_" );
01153 tmpBuf.replace( lastpos, word.length(), marker );
01154 QString context = tmpBuf.mid(QMAX(lastpos-18,0), 2*18+marker.length());
01155 context.replace( '\n',QString::fromLatin1(" "));
01156 context.replace( '<', QString::fromLatin1("<") );
01157 context.replace( '>', QString::fromLatin1(">") );
01158 context.replace( marker, QString::fromLatin1("<b>%1</b>").arg( word ) );
01159 context = "<qt>" + context + "</qt>";
01160
01161 ksdlg->init( word, &sugg, context );
01162 d->m_bNoMisspellingsEncountered = false;
01163 emit misspelling( word, sugg, lastpos );
01164
01165 emitProgress();
01166 ksdlg->show();
01167 }
01168
01169 void KSpell::dialog2( int result )
01170 {
01171 QString qs;
01172
01173 disconnect( ksdlg, SIGNAL(command(int)), this, SLOT(dialog2(int)) );
01174 dialogwillprocess = false;
01175 dlgresult = result;
01176 ksdlg->standby();
01177
01178 dlgreplacement = ksdlg->replacement();
01179
01180 //process result here
01181 switch ( dlgresult )
01182 {
01183 case KS_IGNORE:
01184 emit ignoreword( dlgorigword );
01185 break;
01186 case KS_IGNOREALL:
01187 // would be better to lower case only words with beginning cap
01188 ignorelist.prepend( dlgorigword.lower() );
01189 emit ignoreall( dlgorigword );
01190 break;
01191 case KS_ADD:
01192 addPersonal( dlgorigword );
01193 personaldict = true;
01194 emit addword( dlgorigword );
01195 // adding to pesonal dict takes effect at the next line, not the current
01196 ignorelist.prepend( dlgorigword.lower() );
01197 break;
01198 case KS_REPLACEALL:
01199 {
01200 replacelist.append( dlgorigword );
01201 QString _replacement = replacement();
01202 replacelist.append( _replacement );
01203 emit replaceall( dlgorigword , _replacement );
01204 }
01205 break;
01206 case KS_SUGGEST:
01207 checkWord( ksdlg->replacement(), false, true );
01208 return;
01209 break;
01210 }
01211
01212 connect( this, SIGNAL(dialog3()), this, dialog3slot.ascii() );
01213 emit dialog3();
01214 }
01215
01216
01217 KSpell::~KSpell()
01218 {
01219 delete proc;
01220 delete ksconfig;
01221 delete ksdlg;
01222 delete d->checkNextTimer;
01223 delete d;
01224 }
01225
01226
01227 KSpellConfig KSpell::ksConfig() const
01228 {
01229 ksconfig->setIgnoreList(ignorelist);
01230 ksconfig->setReplaceAllList(replacelist);
01231 return *ksconfig;
01232 }
01233
01234 void KSpell::cleanUp()
01235 {
01236 if ( m_status == Cleaning )
01237 return; // Ignore
01238
01239 if ( m_status == Running )
01240 {
01241 if ( personaldict )
01242 writePersonalDictionary();
01243 m_status = Cleaning;
01244 }
01245 proc->closeStdin();
01246 }
01247
01248 void KSpell::ispellExit( KProcess* )
01249 {
01250 kdDebug() << "KSpell::ispellExit() " << m_status << endl;
01251
01252 if ( (m_status == Starting) && (trystart < maxtrystart) )
01253 {
01254 trystart++;
01255 startIspell();
01256 return;
01257 }
01258
01259 if ( m_status == Starting )
01260 m_status = Error;
01261 else if (m_status == Cleaning)
01262 m_status = d->m_bNoMisspellingsEncountered ? FinishedNoMisspellingsEncountered : Finished;
01263 else if ( m_status == Running )
01264 m_status = Crashed;
01265 else // Error, Finished, Crashed
01266 return; // Dead already
01267
01268 kdDebug(750) << "Death" << endl;
01269 QTimer::singleShot( 0, this, SLOT(emitDeath()) );
01270 }
01271
01272 // This is always called from the event loop to make
01273 // sure that the receiver can safely delete the
01274 // KSpell object.
01275 void KSpell::emitDeath()
01276 {
01277 bool deleteMe = autoDelete; // Can't access object after next call!
01278 emit death();
01279 if ( deleteMe )
01280 deleteLater();
01281 }
01282
01283 void KSpell::setProgressResolution (unsigned int res)
01284 {
01285 progres=res;
01286 }
01287
01288 void KSpell::emitProgress ()
01289 {
01290 uint nextprog = (uint) (100.*lastpos/(double)totalpos);
01291
01292 if ( nextprog >= curprog )
01293 {
01294 curprog = nextprog;
01295 emit progress( curprog );
01296 }
01297 }
01298
01299 void KSpell::moveDlg( int x, int y )
01300 {
01301 QPoint pt( x,y ), pt2;
01302 pt2 = parent->mapToGlobal( pt );
01303 ksdlg->move( pt2.x(),pt2.y() );
01304 }
01305
01306 void KSpell::setIgnoreUpperWords(bool _ignore)
01307 {
01308 d->m_bIgnoreUpperWords=_ignore;
01309 }
01310
01311 void KSpell::setIgnoreTitleCase(bool _ignore)
01312 {
01313 d->m_bIgnoreTitleCase=_ignore;
01314 }
01315 // --------------------------------------------------
01316 // Stuff for modal (blocking) spell checking
01317 //
01318 // Written by Torben Weis <weis@kde.org>. So please
01319 // send bug reports regarding the modal stuff to me.
01320 // --------------------------------------------------
01321
01322 int
01323 KSpell::modalCheck( QString& text )
01324 {
01325 return modalCheck( text,0 );
01326 }
01327
01328 int
01329 KSpell::modalCheck( QString& text, KSpellConfig* _kcs )
01330 {
01331 modalreturn = 0;
01332 modaltext = text;
01333
01334 KSpell* spell = new KSpell( 0L, i18n("Spell Checker"), 0 ,
01335 0, _kcs, true, true );
01336
01337 while (spell->status()!=Finished)
01338 kapp->processEvents();
01339
01340 text = modaltext;
01341
01342 delete spell;
01343 return modalreturn;
01344 }
01345
01346 void KSpell::slotSpellCheckerCorrected( const QString & oldText, const QString & newText, unsigned int pos )
01347 {
01348 modaltext=modaltext.replace(pos,oldText.length(),newText);
01349 }
01350
01351
01352 void KSpell::slotModalReady()
01353 {
01354 //kdDebug() << qApp->loopLevel() << endl;
01355 //kdDebug(750) << "MODAL READY------------------" << endl;
01356
01357 Q_ASSERT( m_status == Running );
01358 connect( this, SIGNAL( done( const QString & ) ),
01359 this, SLOT( slotModalDone( const QString & ) ) );
01360 QObject::connect( this, SIGNAL( corrected( const QString&, const QString&, unsigned int ) ),
01361 this, SLOT( slotSpellCheckerCorrected( const QString&, const QString &, unsigned int ) ) );
01362 QObject::connect( this, SIGNAL( death() ),
01363 this, SLOT( slotModalSpellCheckerFinished( ) ) );
01364 check( modaltext );
01365 }
01366
01367 void KSpell::slotModalDone( const QString &/*_buffer*/ )
01368 {
01369 //kdDebug(750) << "MODAL DONE " << _buffer << endl;
01370 //modaltext = _buffer;
01371 cleanUp();
01372
01373 //kdDebug() << "ABOUT TO EXIT LOOP" << endl;
01374 //qApp->exit_loop();
01375
01376 //modalWidgetHack->close(true);
01377 slotModalSpellCheckerFinished();
01378 }
01379
01380 void KSpell::slotModalSpellCheckerFinished( )
01381 {
01382 modalreturn=(int)this->status();
01383 }
01384
01385 void KSpell::initialize( QWidget *_parent, const QString &_caption,
01386 QObject *obj, const char *slot, KSpellConfig *_ksc,
01387 bool _progressbar, bool _modal, SpellerType type )
01388 {
01389 d = new KSpellPrivate;
01390
01391 d->m_bIgnoreUpperWords =false;
01392 d->m_bIgnoreTitleCase =false;
01393 d->m_bNoMisspellingsEncountered = true;
01394 d->type = type;
01395 d->checking = false;
01396 d->checkNextTimer = new QTimer( this );
01397 connect( d->checkNextTimer, SIGNAL( timeout() ),
01398 this, SLOT( checkNext() ));
01399 autoDelete = false;
01400 modaldlg = _modal;
01401 progressbar = _progressbar;
01402
01403 proc = 0;
01404 ksconfig = 0;
01405 ksdlg = 0;
01406 lastpos = 0;
01407
01408 //won't be using the dialog in ksconfig, just the option values
01409 if ( _ksc != 0 )
01410 ksconfig = new KSpellConfig( *_ksc );
01411 else
01412 ksconfig = new KSpellConfig;
01413
01414 codec = 0;
01415 switch ( ksconfig->encoding() )
01416 {
01417 case KS_E_LATIN1:
01418 codec = QTextCodec::codecForName("ISO 8859-1");
01419 break;
01420 case KS_E_LATIN2:
01421 codec = QTextCodec::codecForName("ISO 8859-2");
01422 break;
01423 case KS_E_LATIN3:
01424 codec = QTextCodec::codecForName("ISO 8859-3");
01425 break;
01426 case KS_E_LATIN4:
01427 codec = QTextCodec::codecForName("ISO 8859-4");
01428 break;
01429 case KS_E_LATIN5:
01430 codec = QTextCodec::codecForName("ISO 8859-5");
01431 break;
01432 case KS_E_LATIN7:
01433 codec = QTextCodec::codecForName("ISO 8859-7");
01434 break;
01435 case KS_E_LATIN8:
01436 codec = QTextCodec::codecForName("ISO 8859-8-i");
01437 break;
01438 case KS_E_LATIN9:
01439 codec = QTextCodec::codecForName("ISO 8859-9");
01440 break;
01441 case KS_E_LATIN13:
01442 codec = QTextCodec::codecForName("ISO 8859-13");
01443 break;
01444 case KS_E_LATIN15:
01445 codec = QTextCodec::codecForName("ISO 8859-15");
01446 break;
01447 case KS_E_UTF8:
01448 codec = QTextCodec::codecForName("UTF-8");
01449 break;
01450 case KS_E_KOI8R:
01451 codec = QTextCodec::codecForName("KOI8-R");
01452 break;
01453 case KS_E_KOI8U:
01454 codec = QTextCodec::codecForName("KOI8-U");
01455 break;
01456 case KS_E_CP1251:
01457 codec = QTextCodec::codecForName("CP1251");
01458 break;
01459 case KS_E_CP1255:
01460 codec = QTextCodec::codecForName("CP1255");
01461 break;
01462 default:
01463 break;
01464 }
01465
01466 kdDebug(750) << __FILE__ << ":" << __LINE__ << " Codec = " << (codec ? codec->name() : "<default>") << endl;
01467
01468 // copy ignore list from ksconfig
01469 ignorelist += ksconfig->ignoreList();
01470
01471 replacelist += ksconfig->replaceAllList();
01472 texmode=dlgon=false;
01473 m_status = Starting;
01474 dialogsetup = false;
01475 progres=10;
01476 curprog=0;
01477
01478 dialogwillprocess = false;
01479 dialog3slot = QString::null;
01480
01481 personaldict = false;
01482 dlgresult = -1;
01483
01484 caption = _caption;
01485
01486 parent = _parent;
01487
01488 trystart = 0;
01489 maxtrystart = 2;
01490
01491 if ( obj && slot )
01492 // caller wants to know when kspell is ready
01493 connect( this, SIGNAL(ready(KSpell *)), obj, slot);
01494 else
01495 // Hack for modal spell checking
01496 connect( this, SIGNAL(ready(KSpell *)), this, SLOT(slotModalReady()) );
01497
01498 proc = new KProcIO( codec );
01499
01500 startIspell();
01501 }
01502
01503 QString KSpell::modaltext;
01504 int KSpell::modalreturn = 0;
01505 QWidget* KSpell::modalWidgetHack = 0;
01506
01507 #include "kspell.moc"
01508
01509