KDECore
filter.cpp
Go to the documentation of this file.
00001 // -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- 00024 #include "filter_p.h" 00025 00026 #include "settings_p.h" 00027 00028 #include <kglobal.h> 00029 #include <kdebug.h> 00030 00031 namespace Sonnet 00032 { 00033 00034 static Word endWord; 00035 00036 class Filter::Private 00037 { 00038 public: 00039 // The reason it's not in the class directly is that 00040 // I'm not 100% sure that having the settings() here is 00041 // the way i want to be doing this. 00042 Settings *settings; 00043 }; 00044 00045 Filter* Filter::defaultFilter() 00046 { 00047 return new Filter(); 00048 } 00049 00050 Word Filter::end() 00051 { 00052 return endWord; 00053 } 00054 00055 Filter::Filter() 00056 : d(new Private) 00057 { 00058 d->settings = 0; 00059 } 00060 00061 Filter::~Filter() 00062 { 00063 delete d; 00064 } 00065 00066 void Filter::setSettings( Settings *conf ) 00067 { 00068 d->settings = conf; 00069 } 00070 00071 Settings *Filter::settings() const 00072 { 00073 return d->settings; 00074 } 00075 00076 void Filter::restart() 00077 { 00078 m_finder.toStart(); 00079 } 00080 00081 void Filter::setBuffer( const QString& buffer ) 00082 { 00083 m_buffer = buffer; 00084 m_finder = QTextBoundaryFinder(QTextBoundaryFinder::Word, m_buffer); 00085 } 00086 00087 QString Filter::buffer() const 00088 { 00089 return m_buffer; 00090 } 00091 00092 bool Filter::atEnd() const 00093 { 00094 return m_finder.position() >= m_buffer.length() || m_finder.position() < 0; 00095 } 00096 00097 // we don't want to spell check empty words, or single-char words of the form 00098 // '<', '=', etc. 00099 static bool 00100 isValidWord(const QString &str) 00101 { 00102 if(str.isEmpty() || (str.length() == 1 && !str[0].isLetter())) { 00103 return false; 00104 } 00105 const int length = str.length(); 00106 for(int i = 0; i < length; ++i) { 00107 if(!str[i].isNumber()) { 00108 return true; 00109 } 00110 } 00111 // 'str' only contains numbers 00112 return false; 00113 } 00114 00115 static bool 00116 finderNextWord(QTextBoundaryFinder &finder, QString &word, int &bufferStart) 00117 { 00118 QTextBoundaryFinder::BoundaryReasons boundary = finder.boundaryReasons(); 00119 int start = finder.position(), end = finder.position(); 00120 bool inWord = (boundary & QTextBoundaryFinder::StartWord) != 0; 00121 while (finder.toNextBoundary() > 0) { 00122 boundary = finder.boundaryReasons(); 00123 if ((boundary & QTextBoundaryFinder::EndWord) && inWord) { 00124 end = finder.position(); 00125 QString str = finder.string().mid(start, end - start); 00126 if (isValidWord(str)) { 00127 word = str; 00128 bufferStart = start; 00129 #if 0 00130 kDebug() << "Word at " << start << " word = '" 00131 << str << "', len = " << str.length(); 00132 #endif 00133 return true; 00134 } 00135 inWord = false; 00136 } 00137 if ((boundary & QTextBoundaryFinder::StartWord)) { 00138 start = finder.position(); 00139 inWord = true; 00140 } 00141 } 00142 return false; 00143 } 00144 00145 static bool finderWordAt(QTextBoundaryFinder &finder, 00146 int at, 00147 QString &word, int &bufferStart) 00148 { 00149 int oldPosition = finder.position(); 00150 00151 finder.setPosition(at); 00152 if (!finder.isAtBoundary() || (finder.boundaryReasons() & QTextBoundaryFinder::EndWord)) { 00153 if (finder.toPreviousBoundary() <= 0) { 00154 /* QTextBoundaryIterator doesn't consider start of the string 00155 * a boundary so we need to rewind to the beginning to catch 00156 * the first word */ 00157 if (at > 0 && finder.string().length() > 0) { 00158 finder.toStart(); 00159 } else 00160 return false; 00161 } 00162 } 00163 bool ret = finderNextWord(finder, word, bufferStart); 00164 finder.setPosition(oldPosition); 00165 return ret; 00166 } 00167 00168 Word Filter::nextWord() const 00169 { 00170 QString foundWord; 00171 int start; 00172 bool allUppercase = false; 00173 bool runTogether = false; 00174 00175 if (!finderNextWord(m_finder, foundWord, start)) 00176 return Filter::end(); 00177 00178 allUppercase = ( foundWord == foundWord.toUpper() ); 00179 00180 //TODO implement runtogether correctly. 00181 //We must ask to sonnet plugins to do it and not directly here. 00182 00183 if ( shouldBeSkipped( allUppercase, runTogether, foundWord ) ) 00184 return nextWord(); 00185 return Word( foundWord, start ); 00186 } 00187 00188 Word Filter::wordAtPosition( unsigned int pos ) const 00189 { 00190 QString foundWord; 00191 int start; 00192 if (!finderWordAt(m_finder, pos, foundWord, start)) 00193 return Filter::end(); 00194 return Word( foundWord, start ); 00195 } 00196 00197 00198 void Filter::setCurrentPosition( int i ) 00199 { 00200 QString word; 00201 int pos; 00202 00203 //to make sure we're at an reasonable word boundary 00204 if (!finderWordAt(m_finder, i, word, pos)) { 00205 return; 00206 } 00207 m_finder.setPosition(pos); 00208 } 00209 00210 int Filter::currentPosition() const 00211 { 00212 return m_finder.position(); 00213 } 00214 00215 void Filter::replace( const Word& w, const QString& newWord) 00216 { 00217 int oldLen = w.word.length(); 00218 00219 //start spell checkin from the just correct word 00220 m_buffer = m_buffer.replace( w.start, oldLen, newWord ); 00221 m_finder = QTextBoundaryFinder(QTextBoundaryFinder::Word, 00222 m_buffer); 00223 m_finder.setPosition(w.start); 00224 } 00225 00226 QString Filter::context() const 00227 { 00228 int len = 60; 00229 //we don't want the expression underneath casted to an unsigned int 00230 //which would cause it to always evaluate to false 00231 int signedPosition = m_finder.position(); 00232 bool begin = (signedPosition - len/2)<=0; 00233 00234 00235 QString buffer = m_buffer; 00236 Word word = wordAtPosition( m_finder.position() ); 00237 buffer = buffer.replace( word.start, word.word.length(), 00238 QString::fromLatin1( "<b>%1</b>" ).arg( word.word ) ); 00239 00240 QString context; 00241 if ( begin ) 00242 context = QString::fromLatin1("%1...") 00243 .arg( buffer.mid( 0, len ) ); 00244 else 00245 context = QString::fromLatin1("...%1...") 00246 .arg( buffer.mid( m_finder.position() - 20, len ) ); 00247 00248 context.replace( QLatin1Char('\n'), QLatin1Char(' ') ); 00249 00250 return context; 00251 } 00252 00253 bool Filter::trySkipLinks() const 00254 { 00255 QChar currentChar; 00256 int currentPosition = m_finder.position(); 00257 00258 if (currentPosition < 0 || currentPosition >= m_buffer.length()) 00259 return false; 00260 currentChar = m_buffer.at( currentPosition ); 00261 00262 int length = m_buffer.length(); 00263 //URL - if so skip 00264 if ( currentChar == QLatin1Char(':') 00265 && (currentPosition+1 < length) 00266 && (m_buffer.at( ++currentPosition ) == QLatin1Char('/') || ( currentPosition + 1 ) >= length ) ) { 00267 //in both cases url is considered finished at the first whitespace occurrence 00268 //TODO hey, "http://en.wikipedia.org/wiki/Main Page" --Nick Shaforostoff 00269 while ( !m_buffer.at( currentPosition++ ).isSpace() && currentPosition < length ) 00270 ; 00271 m_finder.setPosition(currentPosition); 00272 return true; 00273 } 00274 00275 //Email - if so skip 00276 if ( currentChar == QLatin1Char('@')) { 00277 while ( ++currentPosition < length && !m_buffer.at( currentPosition ).isSpace() ) 00278 ; 00279 m_finder.setPosition(currentPosition); 00280 return true; 00281 } 00282 00283 return false; 00284 } 00285 00286 bool Filter::ignore( const QString& word ) const 00287 { 00288 return d->settings && d->settings->ignore( word ); 00289 } 00290 00291 bool Filter::shouldBeSkipped( bool wordWasUppercase, bool wordWasRunTogether, 00292 const QString& foundWord ) const 00293 { 00294 bool checkUpper = ( d->settings ) ? 00295 d->settings->checkUppercase () : true; 00296 00297 bool skipRunTogether = ( d->settings ) ? 00298 d->settings->skipRunTogether() : true; 00299 00300 if ( trySkipLinks() ) 00301 return true; 00302 00303 if ( wordWasUppercase && !checkUpper ) 00304 return true; 00305 00306 if ( wordWasRunTogether && skipRunTogether ) 00307 return true; 00308 00309 return ignore( foundWord ); 00310 } 00311 00312 }
KDE 4.6 API Reference