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;
KDE 4.6 API Reference