• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

Kate

spellcheck.cpp

Go to the documentation of this file.
00001 /*  This file is part of the KDE libraries and the Kate part.
00002  *
00003  *  Copyright (C) 2009 by Michel Ludwig <michel.ludwig@kdemail.net>
00004  *
00005  *  This library is free software; you can redistribute it and/or
00006  *  modify it under the terms of the GNU Library General Public
00007  *  License as published by the Free Software Foundation; either
00008  *  version 2 of the License, or (at your option) any later version.
00009  *
00010  *  This library is distributed in the hope that it will be useful,
00011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  *  Library General Public License for more details.
00014  *
00015  *  You should have received a copy of the GNU Library General Public License
00016  *  along with this library; see the file COPYING.LIB.  If not, write to
00017  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  *  Boston, MA 02110-1301, USA.
00019  */
00020 
00021 /* If ever threads should be used again, thread communication and
00022  * synchronization ought to be done with blocking queued signal connections.
00023  */
00024 
00025 #include "spellcheck.h"
00026 
00027 #include <QHash>
00028 #include <QtAlgorithms>
00029 #include <QTimer>
00030 
00031 #include <kactioncollection.h>
00032 #include <ktexteditor/view.h>
00033 #include <sonnet/speller.h>
00034 
00035 #include "katedocument.h"
00036 #include "katehighlight.h"
00037 
00038 KateSpellCheckManager::KateSpellCheckManager(QObject *parent)
00039 : QObject(parent)
00040 {
00041 }
00042 
00043 KateSpellCheckManager::~KateSpellCheckManager()
00044 {
00045 }
00046 
00047 QStringList KateSpellCheckManager::suggestions(const QString& word, const QString& dictionary)
00048 {
00049   Sonnet::Speller speller;
00050   speller.setLanguage(dictionary);
00051   return speller.suggest(word);
00052 }
00053 
00054 void KateSpellCheckManager::ignoreWord(const QString& word, const QString& dictionary)
00055 {
00056   Sonnet::Speller speller;
00057   speller.setLanguage(dictionary);
00058   speller.addToSession(word);
00059 }
00060 
00061 void KateSpellCheckManager::addToDictionary(const QString& word, const QString& dictionary)
00062 {
00063   Sonnet::Speller speller;
00064   speller.setLanguage(dictionary);
00065   speller.addToPersonal(word);
00066 }
00067 
00068 QList<KTextEditor::Range> KateSpellCheckManager::rangeDifference(const KTextEditor::Range& r1,
00069                                                                  const KTextEditor::Range& r2)
00070 {
00071   Q_ASSERT(r1.contains(r2));
00072   QList<KTextEditor::Range> toReturn;
00073   KTextEditor::Range before(r1.start(), r2.start());
00074   KTextEditor::Range after(r2.end(), r1.end());
00075   if(!before.isEmpty()) {
00076     toReturn.push_back(before);
00077   }
00078   if(!after.isEmpty()) {
00079     toReturn.push_back(after);
00080   }
00081   return toReturn;
00082 }
00083 
00084 namespace {
00085   bool lessThanRangeDictionaryPair(const QPair<KTextEditor::Range, QString> &s1,
00086                                           const QPair<KTextEditor::Range, QString> &s2)
00087   {
00088       return s1.first.end() <= s2.first.start();
00089   }
00090 }
00091 
00092 QList<QPair<KTextEditor::Range, QString> > KateSpellCheckManager::spellCheckLanguageRanges(KateDocument *doc,
00093                                                                                            const KTextEditor::Range& range)
00094 {
00095   QString defaultDict = doc->defaultDictionary();
00096   QList<RangeDictionaryPair> toReturn;
00097   QList<QPair<KTextEditor::MovingRange*, QString> > dictionaryRanges = doc->dictionaryRanges();
00098   if(dictionaryRanges.isEmpty()) {
00099     toReturn.push_back(RangeDictionaryPair(range, defaultDict));
00100     return toReturn;
00101   }
00102   QList<KTextEditor::Range> splitQueue;
00103   splitQueue.push_back(range);
00104   while(!splitQueue.isEmpty()) {
00105     bool handled = false;
00106     KTextEditor::Range consideredRange = splitQueue.takeFirst();
00107     for(QList<QPair<KTextEditor::MovingRange*, QString> >::iterator i = dictionaryRanges.begin();
00108         i != dictionaryRanges.end(); ++i) {
00109       KTextEditor::Range languageRange = *((*i).first);
00110       KTextEditor::Range intersection = languageRange.intersect(consideredRange);
00111       if(intersection.isEmpty()) {
00112         continue;
00113       }
00114       toReturn.push_back(RangeDictionaryPair(intersection, (*i).second));
00115       splitQueue += rangeDifference(consideredRange, intersection);
00116       handled = true;
00117       break;
00118     }
00119     if(!handled) {
00120       // 'consideredRange' did not intersect with any dictionary range, so we add it with the default dictionary
00121       toReturn.push_back(RangeDictionaryPair(consideredRange, defaultDict));
00122     }
00123   }
00124   // finally, we still have to sort the list
00125   qStableSort(toReturn.begin(), toReturn.end(), lessThanRangeDictionaryPair);
00126   return toReturn;
00127 }
00128 
00129 QList<QPair<KTextEditor::Range, QString> > KateSpellCheckManager::spellCheckWrtHighlightingRanges(KateDocument *document,
00130                                                                                          const KTextEditor::Range& range,
00131                                                                                          const QString& dictionary,
00132                                                                                          bool singleLine,
00133                                                                                          bool returnSingleRange)
00134 {
00135   QList<QPair<KTextEditor::Range, QString> > toReturn;
00136   if(range.isEmpty()) {
00137     return toReturn;
00138   }
00139 
00140   KateHighlighting *highlighting = document->highlight();
00141 
00142   QList<KTextEditor::Range> rangesToSplit;
00143   if(!singleLine || range.onSingleLine()) {
00144     rangesToSplit.push_back(range);
00145   }
00146   else {
00147     const int startLine = range.start().line();
00148     const int startColumn = range.start().column();
00149     const int endLine = range.end().line();
00150     const int endColumn = range.end().column();
00151     for(int line = startLine; line <= endLine; ++line) {
00152       const int start = (line == startLine) ? startColumn : 0;
00153       const int end = (line == endLine) ? endColumn : document->lineLength(line);
00154       KTextEditor::Range toAdd(line, start, line, end);
00155       if(!toAdd.isEmpty()) {
00156         rangesToSplit.push_back(toAdd);
00157       }
00158     }
00159   }
00160   for(QList<KTextEditor::Range>::iterator i = rangesToSplit.begin(); i != rangesToSplit.end(); ++i) {
00161     KTextEditor::Range rangeToSplit = *i;
00162     KTextEditor::Cursor begin = KTextEditor::Cursor::invalid();
00163     const int startLine = rangeToSplit.start().line();
00164     const int startColumn = rangeToSplit.start().column();
00165     const int endLine = rangeToSplit.end().line();
00166     const int endColumn = rangeToSplit.end().column();
00167     bool inSpellCheckArea = false;
00168     for(int line = startLine; line <= endLine; ++line) {
00169       Kate::TextLine kateTextLine = document->kateTextLine(line);
00170       const int start = (line == startLine) ? startColumn : 0;
00171       const int end = (line == endLine) ? endColumn : kateTextLine->length();
00172       const KTextEditor::Cursor startCursor();
00173       for(int i = start; i < end;) { // WARNING: 'i' has to be incremented manually!
00174         int attr = kateTextLine->attribute(i);
00175         const KatePrefixStore& prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr);
00176         QString prefixFound = prefixStore.findPrefix(kateTextLine, i);
00177         unsigned int attribute = kateTextLine->attribute(i);
00178         if(!document->highlight()->attributeRequiresSpellchecking(attribute)
00179            && prefixFound.isEmpty()) {
00180           if(i == start) {
00181             ++i;
00182             continue;
00183           }
00184           else if(inSpellCheckArea) {
00185             KTextEditor::Range spellCheckRange(begin, KTextEditor::Cursor(line, i));
00186             // work around Qt bug 6498
00187             trimRange(document, spellCheckRange);
00188             if(!spellCheckRange.isEmpty()) {
00189               toReturn.push_back(RangeDictionaryPair(spellCheckRange, dictionary));
00190               if(returnSingleRange) {
00191                 return toReturn;
00192               }
00193             }
00194             begin = KTextEditor::Cursor::invalid();
00195             inSpellCheckArea = false;
00196           }
00197         }
00198         else if(!inSpellCheckArea) {
00199           begin = KTextEditor::Cursor(line, i);
00200           inSpellCheckArea = true;
00201         }
00202         if(!prefixFound.isEmpty()) {
00203           i += prefixFound.length();
00204         }
00205         else {
00206           ++i;
00207         }
00208       }
00209     }
00210     if(inSpellCheckArea) {
00211       KTextEditor::Range spellCheckRange(begin, rangeToSplit.end());
00212       // work around Qt bug 6498
00213       trimRange(document, spellCheckRange);
00214       if(!spellCheckRange.isEmpty()) {
00215         toReturn.push_back(RangeDictionaryPair(spellCheckRange, dictionary));
00216         if(returnSingleRange) {
00217           return toReturn;
00218         }
00219       }
00220     }
00221   }
00222 
00223   return toReturn;
00224 }
00225 
00226 QList<QPair<KTextEditor::Range, QString> > KateSpellCheckManager::spellCheckRanges(KateDocument *doc,
00227                                                                                    const KTextEditor::Range& range,
00228                                                                                    bool singleLine)
00229 {
00230   QList<RangeDictionaryPair> toReturn;
00231   QList<RangeDictionaryPair> languageRangeList = spellCheckLanguageRanges(doc, range);
00232   for(QList<RangeDictionaryPair>::iterator i = languageRangeList.begin(); i != languageRangeList.end(); ++i) {
00233     const RangeDictionaryPair& p = *i;
00234     toReturn += spellCheckWrtHighlightingRanges(doc, p.first, p.second, singleLine);
00235   }
00236   return toReturn;
00237 }
00238 
00239 void KateSpellCheckManager::replaceCharactersEncodedIfNecessary(const QString& newWord, KateDocument *doc,
00240                                                                                         const KTextEditor::Range& replacementRange)
00241 {
00242   const QString replacedString = doc->text(replacementRange);
00243   int attr = doc->kateTextLine(replacementRange.start().line())->attribute(replacementRange.start().column());
00244   int p = doc->highlight()->getEncodedCharactersInsertionPolicy(attr);
00245 
00246   if((p == KateDocument::EncodeAlways)
00247   || (p == KateDocument::EncodeWhenPresent && doc->containsCharacterEncoding(replacementRange))) {
00248     doc->replaceText(replacementRange, newWord);
00249     doc->replaceCharactersByEncoding(KTextEditor::Range(replacementRange.start(),
00250                                                         replacementRange.start() + KTextEditor::Cursor(0, newWord.length())));
00251   }
00252   else {
00253     doc->replaceText(replacementRange, newWord);
00254   }
00255 }
00256 
00257 void KateSpellCheckManager::trimRange(KateDocument *doc, KTextEditor::Range &r)
00258 {
00259   if(r.isEmpty()) {
00260     return;
00261   }
00262   KTextEditor::Cursor cursor = r.start();
00263   while(cursor < r.end()) {
00264     if(doc->lineLength(cursor.line()) > 0
00265        && !doc->character(cursor).isSpace() && doc->character(cursor).category() != QChar::Other_Control) {
00266         break;
00267     }
00268     cursor.setColumn(cursor.column() + 1);
00269     if(cursor.column() >= doc->lineLength(cursor.line())) {
00270       cursor.setPosition(cursor.line() + 1, 0);
00271     }
00272   }
00273   r.start() = cursor;
00274   if(r.isEmpty()) {
00275     return;
00276   }
00277 
00278   cursor = r.end();
00279   KTextEditor::Cursor prevCursor = cursor;
00280   // the range cannot be empty now
00281   do {
00282     prevCursor = cursor;
00283     if(cursor.column() <= 0) {
00284       cursor.setPosition(cursor.line() - 1, doc->lineLength(cursor.line() - 1));
00285     }
00286     else {
00287       cursor.setColumn(cursor.column() - 1);
00288     }
00289     if(cursor.column() < doc->lineLength(cursor.line())
00290        && !doc->character(cursor).isSpace() && doc->character(cursor).category() != QChar::Other_Control) {
00291         break;
00292     }
00293   }
00294   while(cursor > r.start());
00295   r.end() = prevCursor;
00296 }
00297 
00298 #include "spellcheck.moc"
00299 
00300 // kate: space-indent on; indent-width 2; replace-tabs on;

Kate

Skip menu "Kate"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal