30 #include <QtGui/QLabel>
31 #include <QtCore/QRegExp>
32 #include <QtCore/QHash>
33 #include <QTextDocument>
39 class KFindNextDialog :
public KDialog
46 KFindNextDialog::KFindNextDialog(
const QString &pattern,
QWidget *parent) :
50 setCaption(
i18n(
"Find Next") );
51 setButtons( User1 |
Close );
53 setDefaultButton( User1 );
55 setMainWidget(
new QLabel(
i18n(
"<qt>Find next occurrence of '<b>%1</b>'?</qt>", pattern),
this ) );
63 d(new
KFind::Private(this))
71 d(new
KFind::Private(this))
73 d->findDialog = findDialog;
78 void KFind::Private::init(
const QString& _pattern )
87 q->setOptions( options );
102 return ( d->index < 0 && d->lastResult !=
Match );
122 id = d->currentId + 1;
124 Q_ASSERT( id <= d->data.size() );
126 if (
id == d->data.size() )
127 d->data.append( Private::Data(
id, data,
true) );
129 d->data.replace(
id, Private::Data(
id, data,
true) );
130 Q_ASSERT( d->data.at(
id).text == data );
137 if ( startPos != -1 )
140 d->index = d->text.length();
144 kDebug() <<
"setData: '" << d->text <<
"' d->index=" << d->index;
155 if ( !d->dialog && create )
157 d->dialog =
new KFindNextDialog( d->pattern,
parentWidget() );
158 connect( d->dialog, SIGNAL(user1Clicked()),
this, SLOT(_k_slotFindNext()) );
159 connect( d->dialog, SIGNAL(finished()),
this, SLOT(_k_slotDialogClosed()) );
168 if ( d->lastResult ==
Match && !d->patternChanged )
173 if ( d->index == -1 )
181 d->patternChanged =
false;
187 if ( d->pattern.length() < d->matchedPattern.length() )
189 Private::Match match;
190 if ( !d->pattern.isEmpty() )
191 match = d->incrementalPath.value( d->pattern );
192 else if ( d->emptyMatch )
193 match = *d->emptyMatch;
194 QString previousPattern (d->matchedPattern);
195 d->matchedPattern = d->pattern;
196 if ( !match.isNull() )
201 while ( d->data.at(match.dataId).dirty ==
true &&
202 !d->pattern.isEmpty() )
204 d->pattern.truncate( d->pattern.length() - 1 );
206 match = d->incrementalPath.value( d->pattern );
212 while ( d->pattern.length() < previousPattern.length() )
214 d->incrementalPath.remove(previousPattern);
215 previousPattern.truncate(previousPattern.length() - 1);
219 d->text = d->data.at(match.dataId).text;
220 d->index = match.index;
221 d->matchedLength = match.matchedLength;
222 d->currentId = match.dataId;
228 emit
highlight(d->currentId, d->index, d->matchedLength);
230 emit
highlight(d->text, d->index, d->matchedLength);
232 d->lastResult =
Match;
233 d->matchedPattern = d->pattern;
241 d->startNewIncrementalSearch();
246 else if ( d->pattern.length() > d->matchedPattern.length() )
249 if ( d->pattern.startsWith(d->matchedPattern) )
257 d->pattern.truncate(d->matchedPattern.length() + 1);
258 d->matchedPattern = temp;
263 d->startNewIncrementalSearch();
268 else if ( d->pattern != d->matchedPattern )
270 d->startNewIncrementalSearch();
275 kDebug() <<
"d->index=" << d->index;
285 d->index =
KFind::find(d->text, *d->regExp, d->index, d->options, &d->matchedLength);
287 d->index =
KFind::find(d->text, d->pattern, d->index, d->options, &d->matchedLength);
290 d->data[d->currentId].dirty =
false;
292 if (d->index == -1 && d->currentId < d->data.count() - 1) {
293 d->text = d->data.at(++d->currentId).text;
296 d->index = d->text.length();
304 if ( d->index != -1 )
313 if ( d->pattern.isEmpty() ) {
314 delete d->emptyMatch;
315 d->emptyMatch =
new Private::Match( d->currentId, d->index, d->matchedLength );
317 d->incrementalPath.insert(d->pattern, Private::Match(d->currentId, d->index, d->matchedLength));
319 if ( d->pattern.length() < d->matchedPattern.length() )
321 d->pattern += d->matchedPattern.mid(d->pattern.length(), 1);
332 emit
highlight(d->currentId, d->index, d->matchedLength);
334 emit
highlight(d->text, d->index, d->matchedLength);
336 if ( !d->dialogClosed )
340 kDebug() <<
"Match. Next d->index=" << d->index;
342 d->lastResult =
Match;
359 temp.truncate(temp.length() - 1);
360 d->pattern = d->matchedPattern;
361 d->matchedPattern = temp;
370 kDebug() <<
"NoMatch. d->index=" << d->index;
376 void KFind::Private::startNewIncrementalSearch()
378 Private::Match *match = emptyMatch;
387 text = data.at(match->dataId).text;
388 index = match->index;
389 currentId = match->dataId;
392 incrementalPath.clear();
393 delete emptyMatch; emptyMatch = 0;
394 matchedPattern = pattern;
400 return ch.isLetter() || ch.isDigit() || ch ==
'_';
405 if (starts == 0 || !
isInWord(text.at(starts-1)))
407 const int ends = starts + matchedLength;
408 if (ends == text.length() || !
isInWord(text.at(ends))) {
415 static bool matchOk(
const QString& text,
int index,
int matchedLength,
long options)
434 Qt::CaseSensitivity caseSensitive = (options &
KFind::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
435 QRegExp regExp(pattern, caseSensitive);
437 return find(text, regExp, index, options, matchedLength);
443 index = qMin( qMax(0, text.length() - pattern.length()), index );
446 Qt::CaseSensitivity caseSensitive = (options &
KFind::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
448 if (options & KFind::FindBackwards) {
452 index = text.lastIndexOf(pattern, index, caseSensitive);
463 while (index <= text.length())
466 index = text.indexOf(pattern, index, caseSensitive);
474 if (index > text.length()) {
475 kDebug() <<
"at" << index <<
"-> not found";
482 *matchedLength = pattern.length();
487 static int doFind(
const QString &text,
const QRegExp &pattern,
int index,
long options,
int *matchedLength)
493 index = text.lastIndexOf(pattern, index);
497 pattern.indexIn( text.mid(index) );
498 *matchedLength = pattern.matchedLength();
499 if (
matchOk(text, index, *matchedLength, options))
505 while (index <= text.length()) {
507 index = text.indexOf(pattern, index);
511 pattern.indexIn( text.mid(index) );
512 *matchedLength = pattern.matchedLength();
513 if (
matchOk(text, index, *matchedLength, options))
517 if (index > text.length()) {
528 static int lineBasedFind(
const QString &text,
const QRegExp &pattern,
int index,
long options,
int *matchedLength)
533 int startLineNumber = 0;
534 for (; startLineNumber < lines.count(); ++startLineNumber) {
535 const QString line = lines.at(startLineNumber);
536 if (index < offset + line.length()) {
539 offset += line.length() + 1 ;
544 if (startLineNumber == lines.count()) {
547 offset -= lines.at(startLineNumber).length() + 1;
550 for (
int lineNumber = startLineNumber; lineNumber >= 0; --lineNumber) {
551 const QString line = lines.at(lineNumber);
552 const int ret =
doFind(line, pattern, lineNumber == startLineNumber ? index - offset : line.length(), options, matchedLength);
555 offset -= line.length() + 1 ;
559 for (
int lineNumber = startLineNumber; lineNumber < lines.count(); ++lineNumber) {
560 const QString line = lines.at(lineNumber);
561 const int ret =
doFind(line, pattern, lineNumber == startLineNumber ? (index - offset) : 0, options, matchedLength);
565 offset += line.length() + 1 ;
572 int KFind::find(
const QString &text,
const QRegExp &pattern,
int index,
long options,
int *matchedLength)
574 if (pattern.pattern().startsWith(
'^') || pattern.pattern().endsWith(
'$')) {
575 return lineBasedFind(text, pattern, index, options, matchedLength);
578 return doFind(text, pattern, index, options, matchedLength);
581 void KFind::Private::_k_slotFindNext()
586 void KFind::Private::_k_slotDialogClosed()
591 emit q->dialogClosed();
603 message =
i18np(
"1 match found.",
"%1 matches found.",
numMatches() );
605 message =
i18n(
"<qt>No matches found for '<b>%1</b>'.</qt>", Qt::escape(d->pattern));
620 if ( showNumMatches )
623 message =
i18np(
"1 match found.",
"%1 matches found.",
numMatches() );
625 message =
i18n(
"No matches found for '<b>%1</b>'.", Qt::escape(d->pattern));
630 message =
i18n(
"Beginning of document reached." );
632 message =
i18n(
"End of document reached." );
635 message +=
"<br><br>";
639 i18n(
"Continue from the end?")
640 :
i18n(
"Continue from the beginning?");
661 Qt::CaseSensitivity caseSensitive = (d->options &
KFind::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
662 d->regExp =
new QRegExp(d->pattern, caseSensitive);
670 d->dialog->deleteLater();
673 d->dialogClosed =
true;
689 d->patternChanged =
true;
720 return d->findDialog ? (
QWidget*)d->findDialog : ( d->dialog ? d->dialog :
parentWidget() );