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