summaryrefslogtreecommitdiffstats
path: root/kbabel/kbabel/kbhighlighting.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kbabel/kbabel/kbhighlighting.cpp')
-rw-r--r--kbabel/kbabel/kbhighlighting.cpp316
1 files changed, 316 insertions, 0 deletions
diff --git a/kbabel/kbabel/kbhighlighting.cpp b/kbabel/kbabel/kbhighlighting.cpp
new file mode 100644
index 00000000..ce58c483
--- /dev/null
+++ b/kbabel/kbabel/kbhighlighting.cpp
@@ -0,0 +1,316 @@
+/* ****************************************************************************
+ This file is part of KBabel
+
+ Copyright (C) 2002 by Marco Wegner <mail@marcowegner.de>
+ 2003 Trolltech AS
+ 2003 Lukas Tinkl <lukas@kde.org>
+ 2003-2005 Stanislav Visnovsky <visnovsky@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+
+**************************************************************************** */
+
+
+#include <qcolor.h>
+#include <qregexp.h>
+#include <qstring.h>
+#include <qtextedit.h>
+
+#include "kapplication.h"
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kglobalsettings.h>
+#include <kbabelsettings.h>
+#include <kspell.h>
+
+#include "kbhighlighting.h"
+#include "resources.h"
+
+KBabelHighlighter::KBabelHighlighter( QTextEdit * edit, KSpell *spell ) : QObject()
+ , _edit( edit )
+ , syntaxHighlighting(true)
+ , mSpell(0), alwaysEndsWithSpace(true)
+{
+ regexps << "(<[_:A-Za-z][-_.:A-Za-z0-9]*([\\s]*[_:A-Za-z][-_.:A-Za-z0-9]*=\\\\\"[^<>]*\\\\\")*[\\s]*/?>)|(</[_:A-Za-z][-_.:A-Za-z0-9]*[\\s]*>)";
+ regexps << "(&[A-Za-z_:][A-Za-z0-9_.:-]*;)";
+ regexps << "(%[\\ddioxXucsfeEgGphln]+)|(%\\d+\\$[dioxXucsfeEgGphln])";
+ regexps << "(\\\\[abfnrtv'\"\?\\\\])|(\\\\\\d+)|(\\\\x[\\dabcdef]+)";
+
+ colors.resize( 8 );
+ colors[Normal] = KGlobalSettings::textColor();
+ colors[Tag] = KBabelSettings::tagColor ();
+ colors[Entity] = KBabelSettings::tagColor ();
+ colors[CFormat] = KBabelSettings::cformatColor ();
+ colors[Masked] = KBabelSettings::quotedColor ();
+ colors[Accel] = KBabelSettings::accelColor ();
+ colors[Error] = KBabelSettings::errorColor ();
+ colors[SpellcheckError] = KBabelSettings::spellcheckErrorColor ();
+
+ _hasErrors = false;
+
+ readSettings( );
+ regexps << accelMarker + "[\\w]";
+
+ connect( _edit, SIGNAL( textChanged( ) ), this, SLOT( highlight( ) ) );
+
+ setSpellChecker(spell);
+}
+
+// spell check the current text and highlight (as red text) those words
+// that fail the spell check
+void KBabelHighlighter::highlight( )
+{
+ // no updates while we're highlighting
+ _edit->blockSignals( true );
+ _edit->setUpdatesEnabled( false );
+
+ // store cursor position
+ int cpara, cindex;
+ _edit->getCursorPosition( &cpara, &cindex );
+
+ _edit->selectAll( );
+ _edit->setColor( _hasErrors ? colors[Error] : colors[Normal] );
+ _edit->removeSelection( );
+
+ // create a single line out of the text: remove "\n", so that we only
+ // have to deal with one single line of text.
+ QString text = _edit->text( );
+ text.replace( "\n", "" );
+
+ QRegExp rx;
+ int pos;
+
+ if (syntaxHighlighting)
+ {
+ for ( uint i = 0; i < regexps.count( ); ++i ) {
+ rx.setPattern( regexps[i] );
+ pos = text.find( rx );
+ while ( pos >= 0 ) {
+ doHighlighting( (HighlightType)(i+1), pos, rx.matchedLength( ) );
+ pos = text.find( rx, pos + rx.matchedLength( ) );
+ }
+ }
+ }
+
+ if( mSpell )
+ {
+ // spell-on-fly start
+ if (!text.endsWith(" "))
+ alwaysEndsWithSpace = false;
+ else
+ alwaysEndsWithSpace = true;
+
+ //MessageHighlighter::highlightParagraph( text, endStateOfLastPara );
+
+ int len = text.length();
+ if (alwaysEndsWithSpace)
+ len--;
+
+ currentPos = 0;
+ currentWord = "";
+ for (int i = 0; i < len; i++) {
+ if (text[i].isSpace() || text[i] == '-' || (text[i]=='\\' && text[i+1]=='n')) {
+ flushCurrentWord();
+ if (text[i]=='\\') i++;
+ currentPos = i + 1;
+ } else {
+ currentWord += text[i];
+ }
+ }
+
+ // this was if (!text[len - 1].isLetter())
+ // but then the last word is never checked
+ if (text[len - 1].isLetter()) {
+ kdDebug(KBABEL) << "flushing last Current word: " << currentWord << endl;
+ flushCurrentWord();
+ }
+ else {
+ kdDebug(KBABEL) << "not flushing last Current word: " << currentWord << endl;
+ }
+ } // spell-on-fly end
+
+ _edit->setColor( colors[Normal] );
+
+ //restore cursor position
+ _edit->setCursorPosition( cpara, cindex );
+
+ // allow updates again now that we're finished highlighting
+ _edit->setUpdatesEnabled( true );
+ _edit->blockSignals( false );
+ _edit->updateContents( );
+ _edit->ensureCursorVisible();
+}
+
+void KBabelHighlighter::doHighlighting( HighlightType type, int pos, int length )
+{
+ uint startPara = 0, endPara = 0, startIndex = pos, endIndex = pos+length;
+
+ // transform the one-dimensional indexes into two-dimensional ones
+ while ( startIndex > _edit->text( startPara ).length( ) )
+ startIndex -= _edit->text( startPara++ ).length( ) - 1;
+ while ( endIndex > _edit->text( endPara ).length( ) )
+ endIndex -= _edit->text( endPara++ ).length( ) - 1;
+
+ // and finally do the actual highlighting
+ _edit->setSelection( startPara, startIndex, endPara, endIndex );
+ _edit->setColor( colors[type] );
+ _edit->removeSelection( );
+}
+
+void KBabelHighlighter::setHighlightColor( HighlightType type, QColor color )
+{
+ colors[type] = color;
+}
+
+void KBabelHighlighter::setHasErrors( bool err )
+{
+ _hasErrors = err;
+}
+
+void KBabelHighlighter::readSettings( )
+{
+ // FIXME: does not care about different projects yet
+ KConfig * config = KGlobal::config( );
+ config->setGroup( "Misc" );
+ QString temp = config->readEntry( "AccelMarker", "&" );
+ accelMarker = temp[0];
+}
+
+static int dummy, dummy2;
+static int *Okay = &dummy;
+static int *NotOkay = &dummy2;
+
+void KBabelHighlighter::flushCurrentWord()
+{
+ while (currentWord[0].isPunct()) {
+ currentWord = currentWord.mid(1);
+ currentPos++;
+ }
+
+ QChar ch;
+ while ((ch = currentWord[(int) currentWord.length() - 1]).isPunct()
+ && ch != '(' && ch != '@')
+ currentWord.truncate( currentWord.length() - 1 );
+
+ // try to remove tags (they might not be fully compliant, but
+ // we don't want to check them anyway
+ QRegExp tags("(<[-_.:A-Za-z0-9]*([\\s]*[-_.:A-Za-z0-9]*=\\\\\"[^<>]*\\\\\")*[\\s]*/?>)|(</[-_.:A-Za-z0-9]*[\\s]*>)");
+ if( tags.search (currentWord) != -1 )
+ {
+ currentPos += tags.matchedLength();
+ }
+
+ currentWord.replace ( tags, "" );
+
+ if (!currentWord.isEmpty()) {
+ bool isPlainWord = true;
+ for (int i = 0; i < (int) currentWord.length(); i++) {
+ QChar ch = currentWord[i];
+ if (ch.upper() == ch) {
+ isPlainWord = false;
+ break;
+ }
+ }
+
+ if (/*isPlainWord && currentWord.length() > 2 &&*/ isMisspelled(currentWord))
+ doHighlighting(SpellcheckError,currentPos, currentWord.length());
+ }
+ currentWord = "";
+}
+
+QDict<int> KBabelHighlighter::dict(50021);
+
+bool KBabelHighlighter::isMisspelled(const QString& wordRaw)
+{
+ // We have to treat ampersands (like in "&go" or "g&o") in a special way.
+ // they must not break the word. And we cannot change the parameter, as
+ // then the highlight would be one character short. So we have to copy the
+ // word first.
+ QString word = wordRaw;
+ kdDebug(KBABEL) << "isampersand: checking (raw):" << word << endl;
+ word.replace("&", "" );
+ kdDebug(KBABEL) << "isMisspelled: checking: " << word << endl;
+
+ // Normally isMisspelled would look up a dictionary and return
+ // true or false, but kspell is asynchronous and slow so things
+ // get tricky...
+
+ // "dict" is used as a cache to store the results of KSpell
+ if (!dict.isEmpty() && dict[word] == NotOkay)
+ return true;
+ if (!dict.isEmpty() && dict[word] == Okay)
+ return false;
+
+ // there is no 'spelt correctly' signal so default to Okay
+ kdDebug(KBABEL) << "Adding word " << word << endl;
+ dict.replace(word, Okay);
+ mSpell->checkWord(word, false);
+ return false;
+}
+
+void KBabelHighlighter::slotMisspelling(const QString & originalword,
+ const QStringList & suggestions, unsigned int)
+{
+ kdDebug(KBABEL) << "Misspelled " << originalword << ", " << suggestions << endl;
+ dict.replace( originalword, NotOkay );
+
+ // this is slow but since kspell is async this will have to do for now
+ highlight();
+}
+
+void KBabelHighlighter::setSpellChecker( KSpell* spell )
+{
+ if( mSpell )
+ {
+ disconnect(mSpell, SIGNAL(misspelling(const QString &, const QStringList &, unsigned int)),
+ this, SLOT(slotMisspelling(const QString &, const QStringList &, unsigned int)));
+
+ // cleanup the cache
+ dict.clear();
+ }
+
+ mSpell = spell;
+
+ if( mSpell )
+ {
+ connect(mSpell, SIGNAL(misspelling(const QString &, const QStringList &, unsigned int)),
+ this, SLOT(slotMisspelling(const QString &, const QStringList &, unsigned int)));
+
+ // wait for KSpell to startup correctly
+ kapp->processEvents(500);
+ }
+
+ highlight();
+}
+
+void KBabelHighlighter::setSyntaxHighlighting( bool enable )
+{
+ syntaxHighlighting = enable;
+
+ // update highlighting
+ highlight();
+}
+#include "kbhighlighting.moc"