00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "kfind.h"
00023 #include "kfinddialog.h"
00024 #include <kapplication.h>
00025 #include <klocale.h>
00026 #include <kmessagebox.h>
00027 #include <qlabel.h>
00028 #include <qregexp.h>
00029 #include <qstylesheet.h>
00030 #include <qguardedptr.h>
00031 #include <qptrvector.h>
00032 #include <kdebug.h>
00033
00034
00035
00036 #define INDEX_NOMATCH -1
00037
00038 class KFindNextDialog : public KDialogBase
00039 {
00040 public:
00041 KFindNextDialog(const QString &pattern, QWidget *parent);
00042 };
00043
00044
00045 KFindNextDialog::KFindNextDialog(const QString &pattern, QWidget *parent) :
00046 KDialogBase(parent, 0, false,
00047 i18n("Find Next"),
00048 User1 | Close,
00049 User1,
00050 false,
00051 i18n("&Find"))
00052 {
00053 setMainWidget( new QLabel( i18n("<qt>Find next occurrence of '<b>%1</b>'?</qt>").arg(pattern), this ) );
00054 }
00055
00057
00058 struct KFind::Private
00059 {
00060 Private() :
00061 findDialog(0),
00062 patternChanged(false),
00063 matchedPattern(""),
00064 incrementalPath(29, true),
00065 currentId(0),
00066 customIds(false)
00067 {
00068 incrementalPath.setAutoDelete(true);
00069 data.setAutoDelete(true);
00070 }
00071
00072 struct Match
00073 {
00074 Match(int dataId, int index, int matchedLength) :
00075 dataId(dataId),
00076 index(index),
00077 matchedLength(matchedLength)
00078 { }
00079
00080 int dataId;
00081 int index;
00082 int matchedLength;
00083 };
00084
00085 struct Data
00086 {
00087 Data() : id(-1), dirty(false) { }
00088 Data(int id, const QString &text, bool dirty = false) :
00089 id(id),
00090 text(text),
00091 dirty(dirty)
00092 { }
00093
00094 int id;
00095 QString text;
00096 bool dirty;
00097 };
00098
00099 QGuardedPtr<QWidget> findDialog;
00100 bool patternChanged;
00101 QString matchedPattern;
00102 QDict<Match> incrementalPath;
00103 QPtrVector<Data> data;
00104 int currentId;
00105 bool customIds;
00106 };
00107
00109
00110 KFind::KFind( const QString &pattern, long options, QWidget *parent )
00111 : QObject( parent )
00112 {
00113 d = new KFind::Private;
00114 m_options = options;
00115 init( pattern );
00116 }
00117
00118 KFind::KFind( const QString &pattern, long options, QWidget *parent, QWidget *findDialog )
00119 : QObject( parent )
00120 {
00121 d = new KFind::Private;
00122 d->findDialog = findDialog;
00123 m_options = options;
00124 init( pattern );
00125 }
00126
00127 void KFind::init( const QString& pattern )
00128 {
00129 m_matches = 0;
00130 m_pattern = pattern;
00131 m_dialog = 0;
00132 m_dialogClosed = false;
00133 m_index = INDEX_NOMATCH;
00134 m_lastResult = NoMatch;
00135 if (m_options & KFindDialog::RegularExpression)
00136 m_regExp = new QRegExp(pattern, m_options & KFindDialog::CaseSensitive);
00137 else {
00138 m_regExp = 0;
00139 }
00140 }
00141
00142 KFind::~KFind()
00143 {
00144 delete m_dialog;
00145 delete d;
00146 }
00147
00148 bool KFind::needData() const
00149 {
00150
00151 if (m_options & KFindDialog::FindBackwards)
00152
00153
00154 return ( m_index < 0 && m_lastResult != Match );
00155 else
00156
00157
00158 return m_index == INDEX_NOMATCH;
00159 }
00160
00161 void KFind::setData( const QString& data, int startPos )
00162 {
00163 setData( -1, data, startPos );
00164 }
00165
00166 void KFind::setData( int id, const QString& data, int startPos )
00167 {
00168
00169 if ( m_options & KFindDialog::FindIncremental )
00170 {
00171 if ( id == -1 )
00172 id = d->currentId + 1;
00173
00174 if ( id >= (int) d->data.size() )
00175 d->data.resize( id + 100 );
00176
00177 if ( id != -1 )
00178 d->customIds = true;
00179
00180 d->data.insert( id, new Private::Data(id, data, true) );
00181 }
00182
00183 if ( !(m_options & KFindDialog::FindIncremental) || needData() )
00184 {
00185 m_text = data;
00186
00187 if ( startPos != -1 )
00188 m_index = startPos;
00189 else if (m_options & KFindDialog::FindBackwards)
00190 m_index = QMAX( (int)m_text.length() - 1, 0 );
00191 else
00192 m_index = 0;
00193 #ifdef DEBUG_FIND
00194 kdDebug() << "setData: '" << m_text << "' m_index=" << m_index << endl;
00195 #endif
00196 Q_ASSERT( m_index != INDEX_NOMATCH );
00197 m_lastResult = NoMatch;
00198
00199 d->currentId = id;
00200 }
00201 }
00202
00203 KDialogBase* KFind::findNextDialog( bool create )
00204 {
00205 if ( !m_dialog && create )
00206 {
00207 m_dialog = new KFindNextDialog( m_pattern, parentWidget() );
00208 connect( m_dialog, SIGNAL( user1Clicked() ), this, SLOT( slotFindNext() ) );
00209 connect( m_dialog, SIGNAL( finished() ), this, SLOT( slotDialogClosed() ) );
00210 }
00211 return m_dialog;
00212 }
00213
00214 KFind::Result KFind::find()
00215 {
00216 Q_ASSERT( m_index != INDEX_NOMATCH || d->patternChanged );
00217
00218 if ( m_lastResult == Match && !d->patternChanged )
00219 {
00220
00221 if (m_options & KFindDialog::FindBackwards) {
00222 m_index--;
00223 if ( m_index == -1 )
00224 {
00225 m_lastResult = NoMatch;
00226 return NoMatch;
00227 }
00228 } else
00229 m_index++;
00230 }
00231 d->patternChanged = false;
00232
00233 if ( m_options & KFindDialog::FindIncremental )
00234 {
00235
00236
00237 if ( m_pattern.length() < d->matchedPattern.length() )
00238 {
00239 Private::Match *match = d->incrementalPath[m_pattern];
00240 QString previousPattern = d->matchedPattern;
00241 d->matchedPattern = m_pattern;
00242 if ( match != 0 )
00243 {
00244 bool clean = true;
00245
00246
00247 while ( d->data[match->dataId]->dirty == true &&
00248 !m_pattern.isEmpty() )
00249 {
00250 m_pattern.truncate( m_pattern.length() - 1 );
00251
00252 match = d->incrementalPath[m_pattern];
00253
00254 clean = false;
00255 }
00256
00257
00258 while ( m_pattern.length() < previousPattern.length() )
00259 {
00260 d->incrementalPath.remove(previousPattern);
00261 previousPattern.truncate(previousPattern.length() - 1);
00262 }
00263
00264
00265 m_text = d->data[match->dataId]->text;
00266 m_index = match->index;
00267 m_matchedLength = match->matchedLength;
00268 d->currentId = match->dataId;
00269
00270
00271 if ( clean )
00272 {
00273 if ( d->customIds )
00274 emit highlight(d->currentId, m_index, m_matchedLength);
00275 else
00276 emit highlight(m_text, m_index, m_matchedLength);
00277
00278 m_lastResult = Match;
00279 d->matchedPattern = m_pattern;
00280 return Match;
00281 }
00282 }
00283
00284
00285 else
00286 {
00287 startNewIncrementalSearch();
00288 }
00289 }
00290
00291
00292 else if ( m_pattern.length() > d->matchedPattern.length() )
00293 {
00294
00295 if ( m_pattern.startsWith(d->matchedPattern) )
00296 {
00297
00298
00299 if ( m_index == INDEX_NOMATCH )
00300 return NoMatch;
00301
00302 QString temp = m_pattern;
00303 m_pattern.truncate(d->matchedPattern.length() + 1);
00304 d->matchedPattern = temp;
00305 }
00306
00307 else
00308 {
00309 startNewIncrementalSearch();
00310 }
00311 }
00312
00313
00314 else if ( m_pattern != d->matchedPattern )
00315 {
00316 startNewIncrementalSearch();
00317 }
00318 }
00319
00320 #ifdef DEBUG_FIND
00321 kdDebug() << k_funcinfo << "m_index=" << m_index << endl;
00322 #endif
00323 do
00324 {
00325
00326
00327 do
00328 {
00329
00330 if ( m_options & KFindDialog::RegularExpression )
00331 m_index = KFind::find(m_text, *m_regExp, m_index, m_options, &m_matchedLength);
00332 else
00333 m_index = KFind::find(m_text, m_pattern, m_index, m_options, &m_matchedLength);
00334
00335 if ( m_options & KFindDialog::FindIncremental )
00336 d->data[d->currentId]->dirty = false;
00337
00338 if ( m_index == -1 && d->currentId < (int) d->data.count() - 1 )
00339 {
00340 m_text = d->data[++d->currentId]->text;
00341
00342 if ( m_options & KFindDialog::FindBackwards )
00343 m_index = QMAX((int) m_text.length() - 1, 0);
00344 else
00345 m_index = 0;
00346 }
00347 else
00348 break;
00349 } while ( !(m_options & KFindDialog::RegularExpression) );
00350
00351 if ( m_index != -1 )
00352 {
00353
00354 if ( validateMatch( m_text, m_index, m_matchedLength ) )
00355 {
00356 bool done = true;
00357
00358 if ( m_options & KFindDialog::FindIncremental )
00359 {
00360 d->incrementalPath.replace(m_pattern, new Private::Match(d->currentId, m_index, m_matchedLength));
00361
00362 if ( m_pattern.length() < d->matchedPattern.length() )
00363 {
00364 m_pattern += d->matchedPattern.mid(m_pattern.length(), 1);
00365 done = false;
00366 }
00367 }
00368
00369 if ( done )
00370 {
00371 m_matches++;
00372
00373
00374 if ( d->customIds )
00375 emit highlight(d->currentId, m_index, m_matchedLength);
00376 else
00377 emit highlight(m_text, m_index, m_matchedLength);
00378
00379 if ( !m_dialogClosed )
00380 findNextDialog(true)->show();
00381
00382 #ifdef DEBUG_FIND
00383 kdDebug() << k_funcinfo << "Match. Next m_index=" << m_index << endl;
00384 #endif
00385 m_lastResult = Match;
00386 return Match;
00387 }
00388 }
00389 else
00390 {
00391 if (m_options & KFindDialog::FindBackwards)
00392 m_index--;
00393 else
00394 m_index++;
00395 }
00396 }
00397 else
00398 {
00399 if ( m_options & KFindDialog::FindIncremental )
00400 {
00401 QString temp = m_pattern;
00402 temp.truncate(temp.length() - 1);
00403 m_pattern = d->matchedPattern;
00404 d->matchedPattern = temp;
00405 }
00406
00407 m_index = INDEX_NOMATCH;
00408 }
00409 }
00410 while (m_index != INDEX_NOMATCH);
00411
00412 #ifdef DEBUG_FIND
00413 kdDebug() << k_funcinfo << "NoMatch. m_index=" << m_index << endl;
00414 #endif
00415 m_lastResult = NoMatch;
00416 return NoMatch;
00417 }
00418
00419 void KFind::startNewIncrementalSearch()
00420 {
00421 Private::Match *match = d->incrementalPath[""];
00422 if(match == 0)
00423 {
00424 m_text = QString::null;
00425 m_index = 0;
00426 d->currentId = 0;
00427 }
00428 else
00429 {
00430 m_text = d->data[match->dataId]->text;
00431 m_index = match->index;
00432 d->currentId = match->dataId;
00433 }
00434 m_matchedLength = 0;
00435 d->incrementalPath.clear();
00436 d->matchedPattern = m_pattern;
00437 m_pattern = QString::null;
00438 }
00439
00440
00441 int KFind::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength)
00442 {
00443
00444 if (options & KFindDialog::RegularExpression)
00445 {
00446 QRegExp regExp(pattern, options & KFindDialog::CaseSensitive);
00447
00448 return find(text, regExp, index, options, matchedLength);
00449 }
00450
00451 bool caseSensitive = (options & KFindDialog::CaseSensitive);
00452
00453 if (options & KFindDialog::WholeWordsOnly)
00454 {
00455 if (options & KFindDialog::FindBackwards)
00456 {
00457
00458 while (index >= 0)
00459 {
00460
00461 index = text.findRev(pattern, index, caseSensitive);
00462 if (index == -1)
00463 break;
00464
00465
00466 *matchedLength = pattern.length();
00467 if (isWholeWords(text, index, *matchedLength))
00468 break;
00469 index--;
00470 }
00471 }
00472 else
00473 {
00474
00475 while (index < (int)text.length())
00476 {
00477
00478 index = text.find(pattern, index, caseSensitive);
00479 if (index == -1)
00480 break;
00481
00482
00483 *matchedLength = pattern.length();
00484 if (isWholeWords(text, index, *matchedLength))
00485 break;
00486 index++;
00487 }
00488 if (index >= (int)text.length())
00489 index = -1;
00490 }
00491 }
00492 else
00493 {
00494
00495 if (options & KFindDialog::FindBackwards)
00496 {
00497 index = text.findRev(pattern, index, caseSensitive);
00498 }
00499 else
00500 {
00501 index = text.find(pattern, index, caseSensitive);
00502 }
00503 if (index != -1)
00504 {
00505 *matchedLength = pattern.length();
00506 }
00507 }
00508 return index;
00509 }
00510
00511
00512 int KFind::find(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength)
00513 {
00514 if (options & KFindDialog::WholeWordsOnly)
00515 {
00516 if (options & KFindDialog::FindBackwards)
00517 {
00518
00519 while (index >= 0)
00520 {
00521
00522 index = text.findRev(pattern, index);
00523 if (index == -1)
00524 break;
00525
00526
00527
00528 pattern.search( text.mid(index) );
00529 *matchedLength = pattern.matchedLength();
00530 if (isWholeWords(text, index, *matchedLength))
00531 break;
00532 index--;
00533 }
00534 }
00535 else
00536 {
00537
00538 while (index < (int)text.length())
00539 {
00540
00541 index = text.find(pattern, index);
00542 if (index == -1)
00543 break;
00544
00545
00546
00547 pattern.search( text.mid(index) );
00548 *matchedLength = pattern.matchedLength();
00549 if (isWholeWords(text, index, *matchedLength))
00550 break;
00551 index++;
00552 }
00553 if (index >= (int)text.length())
00554 index = -1;
00555 }
00556 }
00557 else
00558 {
00559
00560 if (options & KFindDialog::FindBackwards)
00561 {
00562 index = text.findRev(pattern, index);
00563 }
00564 else
00565 {
00566 index = text.find(pattern, index);
00567 }
00568 if (index != -1)
00569 {
00570
00571 pattern.search( text.mid(index) );
00572 *matchedLength = pattern.matchedLength();
00573 }
00574 }
00575 return index;
00576 }
00577
00578 bool KFind::isInWord(QChar ch)
00579 {
00580 return ch.isLetter() || ch.isDigit() || ch == '_';
00581 }
00582
00583 bool KFind::isWholeWords(const QString &text, int starts, int matchedLength)
00584 {
00585 if ((starts == 0) || (!isInWord(text[starts - 1])))
00586 {
00587 int ends = starts + matchedLength;
00588
00589 if ((ends == (int)text.length()) || (!isInWord(text[ends])))
00590 return true;
00591 }
00592 return false;
00593 }
00594
00595 void KFind::slotFindNext()
00596 {
00597 emit findNext();
00598 }
00599
00600 void KFind::slotDialogClosed()
00601 {
00602 emit dialogClosed();
00603 m_dialogClosed = true;
00604 }
00605
00606 void KFind::displayFinalDialog() const
00607 {
00608 QString message;
00609 if ( numMatches() )
00610 message = i18n( "1 match found.", "%n matches found.", numMatches() );
00611 else
00612 message = i18n("<qt>No matches found for '<b>%1</b>'.</qt>").arg(QStyleSheet::escape(m_pattern));
00613 KMessageBox::information(dialogsParent(), message);
00614 }
00615
00616 bool KFind::shouldRestart( bool forceAsking, bool showNumMatches ) const
00617 {
00618
00619
00620
00621 if ( !forceAsking && (m_options & KFindDialog::FromCursor) == 0 )
00622 {
00623 displayFinalDialog();
00624 return false;
00625 }
00626 QString message;
00627 if ( showNumMatches )
00628 {
00629 if ( numMatches() )
00630 message = i18n( "1 match found.", "%n matches found.", numMatches() );
00631 else
00632 message = i18n("No matches found for '<b>%1</b>'.").arg(QStyleSheet::escape(m_pattern));
00633 }
00634 else
00635 {
00636 if ( m_options & KFindDialog::FindBackwards )
00637 message = i18n( "Beginning of document reached." );
00638 else
00639 message = i18n( "End of document reached." );
00640 }
00641
00642 message += "\n";
00643
00644 message +=
00645 ( m_options & KFindDialog::FindBackwards ) ?
00646 i18n("Do you want to restart search from the end?")
00647 : i18n("Do you want to restart search at the beginning?");
00648
00649 int ret = KMessageBox::questionYesNo( dialogsParent(), QString("<qt>")+message+QString("</qt>") );
00650 bool yes = ( ret == KMessageBox::Yes );
00651 if ( yes )
00652 const_cast<KFind*>(this)->m_options &= ~KFindDialog::FromCursor;
00653 return yes;
00654 }
00655
00656 void KFind::setOptions( long options )
00657 {
00658 m_options = options;
00659
00660 delete m_regExp;
00661 if (m_options & KFindDialog::RegularExpression)
00662 m_regExp = new QRegExp(m_pattern, m_options & KFindDialog::CaseSensitive);
00663 else
00664 m_regExp = 0;
00665 }
00666
00667 void KFind::closeFindNextDialog()
00668 {
00669 delete m_dialog;
00670 m_dialog = 0L;
00671 m_dialogClosed = true;
00672 }
00673
00674 int KFind::index() const
00675 {
00676 return m_index;
00677 }
00678
00679 void KFind::setPattern( const QString& pattern )
00680 {
00681 if ( m_options & KFindDialog::FindIncremental && m_pattern != pattern )
00682 d->patternChanged = true;
00683
00684 m_pattern = pattern;
00685 setOptions( options() );
00686 }
00687
00688 QWidget* KFind::dialogsParent() const
00689 {
00690
00691
00692
00693 return d->findDialog ? (QWidget*)d->findDialog : ( m_dialog ? m_dialog : parentWidget() );
00694 }
00695
00696 #include "kfind.moc"