kspell Library API Documentation

kspell.cpp

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