KDEUI
highlighter.cpp
Go to the documentation of this file.
00001 00023 #include "highlighter.h" 00024 #include "highlighter.moc" 00025 00026 #include "speller.h" 00027 #include "loader_p.h" 00028 #include "filter_p.h" 00029 #include "settings_p.h" 00030 00031 #include <kconfig.h> 00032 #include <kdebug.h> 00033 #include <klocale.h> 00034 #include <kmessagebox.h> 00035 00036 #include <QTextEdit> 00037 #include <QTextCharFormat> 00038 #include <QTimer> 00039 #include <QColor> 00040 #include <QHash> 00041 #include <QTextCursor> 00042 #include <QEvent> 00043 #include <QKeyEvent> 00044 #include <QApplication> 00045 00046 namespace Sonnet { 00047 00048 class Highlighter::Private 00049 { 00050 public: 00051 ~Private(); 00052 Filter *filter; 00053 Loader *loader; 00054 Speller *dict; 00055 QHash<QString, Speller*> dictCache; 00056 QTextEdit *edit; 00057 bool active; 00058 bool automatic; 00059 bool completeRehighlightRequired; 00060 bool intraWordEditing; 00061 bool spellCheckerFound; 00062 int disablePercentage; 00063 int disableWordCount; 00064 int wordCount, errorCount; 00065 QTimer *rehighlightRequest; 00066 QColor spellColor; 00067 int suggestionListeners; // #of connections for the newSuggestions signal 00068 }; 00069 00070 Highlighter::Private::~Private() 00071 { 00072 qDeleteAll(dictCache); 00073 delete filter; 00074 } 00075 00076 Highlighter::Highlighter(QTextEdit *textEdit, 00077 const QString& configFile, 00078 const QColor& _col) 00079 : QSyntaxHighlighter(textEdit), 00080 d(new Private) 00081 { 00082 d->filter = Filter::defaultFilter(); 00083 d->edit = textEdit; 00084 d->active = true; 00085 d->automatic = true; 00086 d->wordCount = 0; 00087 d->errorCount = 0; 00088 d->intraWordEditing = false; 00089 d->completeRehighlightRequired = false; 00090 d->spellCheckerFound = true; 00091 d->spellColor = _col.isValid() ? _col : Qt::red; 00092 d->suggestionListeners = 0; 00093 00094 textEdit->installEventFilter( this ); 00095 textEdit->viewport()->installEventFilter( this ); 00096 00097 d->loader = Loader::openLoader(); 00098 00099 //Do not load an empty settings file as it will cause the spellchecker to fail 00100 //if the KGlobal::locale()->language() (default value) spellchecker is not installed, 00101 //and we have a global sonnetrc file with a spellcheck lang installed which could be used. 00102 if (!configFile.isEmpty()) { 00103 KConfig conf(configFile); 00104 if (conf.hasGroup("Spelling")) { 00105 d->loader->settings()->restore(&conf); 00106 d->filter->setSettings(d->loader->settings()); 00107 } 00108 } 00109 00110 d->dict = new Sonnet::Speller(); 00111 if(!d->dict->isValid()) { 00112 d->spellCheckerFound = false; 00113 } else { 00114 d->dictCache.insert(d->dict->language(), 00115 d->dict); 00116 00117 d->disablePercentage = d->loader->settings()->disablePercentageWordError(); 00118 00119 d->disableWordCount = d->loader->settings()->disableWordErrorCount(); 00120 00121 //Add kde personal word 00122 const QStringList l = Highlighter::personalWords(); 00123 for ( QStringList::ConstIterator it = l.begin(); it != l.end(); ++it ) { 00124 d->dict->addToSession( *it ); 00125 } 00126 d->rehighlightRequest = new QTimer(this); 00127 connect( d->rehighlightRequest, SIGNAL( timeout() ), 00128 this, SLOT( slotRehighlight() )); 00129 d->completeRehighlightRequired = true; 00130 d->rehighlightRequest->setInterval(0); 00131 d->rehighlightRequest->setSingleShot(true); 00132 d->rehighlightRequest->start(); 00133 } 00134 } 00135 00136 Highlighter::~Highlighter() 00137 { 00138 delete d; 00139 } 00140 00141 bool Highlighter::spellCheckerFound() const 00142 { 00143 return d->spellCheckerFound; 00144 } 00145 00146 // Since figuring out spell correction suggestions is extremely costly, 00147 // we keep track of whether the user actually wants some, and only offer them 00148 // in that case 00149 void Highlighter::connectNotify(const char* signal) 00150 { 00151 if (QLatin1String(signal) == SIGNAL(newSuggestions(QString,QStringList))) 00152 ++d->suggestionListeners; 00153 QSyntaxHighlighter::connectNotify(signal); 00154 } 00155 00156 void Highlighter::disconnectNotify(const char* signal) 00157 { 00158 if (QLatin1String(signal) == SIGNAL(newSuggestions(QString,QStringList))) 00159 --d->suggestionListeners; 00160 QSyntaxHighlighter::disconnectNotify(signal); 00161 } 00162 00163 void Highlighter::slotRehighlight() 00164 { 00165 kDebug(0) << "Highlighter::slotRehighlight()"; 00166 if (d->completeRehighlightRequired) { 00167 d->wordCount = 0; 00168 d->errorCount = 0; 00169 rehighlight(); 00170 00171 } else { 00172 //rehighlight the current para only (undo/redo safe) 00173 QTextCursor cursor = d->edit->textCursor(); 00174 cursor.insertText( "" ); 00175 } 00176 //if (d->checksDone == d->checksRequested) 00177 //d->completeRehighlightRequired = false; 00178 QTimer::singleShot( 0, this, SLOT( slotAutoDetection() )); 00179 } 00180 00181 00182 QStringList Highlighter::personalWords() 00183 { 00184 QStringList l; 00185 l.append( "KMail" ); 00186 l.append( "KOrganizer" ); 00187 l.append( "KAddressBook" ); 00188 l.append( "KHTML" ); 00189 l.append( "KIO" ); 00190 l.append( "KJS" ); 00191 l.append( "Konqueror" ); 00192 l.append( "Sonnet" ); 00193 l.append( "Kontact" ); 00194 l.append( "Qt" ); 00195 return l; 00196 } 00197 00198 bool Highlighter::automatic() const 00199 { 00200 return d->automatic; 00201 } 00202 00203 bool Highlighter::intraWordEditing() const 00204 { 00205 return d->intraWordEditing; 00206 } 00207 00208 void Highlighter::setIntraWordEditing( bool editing ) 00209 { 00210 d->intraWordEditing = editing; 00211 } 00212 00213 00214 void Highlighter::setAutomatic( bool automatic ) 00215 { 00216 if ( automatic == d->automatic ) 00217 return; 00218 00219 d->automatic = automatic; 00220 if ( d->automatic ) 00221 slotAutoDetection(); 00222 } 00223 00224 void Highlighter::slotAutoDetection() 00225 { 00226 bool savedActive = d->active; 00227 00228 //don't disable just because 1 of 4 is misspelled. 00229 if (d->automatic && d->wordCount >= 10) { 00230 // tme = Too many errors 00231 bool tme = (d->errorCount >= d->disableWordCount) && ( 00232 d->errorCount * 100 >= d->disablePercentage * d->wordCount); 00233 if (d->active && tme) { 00234 d->active = false; 00235 } else if (!d->active && !tme) { 00236 d->active = true; 00237 } 00238 } 00239 00240 if (d->active != savedActive) { 00241 if (d->active) { 00242 emit activeChanged(i18n("As-you-type spell checking enabled.")); 00243 } else { 00244 emit activeChanged(i18n( "Too many misspelled words. " 00245 "As-you-type spell checking disabled.")); 00246 } 00247 00248 d->completeRehighlightRequired = true; 00249 d->rehighlightRequest->setInterval(100); 00250 d->rehighlightRequest->setSingleShot(true); 00251 kDebug()<<" Highlighter::slotAutoDetection :"<<d->active; 00252 } 00253 00254 } 00255 00256 void Highlighter::setActive( bool active ) 00257 { 00258 if ( active == d->active ) 00259 return; 00260 d->active = active; 00261 rehighlight(); 00262 00263 00264 if ( d->active ) 00265 emit activeChanged( i18n("As-you-type spell checking enabled.") ); 00266 else 00267 emit activeChanged( i18n("As-you-type spell checking disabled.") ); 00268 } 00269 00270 bool Highlighter::isActive() const 00271 { 00272 return d->active; 00273 } 00274 00275 void Highlighter::highlightBlock(const QString &text) 00276 { 00277 if (text.isEmpty() || !d->active || !d->spellCheckerFound) 00278 return; 00279 00280 d->filter->setBuffer( text ); 00281 Word w = d->filter->nextWord(); 00282 while ( !w.end ) { 00283 ++d->wordCount; 00284 if (d->dict->isMisspelled(w.word)) { 00285 ++d->errorCount; 00286 setMisspelled(w.start, w.word.length()); 00287 if (d->suggestionListeners) 00288 emit newSuggestions(w.word, d->dict->suggest(w.word)); 00289 } else 00290 unsetMisspelled(w.start, w.word.length()); 00291 w = d->filter->nextWord(); 00292 } 00293 //QTimer::singleShot( 0, this, SLOT(checkWords()) ); 00294 setCurrentBlockState(0); 00295 } 00296 00297 QString Highlighter::currentLanguage() const 00298 { 00299 return d->dict->language(); 00300 } 00301 00302 void Highlighter::setCurrentLanguage(const QString &lang) 00303 { 00304 if (!d->dictCache.contains(lang)) { 00305 d->dict = new Speller(*d->dict); 00306 d->dict->setLanguage(lang); 00307 if (d->dict->isValid()) { 00308 d->dictCache.insert(lang, d->dict); 00309 } else { 00310 kDebug()<<"No dictionary for \"" 00311 <<lang 00312 <<"\" staying with the current language." 00313 <<endl; 00314 return; 00315 } 00316 } 00317 d->dict = d->dictCache[lang]; 00318 d->wordCount = 0; 00319 d->errorCount = 0; 00320 if (d->automatic) 00321 slotAutoDetection(); 00322 } 00323 00324 void Highlighter::setMisspelled(int start, int count) 00325 { 00326 QTextCharFormat format; 00327 format.setFontUnderline(true); 00328 format.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); 00329 format.setUnderlineColor(d->spellColor); 00330 setFormat(start, count, format); 00331 } 00332 00333 void Highlighter::unsetMisspelled( int start, int count ) 00334 { 00335 setFormat(start, count, QTextCharFormat()); 00336 } 00337 00338 bool Highlighter::eventFilter( QObject *o, QEvent *e) 00339 { 00340 #if 0 00341 if (o == textEdit() && (e->type() == QEvent::FocusIn)) { 00342 if ( d->globalConfig ) { 00343 QString skey = spellKey(); 00344 if ( d->spell && d->spellKey != skey ) { 00345 d->spellKey = skey; 00346 KDictSpellingHighlighter::dictionaryChanged(); 00347 } 00348 } 00349 } 00350 #endif 00351 if (!d->spellCheckerFound) 00352 return false; 00353 if (o == d->edit && (e->type() == QEvent::KeyPress)) { 00354 QKeyEvent *k = static_cast<QKeyEvent *>(e); 00355 //d->autoReady = true; 00356 if (d->rehighlightRequest->isActive()) // try to stay out of the users way 00357 d->rehighlightRequest->start( 500 ); 00358 if ( k->key() == Qt::Key_Enter || 00359 k->key() == Qt::Key_Return || 00360 k->key() == Qt::Key_Up || 00361 k->key() == Qt::Key_Down || 00362 k->key() == Qt::Key_Left || 00363 k->key() == Qt::Key_Right || 00364 k->key() == Qt::Key_PageUp || 00365 k->key() == Qt::Key_PageDown || 00366 k->key() == Qt::Key_Home || 00367 k->key() == Qt::Key_End || 00368 (( k->modifiers()== Qt::ControlModifier ) && 00369 ((k->key() == Qt::Key_A) || 00370 (k->key() == Qt::Key_B) || 00371 (k->key() == Qt::Key_E) || 00372 (k->key() == Qt::Key_N) || 00373 (k->key() == Qt::Key_P))) ) { 00374 if ( intraWordEditing() ) { 00375 setIntraWordEditing( false ); 00376 d->completeRehighlightRequired = true; 00377 d->rehighlightRequest->setInterval(500); 00378 d->rehighlightRequest->setSingleShot(true); 00379 d->rehighlightRequest->start(); 00380 } 00381 #if 0 00382 if (d->checksDone != d->checksRequested) { 00383 // Handle possible change of paragraph while 00384 // words are pending spell checking 00385 d->completeRehighlightRequired = true; 00386 d->rehighlightRequest->start( 500, true ); 00387 } 00388 #endif 00389 } else { 00390 setIntraWordEditing( true ); 00391 } 00392 if ( k->key() == Qt::Key_Space || 00393 k->key() == Qt::Key_Enter || 00394 k->key() == Qt::Key_Return ) { 00395 QTimer::singleShot( 0, this, SLOT( slotAutoDetection() )); 00396 } 00397 } 00398 00399 else if ( o == d->edit->viewport() && 00400 ( e->type() == QEvent::MouseButtonPress )) { 00401 //d->autoReady = true; 00402 if ( intraWordEditing() ) { 00403 setIntraWordEditing( false ); 00404 d->completeRehighlightRequired = true; 00405 d->rehighlightRequest->setInterval(0); 00406 d->rehighlightRequest->setSingleShot(true); 00407 d->rehighlightRequest->start(); 00408 } 00409 } 00410 return false; 00411 } 00412 00413 void Highlighter::addWordToDictionary(const QString &word) 00414 { 00415 d->dict->addToPersonal(word); 00416 } 00417 00418 void Highlighter::ignoreWord(const QString &word) 00419 { 00420 d->dict->addToSession(word); 00421 } 00422 00423 QStringList Highlighter::suggestionsForWord(const QString &word, int max) 00424 { 00425 QStringList suggestions = d->dict->suggest(word); 00426 if ( max != -1 ) { 00427 while ( suggestions.count() > max ) 00428 suggestions.removeLast(); 00429 } 00430 return suggestions; 00431 } 00432 00433 bool Highlighter::isWordMisspelled(const QString &word) 00434 { 00435 return d->dict->isMisspelled(word); 00436 } 00437 00438 void Highlighter::setMisspelledColor(const QColor &color) 00439 { 00440 d->spellColor = color; 00441 } 00442 00443 bool Highlighter::checkerEnabledByDefault() const 00444 { 00445 return d->loader->settings()->checkerEnabledByDefault(); 00446 } 00447 00448 00449 }
KDE 4.6 API Reference