You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdelibs/kdeui/kspell.cpp

1578 lines
38 KiB

/* This file is part of the KDE libraries
Copyright (C) 1997 David Sweet <dsweet@kde.org>
Copyright (C) 2000-2001 Wolfram Diestel <wolfram@steloj.de>
Copyright (C) 2003 Zack Rusin <zack@kde.org>
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 <config.h>
#endif
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h> // atoi
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#include <tqregexp.h>
#include <tqtextcodec.h>
#include <tqtimer.h>
#include <kapplication.h>
#include <kmessagebox.h>
#include <kdebug.h>
#include <klocale.h>
#include "kspell.h"
#include "kspelldlg.h"
#include <kwin.h>
#include <kprocio.h>
#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<BufferedWord> 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.tqfind(' ') != -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.tqfind (' ') != -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 ( l<MAXLINELENGTH )
{
if ( qs.isEmpty() )
qs="";
return proc->writeStdin( "^"+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.tqfind (' ') != -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.tqfind (' ') != -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."
<sanders>
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.
</sanders>
*/
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.tqfindRev(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.tqfind( ' ', 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.tqfindIndex( word.lower() ) != -1 )
return IGNORE;
//// Position in line ///
TQString qs2;
if ( buffer.tqfind( ':' ) != -1 )
qs2 = buffer.left( buffer.tqfind(':') );
else
qs2 = buffer;
posinline = qs2.right( qs2.length()-qs2.tqfindRev(' ') ).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.tqfind(':')+2, buffer.length() );
qs += ',';
sugg.clear();
i = j = 0;
while( (unsigned int)i < qs.length() )
{
TQString temp = qs.mid( i, (j=qs.tqfind (',',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"<<endl;
return GOOD;
}
kdError(750) << "HERE?: [" << buffer << "]" << endl;
kdError(750) << "Please report this to zack@kde.org" << endl;
kdError(750) << "Thank you!" << endl;
emit done( false );
emit done( KSpell::origbuffer );
return MISTAKE;
}
bool KSpell::checkList (TQStringList *_wordlist, bool _usedialog)
// prepare check of string list
{
wordlist=_wordlist;
if ((totalpos=wordlist->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.tqfind( '\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.tqfind('\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 = "<<lastpos<<endl;
// extract a context string, replace all characters which might confuse
// the RichText display and highlight the possibly wrong word
TQString marker( "_MARKER_" );
tmpBuf.replace( lastpos, word.length(), marker );
TQString context = tmpBuf.mid(QMAX(lastpos-18,0), 2*18+marker.length());
context.replace( '\n',TQString::tqfromLatin1(" "));
context.replace( '<', TQString::tqfromLatin1("&lt;") );
context.replace( '>', TQString::tqfromLatin1("&gt;") );
context.replace( marker, TQString::tqfromLatin1("<b>%1</b>").arg( word ) );
context = "<qt>" + context + "</qt>";
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 <weis@kde.org>. 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() : "<default>") << 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"