lib

KoBgSpellCheck.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2004 Zack Rusin <zack@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
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., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #ifdef HAVE_CONFIG_H
00021 #include <config.h>
00022 #endif
00023 
00024 #include "KoBgSpellCheck.h"
00025 #include "KoBgSpellCheck.moc"
00026 #include "KoTextParag.h"
00027 
00028 #include "KoSpell.h"
00029 
00030 #include "KoTextObject.h"
00031 #include "KoTextDocument.h"
00032 
00033 
00034 #include <kspell2/backgroundchecker.h>
00035 #include <kspell2/broker.h>
00036 #include <kspell2/dictionary.h>
00037 #include <kspell2/settings.h>
00038 #include <kspell2/filter.h>
00039 using namespace KSpell2;
00040 
00041 #include <klocale.h>
00042 #include <kdebug.h>
00043 #include <kdeversion.h>
00044 #include <qtimer.h>
00045 #include <qptrdict.h>
00046 
00047 // #define DEBUG_BGSPELLCHECKING
00048 
00049 class KoBgSpellCheck::Private
00050 {
00051 public:
00052     int marked;
00053     KoSpell *backSpeller;
00054     QPtrDict<KoTextParag> paragCache;
00055     bool startupChecking;
00056     KoTextParag* intraWordParag;
00057     int intraWordPosition;
00058 };
00059 
00060 static const int delayAfterMarked = 10;
00061 
00062 KoBgSpellCheck::KoBgSpellCheck( const Broker::Ptr& broker, QObject *parent,
00063                                 const char *name )
00064     : QObject( parent, name )
00065 {
00066 #ifdef DEBUG_BGSPELLCHECKING
00067     kdDebug(32500) << "KoBgSpellCheck::KoBgSpellCheck " << this << endl;
00068 #endif
00069     d = new Private;
00070     d->startupChecking = false;
00071     d->marked = 0;
00072     d->intraWordParag = 0;
00073     d->intraWordPosition = 0;
00074     d->backSpeller = new KoSpell( broker, this, "KoSpell" );
00075 
00076     connect( d->backSpeller, SIGNAL(misspelling(const QString&, int)),
00077              SLOT(spellCheckerMisspelling(const QString &, int )) );
00078     connect( d->backSpeller, SIGNAL(done()),
00079              SLOT(spellCheckerDone()) );
00080     connect( d->backSpeller, SIGNAL(aboutToFeedText()),
00081              SLOT(slotClearPara()) );
00082 }
00083 
00084 KoBgSpellCheck::~KoBgSpellCheck()
00085 {
00086     delete d; d = 0;
00087 }
00088 
00089 void KoBgSpellCheck::registerNewTextObject( KoTextObject *obj )
00090 {
00091     Q_ASSERT( obj );
00092 
00093     connect( obj, SIGNAL(paragraphCreated(KoTextParag*)),
00094              SLOT(slotParagraphCreated(KoTextParag*)) );
00095     connect( obj, SIGNAL(paragraphModified(KoTextParag*, int, int, int)),
00096              SLOT(slotParagraphModified(KoTextParag*, int, int, int)) );
00097     connect( obj, SIGNAL(paragraphDeleted(KoTextParag*)),
00098              SLOT(slotParagraphDeleted(KoTextParag*)) );
00099 }
00100 
00101 void KoBgSpellCheck::setEnabled( bool b )
00102 {
00103     d->backSpeller->settings()->setBackgroundCheckerEnabled( b );
00104     if ( b )
00105         start();
00106     else
00107         stop();
00108 }
00109 
00110 bool KoBgSpellCheck::enabled() const
00111 {
00112     return d->backSpeller->settings()->backgroundCheckerEnabled();
00113 }
00114 
00115 void KoBgSpellCheck::start()
00116 {
00117     if ( !enabled() )
00118         return;
00119 
00120     d->startupChecking = true;
00121     d->marked = 0;
00122     KoTextIterator *itr = createWholeDocIterator();
00123     d->backSpeller->check( itr );
00124     d->backSpeller->start();
00125 }
00126 
00127 void KoBgSpellCheck::spellCheckerMisspelling( const QString &old, int pos )
00128 {
00129     KoTextParag* parag = d->backSpeller->currentParag();
00130 #ifdef DEBUG_BGSPELLCHECKING
00131     kdDebug(32500) << "KoBgSpellCheck::spellCheckerMisspelling parag=" << parag
00132                    << " (id=" << parag->paragId() << ", length="
00133                    << parag->length() << ") pos=" << pos << " length="
00134                    << old.length() << endl;
00135 #endif
00136     markWord( parag, pos, old.length(), true );
00137     // Repaint immediately, since the checking is timer-based (slow), it looks
00138     // slow (chunky) if we only repaint once a paragraph is completely done.
00139     parag->document()->emitRepaintChanged();
00140 
00141     if ( d->startupChecking && d->marked > delayAfterMarked ) {
00142         d->marked = 0;
00143         QTimer::singleShot( 1000, this, SLOT(checkerContinue()) );
00144     } else {
00145         if ( d->startupChecking )
00146             ++d->marked;
00147         checkerContinue();
00148     }
00149 }
00150 
00151 void KoBgSpellCheck::markWord( KoTextParag* parag, int pos, int length, bool misspelled )
00152 {
00153     if ( pos >= parag->length() ) {
00154         kdDebug(32500) << "markWord: " << pos << " is out of parag (length=" << parag->length() << ")" << endl;
00155         return;
00156     }
00157     if ( misspelled && parag == d->intraWordParag &&
00158          d->intraWordPosition >= pos &&
00159          d->intraWordPosition < pos+length ) {
00160 #ifdef DEBUG_BGSPELLCHECKING
00161         kdDebug(32500) << "markWord: " << parag << " " << pos << " to " << pos+length << " - word being edited" << endl;
00162 #endif
00163         return; // not yet
00164     }
00165 
00166     KoTextStringChar *ch = parag->at( pos );
00167     KoTextFormat format( *ch->format() );
00168     format.setMisspelled( misspelled );
00169 #ifdef DEBUG_BGSPELLCHECKING
00170     kdDebug(32500) << "markWord: changing mark from " << pos << " length=" << length << " misspelled=" << misspelled << endl;
00171 #endif
00172     parag->setFormat( pos, length, &format, true, KoTextFormat::Misspelled );
00173     parag->setChanged( true );
00174     // don't repaint here, in the slotParagraphModified case we want to repaint only once at the end
00175 }
00176 
00177 void KoBgSpellCheck::checkerContinue()
00178 {
00179     if(enabled())
00180         d->backSpeller->continueChecking();
00181 }
00182 
00183 void KoBgSpellCheck::spellCheckerDone()
00184 {
00185     d->startupChecking = false;
00186 
00187     if ( d->paragCache.isEmpty() )
00188         return;
00189 
00190     QPtrDictIterator<KoTextParag> itr( d->paragCache );
00191     KoTextParag *parag = d->paragCache.take( itr.currentKey() );
00192 #ifdef DEBUG_BGSPELLCHECKING
00193     kdDebug(32500) << "spellCheckerDone : " << parag << ", cache = "<< d->paragCache.count() <<endl;
00194 #endif
00195     d->backSpeller->check( parag );
00196 }
00197 
00198 void KoBgSpellCheck::stop()
00199 {
00200 #ifdef DEBUG_BGSPELLCHECKING
00201   kdDebug(32500) << "KoBgSpellCheck::stopSpellChecking" << endl;
00202 #endif
00203   d->backSpeller->stop();
00204 }
00205 
00206 void KoBgSpellCheck::slotParagraphCreated( KoTextParag* parag )
00207 {
00208     parag->string()->setNeedsSpellCheck( true );
00209     if ( !enabled() )
00210         return;
00211     if ( !d->backSpeller->check( parag ) ) {
00212         d->paragCache.insert( parag, parag );
00213     }
00214 }
00215 
00216 void KoBgSpellCheck::slotParagraphModified( KoTextParag* parag, int /*ParagModifyType*/,
00217                                             int pos, int length )
00218 {
00219     parag->string()->setNeedsSpellCheck( true );
00220     if ( !enabled() )
00221         return;
00222 
00223     if ( d->backSpeller->checking() ) {
00224         d->paragCache.insert( parag, parag );
00225         return;
00226     }
00227 #ifdef DEBUG_BGSPELLCHECKING
00228     kdDebug(32500) << "Para modified " << parag << " pos = "<<pos<<", length = "<< length <<endl;
00229 #endif
00230 
00231 #if KDE_VERSION > KDE_MAKE_VERSION(3,3,0)
00232     if ( length < 10 ) {
00233         QString str = parag->string()->stringToSpellCheck();
00235         Filter filter;
00236         filter.setBuffer( str );
00237         // pos - 1 wasn't enough for the case a splitting a word into two misspelled halves
00238         filter.setCurrentPosition( QMAX( 0, pos - 2 ) );
00239         int curPos = filter.currentPosition(); // Filter adjusted it by going back to the last word
00240         //kdDebug() << "str='" << str << "' set position " << QMAX(0, pos-2) << " got back curPos=" << curPos << endl;
00241         filter.setSettings( d->backSpeller->settings() );
00242 
00243         // Tricky: KSpell2::Filter::nextWord's behavior makes the for() loop skip ignored words,
00244         // so it doesn't mark them as OK... So we need to clear the marks everywhere first.
00245         // To avoid flickering the repainting is only done once, after checking the parag.
00246         markWord( parag, curPos, parag->length() - curPos, false );
00247 
00248         for ( Word w = filter.nextWord(); !w.end; w = filter.nextWord() ) {
00249             bool misspelling = !d->backSpeller->checkWord( w.word );
00250             //kdDebug()<<"Word = \""<< w.word<< "\" , misspelled = "<<misspelling<<endl;
00251             markWord( parag, w.start, w.word.length(), misspelling );
00252         }
00253         if ( parag->hasChanged() ) // always true currently
00254             parag->document()->emitRepaintChanged();
00255 #else
00256     if ( length < 3 ) {
00257         QString word;
00258         int start;
00259         bool misspelled = !d->backSpeller->checkWordInParagraph( parag, pos,
00260                                                                  word, start );
00261         markWord( parag, start, word.length(), misspelled );
00262         parag->document()->emitRepaintChanged();
00263 #endif
00264     } else
00265     {
00266         d->backSpeller->check( parag );
00267     }
00268 }
00269 
00270 void KoBgSpellCheck::slotParagraphDeleted( KoTextParag* parag )
00271 {
00272     d->paragCache.take( parag );
00273     if ( parag == d->intraWordParag )
00274         d->intraWordParag = 0;
00275 
00276     // don't do it here, let KoTextIterator do that after adjusting itself better...
00277     //if ( parag == d->backSpeller->currentParag() )
00278     //    d->backSpeller->slotCurrentParagraphDeleted();
00279 }
00280 
00281 void KoBgSpellCheck::slotClearPara()
00282 {
00283     KoTextParag *parag = d->backSpeller->currentParag();
00284 
00285     // We remove any misspelled format from the paragraph
00286     // - otherwise we'd never notice words being ok again :)
00287     // (e.g. due to adding a word to the ignore list, not due to editing)
00288     //
00289     // TODO: do this all only if there was a format with 'misspelled' in the paragraph,
00290     // to minimize repaints
00291     KoTextStringChar *ch = parag->at( 0 );
00292     KoTextFormat format( *ch->format() );
00293     format.setMisspelled( false );
00294 #ifdef DEBUG_BGSPELLCHECKING
00295     kdDebug(32500) << "clearPara: resetting mark on paragraph " << parag->paragId() << endl;
00296 #endif
00297     parag->setFormat( 0, parag->length()-1, &format, true,
00298                       KoTextFormat::Misspelled );
00299     parag->setChanged( true );
00300     parag->document()->emitRepaintChanged();
00301 }
00302 
00303 KSpell2::Settings * KoBgSpellCheck::settings() const
00304 {
00305     return d->backSpeller->settings();
00306 }
00307 
00308 void KoBgSpellCheck::setIntraWordEditing( KoTextParag* parag, int index )
00309 {
00310     KoTextParag* oldIntraWordParag = d->intraWordParag;
00311     int oldIntraWordPosition = d->intraWordPosition;
00312 
00313     d->intraWordParag = parag;
00314     d->intraWordPosition = index;
00315 
00316     if ( oldIntraWordParag && !parag ) {
00317         // When typing a letter into an existing word and then going somewhere else,
00318         // we need to re-check that word - after moving d->intra* out of the way of course.
00319         slotParagraphModified( oldIntraWordParag, 0 /*unused*/, oldIntraWordPosition, 1 );
00320     }
00321 }
KDE Home | KDE Accessibility Home | Description of Access Keys