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