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