KTextWidgets

kreplace.cpp
1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2001 S.R. Haque <srhaque@iee.org>.
4 SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
5
6 SPDX-License-Identifier: LGPL-2.0-only
7*/
8
9#include "kreplace.h"
10
11#include "kfind_p.h"
12#include "kreplacedialog.h"
13
14#include <QDialogButtonBox>
15#include <QLabel>
16#include <QPushButton>
17#include <QRegularExpression>
18#include <QVBoxLayout>
19
20#include <KLocalizedString>
21#include <KMessageBox>
22
23//#define DEBUG_REPLACE
24#define INDEX_NOMATCH -1
25
26class KReplaceNextDialog : public QDialog
27{
29public:
30 explicit KReplaceNextDialog(QWidget *parent);
31 void setLabel(const QString &pattern, const QString &replacement);
32
33 QPushButton *replaceAllButton() const;
34 QPushButton *skipButton() const;
35 QPushButton *replaceButton() const;
36
37private:
38 QLabel *m_mainLabel = nullptr;
39 QPushButton *m_allButton = nullptr;
40 QPushButton *m_skipButton = nullptr;
41 QPushButton *m_replaceButton = nullptr;
42};
43
44KReplaceNextDialog::KReplaceNextDialog(QWidget *parent)
45 : QDialog(parent)
46{
47 setModal(false);
48 setWindowTitle(i18n("Replace"));
49
50 QVBoxLayout *layout = new QVBoxLayout(this);
51
52 m_mainLabel = new QLabel(this);
53 layout->addWidget(m_mainLabel);
54
55 m_allButton = new QPushButton(i18nc("@action:button Replace all occurrences", "&All"));
56 m_allButton->setObjectName(QStringLiteral("allButton"));
57 m_skipButton = new QPushButton(i18n("&Skip"));
58 m_skipButton->setObjectName(QStringLiteral("skipButton"));
59 m_replaceButton = new QPushButton(i18n("Replace"));
60 m_replaceButton->setObjectName(QStringLiteral("replaceButton"));
61 m_replaceButton->setDefault(true);
62
63 QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
64 buttonBox->addButton(m_allButton, QDialogButtonBox::ActionRole);
65 buttonBox->addButton(m_skipButton, QDialogButtonBox::ActionRole);
66 buttonBox->addButton(m_replaceButton, QDialogButtonBox::ActionRole);
68 layout->addWidget(buttonBox);
69
72}
73
74void KReplaceNextDialog::setLabel(const QString &pattern, const QString &replacement)
75{
76 m_mainLabel->setText(i18n("Replace '%1' with '%2'?", pattern, replacement));
77}
78
79QPushButton *KReplaceNextDialog::replaceAllButton() const
80{
81 return m_allButton;
82}
83
84QPushButton *KReplaceNextDialog::skipButton() const
85{
86 return m_skipButton;
87}
88
89QPushButton *KReplaceNextDialog::replaceButton() const
90{
91 return m_replaceButton;
92}
93
94////
95
96class KReplacePrivate : public KFindPrivate
97{
98 Q_DECLARE_PUBLIC(KReplace)
99
100public:
101 KReplacePrivate(KReplace *q, const QString &replacement)
102 : KFindPrivate(q)
103 , m_replacement(replacement)
104 {
105 }
106
107 KReplaceNextDialog *nextDialog();
108 void doReplace();
109
110 void slotSkip();
111 void slotReplace();
112 void slotReplaceAll();
113
114 QString m_replacement;
115 int m_replacements = 0;
117};
118
119////
120
121KReplace::KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent)
122 : KFind(*new KReplacePrivate(this, replacement), pattern, options, parent)
123{
124}
125
126KReplace::KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent, QWidget *dlg)
127 : KFind(*new KReplacePrivate(this, replacement), pattern, options, parent, dlg)
128{
129}
130
131KReplace::~KReplace() = default;
132
134{
135 Q_D(const KReplace);
136
137 return d->m_replacements;
138}
139
141{
142 Q_D(KReplace);
143
144 if (d->dialog || create) {
145 return d->nextDialog();
146 }
147 return nullptr;
148}
149
150KReplaceNextDialog *KReplacePrivate::nextDialog()
151{
152 Q_Q(KReplace);
153
154 if (!dialog) {
155 auto *nextDialog = new KReplaceNextDialog(q->parentWidget());
156 q->connect(nextDialog->replaceAllButton(), &QPushButton::clicked, q, [this]() {
157 slotReplaceAll();
158 });
159 q->connect(nextDialog->skipButton(), &QPushButton::clicked, q, [this]() {
160 slotSkip();
161 });
162 q->connect(nextDialog->replaceButton(), &QPushButton::clicked, q, [this]() {
163 slotReplace();
164 });
165 q->connect(nextDialog, &QDialog::finished, q, [this]() {
166 slotDialogClosed();
167 });
168 dialog = nextDialog;
169 }
170 return static_cast<KReplaceNextDialog *>(dialog);
171}
172
174{
175 Q_D(const KReplace);
176
177 if (!d->m_replacements) {
178 KMessageBox::information(parentWidget(), i18n("No text was replaced."));
179 } else {
180 KMessageBox::information(parentWidget(), i18np("1 replacement done.", "%1 replacements done.", d->m_replacements));
181 }
182}
183
184static int replaceHelper(QString &text, const QString &replacement, int index, long options, const QRegularExpressionMatch *match, int length)
185{
186 QString rep(replacement);
187 if (options & KReplaceDialog::BackReference) {
188 // Handle backreferences
189 if (options & KFind::RegularExpression) { // regex search
190 Q_ASSERT(match);
191 const int capNum = match->regularExpression().captureCount();
192 for (int i = 0; i <= capNum; ++i) {
193 rep.replace(QLatin1String("\\") + QString::number(i), match->captured(i));
194 }
195 } else { // with non-regex search only \0 is supported, replace it with the
196 // right portion of 'text'
197 rep.replace(QLatin1String("\\0"), text.mid(index, length));
198 }
199 }
200
201 // Then replace rep into the text
202 text.replace(index, length, rep);
203 return rep.length();
204}
205
206KFind::Result KReplace::replace()
207{
208 Q_D(KReplace);
209
210#ifdef DEBUG_REPLACE
211 // qDebug() << "d->index=" << d->index;
212#endif
213 if (d->index == INDEX_NOMATCH && d->lastResult == Match) {
214 d->lastResult = NoMatch;
215 return NoMatch;
216 }
217
218 do { // this loop is only because validateMatch can fail
219#ifdef DEBUG_REPLACE
220 // qDebug() << "beginning of loop: d->index=" << d->index;
221#endif
222 // Find the next match.
223 d->index = KFind::find(d->text, d->pattern, d->index, d->options, &d->matchedLength, d->options & KFind::RegularExpression ? &d->m_match : nullptr);
224
225#ifdef DEBUG_REPLACE
226 // qDebug() << "KFind::find returned d->index=" << d->index;
227#endif
228 if (d->index != -1) {
229 // Flexibility: the app can add more rules to validate a possible match
230 if (validateMatch(d->text, d->index, d->matchedLength)) {
231 if (d->options & KReplaceDialog::PromptOnReplace) {
232#ifdef DEBUG_REPLACE
233 // qDebug() << "PromptOnReplace";
234#endif
235 // Display accurate initial string and replacement string, they can vary
236 QString matchedText(d->text.mid(d->index, d->matchedLength));
237 QString rep(matchedText);
238 replaceHelper(rep, d->m_replacement, 0, d->options, d->options & KFind::RegularExpression ? &d->m_match : nullptr, d->matchedLength);
239 d->nextDialog()->setLabel(matchedText, rep);
240 d->nextDialog()->show(); // TODO kde5: virtual void showReplaceNextDialog(QString,QString), so that kreplacetest can skip the show()
241
242 // Tell the world about the match we found, in case someone wants to
243 // highlight it.
244 Q_EMIT textFound(d->text, d->index, d->matchedLength);
245
246 d->lastResult = Match;
247 return Match;
248 } else {
249 d->doReplace(); // this moves on too
250 }
251 } else {
252 // not validated -> move on
253 if (d->options & KFind::FindBackwards) {
254 d->index--;
255 } else {
256 d->index++;
257 }
258 }
259 } else {
260 d->index = INDEX_NOMATCH; // will exit the loop
261 }
262 } while (d->index != INDEX_NOMATCH);
263
264 d->lastResult = NoMatch;
265 return NoMatch;
266}
267
268int KReplace::replace(QString &text, const QString &pattern, const QString &replacement, int index, long options, int *replacedLength)
269{
270 int matchedLength;
272 index = KFind::find(text, pattern, index, options, &matchedLength, options & KFind::RegularExpression ? &match : nullptr);
273
274 if (index != -1) {
275 *replacedLength = replaceHelper(text, replacement, index, options, options & KFind::RegularExpression ? &match : nullptr, matchedLength);
277 index--;
278 } else {
279 index += *replacedLength;
280 }
281 }
282 return index;
283}
284
285void KReplacePrivate::slotReplaceAll()
286{
287 Q_Q(KReplace);
288
289 doReplace();
290 options &= ~KReplaceDialog::PromptOnReplace;
291 Q_EMIT q->optionsChanged();
292 Q_EMIT q->findNext();
293}
294
295void KReplacePrivate::slotSkip()
296{
297 Q_Q(KReplace);
298
299 if (options & KFind::FindBackwards) {
300 index--;
301 } else {
302 index++;
303 }
304 if (dialogClosed) {
305 dialog->deleteLater();
306 dialog = nullptr; // hide it again
307 } else {
308 Q_EMIT q->findNext();
309 }
310}
311
312void KReplacePrivate::slotReplace()
313{
314 Q_Q(KReplace);
315
316 doReplace();
317 if (dialogClosed) {
318 dialog->deleteLater();
319 dialog = nullptr; // hide it again
320 } else {
321 Q_EMIT q->findNext();
322 }
323}
324
325void KReplacePrivate::doReplace()
326{
327 Q_Q(KReplace);
328
329 Q_ASSERT(index >= 0);
330 const int replacedLength = replaceHelper(text, m_replacement, index, options, &m_match, matchedLength);
331
332 // Tell the world about the replacement we made, in case someone wants to
333 // highlight it.
334 Q_EMIT q->textReplaced(text, index, replacedLength, matchedLength);
335
336#ifdef DEBUG_REPLACE
337 // qDebug() << "after replace() signal: d->index=" << d->index << " replacedLength=" << replacedLength;
338#endif
339 m_replacements++;
340 if (options & KFind::FindBackwards) {
341 Q_ASSERT(index >= 0);
342 index--;
343 } else {
344 index += replacedLength;
345 // when replacing the empty pattern, move on. See also kjs/regexp.cpp for how this should be done for regexps.
346 if (pattern.isEmpty()) {
347 ++index;
348 }
349 }
350#ifdef DEBUG_REPLACE
351 // qDebug() << "after adjustment: d->index=" << d->index;
352#endif
353}
354
356{
357 Q_D(KReplace);
358
360 d->m_replacements = 0;
361}
362
363bool KReplace::shouldRestart(bool forceAsking, bool showNumMatches) const
364{
365 Q_D(const KReplace);
366
367 // Only ask if we did a "find from cursor", otherwise it's pointless.
368 // ... Or if the prompt-on-replace option was set.
369 // Well, unless the user can modify the document during a search operation,
370 // hence the force boolean.
371 if (!forceAsking && (d->options & KFind::FromCursor) == 0 && (d->options & KReplaceDialog::PromptOnReplace) == 0) {
373 return false;
374 }
375 QString message;
376 if (showNumMatches) {
377 if (!d->m_replacements) {
378 message = i18n("No text was replaced.");
379 } else {
380 message = i18np("1 replacement done.", "%1 replacements done.", d->m_replacements);
381 }
382 } else {
383 if (d->options & KFind::FindBackwards) {
384 message = i18n("Beginning of document reached.");
385 } else {
386 message = i18n("End of document reached.");
387 }
388 }
389
390 message += QLatin1Char('\n');
391 // Hope this word puzzle is ok, it's a different sentence
392 message +=
393 (d->options & KFind::FindBackwards) ? i18n("Do you want to restart search from the end?") : i18n("Do you want to restart search at the beginning?");
394
395 int ret = KMessageBox::questionTwoActions(parentWidget(),
396 message,
397 QString(),
398 KGuiItem(i18nc("@action:button Restart find & replace", "Restart")),
399 KGuiItem(i18nc("@action:button Stop find & replace", "Stop")));
400 return (ret == KMessageBox::PrimaryAction);
401}
402
407
408#include "kreplace.moc"
409#include "moc_kreplace.cpp"
void optionsChanged()
This signal is sent whenever one of the option checkboxes is toggled.
A generic implementation of the "find" function.
Definition kfind.h:94
void textFound(const QString &text, int matchingIndex, int matchedLength)
Connect to this signal to implement highlighting of found text during the find operation.
void closeFindNextDialog()
Close the "find next?" dialog.
Definition kfind.cpp:636
int index() const
Definition kfind.cpp:647
QString pattern() const
Definition kfind.cpp:654
virtual void resetCounts()
Call this to reset the numMatches count (and the numReplacements count for a KReplace).
Definition kfind.cpp:684
@ RegularExpression
Interpret the pattern as a regular expression.
Definition kfind.h:107
@ FromCursor
Start from current cursor position.
Definition kfind.h:103
@ FindBackwards
Go backwards.
Definition kfind.h:106
virtual bool validateMatch(const QString &text, int index, int matchedlength)
Virtual method, which allows applications to add extra checks for validating a candidate match.
Definition kfind.cpp:691
Result find()
Walk the text fragment (e.g.
Definition kfind.cpp:205
long options() const
Return the current options.
Definition kfind.cpp:622
A generic "replace" dialog.
@ PromptOnReplace
Should the user be prompted before the replace operation?
A generic implementation of the "replace" function.
Definition kreplace.h:90
void resetCounts() override
Call this to reset the numMatches & numReplacements counts.
Definition kreplace.cpp:355
QDialog * replaceNextDialog(bool create=false)
Return (or create) the dialog that shows the "find next?" prompt.
Definition kreplace.cpp:140
Result replace()
Walk the text fragment (e.g.
Definition kreplace.cpp:206
int numReplacements() const
Return the number of replacements made (i.e.
Definition kreplace.cpp:133
void closeReplaceNextDialog()
Close the "replace next?" dialog.
Definition kreplace.cpp:403
bool shouldRestart(bool forceAsking=false, bool showNumMatches=true) const override
Returns true if we should restart the search from scratch.
Definition kreplace.cpp:363
void displayFinalDialog() const override
Displays the final dialog telling the user how many replacements were made.
Definition kreplace.cpp:173
KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent=nullptr)
Only use this constructor if you don't use KFindDialog, or if you use it as a modal dialog.
Definition kreplace.cpp:121
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
ButtonCode questionTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
void clicked(bool checked)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
virtual void accept()
void finished(int result)
virtual void reject()
QPushButton * addButton(StandardButton button)
void setStandardButtons(StandardButtons buttons)
void setText(const QString &)
Q_EMITQ_EMIT
Q_OBJECTQ_OBJECT
QObject * parent() const const
bool isEmpty() const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:07 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.