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 ("@");

  return proc->fputs(qs);
}

bool
KSpell::cleanFputsWord (const QString & s, bool appendCR)
{
  QString qs(s);
  //bool firstchar = TRUE;
  bool empty = TRUE;

  for (unsigned int i=0; i<qs.length(); i++)
  {
    //we need some punctuation for ornaments
    if (qs[i] != '\'' && qs[i] != '\"' && qs[i] != '-'
00420     && qs[i].isPunct() || qs[i].isSpace())
00421     {
00422       qs.remove(i,1);
00423       i--;
00424     } else {
00425       if (qs[i].isLetter()) empty=FALSE;
00426     }
00427   }
00428 
00429   // don't check empty words, otherwise synchronisation will lost
00430   if (empty) return FALSE;
00431 
00432   return proc->fputs("^"+qs, appendCR);
00433 }
00434 
00435 bool
00436 KSpell::cleanFputs (const QString & s, bool appendCR)
00437 {
00438   QString qs(s);
00439   unsigned l = qs.length();
00440 
00441   // some uses of '$' (e.g. "$0") cause ispell to skip all following text
00442   for(unsigned int i = 0; i < l; ++i)
00443   {
00444     if(qs[i] == '$')
00445       qs[i] = ' ';
00446   }
00447 
00448   if (l<MAXLINELENGTH)
00449     {
00450       if (qs.isEmpty())
00451     qs="";
00452 
00453       return proc->fputs ("^"+qs, appendCR);
00454     }
00455   else
00456     return proc->fputs ("^\n",appendCR);
00457 }
00458 
00459 bool KSpell::checkWord (const QString & buffer, bool _usedialog)
00460 {
00461   QString qs = buffer.simplifyWhiteSpace();
00462 
00463   if (qs.find (' ')!=-1 || qs.isEmpty())    // make sure it's a _word_
00464     return FALSE;
00465 
00467   dialog3slot = SLOT (checkWord3());
00468 
00469   usedialog=_usedialog;
00470   setUpDialog(FALSE);
00471   if (_usedialog)
00472     {
00473       emitProgress();
00474       ksdlg->show();
00475     }
00476   else
00477     ksdlg->hide();
00478 
00479   OUTPUT (checkWord2);
00480   //  connect (this, SIGNAL (dialog3()), this, SLOT (checkWord3()));
00481 
00482   proc->fputs ("%"); // turn off terse mode
00483   proc->fputs (buffer); // send the word to ispell
00484 
00485   return TRUE;
00486 }
00487 
00488 void KSpell::checkWord2 (KProcIO *)
00489 {
00490   QString word;
00491 
00492   QString line;
00493   proc->fgets (line, TRUE); //get ispell's response
00494 
00495 /* ispell man page: "Each sentence of text input is terminated with an
00496    additional blank line,  indicating that ispell has completed processing
00497    the input line." */
00498   QString blank_line;
00499   proc->fgets(blank_line, TRUE); // eat the blank line
00500 
00501   NOOUTPUT(checkWord2);
00502 
00503   bool mistake = (parseOneResponse(line, word, sugg) == MISTAKE);
00504   if ( mistake && usedialog )
00505     {
00506       cwword=word;
00507       dialog (word, sugg, SLOT (checkWord3()));
00508       return;
00509     }
00510   else if( mistake )
00511     {
00512       emit misspelling (word, sugg, lastpos);
00513     }
00514 
00515   //emits a "corrected" signal _even_ if no change was made
00516   //so that the calling program knows when the check is complete
00517   emit corrected (word, word, 0L);
00518 }
00519 
00520 void KSpell::checkWord3 ()
00521 {
00522   disconnect (this, SIGNAL (dialog3()), this, SLOT (checkWord3()));
00523 
00524   emit corrected (cwword, replacement(), 0L);
00525 }
00526 
00527 QString KSpell::funnyWord (const QString & word)
00528   // composes a guess from ispell to a readable word
00529   // e.g. "re+fry-y+ies" -> "refries"
00530 {
00531   QString qs;
00532   unsigned int i=0;
00533 
00534   for (i=0; word [i]!='\0';i++)
00535     {
00536       if (word [i]=='+')
00537     continue;
00538       if (word [i]=='-')
00539     {
00540       QString shorty;
00541       unsigned int j;
00542       int k;
00543 
00544       for (j=i+1;word [j]!='\0' && word [j]!='+' &&
00545          word [j]!='-';j++)
00546         shorty+=word [j];
00547       i=j-1;
00548 
00549       if ((k=qs.findRev (shorty))==0 || k!=-1)
00550         qs.remove (k,shorty.length());
00551       else
00552         {
00553               qs+='-';
00554               qs+=shorty;  //it was a hyphen, not a '-' from ispell
00555             }
00556     }
00557       else
00558     qs+=word [i];
00559     }
00560   return qs;
00561 }
00562 
00563 
00564 int KSpell::parseOneResponse (const QString &buffer, QString &word, QStringList & sugg)
00565   // buffer is checked, word and sugg are filled in
00566   // returns
00567   //   GOOD    if word is fine
00568   //   IGNORE  if word is in ignorelist
00569   //   REPLACE if word is in replacelist
00570   //   MISTAKE if word is misspelled
00571 {
00572   word = "";
00573   posinline=0;
00574 
00575   sugg.clear();
00576 
00577   if (buffer [0]=='*' || buffer[0] == '+' || buffer[0] == '-')
00578     {
00579       return GOOD;
00580     }
00581 
00582   if (buffer [0]=='&' || buffer [0]=='?' || buffer [0]=='#')
00583     {
00584       int i,j;
00585 
00586 
00587       word = buffer.mid (2,buffer.find (' ',3)-2);
00588       //check() needs this
00589       orig=word;
00590 
00591       if(d->m_bIgnoreTitleCase && word==word.upper())
00592           return IGNORE;
00593 
00594       if(d->m_bIgnoreUpperWords && word[0]==word[0].upper())
00595       {
00596           QString text=word[0]+word.right(word.length()-1).lower();
00597           if(text==word)
00598               return IGNORE;
00599       }
00600 
00602       //We don't take advantage of ispell's ignore function because
00603       //we can't interrupt ispell's output (when checking a large
00604       //buffer) to add a word to _it's_ ignore-list.
00605       if (ignorelist.findIndex(word.lower())!=-1)
00606     return IGNORE;
00607 
00609       QString qs2;
00610 
00611       if (buffer.find(':')!=-1)
00612     qs2=buffer.left (buffer.find (':'));
00613       else
00614     qs2=buffer;
00615 
00616       posinline = qs2.right( qs2.length()-qs2.findRev(' ') ).toInt()-1;
00617 
00619       QStringList::Iterator it = replacelist.begin();
00620       for(;it != replacelist.end(); ++it, ++it) // Skip two entries at a time.
00621       {
00622          if (word == *it) // Word matches
00623          {
00624             ++it;
00625             word = *it;   // Replace it with the next entry
00626             return REPLACE;
00627      }
00628       }
00629 
00631       if (buffer [0] != '#')
00632     {
00633       QString qs = buffer.mid(buffer.find(':')+2, buffer.length());
00634       qs+=',';
00635       sugg.clear();
00636       i=j=0;
00637       while ((unsigned int)i<qs.length())
00638         {
00639           QString temp = qs.mid (i,(j=qs.find (',',i))-i);
00640           sugg.append (funnyWord (temp));
00641 
00642           i=j+2;
00643         }
00644     }
00645 
00646       if ((sugg.count()==1) && (sugg.first() == word))
00647     return GOOD;
00648 
00649       return MISTAKE;
00650     }
00651 
00652 
00653   kdError(750) << "HERE?: [" << buffer << "]" << endl;
00654   kdError(750) << "Please report this to dsweet@kde.org" << endl;
00655   kdError(750) << "Thank you!" << endl;
00656   emit done((bool)FALSE);
00657   emit done (KSpell::origbuffer);
00658   return MISTAKE;
00659 }
00660 
00661 bool KSpell::checkList (QStringList *_wordlist, bool _usedialog)
00662   // prepare check of string list
00663 {
00664   wordlist=_wordlist;
00665   if ((totalpos=wordlist->count())==0)
00666     return FALSE;
00667   wlIt = wordlist->begin();
00668   usedialog=_usedialog;
00669 
00670   // prepare the dialog
00671   setUpDialog();
00672 
00673   //set the dialog signal handler
00674   dialog3slot = SLOT (checkList4 ());
00675 
00676   proc->fputs ("%"); // turn off terse mode & check one word at a time
00677 
00678   //lastpos now counts which *word number* we are at in checkListReplaceCurrent()
00679   lastpos = -1;
00680   checkList2();
00681 
00682   // when checked, KProcIO calls checkList3a
00683   OUTPUT(checkList3a);
00684 
00685   return TRUE;
00686 }
00687 
00688 void KSpell::checkList2 ()
00689   // send one word from the list to KProcIO
00690   // invoked first time by checkList, later by checkListReplaceCurrent and checkList4
00691 {
00692   // send next word
00693   if (wlIt != wordlist->end())
00694     {
00695       kdDebug(750) << "KS::cklist2 " << lastpos << ": " << *wlIt << endl;
00696 
00697       d->endOfResponse = FALSE;
00698       bool put;
00699       lastpos++; offset=0;
00700       put = cleanFputsWord (*wlIt);
00701       ++wlIt;
00702 
00703       // when cleanFPutsWord failed (e.g. on empty word)
00704       // try next word; may be this is not good for other
00705       // problems, because this will make read the list up to the end
00706       if (!put) {
00707     checkList2();
00708       }
00709     }
00710   else
00711     // end of word list
00712     {
00713       NOOUTPUT(checkList3a);
00714       ksdlg->hide();
00715       emit done(TRUE);
00716     }
00717 }
00718 
00719 void KSpell::checkList3a (KProcIO *)
00720   // invoked by KProcIO, when data from ispell are read
00721 {
00722   //kdDebug(750) << "start of checkList3a" << endl;
00723 
00724   // don't read more data, when dialog is waiting
00725   // for user interaction
00726   if (dlgon) {
00727     //kdDebug(750) << "dlgon: don't read more data" << endl;
00728     return;
00729   }
00730 
00731   int e, tempe;
00732 
00733   QString word;
00734   QString line;
00735 
00736     do
00737       {
00738     tempe=proc->fgets (line, TRUE); //get ispell's response
00739 
00740     //kdDebug(750) << "checkList3a: read bytes [" << tempe << "]" << endl;
00741 
00742 
00743     if (tempe == 0) {
00744       d->endOfResponse = TRUE;
00745       //kdDebug(750) << "checkList3a: end of resp" << endl;
00746     } else if (tempe>0) {
00747       if ((e=parseOneResponse (line, word, sugg))==MISTAKE ||
00748           e==REPLACE)
00749         {
00750           dlgresult=-1;
00751 
00752           if (e==REPLACE)
00753         {
00754           QString old = *(--wlIt); ++wlIt;
00755           dlgreplacement=word;
00756           checkListReplaceCurrent();
00757           // inform application
00758           emit corrected (old, *(--wlIt), lastpos); ++wlIt;
00759         }
00760           else
00761         {
00762           cwword=word;
00763           dlgon=TRUE;
00764           // show the dialog
00765           dialog (word, sugg, SLOT (checkList4()));
00766           return;
00767         }
00768         }
00769 
00770     }
00771         emitProgress (); //maybe
00772 
00773     // stop when empty line or no more data
00774       } while (tempe > 0);
00775 
00776     //kdDebug(750) << "checkList3a: exit loop with [" << tempe << "]" << endl;
00777 
00778     // if we got an empty line, t.e. end of ispell/aspell response
00779     // and the dialog isn't waiting for user interaction, send next word
00780     if (d->endOfResponse && !dlgon) {
00781       //kdDebug(750) << "checkList3a: send next word" << endl;
00782       checkList2();
00783     }
00784 }
00785 
00786 void KSpell::checkListReplaceCurrent () {
00787 
00788   // go back to misspelled word
00789   wlIt--;
00790 
00791   QString s = *wlIt;
00792   s.replace(posinline+offset,orig.length(),replacement());
00793   offset += replacement().length()-orig.length();
00794   wordlist->insert (wlIt, s);
00795   wlIt = wordlist->remove (wlIt);
00796   // wlIt now points to the word after the repalced one
00797 
00798 }
00799 
00800 void KSpell::checkList4 ()
00801   // evaluate dialog return, when a button was pressed there
00802 {
00803   dlgon=FALSE;
00804   QString old;
00805 
00806   disconnect (this, SIGNAL (dialog3()), this, SLOT (checkList4()));
00807 
00808   //others should have been processed by dialog() already
00809   switch (dlgresult)
00810     {
00811     case KS_REPLACE:
00812     case KS_REPLACEALL:
00813       kdDebug(750) << "KS: cklist4: lastpos: " << lastpos << endl;
00814       old = *(--wlIt); ++wlIt;
00815       // replace word
00816       checkListReplaceCurrent();
00817       emit corrected (old, *(--wlIt), lastpos); ++wlIt;
00818       break;
00819     case KS_CANCEL:
00820       ksdlg->hide();
00821       emit done ((bool)FALSE);
00822       return;
00823     case KS_STOP:
00824       ksdlg->hide();
00825       emit done (TRUE);
00826       break;
00827     };
00828 
00829   // read more if there is more, otherwise send next word
00830   if (!d->endOfResponse) {
00831     //kdDebug(750) << "checkList4: read more from response" << endl;
00832       checkList3a(NULL);
00833   }
00834 }
00835 
00836 bool KSpell::check( const QString &_buffer, bool _usedialog )
00837 {
00838   QString qs;
00839 
00840   usedialog=_usedialog;
00841   setUpDialog ();
00842   //set the dialog signal handler
00843   dialog3slot = SLOT (check3 ());
00844 
00845   kdDebug(750) << "KS: check" << endl;
00846   origbuffer = _buffer;
00847   if ( ( totalpos = origbuffer.length() ) == 0 )
00848     {
00849       emit done(origbuffer);
00850       return FALSE;
00851     }
00852 
00853 
00854   // Torben: I corrected the \n\n problem directly in the
00855   //         origbuffer since I got errors otherwise
00856   if ( origbuffer.right(2) != "\n\n" )
00857     {
00858       if (origbuffer.at(origbuffer.length()-1)!='\n')
00859     {
00860       origbuffer+='\n';
00861       origbuffer+='\n'; //shouldn't these be removed at some point?
00862     }
00863       else
00864     origbuffer+='\n';
00865     }
00866 
00867   newbuffer=origbuffer;
00868 
00869   // KProcIO calls check2 when read from ispell
00870   OUTPUT(check2);
00871   proc->fputs ("!");
00872 
00873   //lastpos is a position in newbuffer (it has offset in it)
00874   offset=lastlastline=lastpos=lastline=0;
00875 
00876   emitProgress ();
00877 
00878   // send first buffer line
00879   int i = origbuffer.find('\n', 0)+1;
00880   qs=origbuffer.mid (0,i);
00881   cleanFputs (qs,FALSE);
00882 
00883   lastline=i; //the character position, not a line number
00884 
00885   if (usedialog)
00886     {
00887       emitProgress();
00888       ksdlg->show();
00889     }
00890   else
00891     ksdlg->hide();
00892 
00893   return TRUE;
00894 }
00895 
00896 void KSpell::check2 (KProcIO *)
00897   // invoked by KProcIO when read from ispell
00898 {
00899   int e, tempe;
00900   QString word;
00901   QString line;
00902 
00903   do
00904     {
00905       tempe=proc->fgets (line); //get ispell's response
00906       kdDebug(750) << "KSpell::check2 (" << tempe << "b)" << endl;
00907 
00908       if (tempe>0)
00909     {
00910       if ((e=parseOneResponse (line, word, sugg))==MISTAKE ||
00911           e==REPLACE)
00912         {
00913           dlgresult=-1;
00914 
00915           // for multibyte encoding posinline needs correction
00916           if (ksconfig->encoding() == KS_E_UTF8) {
00917         // kdDebug(750) << "line: " << origbuffer.mid(lastlastline,
00918         // lastline-lastlastline) << endl;
00919         // kdDebug(750) << "posinline uncorr: " << posinline << endl;
00920 
00921         // convert line to UTF-8, cut at pos, convert back to UCS-2
00922         // and get string length
00923         posinline = (QString::fromUtf8(
00924            origbuffer.mid(lastlastline,lastline-lastlastline).utf8(),
00925            posinline)).length();
00926         // kdDebug(750) << "posinline corr: " << posinline << endl;
00927           }
00928 
00929           lastpos=posinline+lastlastline+offset;
00930 
00931           //orig is set by parseOneResponse()
00932 
00933           if (e==REPLACE)
00934         {
00935           dlgreplacement=word;
00936           emit corrected (orig, replacement(), lastpos);
00937           offset+=replacement().length()-orig.length();
00938           newbuffer.replace (lastpos, orig.length(), word);
00939         }
00940           else  //MISTAKE
00941         {
00942           cwword=word;
00943           //kdDebug(750) << "(Before dialog) word=[" << word << "] cwword =[" << cwword << "]\n" << endl;
00944                   if ( usedialog ) {
00945                       // show the word in the dialog
00946                       dialog (word, sugg, SLOT (check3()));
00947                   } else {
00948                       // No dialog, just emit misspelling and continue
00949                       emit misspelling (word, sugg, lastpos);
00950                       dlgresult = KS_IGNORE;
00951                       check3();
00952                   }
00953           return;
00954         }
00955         }
00956 
00957       }
00958 
00959       emitProgress (); //maybe
00960 
00961     } while (tempe>0);
00962 
00963   proc->ackRead();
00964 
00965 
00966   if (tempe==-1) //we were called, but no data seems to be ready...
00967     return;
00968 
00969   //If there is more to check, then send another line to ISpell.
00970   if ((unsigned int)lastline<origbuffer.length())
00971     {
00972       int i;
00973       QString qs;
00974 
00975       //kdDebug(750) << "[EOL](" << tempe << ")[" << temp << "]" << endl;
00976 
00977       lastpos=(lastlastline=lastline)+offset; //do we really want this?
00978       i=origbuffer.find('\n', lastline)+1;
00979       qs=origbuffer.mid (lastline, i-lastline);
00980       cleanFputs (qs,FALSE);
00981       lastline=i;
00982       return;
00983     }
00984   else
00985   //This is the end of it all
00986     {
00987       ksdlg->hide();
00988       //      kdDebug(750) << "check2() done" << endl;
00989       newbuffer.truncate (newbuffer.length()-2);
00990       emitProgress();
00991       emit done (newbuffer);
00992     }
00993 }
00994 
00995 void KSpell::check3 ()
00996   // evaluates the return value of the dialog
00997 {
00998   disconnect (this, SIGNAL (dialog3()), this, SLOT (check3()));
00999 
01000   kdDebug(750) << "check3 [" << cwword << "] [" << replacement() << "] " << dlgresult << endl;
01001 
01002   //others should have been processed by dialog() already
01003   switch (dlgresult)
01004     {
01005     case KS_REPLACE:
01006     case KS_REPLACEALL:
01007       offset+=replacement().length()-cwword.length();
01008       newbuffer.replace (lastpos, cwword.length(),
01009              replacement());
01010       emit corrected (dlgorigword, replacement(), lastpos);
01011       break;
01012     case KS_CANCEL:
01013     //      kdDebug(750) << "cancelled\n" << endl;
01014       ksdlg->hide();
01015       emit done (origbuffer);
01016       return;
01017     case KS_STOP:
01018       ksdlg->hide();
01019       //buffer=newbuffer);
01020       emitProgress();
01021       emit done (newbuffer);
01022       return;
01023     };
01024 
01025   proc->ackRead();
01026 }
01027 
01028 void
01029 KSpell::slotStopCancel (int result)
01030 {
01031   if (dialogwillprocess)
01032     return;
01033 
01034   kdDebug(750) << "KSpell::slotStopCancel [" << result << "]" << endl;
01035 
01036   if (result==KS_STOP || result==KS_CANCEL)
01037     if (!dialog3slot.isEmpty())
01038       {
01039     dlgresult=result;
01040     connect (this, SIGNAL (dialog3()), this, dialog3slot.ascii());
01041     emit dialog3();
01042       }
01043 }
01044 
01045 
01046 void KSpell::dialog(const QString & word, QStringList & sugg, const char *_slot)
01047 {
01048   dlgorigword=word;
01049 
01050   dialog3slot=_slot;
01051   dialogwillprocess=TRUE;
01052   connect (ksdlg, SIGNAL (command (int)), this, SLOT (dialog2(int)));
01053   ksdlg->init (word, &sugg);
01054   emit misspelling (word, sugg, lastpos);
01055 
01056   emitProgress();
01057   ksdlg->show();
01058 }
01059 
01060 void KSpell::dialog2 (int result)
01061 {
01062   QString qs;
01063 
01064   disconnect (ksdlg, SIGNAL (command (int)), this, SLOT (dialog2(int)));
01065   dialogwillprocess=FALSE;
01066   dlgresult=result;
01067   ksdlg->standby();
01068 
01069   dlgreplacement=ksdlg->replacement();
01070 
01071   //process result here
01072   switch (dlgresult)
01073     {
01074 
01075     case KS_IGNORE:
01076       emit ignoreword(dlgorigword);
01077       break;
01078     case KS_IGNOREALL:
01079       // would be better to lower case only words with beginning cap
01080       ignorelist.prepend(dlgorigword.lower());
01081       emit ignoreall (dlgorigword);
01082       break;
01083     case KS_ADD:
01084       addPersonal (dlgorigword);
01085       personaldict=TRUE;
01086       emit addword (dlgorigword);
01087       // adding to pesonal dict takes effect at the next line, not the current
01088       ignorelist.prepend(dlgorigword.lower());
01089       break;
01090     case KS_REPLACEALL:
01091       replacelist.append (dlgorigword);
01092       QString _replacement = replacement();
01093       replacelist.append (_replacement);
01094       emit replaceall( dlgorigword ,  _replacement );
01095       break;
01096     }
01097 
01098   connect (this, SIGNAL (dialog3()), this, dialog3slot.ascii());
01099   emit dialog3();
01100 }
01101 
01102 
01103 KSpell:: ~KSpell ()
01104 {
01105   if(d)
01106       delete d;
01107 
01108   if (proc)
01109       delete proc;
01110   if (ksconfig)
01111     delete ksconfig;
01112 
01113   if (ksdlg)
01114     delete  ksdlg;
01115 }
01116 
01117 
01118 KSpellConfig KSpell::ksConfig () const
01119 {
01120   ksconfig->setIgnoreList(ignorelist);
01121   ksconfig->setReplaceAllList(replacelist);
01122   return *ksconfig;
01123 }
01124 
01125 void KSpell::cleanUp ()
01126 {
01127   if (m_status == Cleaning) return; // Ignore
01128   if (m_status == Running)
01129   {
01130     if (personaldict)
01131        writePersonalDictionary();
01132     m_status = Cleaning;
01133   }
01134   proc->closeStdin();
01135 }
01136 
01137 void KSpell::ispellExit (KProcess *)
01138 {
01139   kdDebug() << "KSpell::ispellExit() " << m_status << endl;
01140 
01141   if ((m_status == Starting) && (trystart<maxtrystart))
01142   {
01143     trystart++;
01144     startIspell();
01145     return;
01146   }
01147 
01148   if (m_status == Starting)
01149      m_status = Error;
01150   else if (m_status == Cleaning)
01151      m_status = Finished;
01152   else if (m_status == Running)
01153      m_status = Crashed;
01154   else // Error, Finished, Crashed
01155      return; // Dead already
01156 
01157   kdDebug(750) << "Death" << endl;
01158   QTimer::singleShot( 0, this, SLOT(emitDeath()));
01159 }
01160 
01161 // This is always called from the event loop to make
01162 // sure that the receiver can safely delete the
01163 // KSpell object.
01164 void KSpell::emitDeath()
01165 {
01166   bool deleteMe = autoDelete; // Can't access object after next call!
01167   emit death();
01168   if (deleteMe)
01169      delete this;
01170 }
01171 
01172 void KSpell::setProgressResolution (unsigned int res)
01173 {
01174   progres=res;
01175 }
01176 
01177 void KSpell::emitProgress ()
01178 {
01179   uint nextprog = (uint) (100.*lastpos/(double)totalpos);
01180 
01181   if (nextprog>=curprog)
01182     {
01183       curprog=nextprog;
01184       emit progress (curprog);
01185     }
01186 }
01187 
01188 void KSpell::moveDlg (int x, int y)
01189 {
01190   QPoint pt (x,y), pt2;
01191   pt2=parent->mapToGlobal (pt);
01192   ksdlg->move (pt2.x(),pt2.y());
01193 }
01194 
01195 void KSpell::setIgnoreUpperWords(bool _ignore)
01196 {
01197     d->m_bIgnoreUpperWords=_ignore;
01198 }
01199 
01200 void KSpell::setIgnoreTitleCase(bool _ignore)
01201 {
01202     d->m_bIgnoreTitleCase=_ignore;
01203 }
01204 // --------------------------------------------------
01205 // Stuff for modal (blocking) spell checking
01206 //
01207 // Written by Torben Weis <weis@kde.org>. So please
01208 // send bug reports regarding the modal stuff to me.
01209 // --------------------------------------------------
01210 
01211 int
01212 KSpell::modalCheck( QString& text )
01213 {
01214     return modalCheck( text,0 );
01215 }
01216 
01217 int
01218 KSpell::modalCheck( QString& text, KSpellConfig* _kcs )
01219 {
01220     modalreturn = 0;
01221     modaltext = text;
01222 
01223     /*modalWidgetHack = new QWidget(0,0,WType_Modal);
01224     modalWidgetHack->setGeometry(-10,-10,2,2);
01225     */
01226 
01227     // kdDebug() << "KSpell1" << endl;
01228     KSpell* spell = new KSpell( 0L, i18n("Spell Checker"), 0 ,
01229                 0, _kcs, true, true );
01230     //modalWidgetHack->show();
01231     //qApp->enter_loop();
01232 
01233     while (spell->status()!=Finished)
01234       kapp->processEvents();
01235 
01236     text = modaltext;
01237 
01238     //delete modalWidgetHack;
01239     //modalWidgetHack = 0;
01240 
01241     delete spell;
01242     return modalreturn;
01243 }
01244 
01245 void KSpell::slotSpellCheckerCorrected( const QString & oldText, const QString & newText, unsigned int pos )
01246 {
01247     modaltext=modaltext.replace(pos,oldText.length(),newText);
01248 }
01249 
01250 
01251 void KSpell::slotModalReady()
01252 {
01253     //kdDebug() << qApp->loopLevel() << endl;
01254     //kdDebug(750) << "MODAL READY------------------" << endl;
01255 
01256     Q_ASSERT( m_status == Running );
01257     connect( this, SIGNAL( done( const QString & ) ),
01258              this, SLOT( slotModalDone( const QString & ) ) );
01259     QObject::connect( this, SIGNAL( corrected( const QString&, const QString&, unsigned int ) ),
01260                       this, SLOT( slotSpellCheckerCorrected( const QString&, const QString &, unsigned int ) ) );
01261      QObject::connect( this, SIGNAL( death() ),
01262                       this, SLOT( slotModalSpellCheckerFinished( ) ) );
01263     check( modaltext );
01264 }
01265 
01266 void KSpell::slotModalDone( const QString &/*_buffer*/ )
01267 {
01268     //kdDebug(750) << "MODAL DONE " << _buffer << endl;
01269     //modaltext = _buffer;
01270     cleanUp();
01271 
01272     //kdDebug() << "ABOUT TO EXIT LOOP" << endl;
01273     //qApp->exit_loop();
01274 
01275     //modalWidgetHack->close(true);
01276     slotModalSpellCheckerFinished();
01277 }
01278 
01279 void KSpell::slotModalSpellCheckerFinished( )
01280 {
01281     modalreturn=(int)this->status();
01282 }
01283 
01284 QString KSpell::modaltext;
01285 int KSpell::modalreturn = 0;
01286 QWidget* KSpell::modalWidgetHack = 0;
01287 
01288 #include "kspell.moc"
01289 
01290 
KDE Logo
This file is part of the documentation for kdelibs Version 3.1.0.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Mon Oct 24 13:06:25 2011 by doxygen 1.3.5 written by Dimitri van Heesch, © 1997-2001