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() );
static const int INDEX_NOMATCH
void message(KMessage::MessageType messageType, const QString &text, const QString &caption=QString())
QString i18n(const char *text)
virtual bool validateMatch(const QString &text, int index, int matchedlength)
Virtual method, which allows applications to add extra checks for validating a candidate match...
Result find()
Walk the text fragment (e.g.
QWidget * parentWidget() const
QString i18np(const char *sing, const char *plur, const A1 &a1)
static void information(QWidget *parent, const QString &text, const QString &caption=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
Display an "Information" dialog.
virtual void setOptions(long options)
Set new options.
A dialog base class with standard buttons and predefined layouts.
static QDebug kDebug(bool cond, int area=KDE_DEFAULT_DEBUG_AREA)
Interpret the pattern as a regular expression.
static bool isInWord(QChar ch)
Start from current cursor position.
void setData(const QString &data, int startPos=-1)
Call this when needData returns true, before calling find().
virtual void displayFinalDialog() const
Displays the final dialog saying "no match was found", if that was the case.
void highlight(const QString &text, int matchingIndex, int matchedLength)
Connect to this signal to implement highlighting of found text during the find operation.
int numMatches() const
Return the number of matches found (i.e.
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...
static bool matchOk(const QString &text, int index, int matchedLength, long options)
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 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.
A generic implementation of the "find" function.
virtual bool shouldRestart(bool forceAsking=false, bool showNumMatches=true) const
Returns true if we should restart the search from scratch.
QWidget * dialogsParent() const
void closeFindNextDialog()
Close the "find next?" dialog.
KGuiItem find()
Returns the 'Find' gui item.
KGuiItem stop()
Returns the 'Stop' gui item.
KAction * create(StandardAction id, const QObject *recvr, const char *slot, QObject *parent)
Creates an action corresponding to one of the KStandardAction::StandardAction actions, which is connected to the given object and slot, and is owned by parent.
virtual void resetCounts()
Call this to reset the numMatches count (and the numReplacements count for a KReplace).
Consider case when matching.
void setPattern(const QString &pattern)
Change the pattern we're looking for.
KGuiItem cont()
Returns the 'Continue' gui item.
static int lineBasedFind(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength)
long options() const
Return the current options.
KDialog * findNextDialog(bool create=false)
Return (or create) the dialog that shows the "find next?" prompt.
KGuiItem yes()
Returns the 'Yes' gui item.