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