/* This file is part of the KDE libraries Copyright (C) 1997 David Sweet Copyright (C) 2000-2001 Wolfram Diestel Copyright (C) 2003 Zack Rusin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include // atoi #ifdef HAVE_STRINGS_H #include #endif #include #include #include #include #include #include #include #include "kspell.h" #include "kspelldlg.h" #include #include #define MAXLINELENGTH 10000 #undef IGNORE //fix possible conflict enum { GOOD= 0, IGNORE= 1, REPLACE= 2, MISTAKE= 3 }; enum checkMethod { Method1 = 0, Method2 }; struct BufferedWord { checkMethod method; TQString word; bool useDialog; bool suggest; }; class KSpell::KSpellPrivate { public: bool endOfResponse; bool m_bIgnoreUpperWords; bool m_bIgnoreTitleCase; bool m_bNoMisspellingsEncountered; SpellerType type; KSpell* suggestSpell; bool checking; TQValueList unchecked; TQTimer *checkNextTimer; bool aspellV6; }; //TODO //Parse stderr output //e.g. -- invalid dictionary name /* Things to put in KSpellConfigDlg: make root/affix combinations that aren't in the dictionary (-m) don't generate any affix/root combinations (-P) Report run-together words with missing blanks as spelling errors. (-B) default dictionary (-d [dictionary]) personal dictionary (-p [dictionary]) path to ispell -- NO: ispell should be in $PATH */ // Connects a slot to KProcIO's output signal #define OUTPUT(x) (connect (proc, TQT_SIGNAL (readReady(KProcIO *)), this, TQT_SLOT (x(KProcIO *)))) // Disconnect a slot from... #define NOOUTPUT(x) (disconnect (proc, TQT_SIGNAL (readReady(KProcIO *)), this, TQT_SLOT (x(KProcIO *)))) KSpell::KSpell( TQWidget *_parent, const TQString &_caption, TQObject *obj, const char *slot, KSpellConfig *_ksc, bool _progressbar, bool _modal ) { initialize( _parent, _caption, obj, slot, _ksc, _progressbar, _modal, Text ); } KSpell::KSpell( TQWidget *_parent, const TQString &_caption, TQObject *obj, const char *slot, KSpellConfig *_ksc, bool _progressbar, bool _modal, SpellerType type ) { initialize( _parent, _caption, obj, slot, _ksc, _progressbar, _modal, type ); } void KSpell::hide() { ksdlg->hide(); } int KSpell::heightDlg() const { return ksdlg->height(); } int KSpell::widthDlg() const { return ksdlg->width(); } // Check if aspell is at least version 0.6 static bool determineASpellV6() { TQString result; FILE *fs = popen("aspell -v", "r"); if (fs) { // Close textstream before we close fs { TQTextStream ts(fs, IO_ReadOnly); result = ts.read().stripWhiteSpace(); } pclose(fs); } TQRegExp rx("Aspell (\\d.\\d)"); if (rx.search(result) != -1) { float version = rx.cap(1).toFloat(); return (version >= 0.6); } return false; } void KSpell::startIspell() //trystart = {0,1,2} { if ((trystart == 0) && (ksconfig->client() == KS_CLIENT_ASPELL)) d->aspellV6 = determineASpellV6(); kdDebug(750) << "Try #" << trystart << endl; if ( trystart > 0 ) { proc->resetAll(); } switch ( ksconfig->client() ) { case KS_CLIENT_ISPELL: *proc << "ispell"; kdDebug(750) << "Using ispell" << endl; break; case KS_CLIENT_ASPELL: *proc << "aspell"; kdDebug(750) << "Using aspell" << endl; break; case KS_CLIENT_HSPELL: *proc << "hspell"; kdDebug(750) << "Using hspell" << endl; break; case KS_CLIENT_ZEMBEREK: *proc << "zpspell"; kdDebug(750) << "Using zemberek(zpspell)" << endl; break; } if ( ksconfig->client() == KS_CLIENT_ISPELL || ksconfig->client() == KS_CLIENT_ASPELL ) { *proc << "-a" << "-S"; switch ( d->type ) { case HTML: //Debian uses an ispell version that has the -h option instead. //Not sure what they did, but the preferred spell checker //on that platform is aspell anyway, so use -H untill I'll come //up with something better. *proc << "-H"; break; case TeX: //same for aspell and ispell *proc << "-t"; break; case Nroff: //only ispell supports if ( ksconfig->client() == KS_CLIENT_ISPELL ) *proc << "-n"; break; case Text: default: //nothing break; } if (ksconfig->noRootAffix()) { *proc<<"-m"; } if (ksconfig->runTogether()) { *proc << "-B"; } else { *proc << "-C"; } if (trystart<2) { if (! ksconfig->dictionary().isEmpty()) { kdDebug(750) << "using dictionary [" << ksconfig->dictionary() << "]" << endl; *proc << "-d"; *proc << ksconfig->dictionary(); } } //Note to potential debuggers: -Tlatin2 _is_ being added on the // _first_ try. But, some versions of ispell will fail with this // option, so kspell tries again without it. That's why as 'ps -ax' // shows "ispell -a -S ..." withou the "-Tlatin2" option. if ( trystart<1 ) { switch ( ksconfig->encoding() ) { case KS_E_LATIN1: *proc << "-Tlatin1"; break; case KS_E_LATIN2: *proc << "-Tlatin2"; break; case KS_E_LATIN3: *proc << "-Tlatin3"; break; // add the other charsets here case KS_E_LATIN4: case KS_E_LATIN5: case KS_E_LATIN7: case KS_E_LATIN8: case KS_E_LATIN9: case KS_E_LATIN13: // will work, if this is the default charset in the dictionary kdError(750) << "charsets ISO-8859-4, -5, -7, -8, -9 and -13 not supported yet" << endl; break; case KS_E_LATIN15: // ISO-8859-15 (Latin 9) if (ksconfig->client() == KS_CLIENT_ISPELL) { /* * As far as I know, there are no ispell dictionary using ISO-8859-15 * but users have the tendency to select this encoding instead of ISO-8859-1 * So put ispell in ISO-8859-1 (Latin 1) mode. */ *proc << "-Tlatin1"; } else kdError(750) << "ISO-8859-15 not supported for aspell yet." << endl; break; case KS_E_UTF8: *proc << "-Tutf8"; if (ksconfig->client() == KS_CLIENT_ASPELL) *proc << "--encoding=utf-8"; break; case KS_E_KOI8U: *proc << "-w'"; // add ' as a word char break; default: break; } } // -a : pipe mode // -S : sort suggestions by probable correctness } else // hspell and Zemberek(zpspell) doesn't need all the rest of the options *proc << "-a"; if (trystart == 0) //don't connect these multiple times { connect( proc, TQT_SIGNAL(receivedStderr(KProcess *, char *, int)), this, TQT_SLOT(ispellErrors(KProcess *, char *, int)) ); connect( proc, TQT_SIGNAL(processExited(KProcess *)), this, TQT_SLOT(ispellExit (KProcess *)) ); OUTPUT(KSpell2); } if ( !proc->start() ) { m_status = Error; TQTimer::singleShot( 0, this, TQT_SLOT(emitDeath())); } } void KSpell::ispellErrors( KProcess *, char *buffer, int buflen ) { buffer[buflen-1] = '\0'; // kdDebug(750) << "ispellErrors [" << buffer << "]\n" << endl; } void KSpell::KSpell2( KProcIO * ) { TQString line; kdDebug(750) << "KSpell::KSpell2" << endl; trystart = maxtrystart; //We've officially started ispell and don't want //to try again if it dies. if ( proc->readln( line, true ) == -1 ) { TQTimer::singleShot( 0, this, TQT_SLOT(emitDeath()) ); return; } if ( line[0] != '@' ) //@ indicates that ispell is working fine { TQTimer::singleShot( 0, this, TQT_SLOT(emitDeath()) ); return; } //We want to recognize KDE in any text! if ( !ignore("kde") ) { kdDebug(750) << "@KDE was false" << endl; TQTimer::singleShot( 0, this, TQT_SLOT(emitDeath()) ); return; } //We want to recognize linux in any text! if ( !ignore("linux") ) { kdDebug(750) << "@Linux was false" << endl; TQTimer::singleShot( 0, this, TQT_SLOT(emitDeath()) ); return; } NOOUTPUT( KSpell2 ); m_status = Running; emit ready( this ); } void KSpell::setUpDialog( bool reallyuseprogressbar ) { if ( dialogsetup ) return; //Set up the dialog box ksdlg = new KSpellDlg( parent, "dialog", progressbar && reallyuseprogressbar, modaldlg ); ksdlg->setCaption( caption ); connect( ksdlg, TQT_SIGNAL(command(int)), this, TQT_SLOT(slotStopCancel(int)) ); connect( this, TQT_SIGNAL(progress(unsigned int)), ksdlg, TQT_SLOT(slotProgress(unsigned int)) ); #ifdef Q_WS_X11 // FIXME(E): Implement for Qt/Embedded KWin::setIcons( ksdlg->winId(), kapp->icon(), kapp->miniIcon() ); #endif if ( modaldlg ) ksdlg->setFocus(); dialogsetup = true; } bool KSpell::addPersonal( const TQString & word ) { TQString qs = word.simplifyWhiteSpace(); //we'll let ispell do the work here b/c we can if ( qs.find(' ') != -1 || qs.isEmpty() ) // make sure it's a _word_ return false; qs.prepend( "*" ); personaldict = true; return proc->writeStdin( qs ); } bool KSpell::writePersonalDictionary() { return proc->writeStdin(TQString("#")); } bool KSpell::ignore( const TQString & word ) { TQString qs = word.simplifyWhiteSpace(); //we'll let ispell do the work here b/c we can if ( qs.find (' ') != -1 || qs.isEmpty() ) // make sure it's a _word_ return false; qs.prepend( "@" ); return proc->writeStdin( qs ); } bool KSpell::cleanFputsWord( const TQString & s, bool appendCR ) { TQString qs(s); 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] != '-' && qs[i].isPunct() || qs[i].isSpace() ) { qs.remove(i,1); i--; } else { if ( qs[i].isLetter() ) empty=false; } } // don't check empty words, otherwise synchronization will lost if (empty) return false; return proc->writeStdin( "^"+qs, appendCR ); } bool KSpell::cleanFputs( const TQString & s, bool appendCR ) { TQString qs(s); unsigned l = qs.length(); // some uses of '$' (e.g. "$0") cause ispell to skip all following text for( unsigned int i = 0; i < l; ++i ) { if( qs[i] == '$' ) qs[i] = ' '; } if ( lwriteStdin( "^"+qs, appendCR ); } else return proc->writeStdin( TQString::fromAscii( "^\n" ),appendCR ); } bool KSpell::checkWord( const TQString & buffer, bool _usedialog ) { if (d->checking) { // don't check multiple words simultaneously BufferedWord bufferedWord; bufferedWord.method = Method1; bufferedWord.word = buffer; bufferedWord.useDialog = _usedialog; d->unchecked.append( bufferedWord ); return true; } d->checking = true; TQString qs = buffer.simplifyWhiteSpace(); if ( qs.find (' ') != -1 || qs.isEmpty() ) { // make sure it's a _word_ d->checkNextTimer->start( 0, true ); return false; } ///set the dialog signal handler dialog3slot = TQT_SLOT(checkWord3()); usedialog = _usedialog; setUpDialog( false ); if ( _usedialog ) { emitProgress(); } else ksdlg->hide(); TQString blank_line; while (proc->readln( blank_line, true ) != -1); // eat spurious blanks OUTPUT(checkWord2); // connect (this, TQT_SIGNAL (dialog3()), this, TQT_SLOT (checkWord3())); proc->writeStdin(TQString("%")); // turn off terse mode proc->writeStdin( buffer ); // send the word to ispell return true; } bool KSpell::checkWord( const TQString & buffer, bool _usedialog, bool suggest ) { if (d->checking) { // don't check multiple words simultaneously BufferedWord bufferedWord; bufferedWord.method = Method2; bufferedWord.word = buffer; bufferedWord.useDialog = _usedialog; bufferedWord.suggest = suggest; d->unchecked.append( bufferedWord ); return true; } d->checking = true; TQString qs = buffer.simplifyWhiteSpace(); if ( qs.find (' ') != -1 || qs.isEmpty() ) { // make sure it's a _word_ d->checkNextTimer->start( 0, true ); return false; } ///set the dialog signal handler if ( !suggest ) { dialog3slot = TQT_SLOT(checkWord3()); usedialog = _usedialog; setUpDialog( false ); if ( _usedialog ) { emitProgress(); } else ksdlg->hide(); } TQString blank_line; while (proc->readln( blank_line, true ) != -1); // eat spurious blanks OUTPUT(checkWord2); // connect (this, TQT_SIGNAL (dialog3()), this, TQT_SLOT (checkWord3())); proc->writeStdin(TQString("%")); // turn off terse mode proc->writeStdin( buffer ); // send the word to ispell return true; } void KSpell::checkWord2( KProcIO* ) { TQString word; TQString line; proc->readln( line, true ); //get ispell's response /* ispell man page: "Each sentence of text input is terminated with an additional blank line, indicating that ispell has completed processing the input line." But there can be multiple lines returned in the case of an error, in this case we should consume all the output given otherwise spell checking can get out of sync. */ TQString blank_line; while (proc->readln( blank_line, true ) != -1); // eat the blank line NOOUTPUT(checkWord2); bool mistake = ( parseOneResponse(line, word, sugg) == MISTAKE ); if ( mistake && usedialog ) { cwword = word; dialog( word, sugg, TQT_SLOT(checkWord3()) ); d->checkNextTimer->start( 0, true ); return; } else if( mistake ) { emit misspelling( word, sugg, lastpos ); } //emits a "corrected" signal _even_ if no change was made //so that the calling program knows when the check is complete emit corrected( word, word, 0L ); d->checkNextTimer->start( 0, true ); } void KSpell::checkNext() { // Queue words to prevent kspell from turning into a fork bomb d->checking = false; if (!d->unchecked.empty()) { BufferedWord buf = d->unchecked.front(); d->unchecked.pop_front(); if (buf.method == Method1) checkWord( buf.word, buf.useDialog ); else checkWord( buf.word, buf.useDialog, buf.suggest ); } } void KSpell::suggestWord( KProcIO * ) { TQString word; TQString line; proc->readln( line, true ); //get ispell's response /* ispell man page: "Each sentence of text input is terminated with an additional blank line, indicating that ispell has completed processing the input line." */ TQString blank_line; proc->readln( blank_line, true ); // eat the blank line NOOUTPUT(checkWord2); bool mistake = ( parseOneResponse(line, word, sugg) == MISTAKE ); if ( mistake && usedialog ) { cwword=word; dialog( word, sugg, TQT_SLOT(checkWord3()) ); return; } } void KSpell::checkWord3() { disconnect( this, TQT_SIGNAL(dialog3()), this, TQT_SLOT(checkWord3()) ); emit corrected( cwword, replacement(), 0L ); } TQString KSpell::funnyWord( const TQString & word ) // composes a guess from ispell to a readable word // e.g. "re+fry-y+ies" -> "refries" { TQString qs; unsigned int i=0; for( i=0; word [i]!='\0';i++ ) { if (word [i]=='+') continue; if (word [i]=='-') { TQString shorty; unsigned int j; int k; for( j = i+1; word[j] != '\0' && word[j] != '+' && word[j] != '-'; j++ ) shorty += word[j]; i = j-1; if ( !( k = qs.findRev(shorty) ) || k != -1 ) qs.remove( k, shorty.length() ); else { qs += '-'; qs += shorty; //it was a hyphen, not a '-' from ispell } } else qs += word[i]; } return qs; } int KSpell::parseOneResponse( const TQString &buffer, TQString &word, TQStringList & sugg ) // buffer is checked, word and sugg are filled in // returns // GOOD if word is fine // IGNORE if word is in ignorelist // REPLACE if word is in replacelist // MISTAKE if word is misspelled { word = ""; posinline=0; sugg.clear(); if ( buffer[0] == '*' || buffer[0] == '+' || buffer[0] == '-' ) { return GOOD; } if ( buffer[0] == '&' || buffer[0] == '?' || buffer[0] == '#' ) { int i,j; word = buffer.mid( 2, buffer.find( ' ', 3 ) -2 ); //check() needs this orig=word; if( d->m_bIgnoreTitleCase && word == word.upper() ) return IGNORE; if( d->m_bIgnoreUpperWords && word[0] == word[0].upper() ) { TQString text = word[0] + word.right( word.length()-1 ).lower(); if( text == word ) return IGNORE; } /////// Ignore-list stuff ////////// //We don't take advantage of ispell's ignore function because //we can't interrupt ispell's output (when checking a large //buffer) to add a word to _it's_ ignore-list. if ( ignorelist.findIndex( word.lower() ) != -1 ) return IGNORE; //// Position in line /// TQString qs2; if ( buffer.find( ':' ) != -1 ) qs2 = buffer.left( buffer.find(':') ); else qs2 = buffer; posinline = qs2.right( qs2.length()-qs2.findRev(' ') ).toInt()-1; ///// Replace-list stuff //// TQStringList::Iterator it = replacelist.begin(); for( ;it != replacelist.end(); ++it, ++it ) // Skip two entries at a time. { if ( word == *it ) // Word matches { ++it; word = *it; // Replace it with the next entry return REPLACE; } } /////// Suggestions ////// if ( buffer[0] != '#' ) { TQString qs = buffer.mid( buffer.find(':')+2, buffer.length() ); qs += ','; sugg.clear(); i = j = 0; while( (unsigned int)i < qs.length() ) { TQString temp = qs.mid( i, (j=qs.find (',',i)) - i ); sugg.append( funnyWord(temp) ); i=j+2; } } if ( (sugg.count()==1) && (sugg.first() == word) ) return GOOD; return MISTAKE; } if ( buffer.isEmpty() ) { kdDebug(750) << "Got an empty response: ignoring"<count())==0) return false; wlIt = wordlist->begin(); usedialog=_usedialog; // prepare the dialog setUpDialog(); //set the dialog signal handler dialog3slot = TQT_SLOT (checkList4 ()); proc->writeStdin (TQString("%")); // turn off terse mode & check one word at a time //lastpos now counts which *word number* we are at in checkListReplaceCurrent() lastpos = -1; checkList2(); // when checked, KProcIO calls checkList3a OUTPUT(checkList3a); return true; } void KSpell::checkList2 () // send one word from the list to KProcIO // invoked first time by checkList, later by checkListReplaceCurrent and checkList4 { // send next word if (wlIt != wordlist->end()) { kdDebug(750) << "KS::cklist2 " << lastpos << ": " << *wlIt << endl; d->endOfResponse = false; bool put; lastpos++; offset=0; put = cleanFputsWord (*wlIt); ++wlIt; // when cleanFPutsWord failed (e.g. on empty word) // try next word; may be this is not good for other // problems, because this will make read the list up to the end if (!put) { checkList2(); } } else // end of word list { NOOUTPUT(checkList3a); ksdlg->hide(); emit done(true); } } void KSpell::checkList3a (KProcIO *) // invoked by KProcIO, when data from ispell are read { //kdDebug(750) << "start of checkList3a" << endl; // don't read more data, when dialog is waiting // for user interaction if ( dlgon ) { //kdDebug(750) << "dlgon: don't read more data" << endl; return; } int e, tempe; TQString word; TQString line; do { tempe=proc->readln( line, true ); //get ispell's response //kdDebug(750) << "checkList3a: read bytes [" << tempe << "]" << endl; if ( tempe == 0 ) { d->endOfResponse = true; //kdDebug(750) << "checkList3a: end of resp" << endl; } else if ( tempe>0 ) { if ( (e=parseOneResponse( line, word, sugg ) ) == MISTAKE || e==REPLACE ) { dlgresult=-1; if ( e == REPLACE ) { TQString old = *(--wlIt); ++wlIt; dlgreplacement = word; checkListReplaceCurrent(); // inform application emit corrected( old, *(--wlIt), lastpos ); ++wlIt; } else if( usedialog ) { cwword = word; dlgon = true; // show the dialog dialog( word, sugg, TQT_SLOT(checkList4()) ); return; } else { d->m_bNoMisspellingsEncountered = false; emit misspelling( word, sugg, lastpos ); } } } emitProgress (); //maybe // stop when empty line or no more data } while (tempe > 0); //kdDebug(750) << "checkList3a: exit loop with [" << tempe << "]" << endl; // if we got an empty line, t.e. end of ispell/aspell response // and the dialog isn't waiting for user interaction, send next word if (d->endOfResponse && !dlgon) { //kdDebug(750) << "checkList3a: send next word" << endl; checkList2(); } } void KSpell::checkListReplaceCurrent() { // go back to misspelled word wlIt--; TQString s = *wlIt; s.replace(posinline+offset,orig.length(),replacement()); offset += replacement().length()-orig.length(); wordlist->insert (wlIt, s); wlIt = wordlist->remove (wlIt); // wlIt now points to the word after the repalced one } void KSpell::checkList4 () // evaluate dialog return, when a button was pressed there { dlgon=false; TQString old; disconnect (this, TQT_SIGNAL (dialog3()), this, TQT_SLOT (checkList4())); //others should have been processed by dialog() already switch (dlgresult) { case KS_REPLACE: case KS_REPLACEALL: kdDebug(750) << "KS: cklist4: lastpos: " << lastpos << endl; old = *(--wlIt); ++wlIt; // replace word checkListReplaceCurrent(); emit corrected( old, *(--wlIt), lastpos ); ++wlIt; break; case KS_CANCEL: ksdlg->hide(); emit done( false ); return; case KS_STOP: ksdlg->hide(); emit done( true ); return; case KS_CONFIG: ksdlg->hide(); emit done( false ); //check( origbuffer.mid( lastpos ), true ); //trystart = 0; //proc->disconnect(); //proc->kill(); //delete proc; //proc = new KProcIO( codec ); //startIspell(); return; }; // read more if there is more, otherwise send next word if (!d->endOfResponse) { //kdDebug(750) << "checkList4: read more from response" << endl; checkList3a(NULL); } } bool KSpell::check( const TQString &_buffer, bool _usedialog ) { TQString qs; usedialog = _usedialog; setUpDialog(); //set the dialog signal handler dialog3slot = TQT_SLOT(check3()); kdDebug(750) << "KS: check" << endl; origbuffer = _buffer; if ( ( totalpos = origbuffer.length() ) == 0 ) { emit done( origbuffer ); return false; } // Torben: I corrected the \n\n problem directly in the // origbuffer since I got errors otherwise if ( !origbuffer.endsWith("\n\n" ) ) { if (origbuffer.at(origbuffer.length()-1)!='\n') { origbuffer+='\n'; origbuffer+='\n'; //shouldn't these be removed at some point? } else origbuffer+='\n'; } newbuffer = origbuffer; // KProcIO calls check2 when read from ispell OUTPUT( check2 ); proc->writeStdin(TQString("!")); //lastpos is a position in newbuffer (it has offset in it) offset = lastlastline = lastpos = lastline = 0; emitProgress(); // send first buffer line int i = origbuffer.find( '\n', 0 ) + 1; qs = origbuffer.mid( 0, i ); cleanFputs( qs, false ); lastline=i; //the character position, not a line number if ( usedialog ) { emitProgress(); } else ksdlg->hide(); return true; } void KSpell::check2( KProcIO * ) // invoked by KProcIO when read from ispell { int e, tempe; TQString word; TQString line; static bool recursive = false; if (recursive && !ksdlg ) { return; } recursive = true; do { tempe = proc->readln( line, false ); //get ispell's response //kdDebug(750) << "KSpell::check2 (" << tempe << "b)" << endl; if ( tempe>0 ) { if ( ( e=parseOneResponse (line, word, sugg) )==MISTAKE || e==REPLACE) { dlgresult=-1; // for multibyte encoding posinline needs correction if ((ksconfig->encoding() == KS_E_UTF8) && !d->aspellV6) { // kdDebug(750) << "line: " << origbuffer.mid(lastlastline, // lastline-lastlastline) << endl; // kdDebug(750) << "posinline uncorr: " << posinline << endl; // convert line to UTF-8, cut at pos, convert back to UCS-2 // and get string length posinline = (TQString::fromUtf8( origbuffer.mid(lastlastline,lastline-lastlastline).utf8(), posinline)).length(); // kdDebug(750) << "posinline corr: " << posinline << endl; } lastpos = posinline+lastlastline+offset; //orig is set by parseOneResponse() if (e==REPLACE) { dlgreplacement=word; emit corrected( orig, replacement(), lastpos ); offset += replacement().length()-orig.length(); newbuffer.replace( lastpos, orig.length(), word ); } else //MISTAKE { cwword = word; //kdDebug(750) << "(Before dialog) word=[" << word << "] cwword =[" << cwword << "]\n" << endl; if ( usedialog ) { // show the word in the dialog dialog( word, sugg, TQT_SLOT(check3()) ); } else { // No dialog, just emit misspelling and continue d->m_bNoMisspellingsEncountered = false; emit misspelling( word, sugg, lastpos ); dlgresult = KS_IGNORE; check3(); } recursive = false; return; } } } emitProgress(); //maybe } while( tempe>0 ); if ( tempe == -1 ) { //we were called, but no data seems to be ready... // Make sure we don't get called directly again and make sure we do get // called when new data arrives. NOOUTPUT( check2 ); proc->enableReadSignals(true); OUTPUT( check2 ); recursive = false; return; } proc->ackRead(); //If there is more to check, then send another line to ISpell. if ( (unsigned int)lastline < origbuffer.length() ) { int i; TQString qs; //kdDebug(750) << "[EOL](" << tempe << ")[" << temp << "]" << endl; lastpos = (lastlastline=lastline) + offset; //do we really want this? i = origbuffer.find('\n', lastline) + 1; qs = origbuffer.mid( lastline, i-lastline ); cleanFputs( qs, false ); lastline = i; recursive = false; return; } else //This is the end of it all { ksdlg->hide(); // kdDebug(750) << "check2() done" << endl; newbuffer.truncate( newbuffer.length()-2 ); emitProgress(); emit done( newbuffer ); } recursive = false; } void KSpell::check3 () // evaluates the return value of the dialog { disconnect (this, TQT_SIGNAL (dialog3()), this, TQT_SLOT (check3())); kdDebug(750) << "check3 [" << cwword << "] [" << replacement() << "] " << dlgresult << endl; //others should have been processed by dialog() already switch (dlgresult) { case KS_REPLACE: case KS_REPLACEALL: offset+=replacement().length()-cwword.length(); newbuffer.replace (lastpos, cwword.length(), replacement()); emit corrected (dlgorigword, replacement(), lastpos); break; case KS_CANCEL: // kdDebug(750) << "canceled\n" << endl; ksdlg->hide(); emit done( origbuffer ); return; case KS_CONFIG: ksdlg->hide(); emit done( origbuffer ); KMessageBox::information( 0, i18n("You have to restart the dialog for changes to take effect") ); //check( origbuffer.mid( lastpos ), true ); return; case KS_STOP: ksdlg->hide(); //buffer=newbuffer); emitProgress(); emit done (newbuffer); return; }; proc->ackRead(); } void KSpell::slotStopCancel (int result) { if (dialogwillprocess) return; kdDebug(750) << "KSpell::slotStopCancel [" << result << "]" << endl; if (result==KS_STOP || result==KS_CANCEL) if (!dialog3slot.isEmpty()) { dlgresult=result; connect (this, TQT_SIGNAL (dialog3()), this, dialog3slot.ascii()); emit dialog3(); } } void KSpell::dialog( const TQString & word, TQStringList & sugg, const char *_slot ) { dlgorigword = word; dialog3slot = _slot; dialogwillprocess = true; connect( ksdlg, TQT_SIGNAL(command(int)), this, TQT_SLOT(dialog2(int)) ); TQString tmpBuf = newbuffer; kdDebug(750)<<" position = "<', TQString::fromLatin1(">") ); context.replace( marker, TQString::fromLatin1("%1").arg( word ) ); context = "" + context + ""; ksdlg->init( word, &sugg, context ); d->m_bNoMisspellingsEncountered = false; emit misspelling( word, sugg, lastpos ); emitProgress(); ksdlg->show(); } void KSpell::dialog2( int result ) { TQString qs; disconnect( ksdlg, TQT_SIGNAL(command(int)), this, TQT_SLOT(dialog2(int)) ); dialogwillprocess = false; dlgresult = result; ksdlg->standby(); dlgreplacement = ksdlg->replacement(); //process result here switch ( dlgresult ) { case KS_IGNORE: emit ignoreword( dlgorigword ); break; case KS_IGNOREALL: // would be better to lower case only words with beginning cap ignorelist.prepend( dlgorigword.lower() ); emit ignoreall( dlgorigword ); break; case KS_ADD: addPersonal( dlgorigword ); personaldict = true; emit addword( dlgorigword ); // adding to pesonal dict takes effect at the next line, not the current ignorelist.prepend( dlgorigword.lower() ); break; case KS_REPLACEALL: { replacelist.append( dlgorigword ); TQString _replacement = replacement(); replacelist.append( _replacement ); emit replaceall( dlgorigword , _replacement ); } break; case KS_SUGGEST: checkWord( ksdlg->replacement(), false, true ); return; break; } connect( this, TQT_SIGNAL(dialog3()), this, dialog3slot.ascii() ); emit dialog3(); } KSpell::~KSpell() { delete proc; delete ksconfig; delete ksdlg; delete d->checkNextTimer; delete d; } KSpellConfig KSpell::ksConfig() const { ksconfig->setIgnoreList(ignorelist); ksconfig->setReplaceAllList(replacelist); return *ksconfig; } void KSpell::cleanUp() { if ( m_status == Cleaning ) return; // Ignore if ( m_status == Running ) { if ( personaldict ) writePersonalDictionary(); m_status = Cleaning; } proc->closeStdin(); } void KSpell::ispellExit( KProcess* ) { kdDebug() << "KSpell::ispellExit() " << m_status << endl; if ( (m_status == Starting) && (trystart < maxtrystart) ) { trystart++; startIspell(); return; } if ( m_status == Starting ) m_status = Error; else if (m_status == Cleaning) m_status = d->m_bNoMisspellingsEncountered ? FinishedNoMisspellingsEncountered : Finished; else if ( m_status == Running ) m_status = Crashed; else // Error, Finished, Crashed return; // Dead already kdDebug(750) << "Death" << endl; TQTimer::singleShot( 0, this, TQT_SLOT(emitDeath()) ); } // This is always called from the event loop to make // sure that the receiver can safely delete the // KSpell object. void KSpell::emitDeath() { bool deleteMe = autoDelete; // Can't access object after next call! emit death(); if ( deleteMe ) deleteLater(); } void KSpell::setProgressResolution (unsigned int res) { progres=res; } void KSpell::emitProgress () { uint nextprog = (uint) (100.*lastpos/(double)totalpos); if ( nextprog >= curprog ) { curprog = nextprog; emit progress( curprog ); } } void KSpell::moveDlg( int x, int y ) { TQPoint pt( x,y ), pt2; pt2 = parent->mapToGlobal( pt ); ksdlg->move( pt2.x(),pt2.y() ); } void KSpell::setIgnoreUpperWords(bool _ignore) { d->m_bIgnoreUpperWords=_ignore; } void KSpell::setIgnoreTitleCase(bool _ignore) { d->m_bIgnoreTitleCase=_ignore; } // -------------------------------------------------- // Stuff for modal (blocking) spell checking // // Written by Torben Weis . So please // send bug reports regarding the modal stuff to me. // -------------------------------------------------- int KSpell::modalCheck( TQString& text ) { return modalCheck( text,0 ); } int KSpell::modalCheck( TQString& text, KSpellConfig* _kcs ) { modalreturn = 0; modaltext = text; KSpell* spell = new KSpell( 0L, i18n("Spell Checker"), 0 , 0, _kcs, true, true ); while (spell->status()!=Finished) kapp->processEvents(); text = modaltext; delete spell; return modalreturn; } void KSpell::slotSpellCheckerCorrected( const TQString & oldText, const TQString & newText, unsigned int pos ) { modaltext=modaltext.replace(pos,oldText.length(),newText); } void KSpell::slotModalReady() { //kdDebug() << tqApp->loopLevel() << endl; //kdDebug(750) << "MODAL READY------------------" << endl; Q_ASSERT( m_status == Running ); connect( this, TQT_SIGNAL( done( const TQString & ) ), this, TQT_SLOT( slotModalDone( const TQString & ) ) ); TQObject::connect( this, TQT_SIGNAL( corrected( const TQString&, const TQString&, unsigned int ) ), this, TQT_SLOT( slotSpellCheckerCorrected( const TQString&, const TQString &, unsigned int ) ) ); TQObject::connect( this, TQT_SIGNAL( death() ), this, TQT_SLOT( slotModalSpellCheckerFinished( ) ) ); check( modaltext ); } void KSpell::slotModalDone( const TQString &/*_buffer*/ ) { //kdDebug(750) << "MODAL DONE " << _buffer << endl; //modaltext = _buffer; cleanUp(); //kdDebug() << "ABOUT TO EXIT LOOP" << endl; //tqApp->exit_loop(); //modalWidgetHack->close(true); slotModalSpellCheckerFinished(); } void KSpell::slotModalSpellCheckerFinished( ) { modalreturn=(int)this->status(); } void KSpell::initialize( TQWidget *_parent, const TQString &_caption, TQObject *obj, const char *slot, KSpellConfig *_ksc, bool _progressbar, bool _modal, SpellerType type ) { d = new KSpellPrivate; d->m_bIgnoreUpperWords =false; d->m_bIgnoreTitleCase =false; d->m_bNoMisspellingsEncountered = true; d->type = type; d->checking = false; d->aspellV6 = false; d->checkNextTimer = new TQTimer( this ); connect( d->checkNextTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( checkNext() )); autoDelete = false; modaldlg = _modal; progressbar = _progressbar; proc = 0; ksconfig = 0; ksdlg = 0; lastpos = 0; //won't be using the dialog in ksconfig, just the option values if ( _ksc ) ksconfig = new KSpellConfig( *_ksc ); else ksconfig = new KSpellConfig; codec = 0; switch ( ksconfig->encoding() ) { case KS_E_LATIN1: codec = TQTextCodec::codecForName("ISO 8859-1"); break; case KS_E_LATIN2: codec = TQTextCodec::codecForName("ISO 8859-2"); break; case KS_E_LATIN3: codec = TQTextCodec::codecForName("ISO 8859-3"); break; case KS_E_LATIN4: codec = TQTextCodec::codecForName("ISO 8859-4"); break; case KS_E_LATIN5: codec = TQTextCodec::codecForName("ISO 8859-5"); break; case KS_E_LATIN7: codec = TQTextCodec::codecForName("ISO 8859-7"); break; case KS_E_LATIN8: codec = TQTextCodec::codecForName("ISO 8859-8-i"); break; case KS_E_LATIN9: codec = TQTextCodec::codecForName("ISO 8859-9"); break; case KS_E_LATIN13: codec = TQTextCodec::codecForName("ISO 8859-13"); break; case KS_E_LATIN15: codec = TQTextCodec::codecForName("ISO 8859-15"); break; case KS_E_UTF8: codec = TQTextCodec::codecForName("UTF-8"); break; case KS_E_KOI8R: codec = TQTextCodec::codecForName("KOI8-R"); break; case KS_E_KOI8U: codec = TQTextCodec::codecForName("KOI8-U"); break; case KS_E_CP1251: codec = TQTextCodec::codecForName("CP1251"); break; case KS_E_CP1255: codec = TQTextCodec::codecForName("CP1255"); break; default: break; } kdDebug(750) << __FILE__ << ":" << __LINE__ << " Codec = " << (codec ? codec->name() : "") << endl; // copy ignore list from ksconfig ignorelist += ksconfig->ignoreList(); replacelist += ksconfig->replaceAllList(); texmode=dlgon=false; m_status = Starting; dialogsetup = false; progres=10; curprog=0; dialogwillprocess = false; dialog3slot = TQString::null; personaldict = false; dlgresult = -1; caption = _caption; parent = _parent; trystart = 0; maxtrystart = 2; if ( obj && slot ) // caller wants to know when kspell is ready connect( this, TQT_SIGNAL(ready(KSpell *)), obj, slot); else // Hack for modal spell checking connect( this, TQT_SIGNAL(ready(KSpell *)), this, TQT_SLOT(slotModalReady()) ); proc = new KProcIO( codec ); startIspell(); } TQString KSpell::modaltext; int KSpell::modalreturn = 0; TQWidget* KSpell::modalWidgetHack = 0; #include "kspell.moc"