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