30#include <QtGui/QLabel>
31#include <QtCore/QRegExp>
32#include <QtCore/QHash>
33#include <QTextDocument>
39class KFindNextDialog :
public KDialog
42 KFindNextDialog(
const QString &pattern,
QWidget *parent);
46KFindNextDialog::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;
78void 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) )
256 QString temp (d->pattern);
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;
358 QString temp (d->pattern);
359 temp.truncate(temp.length() - 1);
360 d->pattern = d->matchedPattern;
361 d->matchedPattern = temp;
370 kDebug() <<
"NoMatch. d->index=" << d->index;
376void 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 ==
'_';
403static bool isWholeWords(
const QString &text,
int starts,
int matchedLength)
405 if (starts == 0 || !
isInWord(text.at(starts-1)))
407 const int ends = starts + matchedLength;
408 if (ends == text.length() || !
isInWord(text.at(ends))) {
415static bool matchOk(
const QString& text,
int index,
int matchedLength,
long options)
429int KFind::find(
const QString &text,
const QString &pattern,
int index,
long options,
int *matchedLength)
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;
452 index = text.lastIndexOf(pattern, index, caseSensitive);
456 if (
matchOk(text, index, pattern.length(), options))
459 kDebug() <<
"decrementing:" << index;
463 while (index <= text.length())
466 index = text.indexOf(pattern, index, caseSensitive);
470 if (
matchOk(text, index, pattern.length(), options))
474 if (index > text.length()) {
475 kDebug() <<
"at" << index <<
"-> not found";
482 *matchedLength = pattern.length();
487static 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()) {
528static int lineBasedFind(
const QString &text,
const QRegExp &pattern,
int index,
long options,
int *matchedLength)
530 const QStringList lines = text.split(
'\n');
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 ;
572int 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);
581void KFind::Private::_k_slotFindNext()
586void KFind::Private::_k_slotDialogClosed()
591 emit q->dialogClosed();
605 message =
i18n(
"<qt>No matches found for '<b>%1</b>'.</qt>", Qt::escape(d->pattern));
620 if ( showNumMatches )
625 message =
i18n(
"No matches found for '<b>%1</b>'.", Qt::escape(d->pattern));
630 message =
i18n(
"Beginning of document reached." );
639 i18n(
"Continue from the end?")
640 :
i18n(
"Continue from the beginning?");
657 d->options = options;
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;
691 d->pattern = pattern;
720 return d->findDialog ? (
QWidget*)d->findDialog : ( d->dialog ? d->dialog :
parentWidget() );
A dialog base class with standard buttons and predefined layouts.
A generic implementation of the "find" function.
QWidget * dialogsParent() const
KDialog * findNextDialog(bool create=false)
Return (or create) the dialog that shows the "find next?" prompt.
int numMatches() const
Return the number of matches found (i.e.
QWidget * parentWidget() const
void setPattern(const QString &pattern)
Change the pattern we're looking for.
void closeFindNextDialog()
Close the "find next?" dialog.
virtual bool shouldRestart(bool forceAsking=false, bool showNumMatches=true) const
Returns true if we should restart the search from scratch.
virtual void setOptions(long options)
Set new options.
virtual void resetCounts()
Call this to reset the numMatches count (and the numReplacements count for a KReplace).
KFind(const QString &pattern, long options, QWidget *parent)
Only use this constructor if you don't use KFindDialog, or if you use it as a modal dialog.
@ CaseSensitive
Consider case when matching.
@ RegularExpression
Interpret the pattern as a regular expression.
@ FromCursor
Start from current cursor position.
@ FindBackwards
Go backwards.
@ FindIncremental
Find incremental.
@ WholeWordsOnly
Match whole words only.
void setData(const QString &data, int startPos=-1)
Call this when needData returns true, before calling find().
virtual bool validateMatch(const QString &text, int index, int matchedlength)
Virtual method, which allows applications to add extra checks for validating a candidate match.
virtual void displayFinalDialog() const
Displays the final dialog saying "no match was found", if that was the case.
Result find()
Walk the text fragment (e.g.
long options() const
Return the current options.
void highlight(const QString &text, int matchingIndex, int matchedLength)
Connect to this signal to implement highlighting of found text during the find operation.
static void information(QWidget *parent, const QString &text, const QString &caption=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
Display an "Information" dialog.
static int questionYesNo(QWidget *parent, const QString &text, const QString &caption=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const QString &dontAskAgainName=QString(), Options options=Notify)
Display a simple "question" dialog.
static const int INDEX_NOMATCH
static bool isInWord(QChar ch)
static bool isWholeWords(const QString &text, int starts, int matchedLength)
static int doFind(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength)
static bool matchOk(const QString &text, int index, int matchedLength, long options)
static int lineBasedFind(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength)
QString i18n(const char *text)
QString i18np(const char *sing, const char *plur, const A1 &a1)
void message(KMessage::MessageType messageType, const QString &text, const QString &caption=QString())
KGuiItem cont()
Returns the 'Continue' gui item.
KGuiItem stop()
Returns the 'Stop' gui item.
KGuiItem find()
Returns the 'Find' gui item.