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     d->backSpeller->continueChecking();
00180 }
00181 
00182 void KoBgSpellCheck::spellCheckerDone()
00183 {
00184     d->startupChecking = false;
00185 
00186     if ( d->paragCache.isEmpty() )
00187         return;
00188 
00189     QPtrDictIterator<KoTextParag> itr( d->paragCache );
00190     KoTextParag *parag = d->paragCache.take( itr.currentKey() );
00191 #ifdef DEBUG_BGSPELLCHECKING
00192     kdDebug(32500) << "spellCheckerDone : " << parag << ", cache = "<< d->paragCache.count() <<endl;
00193 #endif
00194     d->backSpeller->check( parag );
00195 }
00196 
00197 void KoBgSpellCheck::stop()
00198 {
00199 #ifdef DEBUG_BGSPELLCHECKING
00200   kdDebug(32500) << "KoBgSpellCheck::stopSpellChecking" << endl;
00201 #endif
00202   d->backSpeller->stop();
00203 }
00204 
00205 void KoBgSpellCheck::slotParagraphCreated( KoTextParag* parag )
00206 {
00207     parag->string()->setNeedsSpellCheck( true );
00208     if ( !enabled() )
00209         return;
00210     if ( !d->backSpeller->check( parag ) ) {
00211         d->paragCache.insert( parag, parag );
00212     }
00213 }
00214 
00215 void KoBgSpellCheck::slotParagraphModified( KoTextParag* parag, int /*ParagModifyType*/,
00216                                             int pos, int length )
00217 {
00218     parag->string()->setNeedsSpellCheck( true );
00219     if ( !enabled() )
00220         return;
00221 
00222     if ( d->backSpeller->checking() ) {
00223         d->paragCache.insert( parag, parag );
00224         return;
00225     }
00226 #ifdef DEBUG_BGSPELLCHECKING
00227     kdDebug(32500) << "Para modified " << parag << " pos = "<<pos<<", length = "<< length <<endl;
00228 #endif
00229 
00230 #if KDE_VERSION > KDE_MAKE_VERSION(3,3,0)
00231     if ( length < 10 ) {
00232         QString str = parag->string()->stringToSpellCheck();
00234         Filter filter;
00235         filter.setBuffer( str );
00236         // pos - 1 wasn't enough for the case a splitting a word into two misspelled halves
00237         filter.setCurrentPosition( QMAX( 0, pos - 2 ) );
00238         int curPos = filter.currentPosition(); // Filter adjusted it by going back to the last word
00239         //kdDebug() << "str='" << str << "' set position " << QMAX(0, pos-2) << " got back curPos=" << curPos << endl;
00240         filter.setSettings( d->backSpeller->settings() );
00241 
00242         // Tricky: KSpell2::Filter::nextWord's behavior makes the for() loop skip ignored words,
00243         // so it doesn't mark them as OK... So we need to clear the marks everywhere first.
00244         // To avoid flickering the repainting is only done once, after checking the parag.
00245         markWord( parag, curPos, parag->length() - curPos, false );
00246 
00247         for ( Word w = filter.nextWord(); !w.end; w = filter.nextWord() ) {
00248             bool misspelling = !d->backSpeller->checkWord( w.word );
00249             //kdDebug()<<"Word = \""<< w.word<< "\" , misspelled = "<<misspelling<<endl;
00250             markWord( parag, w.start, w.word.length(), misspelling );
00251         }
00252         if ( parag->hasChanged() ) // always true currently
00253             parag->document()->emitRepaintChanged();
00254 #else
00255     if ( length < 3 ) {
00256         QString word;
00257         int start;
00258         bool misspelled = !d->backSpeller->checkWordInParagraph( parag, pos,
00259                                                                  word, start );
00260         markWord( parag, start, word.length(), misspelled );
00261         parag->document()->emitRepaintChanged();
00262 #endif
00263     } else
00264     {
00265         d->backSpeller->check( parag );
00266     }
00267 }
00268 
00269 void KoBgSpellCheck::slotParagraphDeleted( KoTextParag* parag )
00270 {
00271     d->paragCache.take( parag );
00272     if ( parag == d->intraWordParag )
00273         d->intraWordParag = 0;
00274 
00275     // don't do it here, let KoTextIterator do that after adjusting itself better...
00276     //if ( parag == d->backSpeller->currentParag() )
00277     //    d->backSpeller->slotCurrentParagraphDeleted();
00278 }
00279 
00280 void KoBgSpellCheck::slotClearPara()
00281 {
00282     KoTextParag *parag = d->backSpeller->currentParag();
00283 
00284     // We remove any misspelled format from the paragraph
00285     // - otherwise we'd never notice words being ok again :)
00286     // (e.g. due to adding a word to the ignore list, not due to editing)
00287     //
00288     // TODO: do this all only if there was a format with 'misspelled' in the paragraph,
00289     // to minimize repaints
00290     KoTextStringChar *ch = parag->at( 0 );
00291     KoTextFormat format( *ch->format() );
00292     format.setMisspelled( false );
00293 #ifdef DEBUG_BGSPELLCHECKING
00294     kdDebug(32500) << "clearPara: resetting mark on paragraph " << parag->paragId() << endl;
00295 #endif
00296     parag->setFormat( 0, parag->length()-1, &format, true,
00297                       KoTextFormat::Misspelled );
00298     parag->setChanged( true );
00299     parag->document()->emitRepaintChanged();
00300 }
00301 
00302 KSpell2::Settings * KoBgSpellCheck::settings() const
00303 {
00304     return d->backSpeller->settings();
00305 }
00306 
00307 void KoBgSpellCheck::setIntraWordEditing( KoTextParag* parag, int index )
00308 {
00309     KoTextParag* oldIntraWordParag = d->intraWordParag;
00310     int oldIntraWordPosition = d->intraWordPosition;
00311 
00312     d->intraWordParag = parag;
00313     d->intraWordPosition = index;
00314 
00315     if ( oldIntraWordParag && !parag ) {
00316         // When typing a letter into an existing word and then going somewhere else,
00317         // we need to re-check that word - after moving d->intra* out of the way of course.
00318         slotParagraphModified( oldIntraWordParag, 0 /*unused*/, oldIntraWordPosition, 1 );
00319     }
00320 }
KDE Home | KDE Accessibility Home | Description of Access Keys