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"