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