kutils Library API Documentation

kfind.cpp

00001 /* 00002 Copyright (C) 2001, S.R.Haque <srhaque@iee.org>. 00003 Copyright (C) 2002, David Faure <david@mandrakesoft.com> 00004 This file is part of the KDE project 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Library General Public 00008 License version 2, as published by the Free Software Foundation. 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., 59 Temple Place - Suite 330, 00018 Boston, MA 02111-1307, USA. 00019 */ 00020 00021 #include "kfind.h" 00022 #include "kfinddialog.h" 00023 #include <kapplication.h> 00024 #include <klocale.h> 00025 #include <kmessagebox.h> 00026 #include <qlabel.h> 00027 #include <qregexp.h> 00028 #include <qstylesheet.h> 00029 #include <qguardedptr.h> 00030 #include <kdebug.h> 00031 00032 //#define DEBUG_FIND 00033 00034 #define INDEX_NOMATCH -1 00035 00036 class KFindNextDialog : public KDialogBase 00037 { 00038 public: 00039 KFindNextDialog(const QString &pattern, QWidget *parent); 00040 }; 00041 00042 // Create the dialog. 00043 KFindNextDialog::KFindNextDialog(const QString &pattern, QWidget *parent) : 00044 KDialogBase(parent, 0, false, // non-modal! 00045 i18n("Find Next"), 00046 User1 | Close, 00047 User1, 00048 false, 00049 i18n("&Find")) 00050 { 00051 setMainWidget( new QLabel( i18n("<qt>Find next occurrence of '<b>%1</b>'?</qt>").arg(pattern), this ) ); 00052 } 00053 00055 00056 struct KFind::Private { 00057 Private() { 00058 findDialog = 0; 00059 } 00060 QGuardedPtr<QWidget> findDialog; 00061 }; 00063 00064 KFind::KFind( const QString &pattern, long options, QWidget *parent ) 00065 : QObject( parent ) 00066 { 00067 d = new KFind::Private; 00068 m_options = options; 00069 init( pattern ); 00070 } 00071 00072 KFind::KFind( const QString &pattern, long options, QWidget *parent, QWidget *findDialog ) 00073 : QObject( parent ) 00074 { 00075 d = new KFind::Private; 00076 d->findDialog = findDialog; 00077 m_options = options; 00078 init( pattern ); 00079 } 00080 00081 void KFind::init( const QString& pattern ) 00082 { 00083 m_matches = 0; 00084 m_pattern = pattern; 00085 m_dialog = 0; 00086 m_dialogClosed = false; 00087 m_index = INDEX_NOMATCH; 00088 m_lastResult = NoMatch; 00089 if (m_options & KFindDialog::RegularExpression) 00090 m_regExp = new QRegExp(pattern, m_options & KFindDialog::CaseSensitive); 00091 else { 00092 m_regExp = 0; 00093 } 00094 } 00095 00096 KFind::~KFind() 00097 { 00098 delete m_dialog; 00099 delete d; 00100 } 00101 00102 bool KFind::needData() const 00103 { 00104 // always true when m_text is empty. 00105 if (m_options & KFindDialog::FindBackwards) 00106 // m_index==-1 and m_lastResult==Match means we haven't answered nomatch yet 00107 // This is important in the "replace with a prompt" case. 00108 return ( m_index < 0 && m_lastResult != Match ); 00109 else 00110 // "index over length" test removed: we want to get a nomatch before we set data again 00111 // This is important in the "replace with a prompt" case. 00112 return m_index == INDEX_NOMATCH; 00113 } 00114 00115 void KFind::setData( const QString& data, int startPos ) 00116 { 00117 m_text = data; 00118 if ( startPos != -1 ) 00119 m_index = startPos; 00120 else if (m_options & KFindDialog::FindBackwards) 00121 m_index = QMAX( (int)m_text.length() - 1, 0 ); 00122 else 00123 m_index = 0; 00124 #ifdef DEBUG_FIND 00125 kdDebug() << "setData: '" << m_text << "' m_index=" << m_index << endl; 00126 #endif 00127 Q_ASSERT( m_index != INDEX_NOMATCH ); 00128 m_lastResult = NoMatch; 00129 } 00130 00131 KDialogBase* KFind::findNextDialog( bool create ) 00132 { 00133 if ( !m_dialog && create ) 00134 { 00135 m_dialog = new KFindNextDialog( m_pattern, parentWidget() ); 00136 connect( m_dialog, SIGNAL( user1Clicked() ), this, SLOT( slotFindNext() ) ); 00137 connect( m_dialog, SIGNAL( finished() ), this, SLOT( slotDialogClosed() ) ); 00138 } 00139 return m_dialog; 00140 } 00141 00142 KFind::Result KFind::find() 00143 { 00144 Q_ASSERT( m_index != INDEX_NOMATCH ); 00145 if ( m_lastResult == Match ) 00146 { 00147 // Move on before looking for the next match, _if_ we just found a match 00148 if (m_options & KFindDialog::FindBackwards) { 00149 m_index--; 00150 if ( m_index == -1 ) // don't call KFind::find with -1, it has a special meaning 00151 { 00152 m_lastResult = NoMatch; 00153 return NoMatch; 00154 } 00155 } else 00156 m_index++; 00157 } 00158 00159 #ifdef DEBUG_FIND 00160 kdDebug() << k_funcinfo << "m_index=" << m_index << endl; 00161 #endif 00162 do // this loop is only because validateMatch can fail 00163 { 00164 // Find the next candidate match. 00165 if ( m_options & KFindDialog::RegularExpression ) 00166 m_index = KFind::find(m_text, *m_regExp, m_index, m_options, &m_matchedLength); 00167 else 00168 m_index = KFind::find(m_text, m_pattern, m_index, m_options, &m_matchedLength); 00169 if ( m_index != -1 ) 00170 { 00171 // Flexibility: the app can add more rules to validate a possible match 00172 if ( validateMatch( m_text, m_index, m_matchedLength ) ) 00173 { 00174 m_matches++; 00175 // Tell the world about the match we found, in case someone wants to 00176 // highlight it. 00177 emit highlight(m_text, m_index, m_matchedLength); 00178 00179 if ( !m_dialogClosed ) 00180 findNextDialog(true)->show(); 00181 00182 #ifdef DEBUG_FIND 00183 kdDebug() << k_funcinfo << "Match. Next m_index=" << m_index << endl; 00184 #endif 00185 m_lastResult = Match; 00186 return Match; 00187 } 00188 else // Skip match 00189 if (m_options & KFindDialog::FindBackwards) 00190 m_index--; 00191 else 00192 m_index++; 00193 } else 00194 m_index = INDEX_NOMATCH; 00195 } 00196 while (m_index != INDEX_NOMATCH); 00197 00198 #ifdef DEBUG_FIND 00199 kdDebug() << k_funcinfo << "NoMatch. m_index=" << m_index << endl; 00200 #endif 00201 m_lastResult = NoMatch; 00202 return NoMatch; 00203 } 00204 00205 // static 00206 int KFind::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength) 00207 { 00208 // Handle regular expressions in the appropriate way. 00209 if (options & KFindDialog::RegularExpression) 00210 { 00211 QRegExp regExp(pattern, options & KFindDialog::CaseSensitive); 00212 00213 return find(text, regExp, index, options, matchedLength); 00214 } 00215 00216 bool caseSensitive = (options & KFindDialog::CaseSensitive); 00217 00218 if (options & KFindDialog::WholeWordsOnly) 00219 { 00220 if (options & KFindDialog::FindBackwards) 00221 { 00222 // Backward search, until the beginning of the line... 00223 while (index >= 0) 00224 { 00225 // ...find the next match. 00226 index = text.findRev(pattern, index, caseSensitive); 00227 if (index == -1) 00228 break; 00229 00230 // Is the match delimited correctly? 00231 *matchedLength = pattern.length(); 00232 if (isWholeWords(text, index, *matchedLength)) 00233 break; 00234 index--; 00235 } 00236 } 00237 else 00238 { 00239 // Forward search, until the end of the line... 00240 while (index < (int)text.length()) 00241 { 00242 // ...find the next match. 00243 index = text.find(pattern, index, caseSensitive); 00244 if (index == -1) 00245 break; 00246 00247 // Is the match delimited correctly? 00248 *matchedLength = pattern.length(); 00249 if (isWholeWords(text, index, *matchedLength)) 00250 break; 00251 index++; 00252 } 00253 if (index >= (int)text.length()) // end of line 00254 index = -1; // not found 00255 } 00256 } 00257 else 00258 { 00259 // Non-whole-word search. 00260 if (options & KFindDialog::FindBackwards) 00261 { 00262 index = text.findRev(pattern, index, caseSensitive); 00263 } 00264 else 00265 { 00266 index = text.find(pattern, index, caseSensitive); 00267 } 00268 if (index != -1) 00269 { 00270 *matchedLength = pattern.length(); 00271 } 00272 } 00273 return index; 00274 } 00275 00276 // static 00277 int KFind::find(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength) 00278 { 00279 if (options & KFindDialog::WholeWordsOnly) 00280 { 00281 if (options & KFindDialog::FindBackwards) 00282 { 00283 // Backward search, until the beginning of the line... 00284 while (index >= 0) 00285 { 00286 // ...find the next match. 00287 index = text.findRev(pattern, index); 00288 if (index == -1) 00289 break; 00290 00291 // Is the match delimited correctly? 00292 //pattern.match(text, index, matchedLength, false); 00293 /*int pos =*/ pattern.search( text.mid(index) ); 00294 *matchedLength = pattern.matchedLength(); 00295 if (isWholeWords(text, index, *matchedLength)) 00296 break; 00297 index--; 00298 } 00299 } 00300 else 00301 { 00302 // Forward search, until the end of the line... 00303 while (index < (int)text.length()) 00304 { 00305 // ...find the next match. 00306 index = text.find(pattern, index); 00307 if (index == -1) 00308 break; 00309 00310 // Is the match delimited correctly? 00311 //pattern.match(text, index, matchedLength, false); 00312 /*int pos =*/ pattern.search( text.mid(index) ); 00313 *matchedLength = pattern.matchedLength(); 00314 if (isWholeWords(text, index, *matchedLength)) 00315 break; 00316 index++; 00317 } 00318 if (index >= (int)text.length()) // end of line 00319 index = -1; // not found 00320 } 00321 } 00322 else 00323 { 00324 // Non-whole-word search. 00325 if (options & KFindDialog::FindBackwards) 00326 { 00327 index = text.findRev(pattern, index); 00328 } 00329 else 00330 { 00331 index = text.find(pattern, index); 00332 } 00333 if (index != -1) 00334 { 00335 //pattern.match(text, index, matchedLength, false); 00336 /*int pos =*/ pattern.search( text.mid(index) ); 00337 *matchedLength = pattern.matchedLength(); 00338 } 00339 } 00340 return index; 00341 } 00342 00343 bool KFind::isInWord(QChar ch) 00344 { 00345 return ch.isLetter() || ch.isDigit() || ch == '_'; 00346 } 00347 00348 bool KFind::isWholeWords(const QString &text, int starts, int matchedLength) 00349 { 00350 if ((starts == 0) || (!isInWord(text[starts - 1]))) 00351 { 00352 int ends = starts + matchedLength; 00353 00354 if ((ends == (int)text.length()) || (!isInWord(text[ends]))) 00355 return true; 00356 } 00357 return false; 00358 } 00359 00360 void KFind::slotFindNext() 00361 { 00362 emit findNext(); 00363 } 00364 00365 void KFind::slotDialogClosed() 00366 { 00367 emit dialogClosed(); 00368 m_dialogClosed = true; 00369 } 00370 00371 void KFind::displayFinalDialog() const 00372 { 00373 QString message; 00374 if ( numMatches() ) 00375 message = i18n( "1 match found.", "%n matches found.", numMatches() ); 00376 else 00377 message = i18n("<qt>No matches found for '<b>%1</b>'.</qt>").arg(QStyleSheet::escape(m_pattern)); 00378 KMessageBox::information(dialogsParent(), message); 00379 } 00380 00381 bool KFind::shouldRestart( bool forceAsking, bool showNumMatches ) const 00382 { 00383 // Only ask if we did a "find from cursor", otherwise it's pointless. 00384 // Well, unless the user can modify the document during a search operation, 00385 // hence the force boolean. 00386 if ( !forceAsking && (m_options & KFindDialog::FromCursor) == 0 ) 00387 { 00388 displayFinalDialog(); 00389 return false; 00390 } 00391 QString message; 00392 if ( showNumMatches ) 00393 { 00394 if ( numMatches() ) 00395 message = i18n( "1 match found.", "%n matches found.", numMatches() ); 00396 else 00397 message = i18n("No matches found for '<b>%1</b>'.").arg(QStyleSheet::escape(m_pattern)); 00398 } 00399 else 00400 { 00401 if ( m_options & KFindDialog::FindBackwards ) 00402 message = i18n( "Beginning of document reached." ); 00403 else 00404 message = i18n( "End of document reached." ); 00405 } 00406 00407 message += "\n"; // can't be in the i18n() of the first if() because of the plural form. 00408 // Hope this word puzzle is ok, it's a different sentence 00409 message += 00410 ( m_options & KFindDialog::FindBackwards ) ? 00411 i18n("Do you want to restart search from the end?") 00412 : i18n("Do you want to restart search at the beginning?"); 00413 00414 int ret = KMessageBox::questionYesNo( dialogsParent(), QString("<qt>")+message+QString("</qt>") ); 00415 bool yes = ( ret == KMessageBox::Yes ); 00416 if ( yes ) 00417 const_cast<KFind*>(this)->m_options &= ~KFindDialog::FromCursor; // clear FromCursor option 00418 return yes; 00419 } 00420 00421 void KFind::setOptions( long options ) 00422 { 00423 m_options = options; 00424 if (m_options & KFindDialog::RegularExpression) 00425 m_regExp = new QRegExp(m_pattern, m_options & KFindDialog::CaseSensitive); 00426 else { 00427 delete m_regExp; 00428 m_regExp = 0; 00429 } 00430 } 00431 00432 void KFind::closeFindNextDialog() 00433 { 00434 delete m_dialog; 00435 m_dialog = 0L; 00436 m_dialogClosed = true; 00437 } 00438 00439 int KFind::index() const 00440 { 00441 return m_index; 00442 } 00443 00444 void KFind::setPattern( const QString& pattern ) 00445 { 00446 m_pattern = pattern; 00447 setOptions( options() ); // rebuild m_regExp if necessary 00448 } 00449 00450 QWidget* KFind::dialogsParent() const 00451 { 00452 // If the find dialog is still up, it should get the focus when closing a message box 00453 // Otherwise, maybe the "find next?" dialog is up 00454 // Otherwise, the "view" is the parent. 00455 return d->findDialog ? (QWidget*)d->findDialog : ( m_dialog ? m_dialog : parentWidget() ); 00456 } 00457 00458 #include "kfind.moc"
KDE Logo
This file is part of the documentation for kutils Library Version 3.2.3.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Mar 16 17:22:59 2005 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003