lib Library API Documentation

koispell.cpp

00001 /* This file is part of the KDE libraries
00002    Copyright (C) 2002-2003 Laurent Montel <lmontel@mandrakesoft.com>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License version 2 as published by the Free Software Foundation.
00007 
00008    This library is distributed in the hope that it will be useful,
00009    but WITHOUT ANY WARRANTY; without even the implied warranty of
00010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011    Library General Public License for more details.
00012 
00013    You should have received a copy of the GNU Library General Public License
00014    along with this library; see the file COPYING.LIB.  If not, write to
00015    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00016    Boston, MA 02111-1307, USA.
00017 */
00018 
00019 #ifdef HAVE_CONFIG_H
00020 #include <config.h>
00021 #endif
00022 
00023 #include <stdio.h>
00024 #include <sys/time.h>
00025 #include <sys/types.h>
00026 #include <unistd.h>
00027 #include <ctype.h>
00028 #include <stdlib.h> // atoi
00029 
00030 #ifdef HAVE_STRINGS_H
00031 #include <strings.h>
00032 #endif
00033 
00034 #include <qtextcodec.h>
00035 #include <qtimer.h>
00036 #include <kapplication.h>
00037 #include <kdebug.h>
00038 #include <klocale.h>
00039 
00040 #include "koSpell.h"
00041 #include "koSpelldlg.h"
00042 #include "koispell.moc"
00043 #include "koispell.h"
00044 #include "koSconfig.h"
00045 
00046 #include <kwin.h>
00047 #include <kprocio.h>
00048 
00049 #define MAXLINELENGTH 10000
00050 
00051 enum {
00052     GOOD=     0,
00053     IGNORE=   1,
00054     REPLACE=  2,
00055     MISTAKE=  3
00056 };
00057 
00058 
00059 // TODO: Parse stderr output e.g. -- invalid dictionary name (bug #40403)
00060 
00061 /*
00062   Things to put in KSpellConfigDlg:
00063     make root/affix combinations that aren't in the dictionary (-m)
00064     don't generate any affix/root combinations (-P)
00065     Report  run-together  words   with   missing blanks as spelling errors.  (-B)
00066     default dictionary (-d [dictionary])
00067     personal dictionary (-p [dictionary])
00068     path to ispell -- NO: ispell should be in $PATH
00069     */
00070 
00071 
00072 //  Connects a slot to KProcIO's output signal
00073 #define OUTPUT(x) (connect (proc, SIGNAL (readReady(KProcIO *)), this, SLOT (x(KProcIO *))))
00074 
00075 // Disconnect a slot from...
00076 #define NOOUTPUT(x) (disconnect (proc, SIGNAL (readReady(KProcIO *)), this, SLOT (x(KProcIO *))))
00077 
00078 
00079 
00080 
00081 
00082 KOISpell::KOISpell( QWidget *_parent, const QString &_caption,
00083         QObject *obj, const char *slot, KOSpellConfig *_ksc,
00084         bool _progressbar, bool _modal, KOSpellerType _type )
00085     :KOSpell(_parent,_caption,_ksc,_modal,/*_autocorrect*/false,  _type)
00086 {
00087     initialize( _parent, _caption, obj, slot, _ksc,
00088               _progressbar, _modal );
00089 }
00090 
00091 void KOISpell::startIspell()
00092   //trystart = {0,1,2}
00093 {
00094 
00095     kdDebug(30006) << "Try #" << trystart << endl;
00096     if (trystart>0)
00097         proc->resetAll();
00098 
00099     switch (ksconfig->client())
00100     {
00101     case KOS_CLIENT_ISPELL:
00102         *proc << "ispell";
00103         kdDebug(30006) << "Using ispell" << endl;
00104         break;
00105     case KOS_CLIENT_ASPELL:
00106         // This can only happen if HAVE_LIBASPELL isn't defined
00107         *proc << "aspell";
00108         kdDebug(30006) << "Using aspell" << endl;
00109         break;
00110     case KOS_CLIENT_HSPELL:
00111         *proc << "hspell";
00112         kdDebug(30006) << "Using hspell" << endl;
00113         break;
00114     default:
00115         kdError(30006) << "Spelling configuration error, client=" << ksconfig->client() <<endl;
00116     }
00117 
00118     if (ksconfig->client() == KOS_CLIENT_ISPELL || ksconfig->client() == KOS_CLIENT_ASPELL)
00119     {
00120         // -a : pipe mode
00121         // -S : sort suggestions by probable correctness
00122         *proc << "-a" << "-S";
00123         switch ( type )
00124         {
00125         case HTML:
00126             //Debian uses an ispell version that has the -h option instead.
00127             //Not sure what they did, but the prefered spell checker
00128             //on that platform is aspell anyway, so use -H until I'll come
00129             //up with something better.
00130             *proc << "-H";
00131             break;
00132         case TeX:
00133             //same for aspell and ispell
00134             *proc << "-t";
00135             break;
00136         case Nroff:
00137             //only ispell supports
00138             if ( ksconfig->client() == KOS_CLIENT_ISPELL )
00139                 *proc << "-n";
00140             break;
00141         case Text:
00142         default:
00143             //nothing
00144             break;
00145         }
00146 
00147         if (ksconfig->noRootAffix())
00148         {
00149             *proc<<"-m";
00150         }
00151         if (ksconfig->runTogether())
00152         {
00153             *proc << "-B";
00154         }
00155         else
00156         {
00157             *proc << "-C";
00158         }
00159 
00160         if (trystart<2)
00161         {
00162             if (! ksconfig->dictionary().isEmpty())
00163             {
00164                 kdDebug(30006) << "using dictionary [" << ksconfig->dictionary() << "]" << endl;
00165                 *proc << "-d";
00166                 *proc << ksconfig->dictionary();
00167             }
00168         }
00169 
00170         //Note to potential debuggers:  -Tlatin2 _is_ being added on the
00171         //  _first_ try.  But, some versions of ispell will fail with this
00172         // option, so kspell tries again without it.  That's why as 'ps -ax'
00173         // shows "ispell -a -S ..." without the "-Tlatin2" option.
00174         /*
00175           The background is that -T does not define something like an
00176           encoding but -T defines something like a mode. And the potential
00177           modes are different for each language. (It is defined in ispell's
00178           *.aff files.)
00179           
00180           Note that ispell called without the appopriate -T means that the
00181           language does not have special characters (e.g. accents) anymore.
00182          */
00183         
00184         if (trystart<1)
00185             switch (ksconfig->encoding())
00186             {
00187             case KOS_E_LATIN1:
00188                 *proc << "-Tlatin1";
00189                 break;
00190             case KOS_E_LATIN2:
00191                 *proc << "-Tlatin2";
00192                 break;
00193             case KOS_E_LATIN3:
00194                 *proc << "-Tlatin3";
00195                 break;
00196             case KOS_E_LATIN15:
00197                 /*
00198                  There is no known ispell dictionary using ISO-8859-15
00199                  but many users are tempted to use that setting. (Bug #33108)
00200                  So use ISO-8859-1 instead.
00201                 */
00202                 *proc << "-Tlatin1";
00203                 break;
00204 
00205                 // add the other charsets here
00206             case KOS_E_LATIN4:
00207             case KOS_E_LATIN5:
00208             case KOS_E_LATIN7:
00209             case KOS_E_LATIN8:
00210             case KOS_E_LATIN9:
00211             case KOS_E_LATIN13:
00212 
00213                 // will work, if this is the default charset in the dictionary
00214                 kdError(30006) << "charsets iso-8859-4 .. iso-8859-13 not supported yet" << endl;
00215                 break;
00216 
00217             case KOS_E_UTF8:
00218                 *proc << "-Tutf8";
00219                 break;
00220 
00221             case KOS_E_KOI8U:
00222                 *proc << "-w'"; // add ' as a word char
00223                 break;
00224 
00225             }
00226         /*
00227           if (ksconfig->personalDict()[0]!='\0')
00228           {
00229           kdDebug(30006) << "personal dictionary [" << ksconfig->personalDict() << "]" << endl;
00230           *proc << "-p";
00231           *proc << ksconfig->personalDict();
00232           }
00233         */
00234 
00235     }
00236     else       // hspell doesn't need all the rest of the options
00237         *proc << "-a";        // -a : pipe mode
00238 
00239     if (trystart==0) //don't connect these multiple times
00240     {
00241         connect (proc, SIGNAL (  receivedStderr (KProcess *, char *, int)),
00242                  this, SLOT (ispellErrors (KProcess *, char *, int)));
00243 
00244 
00245         connect(proc, SIGNAL(processExited(KProcess *)),
00246                 this, SLOT (ispellExit (KProcess *)));
00247 
00248         OUTPUT(KSpell2);
00249     }
00250 
00251     if ( proc->start() == false )
00252     {
00253         m_status = Error;
00254         QTimer::singleShot( 0, this, SLOT(emitDeath()));
00255     }
00256 }
00257 
00258 QStringList KOISpell::resultCheckWord( const QString &_word )
00259 {
00260     disconnect();
00261     checkWord (_word, false, true);
00262     QStringList sug = suggestions();
00263     return sug;
00264 }
00265 
00266 
00267 void KOISpell::ispellErrors (KProcess *, char *buffer, int buflen)
00268 {
00269   buffer [buflen-1] = '\0';
00270   //kdDebug(30006) << "ispellErrors [" << buffer << "]\n" << endl;
00271 }
00272 
00273 void KOISpell::KSpell2 (KProcIO *)
00274 
00275 {
00276     kdDebug(30006) << "KSpell::KSpell2" << endl;
00277   trystart=maxtrystart;  //We've officially started ispell and don't want
00278        //to try again if it dies.
00279   QString line;
00280 
00281   if (proc->fgets (line, true)==-1)
00282   {
00283      QTimer::singleShot( 0, this, SLOT(emitDeath()));
00284      return;
00285   }
00286 
00287 
00288   if (line[0]!='@') //@ indicates that ispell is working fine
00289   {
00290      QTimer::singleShot( 0, this, SLOT(emitDeath()));
00291      return;
00292   }
00293 
00294   //We want to recognize KDE in any text!
00295   if (ignore ("kde")==false)
00296   {
00297      kdDebug(30006) << "@KDE was false" << endl;
00298      QTimer::singleShot( 0, this, SLOT(emitDeath()));
00299      return;
00300   }
00301 
00302   //We want to recognize linux in any text!
00303   if (ignore ("linux")==false)
00304   {
00305      kdDebug(30006) << "@Linux was false" << endl;
00306      QTimer::singleShot( 0, this, SLOT(emitDeath()));
00307      return;
00308   }
00309 
00310   NOOUTPUT (KSpell2);
00311 
00312   m_status = Running;
00313   m_ready = true;
00314   emit ready(this);
00315 }
00316 
00317 void
00318 KOISpell::setUpDialog (bool reallyuseprogressbar)
00319 {
00320   if (dialogsetup)
00321     return;
00322 
00323   //Set up the dialog box
00324   ksdlg=new KOSpellDlg (parent, ksconfig, "dialog",
00325                progressbar && reallyuseprogressbar, modaldlg );
00326   ksdlg->setCaption (caption);
00327   connect (ksdlg, SIGNAL (command (int)), this,
00328         SLOT (slotStopCancel (int)) );
00331 #ifdef Q_WS_X11 // FIXME(E): Implement for Qt/Embedded
00332   KWin::setIcons (ksdlg->winId(), kapp->icon(), kapp->miniIcon());
00333 #endif
00334   if ( modaldlg )
00335       ksdlg->setFocus();
00336   dialogsetup = true;
00337 }
00338 
00339 bool KOISpell::addPersonal (const QString & word)
00340 {
00341   QString qs = word.simplifyWhiteSpace();
00342 
00343   //we'll let ispell do the work here b/c we can
00344   if (qs.find (' ')!=-1 || qs.isEmpty())    // make sure it's a _word_
00345     return false;
00346 
00347   qs.prepend ("*");
00348   personaldict=true;
00349 
00350   return proc->fputs(qs);
00351 }
00352 
00353 bool KOISpell::writePersonalDictionary ()
00354 {
00355   return proc->fputs ("#");
00356 }
00357 
00358 bool KOISpell::ignore (const QString & word)
00359 {
00360   QString qs = word.simplifyWhiteSpace();
00361 
00362   //we'll let ispell do the work here b/c we can
00363   if (qs.find (' ')!=-1 || qs.isEmpty())    // make sure it's a _word_
00364     return false;
00365 
00366   qs.prepend ("@");
00367 
00368   return proc->fputs(qs);
00369 }
00370 
00371 bool
00372 KOISpell::cleanFputsWord (const QString & s, bool appendCR)
00373 {
00374   QString qs(s);
00375   //bool firstchar = true;
00376   bool empty = true;
00377 
00378   for (unsigned int i=0; i<qs.length(); i++)
00379   {
00380     //we need some punctuation for ornaments
00381     if (qs[i] != '\'' && qs[i] != '\"' && qs[i] != '-'
00382     && qs[i].isPunct() || qs[i].isSpace())
00383     {
00384       qs.remove(i,1);
00385       i--;
00386     } else {
00387       if (qs[i].isLetter()) empty=false;
00388     }
00389   }
00390 
00391   // don't check empty words, otherwise synchronisation will lost
00392   if (empty) return false;
00393 
00394   return proc->fputs("^"+qs, appendCR);
00395 }
00396 
00397 bool
00398 KOISpell::cleanFputs (const QString & s, bool appendCR)
00399 {
00400   QString qs(s);
00401   unsigned l = qs.length();
00402 
00403   // some uses of '$' (e.g. "$0") cause ispell to skip all following text
00404   for(unsigned int i = 0; i < l; ++i)
00405   {
00406     if(qs[i] == '$')
00407       qs[i] = ' ';
00408   }
00409 
00410   if (l<MAXLINELENGTH)
00411     {
00412       if (qs.isEmpty())
00413     qs="";
00414 
00415       return proc->fputs ("^"+qs, appendCR);
00416     }
00417   else
00418     return proc->fputs ("^\n",appendCR);
00419 }
00420 
00421 bool KOISpell::checkWord (const QString & buffer, bool _usedialog)
00422 {
00423   QString qs = buffer.simplifyWhiteSpace();
00424   if (qs.find (' ')!=-1 || qs.isEmpty())    // make sure it's a _word_
00425     return false;
00426 
00428   dialog3slot = SLOT(checkWord3());
00429 
00430   usedialog=_usedialog;
00431   setUpDialog(false);
00432   if (_usedialog)
00433     {
00434       emitProgress();
00435       ksdlg->show();
00436     }
00437   else
00438     ksdlg->hide();
00439 
00440   OUTPUT (checkWord2);
00441   //  connect (this, SIGNAL (dialog3()), this, SLOT (checkWord3()));
00442 
00443   proc->fputs ("%"); // turn off terse mode
00444   cleanFputsWord( qs ); // send the word to ispell
00445 
00446   return true;
00447 }
00448 
00449 //it can't use dialog anyway
00450 bool KOISpell::checkWord (const QString & buffer, bool _usedialog, bool synchronous )
00451 {
00452   QString qs = buffer.simplifyWhiteSpace();
00453   if (qs.find (' ')!=-1 || qs.isEmpty())    // make sure it's a _word_
00454     return false;
00455 
00457   dialog3slot = SLOT(checkWord3());
00458 
00459   usedialog=_usedialog;
00460   setUpDialog(false);
00461 
00462   ksdlg->hide();
00463   if ( synchronous ) {
00464     //ready signal is never called, after initialize
00465     if ( !m_ready ) {
00466       connect( this, SIGNAL(ready(KOSpell*)),
00467                this, SLOT(slotSynchronousReady()) );
00468       connect( this, SIGNAL(death()), // in case of init failure
00469                this, SLOT(slotSynchronousReady()) );
00470       //MAGIC 1: here we wait for the initialization to finish
00471       enter_loop();
00472       disconnect( this, SIGNAL(ready(KOSpell*)),
00473                this, SLOT(slotSynchronousReady()) );
00474       disconnect( this, SIGNAL(death()), // in case of init failure
00475                this, SLOT(slotSynchronousReady()) );
00476     }
00477     if ( m_status == Error ) // init failure
00478         return false;
00479     OUTPUT (checkWord2Synchronous);
00480   }
00481   else
00482     OUTPUT (checkWord2);
00483 
00484   proc->fputs ("%"); // turn off terse mode
00485 
00486   if (cleanFputsWord( qs )) // send the word to ispell
00487      enter_loop(); //MAGIC 2: and here we wait for the results
00488 
00489   return true;
00490 }
00491 
00492 void KOISpell::checkWord2 (KProcIO *)
00493 {
00494   QString word;
00495 
00496   QString line;
00497   proc->fgets (line, true); //get ispell's response
00498 
00499 /* ispell man page: "Each sentence of text input is terminated with an
00500    additional blank line,  indicating that ispell has completed processing
00501    the input line." */
00502   QString blank_line;
00503   proc->fgets(blank_line, true); // eat the blank line
00504 
00505   NOOUTPUT(checkWord2);
00506 
00507   bool mistake = (parseOneResponse(line, word, sugg) == MISTAKE);
00508   if ( mistake && usedialog )
00509     {
00510       cwword=word;
00511       dialog (word, sugg, SLOT (checkWord3()));
00512       return;
00513     }
00514   else if( mistake )
00515     {
00516       misspellingWord (word, sugg, lastpos);
00517     }
00518 
00519   //emits a "corrected" signal _even_ if no change was made
00520   //so that the calling program knows when the check is complete
00521   emit corrected (word, word, 0L);
00522 }
00523 
00524 // This is not even cute... Just watch me abuse
00525 // Qt, KDE, candy, cookies and make this stuff work
00526 // through pure magic
00527 void KOISpell::checkWord2Synchronous (KProcIO *)
00528 {
00529   QString word;
00530 
00531   QString line;
00532   proc->fgets (line, true); //get ispell's response
00533 
00534 /* ispell man page: "Each sentence of text input is terminated with an
00535    additional blank line,  indicating that ispell has completed processing
00536    the input line." */
00537   QString blank_line;
00538   proc->fgets(blank_line, true); // eat the blank line
00539 
00540   NOOUTPUT(checkWord2);
00541 
00542   bool mistake = (parseOneResponse(line, word, sugg) == MISTAKE);
00543   if( mistake )
00544   {
00545     misspellingWord (word, sugg, lastpos);
00546   }
00547   //emits a "corrected" signal _even_ if no change was made
00548   //so that the calling program knows when the check is complete
00549   emit corrected (word, word, 0L);
00550   qApp->exit_loop();
00551 }
00552 
00553 void KOISpell::checkWord3 ()
00554 {
00555   disconnect (this, SIGNAL (dialog3()), this, SLOT (checkWord3()));
00556 
00557   emit corrected (cwword, replacement(), 0L);
00558 }
00559 
00560 QString KOISpell::funnyWord (const QString & word)
00561   // composes a guess from ispell to a readable word
00562   // e.g. "re+fry-y+ies" -> "refries"
00563 {
00564   QString qs;
00565   unsigned int i=0;
00566 
00567   for (i=0; word [i]!='\0';i++)
00568     {
00569       if (word [i]=='+')
00570     continue;
00571       if (word [i]=='-')
00572     {
00573       QString shorty;
00574       unsigned int j;
00575       int k;
00576 
00577       for (j=i+1;word [j]!='\0' && word [j]!='+' &&
00578          word [j]!='-';j++)
00579         shorty+=word [j];
00580       i=j-1;
00581 
00582       if ((k=qs.findRev (shorty))==0 || k!=-1)
00583         qs.remove (k,shorty.length());
00584       else
00585         {
00586               qs+='-';
00587               qs+=shorty;  //it was a hyphen, not a '-' from ispell
00588             }
00589     }
00590       else
00591     qs+=word [i];
00592     }
00593   return qs;
00594 }
00595 
00596 
00597 int KOISpell::parseOneResponse (const QString &buffer, QString &word, QStringList & sugg)
00598   // buffer is checked, word and sugg are filled in
00599   // returns
00600   //   GOOD    if word is fine
00601   //   IGNORE  if word is in ignorelist
00602   //   REPLACE if word is in replacelist
00603   //   MISTAKE if word is misspelled
00604 {
00605   word = "";
00606   posinline=0;
00607 
00608   sugg.clear();
00609 
00610   if (buffer [0]=='*' || buffer[0] == '+' || buffer[0] == '-')
00611     {
00612       return GOOD;
00613     }
00614 
00615   if (buffer [0]=='&' || buffer [0]=='?' || buffer [0]=='#')
00616     {
00617       int i,j;
00618 
00619 
00620       word = buffer.mid (2,buffer.find (' ',3)-2);
00621       //check() needs this
00622       orig=word;
00623 
00624       if(m_bIgnoreTitleCase && word==word.upper())
00625           return IGNORE;
00626 
00627       if(m_bIgnoreUpperWords && word[0]==word[0].upper())
00628       {
00629           QString text=word[0]+word.right(word.length()-1).lower();
00630           if(text==word)
00631               return IGNORE;
00632       }
00633 
00635       //We don't take advantage of ispell's ignore function because
00636       //we can't interrupt ispell's output (when checking a large
00637       //buffer) to add a word to _it's_ ignore-list.
00638       if (ignorelist.findIndex(word.lower())!=-1)
00639     return IGNORE;
00640 
00642       QString qs2;
00643 
00644       if (buffer.find(':')!=-1)
00645     qs2=buffer.left (buffer.find (':'));
00646       else
00647     qs2=buffer;
00648 
00649       posinline = qs2.right( qs2.length()-qs2.findRev(' ') ).toInt()-1;
00650 
00652       QStringList::Iterator it = replacelist.begin();
00653       for(;it != replacelist.end(); ++it, ++it) // Skip two entries at a time.
00654       {
00655          if (word == *it) // Word matches
00656          {
00657             ++it;
00658             word = *it;   // Replace it with the next entry
00659             return REPLACE;
00660      }
00661       }
00662 
00664       if (buffer [0] != '#')
00665     {
00666       QString qs = buffer.mid(buffer.find(':')+2, buffer.length());
00667       qs+=',';
00668       sugg.clear();
00669       i=j=0;
00670       while ((unsigned int)i<qs.length())
00671         {
00672           QString temp = qs.mid (i,(j=qs.find (',',i))-i);
00673           sugg.append (funnyWord (temp));
00674 
00675           i=j+2;
00676         }
00677     }
00678 
00679       if ((sugg.count()==1) && (sugg.first() == word))
00680     return GOOD;
00681 
00682       return MISTAKE;
00683     }
00684 
00685 
00686   kdError(750) << "HERE?: [" << buffer << "]" << endl;
00687   kdError(750) << "Please report this to dsweet@kde.org" << endl;
00688   kdError(750) << "Thank you!" << endl;
00689   emit done(false);
00690   emit done (KOISpell::origbuffer);
00691   return MISTAKE;
00692 }
00693 
00694 bool KOISpell::checkList (QStringList *_wordlist, bool _usedialog)
00695   // prepare check of string list
00696 {
00697   wordlist=_wordlist;
00698   if ((totalpos=wordlist->count())==0)
00699     return false;
00700   wlIt = wordlist->begin();
00701   usedialog=_usedialog;
00702 
00703   // prepare the dialog
00704   setUpDialog();
00705 
00706   //set the dialog signal handler
00707   dialog3slot = SLOT (checkList4 ());
00708 
00709   proc->fputs ("%"); // turn off terse mode & check one word at a time
00710 
00711   //lastpos now counts which *word number* we are at in checkListReplaceCurrent()
00712   lastpos = -1;
00713   checkList2();
00714 
00715   // when checked, KProcIO calls checkList3a
00716   OUTPUT(checkList3a);
00717 
00718   return true;
00719 }
00720 
00721 void KOISpell::checkList2 ()
00722   // send one word from the list to KProcIO
00723   // invoked first time by checkList, later by checkListReplaceCurrent and checkList4
00724 {
00725   // send next word
00726   if (wlIt != wordlist->end())
00727     {
00728       kdDebug(30006) << "KS::cklist2 " << lastpos << ": " << *wlIt << endl;
00729 
00730       endOfResponse = false;
00731       bool put;
00732       lastpos++; offset=0;
00733       put = cleanFputsWord (*wlIt);
00734       ++wlIt;
00735 
00736       // when cleanFPutsWord failed (e.g. on empty word)
00737       // try next word; may be this is not good for other
00738       // problems, because this will make read the list up to the end
00739       if (!put) {
00740     checkList2();
00741       }
00742     }
00743   else
00744     // end of word list
00745     {
00746       NOOUTPUT(checkList3a);
00747       ksdlg->hide();
00748       emit done(true);
00749     }
00750 }
00751 
00752 void KOISpell::checkList3a (KProcIO *)
00753   // invoked by KProcIO, when data from ispell are read
00754 {
00755   //kdDebug(30006) << "start of checkList3a" << endl;
00756 
00757   // don't read more data, when dialog is waiting
00758   // for user interaction
00759   if (dlgon) {
00760     //kdDebug(30006) << "dlgon: don't read more data" << endl;
00761     return;
00762   }
00763 
00764   int e, tempe;
00765 
00766   QString word;
00767   QString line;
00768 
00769     do
00770       {
00771     tempe=proc->fgets (line, true); //get ispell's response
00772 
00773     //kdDebug(30006) << "checkList3a: read bytes [" << tempe << "]" << endl;
00774 
00775 
00776     if (tempe == 0) {
00777       endOfResponse = true;
00778       //kdDebug(30006) << "checkList3a: end of resp" << endl;
00779     } else if (tempe>0) {
00780       if ((e=parseOneResponse (line, word, sugg))==MISTAKE ||
00781           e==REPLACE)
00782         {
00783           dlgresult=-1;
00784 
00785           if (e==REPLACE)
00786         {
00787           QString old = *(--wlIt); ++wlIt;
00788           dlgreplacement=word;
00789           checkListReplaceCurrent();
00790           // inform application
00791           emit corrected (old, *(--wlIt), lastpos); ++wlIt;
00792         }
00793           else if( usedialog )
00794         {
00795           cwword=word;
00796           dlgon=true;
00797           // show the dialog
00798           dialog (word, sugg, SLOT (checkList4()));
00799           return;
00800         }
00801           else
00802         {
00803           misspellingWord (word, sugg, lastpos);
00804         }
00805         }
00806 
00807     }
00808         emitProgress (); //maybe
00809 
00810     // stop when empty line or no more data
00811       } while (tempe > 0);
00812 
00813     //kdDebug(30006) << "checkList3a: exit loop with [" << tempe << "]" << endl;
00814 
00815     // if we got an empty line, t.e. end of ispell/aspell response
00816     // and the dialog isn't waiting for user interaction, send next word
00817     if (endOfResponse && !dlgon) {
00818       //kdDebug(30006) << "checkList3a: send next word" << endl;
00819       checkList2();
00820     }
00821 }
00822 
00823 void KOISpell::checkListReplaceCurrent () {
00824 
00825   // go back to misspelled word
00826   wlIt--;
00827 
00828   QString s = *wlIt;
00829   s.replace(posinline+offset,orig.length(),replacement());
00830   offset += replacement().length()-orig.length();
00831   wordlist->insert (wlIt, s);
00832   wlIt = wordlist->remove (wlIt);
00833   // wlIt now points to the word after the repalced one
00834 
00835 }
00836 
00837 void KOISpell::checkList4 ()
00838   // evaluate dialog return, when a button was pressed there
00839 {
00840   dlgon=false;
00841   QString old;
00842 
00843   disconnect (this, SIGNAL (dialog3()), this, SLOT (checkList4()));
00844 
00845   //others should have been processed by dialog() already
00846   switch (dlgresult)
00847     {
00848     case KOS_REPLACE:
00849     case KOS_REPLACEALL:
00850       kdDebug(30006) << "KS: cklist4: lastpos: " << lastpos << endl;
00851       old = *(--wlIt); ++wlIt;
00852       // replace word
00853       checkListReplaceCurrent();
00854       emit corrected (old, *(--wlIt), lastpos); ++wlIt;
00855       break;
00856     case KOS_CANCEL:
00857       ksdlg->hide();
00858       emit done (false);
00859       return;
00860     case KOS_STOP:
00861       ksdlg->hide();
00862       emit done (true);
00863       break;
00864     };
00865 
00866   // read more if there is more, otherwise send next word
00867   if (!endOfResponse) {
00868     //kdDebug(30006) << "checkList4: read more from response" << endl;
00869       checkList3a(NULL);
00870   }
00871 }
00872 
00873 bool KOISpell::check( const QString &_buffer, bool _usedialog )
00874 {
00875   QString qs;
00876 
00877   usedialog=_usedialog;
00878   setUpDialog ();
00879   //set the dialog signal handler
00880   dialog3slot = SLOT (check3 ());
00881 
00882   kdDebug(30006) << "KS: check" << endl;
00883   origbuffer = _buffer;
00884   if ( ( totalpos = origbuffer.length() ) == 0 )
00885     {
00886       emit done(origbuffer);
00887       return false;
00888     }
00889 
00890 
00891   // Torben: I corrected the \n\n problem directly in the
00892   //         origbuffer since I got errors otherwise
00893   if ( origbuffer.right(2) != "\n\n" )
00894     {
00895       if (origbuffer.at(origbuffer.length()-1)!='\n')
00896     {
00897       origbuffer+='\n';
00898       origbuffer+='\n'; //shouldn't these be removed at some point?
00899     }
00900       else
00901     origbuffer+='\n';
00902     }
00903 
00904   newbuffer=origbuffer;
00905 
00906   // KProcIO calls check2 when read from ispell
00907   OUTPUT(check2);
00908   proc->fputs ("!");
00909 
00910   //lastpos is a position in newbuffer (it has offset in it)
00911   offset=lastlastline=lastpos=lastline=0;
00912 
00913   emitProgress ();
00914 
00915   // send first buffer line
00916   int i = origbuffer.find('\n', 0)+1;
00917   qs=origbuffer.mid (0,i);
00918   cleanFputs (qs,false);
00919 
00920   lastline=i; //the character position, not a line number
00921 
00922   if (usedialog)
00923     {
00924       emitProgress();
00925       ksdlg->show();
00926     }
00927   else
00928     ksdlg->hide();
00929 
00930   return true;
00931 }
00932 
00933 void KOISpell::check2 (KProcIO *)
00934   // invoked by KProcIO when read from ispell
00935 {
00936   int e, tempe;
00937   QString word;
00938   QString line;
00939   static bool recursive = false;
00940   if (recursive &&
00941       (!ksdlg || ksdlg->isHidden()))
00942   {
00943     return;
00944   }
00945   recursive = true;
00946 
00947   do
00948     {
00949       tempe=proc->fgets (line); //get ispell's response
00950       kdDebug(30006) << "KSpell::check2 (" << tempe << "b)" << endl;
00951 
00952       if (tempe>0)
00953     {
00954       if ((e=parseOneResponse (line, word, sugg))==MISTAKE ||
00955           e==REPLACE)
00956         {
00957           dlgresult=-1;
00958 
00959           // for multibyte encoding posinline needs correction
00960           if (ksconfig->encoding() == KOS_E_UTF8) {
00961         // kdDebug(30006) << "line: " << origbuffer.mid(lastlastline,
00962         // lastline-lastlastline) << endl;
00963         // kdDebug(30006) << "posinline uncorr: " << posinline << endl;
00964 
00965         // convert line to UTF-8, cut at pos, convert back to UCS-2
00966         // and get string length
00967         posinline = (QString::fromUtf8(
00968            origbuffer.mid(lastlastline,lastline-lastlastline).utf8(),
00969            posinline)).length();
00970         // kdDebug(30006) << "posinline corr: " << posinline << endl;
00971           }
00972 
00973           lastpos=posinline+lastlastline+offset;
00974 
00975           //orig is set by parseOneResponse()
00976 
00977           if (e==REPLACE)
00978         {
00979           dlgreplacement=word;
00980           emit corrected (orig, replacement(), lastpos);
00981           offset+=replacement().length()-orig.length();
00982           newbuffer.replace (lastpos, orig.length(), word);
00983         }
00984           else  //MISTAKE
00985         {
00986           cwword=word;
00987           //kdDebug(30006) << "(Before dialog) word=[" << word << "] cwword =[" << cwword << "]\n" << endl;
00988                   if ( usedialog ) {
00989                       // show the word in the dialog
00990                       dialog (word, sugg, SLOT (check3()));
00991                   } else {
00992                       // No dialog, just emit misspelling and continue
00993                       misspellingWord (word, sugg, lastpos);
00994                       dlgresult = KOS_IGNORE;
00995                       check3();
00996                   }
00997                   recursive = false;
00998           return;
00999         }
01000         }
01001 
01002       }
01003 
01004       emitProgress (); //maybe
01005 
01006     } while (tempe>0);
01007 
01008   proc->ackRead();
01009 
01010   if (tempe==-1) {//we were called, but no data seems to be ready...
01011     recursive = false;
01012     return;
01013   }
01014 
01015   proc->ackRead();
01016   //If there is more to check, then send another line to ISpell.
01017   if ((unsigned int)lastline<origbuffer.length())
01018     {
01019       int i;
01020       QString qs;
01021 
01022       //kdDebug(30006) << "[EOL](" << tempe << ")[" << temp << "]" << endl;
01023 
01024       lastpos=(lastlastline=lastline)+offset; //do we really want this?
01025       i=origbuffer.find('\n', lastline)+1;
01026       qs=origbuffer.mid (lastline, i-lastline);
01027       cleanFputs (qs,false);
01028       lastline=i;
01029       recursive = false;
01030       return;
01031     }
01032   else
01033   //This is the end of it all
01034     {
01035       ksdlg->hide();
01036       //      kdDebug(30006) << "check2() done" << endl;
01037       newbuffer.truncate (newbuffer.length()-2);
01038       emitProgress();
01039       NOOUTPUT( check2 );
01040       emit done (newbuffer);
01041     }
01042   recursive = false;
01043 }
01044 
01045 void KOISpell::check3 ()
01046   // evaluates the return value of the dialog
01047 {
01048   disconnect (this, SIGNAL (dialog3()), this, SLOT (check3()));
01049 
01050   kdDebug(30006) << "check3 [" << cwword << "] [" << replacement() << "] " << dlgresult << endl;
01051 
01052   //others should have been processed by dialog() already
01053   switch (dlgresult)
01054     {
01055     case KOS_REPLACE:
01056     case KOS_REPLACEALL:
01057       offset+=replacement().length()-cwword.length();
01058       newbuffer.replace (lastpos, cwword.length(),
01059              replacement());
01060       emit corrected (dlgorigword, replacement(), lastpos);
01061       break;
01062     case KOS_CANCEL:
01063     //      kdDebug(30006) << "cancelled\n" << endl;
01064       ksdlg->hide();
01065       emit done (origbuffer);
01066       return;
01067     case KOS_STOP:
01068       ksdlg->hide();
01069       //buffer=newbuffer);
01070       emitProgress();
01071       emit done (newbuffer);
01072       return;
01073     };
01074 
01075   proc->ackRead();
01076 }
01077 
01078 void
01079 KOISpell::slotStopCancel (int result)
01080 {
01081   if (dialogwillprocess)
01082     return;
01083 
01084   kdDebug(30006) << "KSpell::slotStopCancel [" << result << "]" << endl;
01085 
01086   if (result==KOS_STOP || result==KOS_CANCEL)
01087     if (!dialog3slot.isEmpty())
01088       {
01089     dlgresult=result;
01090     connect (this, SIGNAL (dialog3()), this, dialog3slot.ascii());
01091     emit dialog3();
01092       }
01093 }
01094 
01095 
01096 void KOISpell::dialog(const QString & word, QStringList & sugg, const char *_slot)
01097 {
01098   dlgorigword=word;
01099 
01100   dialog3slot=_slot;
01101   dialogwillprocess=true;
01102   connect (ksdlg, SIGNAL (command (int)), this, SLOT (dialog2(int)));
01103   ksdlg->init (word, &sugg);
01104   misspellingWord (word, sugg, lastpos);
01105 
01106   emitProgress();
01107   ksdlg->show();
01108 }
01109 
01110 void KOISpell::dialog2 (int result)
01111 {
01112   QString qs;
01113 
01114   disconnect (ksdlg, SIGNAL (command (int)), this, SLOT (dialog2(int)));
01115   dialogwillprocess=false;
01116   dlgresult=result;
01117   ksdlg->standby();
01118 
01119   dlgreplacement=ksdlg->replacement();
01120 
01121   //process result here
01122   switch (dlgresult)
01123     {
01124 
01125     case KOS_IGNORE:
01126       emit ignoreword(dlgorigword);
01127       break;
01128     case KOS_IGNOREALL:
01129       // would be better to lower case only words with beginning cap
01130       ignorelist.prepend(dlgorigword.lower());
01131       emit ignoreall (dlgorigword);
01132       break;
01133     case KOS_ADD:
01134       addPersonal (dlgorigword);
01135       personaldict=true;
01136       emit addword (dlgorigword);
01137       // adding to pesonal dict takes effect at the next line, not the current
01138       ignorelist.prepend(dlgorigword.lower());
01139       break;
01140     case KOS_REPLACEALL:
01141     {
01142       replacelist.append (dlgorigword);
01143       QString _replacement = replacement();
01144       replacelist.append (_replacement);
01145       emit replaceall( dlgorigword ,  _replacement );
01146     }
01147     break;
01148     case KOS_ADDAUTOCORRECT:
01149     {
01150         //todo add new word ????
01151         QString _replacement = replacement();
01152         emit addAutoCorrect (dlgorigword , _replacement);
01153         break;
01154     }
01155     }
01156 
01157   connect (this, SIGNAL (dialog3()), this, dialog3slot.ascii());
01158   emit dialog3();
01159 }
01160 
01161 
01162 KOISpell:: ~KOISpell ()
01163 {
01164     delete proc;
01165 }
01166 
01167 
01168 void KOISpell::cleanUp ()
01169 {
01170   if (m_status == Cleaning) return; // Ignore
01171   if (m_status == Running)
01172   {
01173     if (personaldict)
01174        writePersonalDictionary();
01175     m_status = Cleaning;
01176   }
01177   proc->closeStdin();
01178 }
01179 
01180 void KOISpell::ispellExit (KProcess *)
01181 {
01182   kdDebug(30006) << "KSpell::ispellExit() " << m_status << endl;
01183 
01184   if ((m_status == Starting) && (trystart<maxtrystart))
01185   {
01186     trystart++;
01187     startIspell();
01188     return;
01189   }
01190 
01191   if (m_status == Starting)
01192      m_status = Error;
01193   else if (m_status == Cleaning)
01194       m_status = m_bNoMisspellingsEncountered ? FinishedNoMisspellingsEncountered : Finished;
01195   else if (m_status == Running)
01196      m_status = Crashed;
01197   else // Error, Finished, Crashed
01198      return; // Dead already
01199 
01200   kdDebug(30006) << "Death" << endl;
01201   QTimer::singleShot( 0, this, SLOT(emitDeath()));
01202 }
01203 
01204 // This is always called from the event loop to make
01205 // sure that the receiver can safely delete the
01206 // KOISpell object.
01207 void KOISpell::emitDeath()
01208 {
01209   bool deleteMe = autoDelete; // Can't access object after next call!
01210   emit death();
01211   if (deleteMe)
01212      deleteLater();
01213 }
01214 
01215 void KOISpell::setProgressResolution (unsigned int res)
01216 {
01217   progres=res;
01218 }
01219 
01220 void KOISpell::emitProgress ()
01221 {
01222   uint nextprog = (uint) (100.*lastpos/(double)totalpos);
01223 
01224   if (nextprog>=curprog)
01225     {
01226       curprog=nextprog;
01227       emit progress (curprog);
01228     }
01229 }
01230 
01231 // --------------------------------------------------
01232 // Stuff for modal (blocking) spell checking
01233 //
01234 // Written by Torben Weis <weis@kde.org>. So please
01235 // send bug reports regarding the modal stuff to me.
01236 // --------------------------------------------------
01237 
01238 
01239 int
01240 KOISpell::modalCheck( QString& text, KOSpellConfig* _kcs )
01241 {
01242     modalreturn = 0;
01243     modaltext = text;
01244 
01245 
01246     // kdDebug(30006) << "KOISpell1" << endl;
01247     KOISpell* spell = new KOISpell( 0L, i18n("Spell Checker"), 0 ,
01248                 0, _kcs, true, true );
01249     //qApp->enter_loop();
01250 
01251     while ((spell->status()==Starting) || (spell->status()==Running) || (spell->status()==Cleaning))
01252       kapp->processEvents();
01253 
01254     text = modaltext;
01255 
01256     delete spell;
01257     return modalreturn;
01258 }
01259 
01260 void KOISpell::slotSpellCheckerCorrected( const QString & oldText, const QString & newText, unsigned int pos )
01261 {
01262     modaltext=modaltext.replace(pos,oldText.length(),newText);
01263 }
01264 
01265 
01266 void KOISpell::slotModalReady()
01267 {
01268     //kdDebug(30006) << qApp->loopLevel() << endl;
01269     //kdDebug(30006) << "MODAL READY------------------" << endl;
01270 
01271     Q_ASSERT( m_status == Running );
01272     connect( this, SIGNAL( done( const QString & ) ),
01273              this, SLOT( slotModalDone( const QString & ) ) );
01274     QObject::connect( this, SIGNAL( corrected( const QString&, const QString&, unsigned int ) ),
01275                       this, SLOT( slotSpellCheckerCorrected( const QString&, const QString &, unsigned int ) ) );
01276      QObject::connect( this, SIGNAL( death() ),
01277                       this, SLOT( slotModalSpellCheckerFinished( ) ) );
01278     check( modaltext );
01279 }
01280 
01281 void KOISpell::slotModalDone( const QString &/*_buffer*/ )
01282 {
01283     //kdDebug(30006) << "MODAL DONE " << _buffer << endl;
01284     //modaltext = _buffer;
01285     cleanUp();
01286 
01287     //kdDebug(30006) << "ABOUT TO EXIT LOOP" << endl;
01288     //qApp->exit_loop();
01289 
01290     slotModalSpellCheckerFinished();
01291 }
01292 
01293 void KOISpell::slotModalSpellCheckerFinished( )
01294 {
01295     modalreturn=(int)this->status();
01296 }
01297 
01298 
01299 void KOISpell::initialize( QWidget *_parent, const QString &_caption,
01300                          QObject *obj, const char *slot, KOSpellConfig *_ksc,
01301                          bool _progressbar, bool _modal )
01302 {
01303   m_ready = false;
01304   m_bIgnoreUpperWords=false;
01305   m_bIgnoreTitleCase=false;
01306 
01307   autoDelete = false;
01308   modaldlg = _modal;
01309   progressbar = _progressbar;
01310 
01311   proc=0;
01312   ksdlg=0;
01313 
01314   texmode=dlgon=false;
01315 
01316   dialogsetup = false;
01317   progres=10;
01318   curprog=0;
01319 
01320   dialogwillprocess=false;
01321   dialog3slot="";
01322 
01323   personaldict=false;
01324   dlgresult=-1;
01325 
01326   caption=_caption;
01327 
01328   parent=_parent;
01329 
01330   trystart=0;
01331   maxtrystart=2;
01332 
01333   if ( obj && slot )
01334       // caller wants to know when kspell is ready
01335       connect (this, SIGNAL (ready(KOSpell *)), obj, slot);
01336   else
01337       // Hack for modal spell checking
01338       connect (this, SIGNAL (ready(KOSpell *)), this, SLOT( slotModalReady() ) );
01339   proc=new KProcIO(codec);
01340 
01341   startIspell();
01342 }
01343 
01344 // This is retarded, if you don't get it, don't worry
01345 // it's me working around 999999999 problems
01346 void qt_enter_modal( QWidget *widget );
01347 void qt_leave_modal( QWidget *widget );
01348 void KOISpell::enter_loop()
01349 {
01350   QWidget dummy(0,0,WType_Dialog | WShowModal);
01351   dummy.setFocusPolicy( QWidget::NoFocus );
01352   qt_enter_modal(&dummy);
01353   qApp->enter_loop();
01354   qt_leave_modal(&dummy);
01355 }
01356 
01357 void KOISpell::slotSynchronousReady()
01358 {
01359   qApp->exit_loop();
01360 }
KDE Logo
This file is part of the documentation for lib Library Version 1.3.5.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sun Mar 20 14:25:25 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003