File indexing completed on 2024-12-01 03:43:35
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 0006 SPDX-License-Identifier: LGPL-2.0-only 0007 */ 0008 0009 #include "kreplace.h" 0010 0011 #include "kfind_p.h" 0012 #include "kreplacedialog.h" 0013 0014 #include <QDialogButtonBox> 0015 #include <QLabel> 0016 #include <QPushButton> 0017 #include <QRegularExpression> 0018 #include <QVBoxLayout> 0019 0020 #include <KLocalizedString> 0021 #include <KMessageBox> 0022 0023 //#define DEBUG_REPLACE 0024 #define INDEX_NOMATCH -1 0025 0026 class KReplaceNextDialog : public QDialog 0027 { 0028 Q_OBJECT 0029 public: 0030 explicit KReplaceNextDialog(QWidget *parent); 0031 void setLabel(const QString &pattern, const QString &replacement); 0032 0033 QPushButton *replaceAllButton() const; 0034 QPushButton *skipButton() const; 0035 QPushButton *replaceButton() const; 0036 0037 private: 0038 QLabel *m_mainLabel = nullptr; 0039 QPushButton *m_allButton = nullptr; 0040 QPushButton *m_skipButton = nullptr; 0041 QPushButton *m_replaceButton = nullptr; 0042 }; 0043 0044 KReplaceNextDialog::KReplaceNextDialog(QWidget *parent) 0045 : QDialog(parent) 0046 { 0047 setModal(false); 0048 setWindowTitle(i18n("Replace")); 0049 0050 QVBoxLayout *layout = new QVBoxLayout(this); 0051 0052 m_mainLabel = new QLabel(this); 0053 layout->addWidget(m_mainLabel); 0054 0055 m_allButton = new QPushButton(i18nc("@action:button Replace all occurrences", "&All")); 0056 m_allButton->setObjectName(QStringLiteral("allButton")); 0057 m_skipButton = new QPushButton(i18n("&Skip")); 0058 m_skipButton->setObjectName(QStringLiteral("skipButton")); 0059 m_replaceButton = new QPushButton(i18n("Replace")); 0060 m_replaceButton->setObjectName(QStringLiteral("replaceButton")); 0061 m_replaceButton->setDefault(true); 0062 0063 QDialogButtonBox *buttonBox = new QDialogButtonBox(this); 0064 buttonBox->addButton(m_allButton, QDialogButtonBox::ActionRole); 0065 buttonBox->addButton(m_skipButton, QDialogButtonBox::ActionRole); 0066 buttonBox->addButton(m_replaceButton, QDialogButtonBox::ActionRole); 0067 buttonBox->setStandardButtons(QDialogButtonBox::Close); 0068 layout->addWidget(buttonBox); 0069 0070 connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 0071 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 0072 } 0073 0074 void KReplaceNextDialog::setLabel(const QString &pattern, const QString &replacement) 0075 { 0076 m_mainLabel->setText(i18n("Replace '%1' with '%2'?", pattern, replacement)); 0077 } 0078 0079 QPushButton *KReplaceNextDialog::replaceAllButton() const 0080 { 0081 return m_allButton; 0082 } 0083 0084 QPushButton *KReplaceNextDialog::skipButton() const 0085 { 0086 return m_skipButton; 0087 } 0088 0089 QPushButton *KReplaceNextDialog::replaceButton() const 0090 { 0091 return m_replaceButton; 0092 } 0093 0094 //// 0095 0096 class KReplacePrivate : public KFindPrivate 0097 { 0098 Q_DECLARE_PUBLIC(KReplace) 0099 0100 public: 0101 KReplacePrivate(KReplace *q, const QString &replacement) 0102 : KFindPrivate(q) 0103 , m_replacement(replacement) 0104 { 0105 } 0106 0107 KReplaceNextDialog *nextDialog(); 0108 void doReplace(); 0109 0110 void slotSkip(); 0111 void slotReplace(); 0112 void slotReplaceAll(); 0113 0114 QString m_replacement; 0115 int m_replacements = 0; 0116 QRegularExpressionMatch m_match; 0117 }; 0118 0119 //// 0120 0121 KReplace::KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent) 0122 : KFind(*new KReplacePrivate(this, replacement), pattern, options, parent) 0123 { 0124 } 0125 0126 KReplace::KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent, QWidget *dlg) 0127 : KFind(*new KReplacePrivate(this, replacement), pattern, options, parent, dlg) 0128 { 0129 } 0130 0131 KReplace::~KReplace() = default; 0132 0133 int KReplace::numReplacements() const 0134 { 0135 Q_D(const KReplace); 0136 0137 return d->m_replacements; 0138 } 0139 0140 QDialog *KReplace::replaceNextDialog(bool create) 0141 { 0142 Q_D(KReplace); 0143 0144 if (d->dialog || create) { 0145 return d->nextDialog(); 0146 } 0147 return nullptr; 0148 } 0149 0150 KReplaceNextDialog *KReplacePrivate::nextDialog() 0151 { 0152 Q_Q(KReplace); 0153 0154 if (!dialog) { 0155 auto *nextDialog = new KReplaceNextDialog(q->parentWidget()); 0156 q->connect(nextDialog->replaceAllButton(), &QPushButton::clicked, q, [this]() { 0157 slotReplaceAll(); 0158 }); 0159 q->connect(nextDialog->skipButton(), &QPushButton::clicked, q, [this]() { 0160 slotSkip(); 0161 }); 0162 q->connect(nextDialog->replaceButton(), &QPushButton::clicked, q, [this]() { 0163 slotReplace(); 0164 }); 0165 q->connect(nextDialog, &QDialog::finished, q, [this]() { 0166 slotDialogClosed(); 0167 }); 0168 dialog = nextDialog; 0169 } 0170 return static_cast<KReplaceNextDialog *>(dialog); 0171 } 0172 0173 void KReplace::displayFinalDialog() const 0174 { 0175 Q_D(const KReplace); 0176 0177 if (!d->m_replacements) { 0178 KMessageBox::information(parentWidget(), i18n("No text was replaced.")); 0179 } else { 0180 KMessageBox::information(parentWidget(), i18np("1 replacement done.", "%1 replacements done.", d->m_replacements)); 0181 } 0182 } 0183 0184 static int replaceHelper(QString &text, const QString &replacement, int index, long options, const QRegularExpressionMatch *match, int length) 0185 { 0186 QString rep(replacement); 0187 if (options & KReplaceDialog::BackReference) { 0188 // Handle backreferences 0189 if (options & KFind::RegularExpression) { // regex search 0190 Q_ASSERT(match); 0191 const int capNum = match->regularExpression().captureCount(); 0192 for (int i = 0; i <= capNum; ++i) { 0193 rep.replace(QLatin1String("\\") + QString::number(i), match->captured(i)); 0194 } 0195 } else { // with non-regex search only \0 is supported, replace it with the 0196 // right portion of 'text' 0197 rep.replace(QLatin1String("\\0"), text.mid(index, length)); 0198 } 0199 } 0200 0201 // Then replace rep into the text 0202 text.replace(index, length, rep); 0203 return rep.length(); 0204 } 0205 0206 KFind::Result KReplace::replace() 0207 { 0208 Q_D(KReplace); 0209 0210 #ifdef DEBUG_REPLACE 0211 // qDebug() << "d->index=" << d->index; 0212 #endif 0213 if (d->index == INDEX_NOMATCH && d->lastResult == Match) { 0214 d->lastResult = NoMatch; 0215 return NoMatch; 0216 } 0217 0218 do { // this loop is only because validateMatch can fail 0219 #ifdef DEBUG_REPLACE 0220 // qDebug() << "beginning of loop: d->index=" << d->index; 0221 #endif 0222 // Find the next match. 0223 d->index = KFind::find(d->text, d->pattern, d->index, d->options, &d->matchedLength, d->options & KFind::RegularExpression ? &d->m_match : nullptr); 0224 0225 #ifdef DEBUG_REPLACE 0226 // qDebug() << "KFind::find returned d->index=" << d->index; 0227 #endif 0228 if (d->index != -1) { 0229 // Flexibility: the app can add more rules to validate a possible match 0230 if (validateMatch(d->text, d->index, d->matchedLength)) { 0231 if (d->options & KReplaceDialog::PromptOnReplace) { 0232 #ifdef DEBUG_REPLACE 0233 // qDebug() << "PromptOnReplace"; 0234 #endif 0235 // Display accurate initial string and replacement string, they can vary 0236 QString matchedText(d->text.mid(d->index, d->matchedLength)); 0237 QString rep(matchedText); 0238 replaceHelper(rep, d->m_replacement, 0, d->options, d->options & KFind::RegularExpression ? &d->m_match : nullptr, d->matchedLength); 0239 d->nextDialog()->setLabel(matchedText, rep); 0240 d->nextDialog()->show(); // TODO kde5: virtual void showReplaceNextDialog(QString,QString), so that kreplacetest can skip the show() 0241 0242 // Tell the world about the match we found, in case someone wants to 0243 // highlight it. 0244 Q_EMIT textFound(d->text, d->index, d->matchedLength); 0245 0246 d->lastResult = Match; 0247 return Match; 0248 } else { 0249 d->doReplace(); // this moves on too 0250 } 0251 } else { 0252 // not validated -> move on 0253 if (d->options & KFind::FindBackwards) { 0254 d->index--; 0255 } else { 0256 d->index++; 0257 } 0258 } 0259 } else { 0260 d->index = INDEX_NOMATCH; // will exit the loop 0261 } 0262 } while (d->index != INDEX_NOMATCH); 0263 0264 d->lastResult = NoMatch; 0265 return NoMatch; 0266 } 0267 0268 int KReplace::replace(QString &text, const QString &pattern, const QString &replacement, int index, long options, int *replacedLength) 0269 { 0270 int matchedLength; 0271 QRegularExpressionMatch match; 0272 index = KFind::find(text, pattern, index, options, &matchedLength, options & KFind::RegularExpression ? &match : nullptr); 0273 0274 if (index != -1) { 0275 *replacedLength = replaceHelper(text, replacement, index, options, options & KFind::RegularExpression ? &match : nullptr, matchedLength); 0276 if (options & KFind::FindBackwards) { 0277 index--; 0278 } else { 0279 index += *replacedLength; 0280 } 0281 } 0282 return index; 0283 } 0284 0285 void KReplacePrivate::slotReplaceAll() 0286 { 0287 Q_Q(KReplace); 0288 0289 doReplace(); 0290 options &= ~KReplaceDialog::PromptOnReplace; 0291 Q_EMIT q->optionsChanged(); 0292 Q_EMIT q->findNext(); 0293 } 0294 0295 void KReplacePrivate::slotSkip() 0296 { 0297 Q_Q(KReplace); 0298 0299 if (options & KFind::FindBackwards) { 0300 index--; 0301 } else { 0302 index++; 0303 } 0304 if (dialogClosed) { 0305 dialog->deleteLater(); 0306 dialog = nullptr; // hide it again 0307 } else { 0308 Q_EMIT q->findNext(); 0309 } 0310 } 0311 0312 void KReplacePrivate::slotReplace() 0313 { 0314 Q_Q(KReplace); 0315 0316 doReplace(); 0317 if (dialogClosed) { 0318 dialog->deleteLater(); 0319 dialog = nullptr; // hide it again 0320 } else { 0321 Q_EMIT q->findNext(); 0322 } 0323 } 0324 0325 void KReplacePrivate::doReplace() 0326 { 0327 Q_Q(KReplace); 0328 0329 Q_ASSERT(index >= 0); 0330 const int replacedLength = replaceHelper(text, m_replacement, index, options, &m_match, matchedLength); 0331 0332 // Tell the world about the replacement we made, in case someone wants to 0333 // highlight it. 0334 Q_EMIT q->textReplaced(text, index, replacedLength, matchedLength); 0335 0336 #ifdef DEBUG_REPLACE 0337 // qDebug() << "after replace() signal: d->index=" << d->index << " replacedLength=" << replacedLength; 0338 #endif 0339 m_replacements++; 0340 if (options & KFind::FindBackwards) { 0341 Q_ASSERT(index >= 0); 0342 index--; 0343 } else { 0344 index += replacedLength; 0345 // when replacing the empty pattern, move on. See also kjs/regexp.cpp for how this should be done for regexps. 0346 if (pattern.isEmpty()) { 0347 ++index; 0348 } 0349 } 0350 #ifdef DEBUG_REPLACE 0351 // qDebug() << "after adjustment: d->index=" << d->index; 0352 #endif 0353 } 0354 0355 void KReplace::resetCounts() 0356 { 0357 Q_D(KReplace); 0358 0359 KFind::resetCounts(); 0360 d->m_replacements = 0; 0361 } 0362 0363 bool KReplace::shouldRestart(bool forceAsking, bool showNumMatches) const 0364 { 0365 Q_D(const KReplace); 0366 0367 // Only ask if we did a "find from cursor", otherwise it's pointless. 0368 // ... Or if the prompt-on-replace option was set. 0369 // Well, unless the user can modify the document during a search operation, 0370 // hence the force boolean. 0371 if (!forceAsking && (d->options & KFind::FromCursor) == 0 && (d->options & KReplaceDialog::PromptOnReplace) == 0) { 0372 displayFinalDialog(); 0373 return false; 0374 } 0375 QString message; 0376 if (showNumMatches) { 0377 if (!d->m_replacements) { 0378 message = i18n("No text was replaced."); 0379 } else { 0380 message = i18np("1 replacement done.", "%1 replacements done.", d->m_replacements); 0381 } 0382 } else { 0383 if (d->options & KFind::FindBackwards) { 0384 message = i18n("Beginning of document reached."); 0385 } else { 0386 message = i18n("End of document reached."); 0387 } 0388 } 0389 0390 message += QLatin1Char('\n'); 0391 // Hope this word puzzle is ok, it's a different sentence 0392 message += 0393 (d->options & KFind::FindBackwards) ? i18n("Do you want to restart search from the end?") : i18n("Do you want to restart search at the beginning?"); 0394 0395 int ret = KMessageBox::questionTwoActions(parentWidget(), 0396 message, 0397 QString(), 0398 KGuiItem(i18nc("@action:button Restart find & replace", "Restart")), 0399 KGuiItem(i18nc("@action:button Stop find & replace", "Stop"))); 0400 return (ret == KMessageBox::PrimaryAction); 0401 } 0402 0403 void KReplace::closeReplaceNextDialog() 0404 { 0405 closeFindNextDialog(); 0406 } 0407 0408 #include "kreplace.moc" 0409 #include "moc_kreplace.cpp"