00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #include <qcolor.h>
00025 #include <qregexp.h>
00026 #include <qsyntaxhighlighter.h>
00027 #include <qtimer.h>
00028
00029 #include <klocale.h>
00030 #include <kconfig.h>
00031 #include <kdebug.h>
00032 #include <kglobal.h>
00033 #include <kspell.h>
00034 #include <kapplication.h>
00035
00036 #include "ksyntaxhighlighter.h"
00037
00038 static int dummy, dummy2, dummy3, dummy4;
00039 static int *Okay = &dummy;
00040 static int *NotOkay = &dummy2;
00041 static int *Ignore = &dummy3;
00042 static int *Unknown = &dummy4;
00043 static const int tenSeconds = 10*1000;
00044
00045 class KSyntaxHighlighter::KSyntaxHighlighterPrivate
00046 {
00047 public:
00048 QColor col1, col2, col3, col4, col5;
00049 SyntaxMode mode;
00050 bool enabled;
00051 };
00052
00053 class KSpellingHighlighter::KSpellingHighlighterPrivate
00054 {
00055 public:
00056
00057 KSpellingHighlighterPrivate() :
00058 alwaysEndsWithSpace( true ),
00059 intraWordEditing( false ) {}
00060
00061 QString currentWord;
00062 int currentPos;
00063 bool alwaysEndsWithSpace;
00064 QColor color;
00065 bool intraWordEditing;
00066 };
00067
00068 class KDictSpellingHighlighter::KDictSpellingHighlighterPrivate
00069 {
00070 public:
00071 KDictSpellingHighlighterPrivate() :
00072 mDict( 0 ),
00073 spell( 0 ),
00074 mSpellConfig( 0 ),
00075 rehighlightRequest( 0 ),
00076 wordCount( 0 ),
00077 errorCount( 0 ),
00078 autoReady( false ),
00079 globalConfig( true ),
00080 spellReady( false ) {}
00081
00082 ~KDictSpellingHighlighterPrivate() {
00083 delete rehighlightRequest;
00084 delete spell;
00085 }
00086
00087 static QDict<int>* sDict()
00088 {
00089 if (!statDict)
00090 statDict = new QDict<int>(50021);
00091 return statDict;
00092 }
00093
00094 QDict<int>* mDict;
00095 QDict<int> autoDict;
00096 QDict<int> autoIgnoreDict;
00097 static QObject *sDictionaryMonitor;
00098 KSpell *spell;
00099 KSpellConfig *mSpellConfig;
00100 QTimer *rehighlightRequest, *spellTimeout;
00101 QString spellKey;
00102 int wordCount, errorCount;
00103 int checksRequested, checksDone;
00104 int disablePercentage;
00105 int disableWordCount;
00106 bool completeRehighlightRequired;
00107 bool active, automatic, autoReady;
00108 bool globalConfig, spellReady;
00109 private:
00110 static QDict<int>* statDict;
00111
00112 };
00113
00114 QDict<int>* KDictSpellingHighlighter::KDictSpellingHighlighterPrivate::statDict = 0;
00115
00116
00117 KSyntaxHighlighter::KSyntaxHighlighter( QTextEdit *textEdit,
00118 bool colorQuoting,
00119 const QColor& depth0,
00120 const QColor& depth1,
00121 const QColor& depth2,
00122 const QColor& depth3,
00123 SyntaxMode mode )
00124 : QSyntaxHighlighter( textEdit )
00125 {
00126 d = new KSyntaxHighlighterPrivate();
00127
00128 d->enabled = colorQuoting;
00129 d->col1 = depth0;
00130 d->col2 = depth1;
00131 d->col3 = depth2;
00132 d->col4 = depth3;
00133 d->col5 = depth0;
00134
00135 d->mode = mode;
00136 }
00137
00138 KSyntaxHighlighter::~KSyntaxHighlighter()
00139 {
00140 delete d;
00141 }
00142
00143 int KSyntaxHighlighter::highlightParagraph( const QString &text, int )
00144 {
00145 if (!d->enabled) {
00146 setFormat( 0, text.length(), textEdit()->viewport()->paletteForegroundColor() );
00147 return 0;
00148 }
00149
00150 QString simplified = text;
00151 simplified = simplified.replace( QRegExp( "\\s" ), QString::null ).replace( '|', QString::fromLatin1(">") );
00152 while ( simplified.startsWith( QString::fromLatin1(">>>>") ) )
00153 simplified = simplified.mid(3);
00154 if ( simplified.startsWith( QString::fromLatin1(">>>") ) || simplified.startsWith( QString::fromLatin1("> > >") ) )
00155 setFormat( 0, text.length(), d->col2 );
00156 else if ( simplified.startsWith( QString::fromLatin1(">>") ) || simplified.startsWith( QString::fromLatin1("> >") ) )
00157 setFormat( 0, text.length(), d->col3 );
00158 else if ( simplified.startsWith( QString::fromLatin1(">") ) )
00159 setFormat( 0, text.length(), d->col4 );
00160 else
00161 setFormat( 0, text.length(), d->col5 );
00162 return 0;
00163 }
00164
00165 KSpellingHighlighter::KSpellingHighlighter( QTextEdit *textEdit,
00166 const QColor& spellColor,
00167 bool colorQuoting,
00168 const QColor& depth0,
00169 const QColor& depth1,
00170 const QColor& depth2,
00171 const QColor& depth3 )
00172 : KSyntaxHighlighter( textEdit, colorQuoting, depth0, depth1, depth2, depth3 )
00173 {
00174 d = new KSpellingHighlighterPrivate();
00175
00176 d->color = spellColor;
00177 }
00178
00179 KSpellingHighlighter::~KSpellingHighlighter()
00180 {
00181 delete d;
00182 }
00183
00184 int KSpellingHighlighter::highlightParagraph( const QString &text,
00185 int paraNo )
00186 {
00187 if ( paraNo == -2 )
00188 paraNo = 0;
00189
00190 QString diffAndCo( ">|" );
00191
00192 bool isCode = diffAndCo.find(text[0]) != -1;
00193
00194 if ( !text.endsWith(" ") )
00195 d->alwaysEndsWithSpace = false;
00196
00197 KSyntaxHighlighter::highlightParagraph( text, -2 );
00198
00199 if ( !isCode ) {
00200 int para, index;
00201 textEdit()->getCursorPosition( ¶, &index );
00202 int len = text.length();
00203 if ( d->alwaysEndsWithSpace )
00204 len--;
00205
00206 d->currentPos = 0;
00207 d->currentWord = "";
00208 for ( int i = 0; i < len; i++ ) {
00209 if ( !text[i].isLetter() && (!(text[i] == '\'')) ) {
00210 if ( ( para != paraNo ) ||
00211 !intraWordEditing() ||
00212 ( i - d->currentWord.length() > (uint)index ) ||
00213 ( i < index ) ) {
00214 flushCurrentWord();
00215 } else {
00216 d->currentWord = "";
00217 }
00218 d->currentPos = i + 1;
00219 } else {
00220 d->currentWord += text[i];
00221 }
00222 }
00223 if ( !text[len - 1].isLetter() ||
00224 (uint)( index + 1 ) != text.length() ||
00225 para != paraNo )
00226 flushCurrentWord();
00227 }
00228 return ++paraNo;
00229 }
00230
00231 QStringList KSpellingHighlighter::personalWords()
00232 {
00233 QStringList l;
00234 l.append( "KMail" );
00235 l.append( "KOrganizer" );
00236 l.append( "KAddressBook" );
00237 l.append( "KHTML" );
00238 l.append( "KIO" );
00239 l.append( "KJS" );
00240 l.append( "Konqueror" );
00241 l.append( "KSpell" );
00242 l.append( "Kontact" );
00243 l.append( "Qt" );
00244 return l;
00245 }
00246
00247 void KSpellingHighlighter::flushCurrentWord()
00248 {
00249 while ( d->currentWord[0].isPunct() ) {
00250 d->currentWord = d->currentWord.mid( 1 );
00251 d->currentPos++;
00252 }
00253
00254 QChar ch;
00255 while ( ( ch = d->currentWord[(int) d->currentWord.length() - 1] ).isPunct() &&
00256 ch != '(' && ch != '@' )
00257 d->currentWord.truncate( d->currentWord.length() - 1 );
00258
00259 if ( !d->currentWord.isEmpty() ) {
00260 if ( isMisspelled( d->currentWord ) ) {
00261 setFormat( d->currentPos, d->currentWord.length(), d->color );
00262
00263 }
00264 }
00265 d->currentWord = "";
00266 }
00267
00268 QObject *KDictSpellingHighlighter::KDictSpellingHighlighterPrivate::sDictionaryMonitor = 0;
00269
00270 KDictSpellingHighlighter::KDictSpellingHighlighter( QTextEdit *textEdit,
00271 bool spellCheckingActive ,
00272 bool autoEnable,
00273 const QColor& spellColor,
00274 bool colorQuoting,
00275 const QColor& depth0,
00276 const QColor& depth1,
00277 const QColor& depth2,
00278 const QColor& depth3,
00279 KSpellConfig *spellConfig )
00280 : KSpellingHighlighter( textEdit, spellColor,
00281 colorQuoting, depth0, depth1, depth2, depth3 )
00282 {
00283 d = new KDictSpellingHighlighterPrivate();
00284
00285 d->mSpellConfig = spellConfig;
00286 d->globalConfig = ( !spellConfig );
00287 d->automatic = autoEnable;
00288 d->active = spellCheckingActive;
00289 d->checksRequested = 0;
00290 d->checksDone = 0;
00291 d->completeRehighlightRequired = false;
00292
00293 KConfig *config = KGlobal::config();
00294 KConfigGroupSaver cs( config, "KSpell" );
00295 d->disablePercentage = config->readNumEntry( "KSpell_AsYouTypeDisablePercentage", 42 );
00296 d->disablePercentage = QMIN( d->disablePercentage, 101 );
00297 d->disableWordCount = config->readNumEntry( "KSpell_AsYouTypeDisableWordCount", 100 );
00298
00299 textEdit->installEventFilter( this );
00300 textEdit->viewport()->installEventFilter( this );
00301
00302 d->rehighlightRequest = new QTimer(this);
00303 connect( d->rehighlightRequest, SIGNAL( timeout() ),
00304 this, SLOT( slotRehighlight() ));
00305 d->spellTimeout = new QTimer(this);
00306 connect( d->spellTimeout, SIGNAL( timeout() ),
00307 this, SLOT( slotKSpellNotResponding() ));
00308
00309 if ( d->globalConfig ) {
00310 d->spellKey = spellKey();
00311
00312 if ( !d->sDictionaryMonitor )
00313 d->sDictionaryMonitor = new QObject();
00314 }
00315 else {
00316 d->mDict = new QDict<int>(4001);
00317 connect( d->mSpellConfig, SIGNAL( configChanged() ),
00318 this, SLOT( slotLocalSpellConfigChanged() ) );
00319 }
00320
00321 slotDictionaryChanged();
00322
00323
00324 }
00325
00326 KDictSpellingHighlighter::~KDictSpellingHighlighter()
00327 {
00328 delete d->spell;
00329 d->spell = 0;
00330 delete d->mDict;
00331 d->mDict = 0;
00332 delete d;
00333 }
00334
00335 void KDictSpellingHighlighter::slotSpellReady( KSpell *spell )
00336 {
00337 kdDebug(0) << "KDictSpellingHighlighter::slotSpellReady( " << spell << " )" << endl;
00338 KConfigGroup cg( KGlobal::config(),"KSpell" );
00339 if ( cg.readEntry("KSpell_DoSpellChecking") != "0" )
00340 {
00341 if ( d->globalConfig ) {
00342 connect( d->sDictionaryMonitor, SIGNAL( destroyed()),
00343 this, SLOT( slotDictionaryChanged() ));
00344 }
00345 if ( spell != d->spell )
00346 {
00347 delete d->spell;
00348 d->spell = spell;
00349 }
00350 d->spellReady = true;
00351 const QStringList l = KSpellingHighlighter::personalWords();
00352 for ( QStringList::ConstIterator it = l.begin(); it != l.end(); ++it ) {
00353 d->spell->addPersonal( *it );
00354 }
00355 connect( spell, SIGNAL( misspelling( const QString &, const QStringList &, unsigned int )),
00356 this, SLOT( slotMisspelling( const QString &, const QStringList &, unsigned int )));
00357 connect( spell, SIGNAL( corrected( const QString &, const QString &, unsigned int )),
00358 this, SLOT( slotCorrected( const QString &, const QString &, unsigned int )));
00359 d->checksRequested = 0;
00360 d->checksDone = 0;
00361 d->completeRehighlightRequired = true;
00362 d->rehighlightRequest->start( 0, true );
00363 }
00364 }
00365
00366 bool KDictSpellingHighlighter::isMisspelled( const QString &word )
00367 {
00368 if (!d->spellReady)
00369 return false;
00370
00371
00372
00373
00374
00375
00376
00377 if ( !d->autoReady )
00378 d->autoIgnoreDict.replace( word, Ignore );
00379
00380
00381 QDict<int>* dict = ( d->globalConfig ? d->sDict() : d->mDict );
00382 if ( !dict->isEmpty() && (*dict)[word] == NotOkay ) {
00383 if ( d->autoReady && ( d->autoDict[word] != NotOkay )) {
00384 if ( !d->autoIgnoreDict[word] )
00385 ++d->errorCount;
00386 d->autoDict.replace( word, NotOkay );
00387 }
00388
00389 return d->active;
00390 }
00391 if ( !dict->isEmpty() && (*dict)[word] == Okay ) {
00392 if ( d->autoReady && !d->autoDict[word] ) {
00393 d->autoDict.replace( word, Okay );
00394 }
00395 return false;
00396 }
00397
00398 if ((dict->isEmpty() || !((*dict)[word])) && d->spell ) {
00399 int para, index;
00400 textEdit()->getCursorPosition( ¶, &index );
00401 ++d->wordCount;
00402 dict->replace( word, Unknown );
00403 ++d->checksRequested;
00404 if (currentParagraph() != para)
00405 d->completeRehighlightRequired = true;
00406 d->spellTimeout->start( tenSeconds, true );
00407 d->spell->checkWord( word, false );
00408 }
00409 return false;
00410 }
00411
00412 bool KSpellingHighlighter::intraWordEditing() const
00413 {
00414 return d->intraWordEditing;
00415 }
00416
00417 void KSpellingHighlighter::setIntraWordEditing( bool editing )
00418 {
00419 d->intraWordEditing = editing;
00420 }
00421
00422 void KDictSpellingHighlighter::slotMisspelling (const QString &originalWord, const QStringList &suggestions,
00423 unsigned int pos)
00424 {
00425 Q_UNUSED( suggestions );
00426
00427 if ( d->globalConfig )
00428 d->sDict()->replace( originalWord, NotOkay );
00429 else
00430 d->mDict->replace( originalWord, NotOkay );
00431
00432
00433
00434 emit newSuggestions( originalWord, suggestions, pos );
00435 }
00436
00437 void KDictSpellingHighlighter::slotCorrected(const QString &word,
00438 const QString &,
00439 unsigned int)
00440
00441 {
00442 QDict<int>* dict = ( d->globalConfig ? d->sDict() : d->mDict );
00443 if ( !dict->isEmpty() && (*dict)[word] == Unknown ) {
00444 dict->replace( word, Okay );
00445 }
00446 ++d->checksDone;
00447 if (d->checksDone == d->checksRequested) {
00448 d->spellTimeout->stop();
00449 slotRehighlight();
00450 } else {
00451 d->spellTimeout->start( tenSeconds, true );
00452 }
00453 }
00454
00455 void KDictSpellingHighlighter::dictionaryChanged()
00456 {
00457 QObject *oldMonitor = KDictSpellingHighlighterPrivate::sDictionaryMonitor;
00458 KDictSpellingHighlighterPrivate::sDictionaryMonitor = new QObject();
00459 KDictSpellingHighlighterPrivate::sDict()->clear();
00460 delete oldMonitor;
00461 }
00462
00463 void KDictSpellingHighlighter::restartBackgroundSpellCheck()
00464 {
00465 kdDebug(0) << "KDictSpellingHighlighter::restartBackgroundSpellCheck()" << endl;
00466 slotDictionaryChanged();
00467 }
00468
00469 void KDictSpellingHighlighter::setActive( bool active )
00470 {
00471 if ( active == d->active )
00472 return;
00473
00474 d->active = active;
00475 rehighlight();
00476 if ( d->active )
00477 emit activeChanged( i18n("As-you-type spell checking enabled.") );
00478 else
00479 emit activeChanged( i18n("As-you-type spell checking disabled.") );
00480 }
00481
00482 bool KDictSpellingHighlighter::isActive() const
00483 {
00484 return d->active;
00485 }
00486
00487 void KDictSpellingHighlighter::setAutomatic( bool automatic )
00488 {
00489 if ( automatic == d->automatic )
00490 return;
00491
00492 d->automatic = automatic;
00493 if ( d->automatic )
00494 slotAutoDetection();
00495 }
00496
00497 bool KDictSpellingHighlighter::automatic() const
00498 {
00499 return d->automatic;
00500 }
00501
00502 void KDictSpellingHighlighter::slotRehighlight()
00503 {
00504 kdDebug(0) << "KDictSpellingHighlighter::slotRehighlight()" << endl;
00505 if (d->completeRehighlightRequired) {
00506 rehighlight();
00507 } else {
00508 int para, index;
00509 textEdit()->getCursorPosition( ¶, &index );
00510
00511 bool modified = textEdit()->isModified();
00512 textEdit()->insertAt( "", para, index );
00513 textEdit()->setModified( modified );
00514 }
00515 if (d->checksDone == d->checksRequested)
00516 d->completeRehighlightRequired = false;
00517 QTimer::singleShot( 0, this, SLOT( slotAutoDetection() ));
00518 }
00519
00520 void KDictSpellingHighlighter::slotDictionaryChanged()
00521 {
00522 delete d->spell;
00523 d->spellReady = false;
00524 d->wordCount = 0;
00525 d->errorCount = 0;
00526 d->autoDict.clear();
00527
00528 d->spell = new KSpell( 0, i18n( "Incremental Spellcheck" ), this,
00529 SLOT( slotSpellReady( KSpell * ) ), d->mSpellConfig );
00530 }
00531
00532 void KDictSpellingHighlighter::slotLocalSpellConfigChanged()
00533 {
00534 kdDebug(0) << "KDictSpellingHighlighter::slotSpellConfigChanged()" << endl;
00535
00536 d->mDict->clear();
00537 slotDictionaryChanged();
00538 }
00539
00540 QString KDictSpellingHighlighter::spellKey()
00541 {
00542 KConfig *config = KGlobal::config();
00543 KConfigGroupSaver cs( config, "KSpell" );
00544 config->reparseConfiguration();
00545 QString key;
00546 key += QString::number( config->readNumEntry( "KSpell_NoRootAffix", 0 ));
00547 key += '/';
00548 key += QString::number( config->readNumEntry( "KSpell_RunTogether", 0 ));
00549 key += '/';
00550 key += config->readEntry( "KSpell_Dictionary", "" );
00551 key += '/';
00552 key += QString::number( config->readNumEntry( "KSpell_DictFromList", false ));
00553 key += '/';
00554 key += QString::number( config->readNumEntry( "KSpell_Encoding", KS_E_ASCII ));
00555 key += '/';
00556 key += QString::number( config->readNumEntry( "KSpell_Client", KS_CLIENT_ISPELL ));
00557 return key;
00558 }
00559
00560
00561
00562
00563
00564
00565
00566
00567
00568 void KDictSpellingHighlighter::slotAutoDetection()
00569 {
00570 if ( !d->autoReady )
00571 return;
00572
00573 bool savedActive = d->active;
00574
00575 if ( d->automatic ) {
00576
00577 bool tme = d->wordCount >= d->disableWordCount && d->errorCount * 100 >= d->disablePercentage * d->wordCount;
00578 if ( d->active && tme )
00579 d->active = false;
00580 else if ( !d->active && !tme )
00581 d->active = true;
00582 }
00583 if ( d->active != savedActive ) {
00584 if ( d->wordCount > 1 )
00585 if ( d->active )
00586 emit activeChanged( i18n("As-you-type spell checking enabled.") );
00587 else
00588 emit activeChanged( i18n( "Too many misspelled words. "
00589 "As-you-type spell checking disabled." ) );
00590 d->completeRehighlightRequired = true;
00591 d->rehighlightRequest->start( 100, true );
00592 }
00593 }
00594
00595 void KDictSpellingHighlighter::slotKSpellNotResponding()
00596 {
00597 static int retries = 0;
00598 if (retries < 10) {
00599 if ( d->globalConfig )
00600 KDictSpellingHighlighter::dictionaryChanged();
00601 else
00602 slotLocalSpellConfigChanged();
00603 } else {
00604 setAutomatic( false );
00605 setActive( false );
00606 }
00607 ++retries;
00608 }
00609
00610 bool KDictSpellingHighlighter::eventFilter( QObject *o, QEvent *e)
00611 {
00612 if (o == textEdit() && (e->type() == QEvent::FocusIn)) {
00613 if ( d->globalConfig ) {
00614 QString skey = spellKey();
00615 if ( d->spell && d->spellKey != skey ) {
00616 d->spellKey = skey;
00617 KDictSpellingHighlighter::dictionaryChanged();
00618 }
00619 }
00620 }
00621
00622 if (o == textEdit() && (e->type() == QEvent::KeyPress)) {
00623 QKeyEvent *k = static_cast<QKeyEvent *>(e);
00624 d->autoReady = true;
00625 if (d->rehighlightRequest->isActive())
00626 d->rehighlightRequest->changeInterval( 500 );
00627 if ( k->key() == Key_Enter ||
00628 k->key() == Key_Return ||
00629 k->key() == Key_Up ||
00630 k->key() == Key_Down ||
00631 k->key() == Key_Left ||
00632 k->key() == Key_Right ||
00633 k->key() == Key_PageUp ||
00634 k->key() == Key_PageDown ||
00635 k->key() == Key_Home ||
00636 k->key() == Key_End ||
00637 (( k->state() & ControlButton ) &&
00638 ((k->key() == Key_A) ||
00639 (k->key() == Key_B) ||
00640 (k->key() == Key_E) ||
00641 (k->key() == Key_N) ||
00642 (k->key() == Key_P))) ) {
00643 if ( intraWordEditing() ) {
00644 setIntraWordEditing( false );
00645 d->completeRehighlightRequired = true;
00646 d->rehighlightRequest->start( 500, true );
00647 }
00648 if (d->checksDone != d->checksRequested) {
00649
00650
00651 d->completeRehighlightRequired = true;
00652 d->rehighlightRequest->start( 500, true );
00653 }
00654 } else {
00655 setIntraWordEditing( true );
00656 }
00657 if ( k->key() == Key_Space ||
00658 k->key() == Key_Enter ||
00659 k->key() == Key_Return ) {
00660 QTimer::singleShot( 0, this, SLOT( slotAutoDetection() ));
00661 }
00662 }
00663
00664 else if ( o == textEdit()->viewport() &&
00665 ( e->type() == QEvent::MouseButtonPress )) {
00666 d->autoReady = true;
00667 if ( intraWordEditing() ) {
00668 setIntraWordEditing( false );
00669 d->completeRehighlightRequired = true;
00670 d->rehighlightRequest->start( 0, true );
00671 }
00672 }
00673
00674 return false;
00675 }
00676
00677 #include "ksyntaxhighlighter.moc"