File indexing completed on 2024-04-28 11:46:02

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"