File indexing completed on 2024-11-03 09:59:51
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2001 S .R.Haque <srhaque@iee.org>. 0004 SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com> 0005 SPDX-FileCopyrightText: 2004 Arend van Beelen jr. <arend@auton.nl> 0006 0007 SPDX-License-Identifier: LGPL-2.0-only 0008 */ 0009 0010 #include "kfind.h" 0011 #include "kfind_p.h" 0012 0013 #include "kfinddialog.h" 0014 0015 #include <KGuiItem> 0016 #include <KLocalizedString> 0017 #include <KMessageBox> 0018 0019 #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(5, 70) 0020 #include <QRegExp> 0021 #endif 0022 0023 #include <QDialog> 0024 #include <QDialogButtonBox> 0025 #include <QHash> 0026 #include <QLabel> 0027 #include <QPushButton> 0028 #include <QRegularExpression> 0029 #include <QVBoxLayout> 0030 0031 // #define DEBUG_FIND 0032 0033 static const int INDEX_NOMATCH = -1; 0034 0035 class KFindNextDialog : public QDialog 0036 { 0037 Q_OBJECT 0038 public: 0039 explicit KFindNextDialog(const QString &pattern, QWidget *parent); 0040 0041 QPushButton *findButton() const; 0042 0043 private: 0044 QPushButton *m_findButton = nullptr; 0045 }; 0046 0047 // Create the dialog. 0048 KFindNextDialog::KFindNextDialog(const QString &pattern, QWidget *parent) 0049 : QDialog(parent) 0050 { 0051 setModal(false); 0052 setWindowTitle(i18n("Find Next")); 0053 0054 QVBoxLayout *layout = new QVBoxLayout(this); 0055 0056 layout->addWidget(new QLabel(i18n("<qt>Find next occurrence of '<b>%1</b>'?</qt>", pattern), this)); 0057 0058 m_findButton = new QPushButton; 0059 KGuiItem::assign(m_findButton, KStandardGuiItem::find()); 0060 m_findButton->setDefault(true); 0061 0062 QDialogButtonBox *buttonBox = new QDialogButtonBox(this); 0063 buttonBox->addButton(m_findButton, QDialogButtonBox::ActionRole); 0064 buttonBox->setStandardButtons(QDialogButtonBox::Close); 0065 layout->addWidget(buttonBox); 0066 0067 connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 0068 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 0069 } 0070 0071 QPushButton *KFindNextDialog::findButton() const 0072 { 0073 return m_findButton; 0074 } 0075 0076 //// 0077 0078 KFind::KFind(const QString &pattern, long options, QWidget *parent) 0079 : KFind(*new KFindPrivate(this), pattern, options, parent) 0080 { 0081 } 0082 0083 KFind::KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent) 0084 : QObject(parent) 0085 , d(&dd) 0086 { 0087 Q_D(KFind); 0088 0089 d->options = options; 0090 d->init(pattern); 0091 } 0092 0093 KFind::KFind(const QString &pattern, long options, QWidget *parent, QWidget *findDialog) 0094 : KFind(*new KFindPrivate(this), pattern, options, parent, findDialog) 0095 { 0096 } 0097 0098 KFind::KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent, QWidget *findDialog) 0099 : QObject(parent) 0100 , d(&dd) 0101 { 0102 Q_D(KFind); 0103 0104 d->findDialog = findDialog; 0105 d->options = options; 0106 d->init(pattern); 0107 } 0108 0109 void KFindPrivate::init(const QString &_pattern) 0110 { 0111 Q_Q(KFind); 0112 0113 matches = 0; 0114 pattern = _pattern; 0115 dialog = nullptr; 0116 dialogClosed = false; 0117 index = INDEX_NOMATCH; 0118 lastResult = KFind::NoMatch; 0119 0120 #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(5, 70) 0121 regExp = nullptr; // QRegExp 0122 0123 #endif 0124 // TODO: KF6 change this comment once d->regExp is removed 0125 // set options and create d->regExp with the right options 0126 q->setOptions(options); 0127 } 0128 0129 KFind::~KFind() = default; 0130 0131 bool KFind::needData() const 0132 { 0133 Q_D(const KFind); 0134 0135 // always true when d->text is empty. 0136 if (d->options & KFind::FindBackwards) 0137 // d->index==-1 and d->lastResult==Match means we haven't answered nomatch yet 0138 // This is important in the "replace with a prompt" case. 0139 { 0140 return (d->index < 0 && d->lastResult != Match); 0141 } else 0142 // "index over length" test removed: we want to get a nomatch before we set data again 0143 // This is important in the "replace with a prompt" case. 0144 { 0145 return d->index == INDEX_NOMATCH; 0146 } 0147 } 0148 0149 void KFind::setData(const QString &data, int startPos) 0150 { 0151 setData(-1, data, startPos); 0152 } 0153 0154 void KFind::setData(int id, const QString &data, int startPos) 0155 { 0156 Q_D(KFind); 0157 0158 // cache the data for incremental find 0159 if (d->options & KFind::FindIncremental) { 0160 if (id != -1) { 0161 d->customIds = true; 0162 } else { 0163 id = d->currentId + 1; 0164 } 0165 0166 Q_ASSERT(id <= d->data.size()); 0167 0168 if (id == d->data.size()) { 0169 d->data.append(KFindPrivate::Data(id, data, true)); 0170 } else { 0171 d->data.replace(id, KFindPrivate::Data(id, data, true)); 0172 } 0173 Q_ASSERT(d->data.at(id).text == data); 0174 } 0175 0176 if (!(d->options & KFind::FindIncremental) || needData()) { 0177 d->text = data; 0178 0179 if (startPos != -1) { 0180 d->index = startPos; 0181 } else if (d->options & KFind::FindBackwards) { 0182 d->index = d->text.length(); 0183 } else { 0184 d->index = 0; 0185 } 0186 #ifdef DEBUG_FIND 0187 // qDebug() << "setData: '" << d->text << "' d->index=" << d->index; 0188 #endif 0189 Q_ASSERT(d->index != INDEX_NOMATCH); 0190 d->lastResult = NoMatch; 0191 0192 d->currentId = id; 0193 } 0194 } 0195 0196 QDialog *KFind::findNextDialog(bool create) 0197 { 0198 Q_D(KFind); 0199 0200 if (!d->dialog && create) { 0201 KFindNextDialog *dialog = new KFindNextDialog(d->pattern, parentWidget()); 0202 connect(dialog->findButton(), &QPushButton::clicked, this, [d]() { 0203 d->slotFindNext(); 0204 }); 0205 connect(dialog, &QDialog::finished, this, [d]() { 0206 d->slotDialogClosed(); 0207 }); 0208 d->dialog = dialog; 0209 } 0210 return d->dialog; 0211 } 0212 0213 KFind::Result KFind::find() 0214 { 0215 Q_D(KFind); 0216 0217 Q_ASSERT(d->index != INDEX_NOMATCH || d->patternChanged); 0218 0219 if (d->lastResult == Match && !d->patternChanged) { 0220 // Move on before looking for the next match, _if_ we just found a match 0221 if (d->options & KFind::FindBackwards) { 0222 d->index--; 0223 if (d->index == -1) { // don't call KFind::find with -1, it has a special meaning 0224 d->lastResult = NoMatch; 0225 return NoMatch; 0226 } 0227 } else { 0228 d->index++; 0229 } 0230 } 0231 d->patternChanged = false; 0232 0233 if (d->options & KFind::FindIncremental) { 0234 // if the current pattern is shorter than the matchedPattern we can 0235 // probably look up the match in the incrementalPath 0236 if (d->pattern.length() < d->matchedPattern.length()) { 0237 KFindPrivate::Match match; 0238 if (!d->pattern.isEmpty()) { 0239 match = d->incrementalPath.value(d->pattern); 0240 } else if (d->emptyMatch) { 0241 match = *d->emptyMatch; 0242 } 0243 QString previousPattern(d->matchedPattern); 0244 d->matchedPattern = d->pattern; 0245 if (!match.isNull()) { 0246 bool clean = true; 0247 0248 // find the first result backwards on the path that isn't dirty 0249 while (d->data.at(match.dataId).dirty == true && !d->pattern.isEmpty()) { 0250 d->pattern.truncate(d->pattern.length() - 1); 0251 0252 match = d->incrementalPath.value(d->pattern); 0253 0254 clean = false; 0255 } 0256 0257 // remove all matches that lie after the current match 0258 while (d->pattern.length() < previousPattern.length()) { 0259 d->incrementalPath.remove(previousPattern); 0260 previousPattern.truncate(previousPattern.length() - 1); 0261 } 0262 0263 // set the current text, index, etc. to the found match 0264 d->text = d->data.at(match.dataId).text; 0265 d->index = match.index; 0266 d->matchedLength = match.matchedLength; 0267 d->currentId = match.dataId; 0268 0269 // if the result is clean we can return it now 0270 if (clean) { 0271 if (d->customIds) { 0272 #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(5, 81) 0273 Q_EMIT highlight(d->currentId, d->index, d->matchedLength); 0274 #endif 0275 Q_EMIT textFoundAtId(d->currentId, d->index, d->matchedLength); 0276 } else { 0277 #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(5, 81) 0278 Q_EMIT highlight(d->text, d->index, d->matchedLength); 0279 #endif 0280 Q_EMIT textFound(d->text, d->index, d->matchedLength); 0281 } 0282 0283 d->lastResult = Match; 0284 d->matchedPattern = d->pattern; 0285 return Match; 0286 } 0287 } 0288 // if we couldn't look up the match, the new pattern isn't a 0289 // substring of the matchedPattern, so we start a new search 0290 else { 0291 d->startNewIncrementalSearch(); 0292 } 0293 } 0294 // if the new pattern is longer than the matchedPattern we might be 0295 // able to proceed from the last search 0296 else if (d->pattern.length() > d->matchedPattern.length()) { 0297 // continue from the previous pattern 0298 if (d->pattern.startsWith(d->matchedPattern)) { 0299 // we can't proceed from the previous position if the previous 0300 // position already failed 0301 if (d->index == INDEX_NOMATCH) { 0302 return NoMatch; 0303 } 0304 0305 QString temp(d->pattern); 0306 d->pattern.truncate(d->matchedPattern.length() + 1); 0307 d->matchedPattern = temp; 0308 } 0309 // start a new search 0310 else { 0311 d->startNewIncrementalSearch(); 0312 } 0313 } 0314 // if the new pattern is as long as the matchedPattern, we reset if 0315 // they are not equal 0316 else if (d->pattern != d->matchedPattern) { 0317 d->startNewIncrementalSearch(); 0318 } 0319 } 0320 0321 #ifdef DEBUG_FIND 0322 // qDebug() << "d->index=" << d->index; 0323 #endif 0324 do { 0325 // if we have multiple data blocks in our cache, walk through these 0326 // blocks till we either searched all blocks or we find a match 0327 do { 0328 // Find the next candidate match. 0329 d->index = KFind::find(d->text, d->pattern, d->index, d->options, &d->matchedLength, nullptr); 0330 0331 if (d->options & KFind::FindIncremental) { 0332 d->data[d->currentId].dirty = false; 0333 } 0334 0335 if (d->index == -1 && d->currentId < d->data.count() - 1) { 0336 d->text = d->data.at(++d->currentId).text; 0337 0338 if (d->options & KFind::FindBackwards) { 0339 d->index = d->text.length(); 0340 } else { 0341 d->index = 0; 0342 } 0343 } else { 0344 break; 0345 } 0346 } while (!(d->options & KFind::RegularExpression)); 0347 0348 if (d->index != -1) { 0349 // Flexibility: the app can add more rules to validate a possible match 0350 if (validateMatch(d->text, d->index, d->matchedLength)) { 0351 bool done = true; 0352 0353 if (d->options & KFind::FindIncremental) { 0354 if (d->pattern.isEmpty()) { 0355 delete d->emptyMatch; 0356 d->emptyMatch = new KFindPrivate::Match(d->currentId, d->index, d->matchedLength); 0357 } else { 0358 d->incrementalPath.insert(d->pattern, KFindPrivate::Match(d->currentId, d->index, d->matchedLength)); 0359 } 0360 0361 if (d->pattern.length() < d->matchedPattern.length()) { 0362 d->pattern += QStringView(d->matchedPattern).mid(d->pattern.length(), 1); 0363 done = false; 0364 } 0365 } 0366 0367 if (done) { 0368 d->matches++; 0369 // Tell the world about the match we found, in case someone wants to 0370 // highlight it. 0371 if (d->customIds) { 0372 #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(5, 81) 0373 Q_EMIT highlight(d->currentId, d->index, d->matchedLength); 0374 #endif 0375 Q_EMIT textFoundAtId(d->currentId, d->index, d->matchedLength); 0376 } else { 0377 #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(5, 81) 0378 Q_EMIT highlight(d->text, d->index, d->matchedLength); 0379 #endif 0380 Q_EMIT textFound(d->text, d->index, d->matchedLength); 0381 } 0382 0383 if (!d->dialogClosed) { 0384 findNextDialog(true)->show(); 0385 } 0386 0387 #ifdef DEBUG_FIND 0388 // qDebug() << "Match. Next d->index=" << d->index; 0389 #endif 0390 d->lastResult = Match; 0391 return Match; 0392 } 0393 } else { // Skip match 0394 if (d->options & KFind::FindBackwards) { 0395 d->index--; 0396 } else { 0397 d->index++; 0398 } 0399 } 0400 } else { 0401 if (d->options & KFind::FindIncremental) { 0402 QString temp(d->pattern); 0403 temp.truncate(temp.length() - 1); 0404 d->pattern = d->matchedPattern; 0405 d->matchedPattern = temp; 0406 } 0407 0408 d->index = INDEX_NOMATCH; 0409 } 0410 } while (d->index != INDEX_NOMATCH); 0411 0412 #ifdef DEBUG_FIND 0413 // qDebug() << "NoMatch. d->index=" << d->index; 0414 #endif 0415 d->lastResult = NoMatch; 0416 return NoMatch; 0417 } 0418 0419 void KFindPrivate::startNewIncrementalSearch() 0420 { 0421 KFindPrivate::Match *match = emptyMatch; 0422 if (match == nullptr) { 0423 text.clear(); 0424 index = 0; 0425 currentId = 0; 0426 } else { 0427 text = data.at(match->dataId).text; 0428 index = match->index; 0429 currentId = match->dataId; 0430 } 0431 matchedLength = 0; 0432 incrementalPath.clear(); 0433 delete emptyMatch; 0434 emptyMatch = nullptr; 0435 matchedPattern = pattern; 0436 pattern.clear(); 0437 } 0438 0439 static bool isInWord(QChar ch) 0440 { 0441 return ch.isLetter() || ch.isDigit() || ch == QLatin1Char('_'); 0442 } 0443 0444 static bool isWholeWords(const QString &text, int starts, int matchedLength) 0445 { 0446 if (starts == 0 || !isInWord(text.at(starts - 1))) { 0447 const int ends = starts + matchedLength; 0448 if (ends == text.length() || !isInWord(text.at(ends))) { 0449 return true; 0450 } 0451 } 0452 return false; 0453 } 0454 0455 static bool matchOk(const QString &text, int index, int matchedLength, long options) 0456 { 0457 if (options & KFind::WholeWordsOnly) { 0458 // Is the match delimited correctly? 0459 if (isWholeWords(text, index, matchedLength)) { 0460 return true; 0461 } 0462 } else { 0463 // Non-whole-word search: this match is good 0464 return true; 0465 } 0466 return false; 0467 } 0468 0469 static int findRegex(const QString &text, const QString &pattern, int index, long options, int *matchedLength, QRegularExpressionMatch *rmatch) 0470 { 0471 QString _pattern = pattern; 0472 0473 // Always enable Unicode support in QRegularExpression 0474 QRegularExpression::PatternOptions opts = QRegularExpression::UseUnicodePropertiesOption; 0475 // instead of this rudimentary test, add a checkbox to toggle MultilineOption ? 0476 if (pattern.startsWith(QLatin1Char('^')) || pattern.endsWith(QLatin1Char('$'))) { 0477 opts |= QRegularExpression::MultilineOption; 0478 } else if (options & KFind::WholeWordsOnly) { // WholeWordsOnly makes no sense with multiline 0479 _pattern = QLatin1String("\\b") + pattern + QLatin1String("\\b"); 0480 } 0481 0482 if (!(options & KFind::CaseSensitive)) { 0483 opts |= QRegularExpression::CaseInsensitiveOption; 0484 } 0485 0486 QRegularExpression re(_pattern, opts); 0487 QRegularExpressionMatch match; 0488 if (options & KFind::FindBackwards) { 0489 // Backward search, until the beginning of the line... 0490 text.lastIndexOf(re, index, &match); 0491 } else { 0492 // Forward search, until the end of the line... 0493 match = re.match(text, index); 0494 } 0495 0496 // index is -1 if no match is found 0497 index = match.capturedStart(0); 0498 // matchedLength is 0 if no match is found 0499 *matchedLength = match.capturedLength(0); 0500 0501 if (rmatch) { 0502 *rmatch = match; 0503 } 0504 0505 return index; 0506 } 0507 0508 #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(5, 70) 0509 // static 0510 int KFind::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength) 0511 { 0512 return find(text, pattern, index, options, matchedLength, nullptr); 0513 } 0514 #endif 0515 0516 // static 0517 int KFind::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength, QRegularExpressionMatch *rmatch) 0518 { 0519 // Handle regular expressions in the appropriate way. 0520 if (options & KFind::RegularExpression) { 0521 return findRegex(text, pattern, index, options, matchedLength, rmatch); 0522 } 0523 0524 // In Qt4 QString("aaaaaa").lastIndexOf("a",6) returns -1; we need 0525 // to start at text.length() - pattern.length() to give a valid index to QString. 0526 if (options & KFind::FindBackwards) { 0527 index = qMin(qMax(0, text.length() - pattern.length()), index); 0528 } 0529 0530 Qt::CaseSensitivity caseSensitive = (options & KFind::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive; 0531 0532 if (options & KFind::FindBackwards) { 0533 // Backward search, until the beginning of the line... 0534 while (index >= 0) { 0535 // ...find the next match. 0536 index = text.lastIndexOf(pattern, index, caseSensitive); 0537 if (index == -1) { 0538 break; 0539 } 0540 0541 if (matchOk(text, index, pattern.length(), options)) { 0542 break; 0543 } 0544 index--; 0545 // qDebug() << "decrementing:" << index; 0546 } 0547 } else { 0548 // Forward search, until the end of the line... 0549 while (index <= text.length()) { 0550 // ...find the next match. 0551 index = text.indexOf(pattern, index, caseSensitive); 0552 if (index == -1) { 0553 break; 0554 } 0555 0556 if (matchOk(text, index, pattern.length(), options)) { 0557 break; 0558 } 0559 index++; 0560 } 0561 if (index > text.length()) { // end of line 0562 // qDebug() << "at" << index << "-> not found"; 0563 index = -1; // not found 0564 } 0565 } 0566 if (index <= -1) { 0567 *matchedLength = 0; 0568 } else { 0569 *matchedLength = pattern.length(); 0570 } 0571 return index; 0572 } 0573 0574 #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(5, 70) 0575 // Core method for the regexp-based find 0576 static int doFind(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength) 0577 { 0578 if (options & KFind::FindBackwards) { 0579 // Backward search, until the beginning of the line... 0580 while (index >= 0) { 0581 // ...find the next match. 0582 index = text.lastIndexOf(pattern, index); 0583 if (index == -1) { 0584 break; 0585 } 0586 0587 /*int pos =*/pattern.indexIn(text.mid(index)); 0588 *matchedLength = pattern.matchedLength(); 0589 if (matchOk(text, index, *matchedLength, options)) { 0590 break; 0591 } 0592 index--; 0593 } 0594 } else { 0595 // Forward search, until the end of the line... 0596 while (index <= text.length()) { 0597 // ...find the next match. 0598 index = text.indexOf(pattern, index); 0599 if (index == -1) { 0600 break; 0601 } 0602 0603 /*int pos =*/pattern.indexIn(text.mid(index)); 0604 *matchedLength = pattern.matchedLength(); 0605 if (matchOk(text, index, *matchedLength, options)) { 0606 break; 0607 } 0608 index++; 0609 } 0610 if (index > text.length()) { // end of line 0611 index = -1; // not found 0612 } 0613 } 0614 if (index == -1) { 0615 *matchedLength = 0; 0616 } 0617 return index; 0618 } 0619 #endif 0620 0621 #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(5, 70) 0622 // Since QRegExp doesn't support multiline searches (the equivalent of perl's /m) 0623 // we have to cut the text into lines if the pattern starts with ^ or ends with $. 0624 static int lineBasedFind(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength) 0625 { 0626 const QStringList lines = text.split(QLatin1Char('\n')); 0627 int offset = 0; 0628 // Use "index" to find the first line we should start from 0629 int startLineNumber = 0; 0630 for (; startLineNumber < lines.count(); ++startLineNumber) { 0631 const QString line = lines.at(startLineNumber); 0632 if (index < offset + line.length()) { 0633 break; 0634 } 0635 offset += line.length() + 1 /*newline*/; 0636 } 0637 0638 if (options & KFind::FindBackwards) { 0639 if (startLineNumber == lines.count()) { 0640 // We went too far, go back to the last line 0641 --startLineNumber; 0642 offset -= lines.at(startLineNumber).length() + 1; 0643 } 0644 0645 for (int lineNumber = startLineNumber; lineNumber >= 0; --lineNumber) { 0646 const QString line = lines.at(lineNumber); 0647 const int ret = doFind(line, pattern, lineNumber == startLineNumber ? index - offset : line.length(), options, matchedLength); 0648 if (ret > -1) { 0649 return ret + offset; 0650 } 0651 offset -= line.length() + 1 /*newline*/; 0652 } 0653 0654 } else { 0655 for (int lineNumber = startLineNumber; lineNumber < lines.count(); ++lineNumber) { 0656 const QString line = lines.at(lineNumber); 0657 const int ret = doFind(line, pattern, lineNumber == startLineNumber ? (index - offset) : 0, options, matchedLength); 0658 if (ret > -1) { 0659 return ret + offset; 0660 } 0661 offset += line.length() + 1 /*newline*/; 0662 } 0663 } 0664 return -1; 0665 } 0666 #endif 0667 0668 #if KTEXTWIDGETS_ENABLE_DEPRECATED_SINCE(5, 70) 0669 // static 0670 int KFind::find(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength) 0671 { 0672 if (pattern.pattern().startsWith(QLatin1Char('^')) || pattern.pattern().endsWith(QLatin1Char('$'))) { 0673 return lineBasedFind(text, pattern, index, options, matchedLength); 0674 } 0675 0676 return doFind(text, pattern, index, options, matchedLength); 0677 } 0678 #endif 0679 0680 void KFindPrivate::slotFindNext() 0681 { 0682 Q_Q(KFind); 0683 0684 Q_EMIT q->findNext(); 0685 } 0686 0687 void KFindPrivate::slotDialogClosed() 0688 { 0689 Q_Q(KFind); 0690 0691 #ifdef DEBUG_FIND 0692 // qDebug() << " Begin"; 0693 #endif 0694 Q_EMIT q->dialogClosed(); 0695 dialogClosed = true; 0696 #ifdef DEBUG_FIND 0697 // qDebug() << " End"; 0698 #endif 0699 } 0700 0701 void KFind::displayFinalDialog() const 0702 { 0703 Q_D(const KFind); 0704 0705 QString message; 0706 if (numMatches()) { 0707 message = i18np("1 match found.", "%1 matches found.", numMatches()); 0708 } else { 0709 message = i18n("<qt>No matches found for '<b>%1</b>'.</qt>", d->pattern.toHtmlEscaped()); 0710 } 0711 KMessageBox::information(dialogsParent(), message); 0712 } 0713 0714 bool KFind::shouldRestart(bool forceAsking, bool showNumMatches) const 0715 { 0716 Q_D(const KFind); 0717 0718 // Only ask if we did a "find from cursor", otherwise it's pointless. 0719 // Well, unless the user can modify the document during a search operation, 0720 // hence the force boolean. 0721 if (!forceAsking && (d->options & KFind::FromCursor) == 0) { 0722 displayFinalDialog(); 0723 return false; 0724 } 0725 QString message; 0726 if (showNumMatches) { 0727 if (numMatches()) { 0728 message = i18np("1 match found.", "%1 matches found.", numMatches()); 0729 } else { 0730 message = i18n("No matches found for '<b>%1</b>'.", d->pattern.toHtmlEscaped()); 0731 } 0732 } else { 0733 if (d->options & KFind::FindBackwards) { 0734 message = i18n("Beginning of document reached."); 0735 } else { 0736 message = i18n("End of document reached."); 0737 } 0738 } 0739 0740 message += QLatin1String("<br><br>"); // can't be in the i18n() of the first if() because of the plural form. 0741 // Hope this word puzzle is ok, it's a different sentence 0742 message += (d->options & KFind::FindBackwards) ? i18n("Continue from the end?") : i18n("Continue from the beginning?"); 0743 0744 int ret = KMessageBox::questionTwoActions(dialogsParent(), 0745 QStringLiteral("<qt>%1</qt>").arg(message), 0746 QString(), 0747 KStandardGuiItem::cont(), 0748 KStandardGuiItem::stop()); 0749 bool yes = (ret == KMessageBox::PrimaryAction); 0750 if (yes) { 0751 const_cast<KFindPrivate *>(d)->options &= ~KFind::FromCursor; // clear FromCursor option 0752 } 0753 return yes; 0754 } 0755 0756 long KFind::options() const 0757 { 0758 Q_D(const KFind); 0759 0760 return d->options; 0761 } 0762 0763 void KFind::setOptions(long options) 0764 { 0765 Q_D(KFind); 0766 0767 d->options = options; 0768 0769 #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(5, 70) 0770 delete d->regExp; 0771 if (d->options & KFind::RegularExpression) { 0772 Qt::CaseSensitivity caseSensitive = (d->options & KFind::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive; 0773 d->regExp = new QRegExp(d->pattern, caseSensitive); 0774 } else { 0775 d->regExp = nullptr; 0776 } 0777 #endif 0778 } 0779 0780 void KFind::closeFindNextDialog() 0781 { 0782 Q_D(KFind); 0783 0784 if (d->dialog) { 0785 d->dialog->deleteLater(); 0786 d->dialog = nullptr; 0787 } 0788 d->dialogClosed = true; 0789 } 0790 0791 int KFind::index() const 0792 { 0793 Q_D(const KFind); 0794 0795 return d->index; 0796 } 0797 0798 QString KFind::pattern() const 0799 { 0800 Q_D(const KFind); 0801 0802 return d->pattern; 0803 } 0804 0805 void KFind::setPattern(const QString &pattern) 0806 { 0807 Q_D(KFind); 0808 0809 if (d->pattern != pattern) { 0810 d->patternChanged = true; 0811 d->matches = 0; 0812 } 0813 0814 d->pattern = pattern; 0815 0816 // TODO: KF6 change this comment once d->regExp is removed 0817 // set the options and rebuild d->regeExp if necessary 0818 setOptions(options()); 0819 } 0820 0821 int KFind::numMatches() const 0822 { 0823 Q_D(const KFind); 0824 0825 return d->matches; 0826 } 0827 0828 void KFind::resetCounts() 0829 { 0830 Q_D(KFind); 0831 0832 d->matches = 0; 0833 } 0834 0835 bool KFind::validateMatch(const QString &, int, int) 0836 { 0837 return true; 0838 } 0839 0840 QWidget *KFind::parentWidget() const 0841 { 0842 return static_cast<QWidget *>(parent()); 0843 } 0844 0845 QWidget *KFind::dialogsParent() const 0846 { 0847 Q_D(const KFind); 0848 0849 // If the find dialog is still up, it should get the focus when closing a message box 0850 // Otherwise, maybe the "find next?" dialog is up 0851 // Otherwise, the "view" is the parent. 0852 return d->findDialog ? static_cast<QWidget *>(d->findDialog) : (d->dialog ? d->dialog : parentWidget()); 0853 } 0854 0855 #include "kfind.moc" 0856 #include "moc_kfind.cpp"