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