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