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