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 "kfinddialog.h" |
10 | #include "kfinddialog_p.h" |
11 | |
12 | #include "kfind.h" |
13 | |
14 | #include <QCheckBox> |
15 | #include <QDialogButtonBox> |
16 | #include <QGridLayout> |
17 | #include <QGroupBox> |
18 | #include <QLabel> |
19 | #include <QLineEdit> |
20 | #include <QMenu> |
21 | #include <QPushButton> |
22 | #include <QRegularExpression> |
23 | |
24 | #include <KGuiItem> |
25 | #include <KHistoryComboBox> |
26 | #include <KLazyLocalizedString> |
27 | #include <KLocalizedString> |
28 | #include <KMessageBox> |
29 | |
30 | #include <assert.h> |
31 | |
32 | KFindDialog::KFindDialog(QWidget *parent, long options, const QStringList &findStrings, bool hasSelection, bool replaceDialog) |
33 | : KFindDialog(*new KFindDialogPrivate(this), parent, options, findStrings, hasSelection, replaceDialog) |
34 | { |
35 | setWindowTitle(i18n("Find Text" )); |
36 | } |
37 | |
38 | KFindDialog::KFindDialog(KFindDialogPrivate &dd, QWidget *parent, long options, const QStringList &findStrings, bool hasSelection, bool replaceDialog) |
39 | : QDialog(parent) |
40 | , d_ptr(&dd) |
41 | { |
42 | Q_D(KFindDialog); |
43 | |
44 | d->init(forReplace: replaceDialog, findStrings, hasSelection); |
45 | setOptions(options); |
46 | } |
47 | |
48 | KFindDialog::~KFindDialog() = default; |
49 | |
50 | QWidget *KFindDialog::findExtension() const |
51 | { |
52 | Q_D(const KFindDialog); |
53 | |
54 | if (!d->findExtension) { |
55 | d->findExtension = new QWidget(d->findGrp); |
56 | d->findLayout->addWidget(d->findExtension, row: 3, column: 0, rowSpan: 1, columnSpan: 2); |
57 | } |
58 | |
59 | return d->findExtension; |
60 | } |
61 | |
62 | QStringList KFindDialog::findHistory() const |
63 | { |
64 | Q_D(const KFindDialog); |
65 | |
66 | return d->find->historyItems(); |
67 | } |
68 | |
69 | void KFindDialogPrivate::init(bool forReplace, const QStringList &_findStrings, bool hasSelection) |
70 | { |
71 | Q_Q(KFindDialog); |
72 | |
73 | // Create common parts of dialog. |
74 | QVBoxLayout *topLayout = new QVBoxLayout(q); |
75 | |
76 | findGrp = new QGroupBox(i18nc("@title:group" , "Find" ), q); |
77 | findLayout = new QGridLayout(findGrp); |
78 | |
79 | QLabel *findLabel = new QLabel(i18n("&Text to find:" ), findGrp); |
80 | find = new KHistoryComboBox(findGrp); |
81 | find->setMaxCount(10); |
82 | find->setDuplicatesEnabled(false); |
83 | regExp = new QCheckBox(i18n("Regular e&xpression" ), findGrp); |
84 | regExpItem = new QPushButton(i18n("&Edit..." ), findGrp); |
85 | regExpItem->setEnabled(false); |
86 | |
87 | findLayout->addWidget(findLabel, row: 0, column: 0); |
88 | findLayout->addWidget(find, row: 1, column: 0, rowSpan: 1, columnSpan: 2); |
89 | findLayout->addWidget(regExp, row: 2, column: 0); |
90 | findLayout->addWidget(regExpItem, row: 2, column: 1); |
91 | topLayout->addWidget(findGrp); |
92 | |
93 | replaceGrp = new QGroupBox(i18n("Replace With" ), q); |
94 | replaceLayout = new QGridLayout(replaceGrp); |
95 | |
96 | QLabel *replaceLabel = new QLabel(i18n("Replace&ment text:" ), replaceGrp); |
97 | replace = new KHistoryComboBox(replaceGrp); |
98 | replace->setMaxCount(10); |
99 | replace->setDuplicatesEnabled(false); |
100 | backRef = new QCheckBox(i18n("Use p&laceholders" ), replaceGrp); |
101 | backRefItem = new QPushButton(i18n("Insert Place&holder" ), replaceGrp); |
102 | backRefItem->setEnabled(false); |
103 | |
104 | replaceLayout->addWidget(replaceLabel, row: 0, column: 0); |
105 | replaceLayout->addWidget(replace, row: 1, column: 0, rowSpan: 1, columnSpan: 2); |
106 | replaceLayout->addWidget(backRef, row: 2, column: 0); |
107 | replaceLayout->addWidget(backRefItem, row: 2, column: 1); |
108 | topLayout->addWidget(replaceGrp); |
109 | |
110 | QGroupBox *optionGrp = new QGroupBox(i18n("Options" ), q); |
111 | QGridLayout *optionsLayout = new QGridLayout(optionGrp); |
112 | |
113 | caseSensitive = new QCheckBox(i18n("C&ase sensitive" ), optionGrp); |
114 | wholeWordsOnly = new QCheckBox(i18n("&Whole words only" ), optionGrp); |
115 | fromCursor = new QCheckBox(i18n("From c&ursor" ), optionGrp); |
116 | findBackwards = new QCheckBox(i18n("Find &backwards" ), optionGrp); |
117 | selectedText = new QCheckBox(i18n("&Selected text" ), optionGrp); |
118 | q->setHasSelection(hasSelection); |
119 | // If we have a selection, we make 'find in selection' default |
120 | // and if we don't, then the option has to be unchecked, obviously. |
121 | selectedText->setChecked(hasSelection); |
122 | slotSelectedTextToggled(hasSelection); |
123 | |
124 | promptOnReplace = new QCheckBox(i18n("&Prompt on replace" ), optionGrp); |
125 | promptOnReplace->setChecked(true); |
126 | |
127 | optionsLayout->addWidget(caseSensitive, row: 0, column: 0); |
128 | optionsLayout->addWidget(wholeWordsOnly, row: 1, column: 0); |
129 | optionsLayout->addWidget(fromCursor, row: 2, column: 0); |
130 | optionsLayout->addWidget(findBackwards, row: 0, column: 1); |
131 | optionsLayout->addWidget(selectedText, row: 1, column: 1); |
132 | optionsLayout->addWidget(promptOnReplace, row: 2, column: 1); |
133 | topLayout->addWidget(optionGrp); |
134 | |
135 | buttonBox = new QDialogButtonBox(q); |
136 | buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Close); |
137 | q->connect(sender: buttonBox, signal: &QDialogButtonBox::accepted, context: q, slot: [this]() { |
138 | slotOk(); |
139 | }); |
140 | q->connect(sender: buttonBox, signal: &QDialogButtonBox::rejected, context: q, slot: [this]() { |
141 | slotReject(); |
142 | }); |
143 | topLayout->addWidget(buttonBox); |
144 | |
145 | // We delay creation of these until needed. |
146 | patterns = nullptr; |
147 | placeholders = nullptr; |
148 | |
149 | // signals and slots connections |
150 | q->connect(sender: selectedText, signal: &QCheckBox::toggled, context: q, slot: [this](bool checked) { |
151 | slotSelectedTextToggled(checked); |
152 | }); |
153 | q->connect(sender: regExp, signal: &QCheckBox::toggled, context: regExpItem, slot: &QWidget::setEnabled); |
154 | q->connect(sender: backRef, signal: &QCheckBox::toggled, context: backRefItem, slot: &QWidget::setEnabled); |
155 | q->connect(sender: regExpItem, signal: &QPushButton::clicked, context: q, slot: [this]() { |
156 | showPatterns(); |
157 | }); |
158 | q->connect(sender: backRefItem, signal: &QPushButton::clicked, context: q, slot: [this]() { |
159 | showPlaceholders(); |
160 | }); |
161 | |
162 | q->connect(sender: find, signal: &KHistoryComboBox::editTextChanged, context: q, slot: [this](const QString &text) { |
163 | textSearchChanged(text); |
164 | }); |
165 | |
166 | q->connect(sender: regExp, signal: &QCheckBox::toggled, context: q, slot: &KFindDialog::optionsChanged); |
167 | q->connect(sender: backRef, signal: &QCheckBox::toggled, context: q, slot: &KFindDialog::optionsChanged); |
168 | q->connect(sender: caseSensitive, signal: &QCheckBox::toggled, context: q, slot: &KFindDialog::optionsChanged); |
169 | q->connect(sender: wholeWordsOnly, signal: &QCheckBox::toggled, context: q, slot: &KFindDialog::optionsChanged); |
170 | q->connect(sender: fromCursor, signal: &QCheckBox::toggled, context: q, slot: &KFindDialog::optionsChanged); |
171 | q->connect(sender: findBackwards, signal: &QCheckBox::toggled, context: q, slot: &KFindDialog::optionsChanged); |
172 | q->connect(sender: selectedText, signal: &QCheckBox::toggled, context: q, slot: &KFindDialog::optionsChanged); |
173 | q->connect(sender: promptOnReplace, signal: &QCheckBox::toggled, context: q, slot: &KFindDialog::optionsChanged); |
174 | |
175 | // tab order |
176 | q->setTabOrder(find, regExp); |
177 | q->setTabOrder(regExp, regExpItem); |
178 | q->setTabOrder(regExpItem, replace); // findExtension widgets are inserted in showEvent() |
179 | q->setTabOrder(replace, backRef); |
180 | q->setTabOrder(backRef, backRefItem); |
181 | q->setTabOrder(backRefItem, caseSensitive); |
182 | q->setTabOrder(caseSensitive, wholeWordsOnly); |
183 | q->setTabOrder(wholeWordsOnly, fromCursor); |
184 | q->setTabOrder(fromCursor, findBackwards); |
185 | q->setTabOrder(findBackwards, selectedText); |
186 | q->setTabOrder(selectedText, promptOnReplace); |
187 | |
188 | // buddies |
189 | findLabel->setBuddy(find); |
190 | replaceLabel->setBuddy(replace); |
191 | |
192 | if (!forReplace) { |
193 | promptOnReplace->hide(); |
194 | replaceGrp->hide(); |
195 | } |
196 | |
197 | findStrings = _findStrings; |
198 | find->setFocus(); |
199 | QPushButton *buttonOk = buttonBox->button(which: QDialogButtonBox::Ok); |
200 | buttonOk->setEnabled(!q->pattern().isEmpty()); |
201 | |
202 | if (forReplace) { |
203 | KGuiItem::assign(button: buttonOk, |
204 | item: KGuiItem(i18n("&Replace" ), |
205 | QString(), |
206 | i18n("Start replace" ), |
207 | i18n("<qt>If you press the <b>Replace</b> button, the text you entered " |
208 | "above is searched for within the document and any occurrence is " |
209 | "replaced with the replacement text.</qt>" ))); |
210 | } else { |
211 | KGuiItem::assign(button: buttonOk, |
212 | item: KGuiItem(i18n("&Find" ), |
213 | QStringLiteral("edit-find" ), |
214 | i18n("Start searching" ), |
215 | i18n("<qt>If you press the <b>Find</b> button, the text you entered " |
216 | "above is searched for within the document.</qt>" ))); |
217 | } |
218 | |
219 | // QWhatsthis texts |
220 | find->setWhatsThis(i18n("Enter a pattern to search for, or select a previous pattern from the list." )); |
221 | regExp->setWhatsThis(i18n("If enabled, search for a regular expression." )); |
222 | regExpItem->setWhatsThis(i18n("Click here to edit your regular expression using a graphical editor." )); |
223 | replace->setWhatsThis(i18n("Enter a replacement string, or select a previous one from the list." )); |
224 | backRef->setWhatsThis( |
225 | i18n("<qt>If enabled, any occurrence of <code><b>\\N</b></code>, where " |
226 | "<code><b>N</b></code> is an integer number, will be replaced with " |
227 | "the corresponding capture (\"parenthesized substring\") from the " |
228 | "pattern.<p>To include (a literal <code><b>\\N</b></code> in your " |
229 | "replacement, put an extra backslash in front of it, like " |
230 | "<code><b>\\\\N</b></code>.</p></qt>" )); |
231 | backRefItem->setWhatsThis(i18n("Click for a menu of available captures." )); |
232 | wholeWordsOnly->setWhatsThis(i18n("Require word boundaries in both ends of a match to succeed." )); |
233 | fromCursor->setWhatsThis(i18n("Start searching at the current cursor location rather than at the top." )); |
234 | selectedText->setWhatsThis(i18n("Only search within the current selection." )); |
235 | caseSensitive->setWhatsThis(i18n("Perform a case sensitive search: entering the pattern 'Joe' will not match 'joe' or 'JOE', only 'Joe'." )); |
236 | findBackwards->setWhatsThis(i18n("Search backwards." )); |
237 | promptOnReplace->setWhatsThis(i18n("Ask before replacing each match found." )); |
238 | |
239 | textSearchChanged(find->lineEdit()->text()); |
240 | } |
241 | |
242 | void KFindDialogPrivate::textSearchChanged(const QString &text) |
243 | { |
244 | buttonBox->button(which: QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); |
245 | } |
246 | |
247 | void KFindDialog::showEvent(QShowEvent *e) |
248 | { |
249 | Q_D(KFindDialog); |
250 | |
251 | if (!d->initialShowDone) { |
252 | d->initialShowDone = true; // only once |
253 | // qDebug() << "showEvent\n"; |
254 | if (!d->findStrings.isEmpty()) { |
255 | setFindHistory(d->findStrings); |
256 | } |
257 | d->findStrings = QStringList(); |
258 | if (!d->pattern.isEmpty()) { |
259 | d->find->lineEdit()->setText(d->pattern); |
260 | d->find->lineEdit()->selectAll(); |
261 | d->pattern.clear(); |
262 | } |
263 | // maintain a user-friendly tab order |
264 | if (d->findExtension) { |
265 | QWidget *prev = d->regExpItem; |
266 | const auto children = d->findExtension->findChildren<QWidget *>(); |
267 | for (QWidget *child : children) { |
268 | setTabOrder(prev, child); |
269 | prev = child; |
270 | } |
271 | setTabOrder(prev, d->replace); |
272 | } |
273 | } |
274 | d->find->setFocus(); |
275 | QDialog::showEvent(e); |
276 | } |
277 | |
278 | long KFindDialog::options() const |
279 | { |
280 | Q_D(const KFindDialog); |
281 | |
282 | long options = 0; |
283 | |
284 | if (d->caseSensitive->isChecked()) { |
285 | options |= KFind::CaseSensitive; |
286 | } |
287 | if (d->wholeWordsOnly->isChecked()) { |
288 | options |= KFind::WholeWordsOnly; |
289 | } |
290 | if (d->fromCursor->isChecked()) { |
291 | options |= KFind::FromCursor; |
292 | } |
293 | if (d->findBackwards->isChecked()) { |
294 | options |= KFind::FindBackwards; |
295 | } |
296 | if (d->selectedText->isChecked()) { |
297 | options |= KFind::SelectedText; |
298 | } |
299 | if (d->regExp->isChecked()) { |
300 | options |= KFind::RegularExpression; |
301 | } |
302 | return options; |
303 | } |
304 | |
305 | QString KFindDialog::pattern() const |
306 | { |
307 | Q_D(const KFindDialog); |
308 | |
309 | return d->find->currentText(); |
310 | } |
311 | |
312 | void KFindDialog::setPattern(const QString &pattern) |
313 | { |
314 | Q_D(KFindDialog); |
315 | |
316 | d->find->lineEdit()->setText(pattern); |
317 | d->find->lineEdit()->selectAll(); |
318 | d->pattern = pattern; |
319 | // qDebug() << "setPattern " << pattern; |
320 | } |
321 | |
322 | void KFindDialog::setFindHistory(const QStringList &strings) |
323 | { |
324 | Q_D(KFindDialog); |
325 | |
326 | if (!strings.isEmpty()) { |
327 | d->find->setHistoryItems(items: strings, setCompletionList: true); |
328 | d->find->lineEdit()->setText(strings.first()); |
329 | d->find->lineEdit()->selectAll(); |
330 | } else { |
331 | d->find->clearHistory(); |
332 | } |
333 | } |
334 | |
335 | void KFindDialog::setHasSelection(bool hasSelection) |
336 | { |
337 | Q_D(KFindDialog); |
338 | |
339 | if (hasSelection) { |
340 | d->enabled |= KFind::SelectedText; |
341 | } else { |
342 | d->enabled &= ~KFind::SelectedText; |
343 | } |
344 | d->selectedText->setEnabled(hasSelection); |
345 | if (!hasSelection) { |
346 | d->selectedText->setChecked(false); |
347 | d->slotSelectedTextToggled(hasSelection); |
348 | } |
349 | } |
350 | |
351 | void KFindDialogPrivate::slotSelectedTextToggled(bool selec) |
352 | { |
353 | // From cursor doesn't make sense if we have a selection |
354 | fromCursor->setEnabled(!selec && (enabled & KFind::FromCursor)); |
355 | if (selec) { // uncheck if disabled |
356 | fromCursor->setChecked(false); |
357 | } |
358 | } |
359 | |
360 | void KFindDialog::setHasCursor(bool hasCursor) |
361 | { |
362 | Q_D(KFindDialog); |
363 | |
364 | if (hasCursor) { |
365 | d->enabled |= KFind::FromCursor; |
366 | } else { |
367 | d->enabled &= ~KFind::FromCursor; |
368 | } |
369 | d->fromCursor->setEnabled(hasCursor); |
370 | d->fromCursor->setChecked(hasCursor && (options() & KFind::FromCursor)); |
371 | } |
372 | |
373 | void KFindDialog::setSupportsBackwardsFind(bool supports) |
374 | { |
375 | Q_D(KFindDialog); |
376 | |
377 | // ########## Shouldn't this hide the checkbox instead? |
378 | if (supports) { |
379 | d->enabled |= KFind::FindBackwards; |
380 | } else { |
381 | d->enabled &= ~KFind::FindBackwards; |
382 | } |
383 | d->findBackwards->setEnabled(supports); |
384 | d->findBackwards->setChecked(supports && (options() & KFind::FindBackwards)); |
385 | } |
386 | |
387 | void KFindDialog::setSupportsCaseSensitiveFind(bool supports) |
388 | { |
389 | Q_D(KFindDialog); |
390 | |
391 | // ########## This should hide the checkbox instead |
392 | if (supports) { |
393 | d->enabled |= KFind::CaseSensitive; |
394 | } else { |
395 | d->enabled &= ~KFind::CaseSensitive; |
396 | } |
397 | d->caseSensitive->setEnabled(supports); |
398 | d->caseSensitive->setChecked(supports && (options() & KFind::CaseSensitive)); |
399 | } |
400 | |
401 | void KFindDialog::setSupportsWholeWordsFind(bool supports) |
402 | { |
403 | Q_D(KFindDialog); |
404 | |
405 | // ########## This should hide the checkbox instead |
406 | if (supports) { |
407 | d->enabled |= KFind::WholeWordsOnly; |
408 | } else { |
409 | d->enabled &= ~KFind::WholeWordsOnly; |
410 | } |
411 | d->wholeWordsOnly->setEnabled(supports); |
412 | d->wholeWordsOnly->setChecked(supports && (options() & KFind::WholeWordsOnly)); |
413 | } |
414 | |
415 | void KFindDialog::setSupportsRegularExpressionFind(bool supports) |
416 | { |
417 | Q_D(KFindDialog); |
418 | |
419 | if (supports) { |
420 | d->enabled |= KFind::RegularExpression; |
421 | } else { |
422 | d->enabled &= ~KFind::RegularExpression; |
423 | } |
424 | d->regExp->setEnabled(supports); |
425 | d->regExp->setChecked(supports && (options() & KFind::RegularExpression)); |
426 | if (!supports) { |
427 | d->regExpItem->hide(); |
428 | d->regExp->hide(); |
429 | } else { |
430 | d->regExpItem->show(); |
431 | d->regExp->show(); |
432 | } |
433 | } |
434 | |
435 | void KFindDialog::setOptions(long options) |
436 | { |
437 | Q_D(KFindDialog); |
438 | |
439 | d->caseSensitive->setChecked((d->enabled & KFind::CaseSensitive) && (options & KFind::CaseSensitive)); |
440 | d->wholeWordsOnly->setChecked((d->enabled & KFind::WholeWordsOnly) && (options & KFind::WholeWordsOnly)); |
441 | d->fromCursor->setChecked((d->enabled & KFind::FromCursor) && (options & KFind::FromCursor)); |
442 | d->findBackwards->setChecked((d->enabled & KFind::FindBackwards) && (options & KFind::FindBackwards)); |
443 | d->selectedText->setChecked((d->enabled & KFind::SelectedText) && (options & KFind::SelectedText)); |
444 | d->regExp->setChecked((d->enabled & KFind::RegularExpression) && (options & KFind::RegularExpression)); |
445 | } |
446 | |
447 | // Create a popup menu with a list of regular expression terms, to help the user |
448 | // compose a regular expression search pattern. |
449 | void KFindDialogPrivate::showPatterns() |
450 | { |
451 | Q_Q(KFindDialog); |
452 | |
453 | typedef struct { |
454 | const KLazyLocalizedString description; |
455 | const char *regExp; |
456 | int cursorAdjustment; |
457 | } Term; |
458 | static const Term items[] = { |
459 | {.description: kli18n(text: "Any Character" ), .regExp: "." , .cursorAdjustment: 0}, |
460 | {.description: kli18n(text: "Start of Line" ), .regExp: "^" , .cursorAdjustment: 0}, |
461 | {.description: kli18n(text: "End of Line" ), .regExp: "$" , .cursorAdjustment: 0}, |
462 | {.description: kli18n(text: "Set of Characters" ), .regExp: "[]" , .cursorAdjustment: -1}, |
463 | {.description: kli18n(text: "Repeats, Zero or More Times" ), .regExp: "*" , .cursorAdjustment: 0}, |
464 | {.description: kli18n(text: "Repeats, One or More Times" ), .regExp: "+" , .cursorAdjustment: 0}, |
465 | {.description: kli18n(text: "Optional" ), .regExp: "?" , .cursorAdjustment: 0}, |
466 | {.description: kli18n(text: "Escape" ), .regExp: "\\" , .cursorAdjustment: 0}, |
467 | {.description: kli18n(text: "TAB" ), .regExp: "\\t" , .cursorAdjustment: 0}, |
468 | {.description: kli18n(text: "Newline" ), .regExp: "\\n" , .cursorAdjustment: 0}, |
469 | {.description: kli18n(text: "Carriage Return" ), .regExp: "\\r" , .cursorAdjustment: 0}, |
470 | {.description: kli18n(text: "White Space" ), .regExp: "\\s" , .cursorAdjustment: 0}, |
471 | {.description: kli18n(text: "Digit" ), .regExp: "\\d" , .cursorAdjustment: 0}, |
472 | }; |
473 | |
474 | class RegExpAction : public QAction |
475 | { |
476 | public: |
477 | RegExpAction(QObject *parent, const QString &text, const QString ®Exp, int cursor) |
478 | : QAction(text, parent) |
479 | , mText(text) |
480 | , mRegExp(regExp) |
481 | , mCursor(cursor) |
482 | { |
483 | } |
484 | |
485 | QString text() const |
486 | { |
487 | return mText; |
488 | } |
489 | QString regExp() const |
490 | { |
491 | return mRegExp; |
492 | } |
493 | int cursor() const |
494 | { |
495 | return mCursor; |
496 | } |
497 | |
498 | private: |
499 | QString mText; |
500 | QString mRegExp; |
501 | int mCursor; |
502 | }; |
503 | |
504 | // Populate the popup menu. |
505 | if (!patterns) { |
506 | patterns = new QMenu(q); |
507 | for (const Term &item : items) { |
508 | patterns->addAction(action: new RegExpAction(patterns, item.description.toString(), QLatin1String(item.regExp), item.cursorAdjustment)); |
509 | } |
510 | } |
511 | |
512 | // Insert the selection into the edit control. |
513 | QAction *action = patterns->exec(pos: regExpItem->mapToGlobal(regExpItem->rect().bottomLeft())); |
514 | if (action) { |
515 | RegExpAction *regExpAction = static_cast<RegExpAction *>(action); |
516 | if (regExpAction) { |
517 | QLineEdit *editor = find->lineEdit(); |
518 | |
519 | editor->insert(regExpAction->regExp()); |
520 | editor->setCursorPosition(editor->cursorPosition() + regExpAction->cursor()); |
521 | } |
522 | } |
523 | } |
524 | |
525 | class PlaceHolderAction : public QAction |
526 | { |
527 | public: |
528 | PlaceHolderAction(QObject *parent, const QString &text, int id) |
529 | : QAction(text, parent) |
530 | , mText(text) |
531 | , mId(id) |
532 | { |
533 | } |
534 | |
535 | QString text() const |
536 | { |
537 | return mText; |
538 | } |
539 | int id() const |
540 | { |
541 | return mId; |
542 | } |
543 | |
544 | private: |
545 | QString mText; |
546 | int mId; |
547 | }; |
548 | |
549 | // Create a popup menu with a list of backreference terms, to help the user |
550 | // compose a regular expression replacement pattern. |
551 | void KFindDialogPrivate::showPlaceholders() |
552 | { |
553 | Q_Q(KFindDialog); |
554 | |
555 | // Populate the popup menu. |
556 | if (!placeholders) { |
557 | placeholders = new QMenu(q); |
558 | q->connect(sender: placeholders, signal: &QMenu::aboutToShow, context: q, slot: [this]() { |
559 | slotPlaceholdersAboutToShow(); |
560 | }); |
561 | } |
562 | |
563 | // Insert the selection into the edit control. |
564 | QAction *action = placeholders->exec(pos: backRefItem->mapToGlobal(backRefItem->rect().bottomLeft())); |
565 | if (action) { |
566 | PlaceHolderAction *placeHolderAction = static_cast<PlaceHolderAction *>(action); |
567 | if (placeHolderAction) { |
568 | QLineEdit *editor = replace->lineEdit(); |
569 | editor->insert(QStringLiteral("\\%1" ).arg(a: placeHolderAction->id())); |
570 | } |
571 | } |
572 | } |
573 | |
574 | void KFindDialogPrivate::slotPlaceholdersAboutToShow() |
575 | { |
576 | Q_Q(KFindDialog); |
577 | |
578 | placeholders->clear(); |
579 | placeholders->addAction(action: new PlaceHolderAction(placeholders, i18n("Complete Match" ), 0)); |
580 | |
581 | const int n = QRegularExpression(q->pattern(), QRegularExpression::UseUnicodePropertiesOption).captureCount(); |
582 | for (int i = 1; i <= n; ++i) { |
583 | placeholders->addAction(action: new PlaceHolderAction(placeholders, i18n("Captured Text (%1)" , i), i)); |
584 | } |
585 | } |
586 | |
587 | void KFindDialogPrivate::slotOk() |
588 | { |
589 | Q_Q(KFindDialog); |
590 | |
591 | // Nothing to find? |
592 | if (q->pattern().isEmpty()) { |
593 | KMessageBox::error(parent: q, i18n("You must enter some text to search for." )); |
594 | return; |
595 | } |
596 | |
597 | if (regExp->isChecked()) { |
598 | // Check for a valid regular expression. |
599 | if (!QRegularExpression(q->pattern(), QRegularExpression::UseUnicodePropertiesOption).isValid()) { |
600 | KMessageBox::error(parent: q, i18n("Invalid PCRE pattern syntax." )); |
601 | return; |
602 | } |
603 | } |
604 | |
605 | find->addToHistory(item: q->pattern()); |
606 | |
607 | if (q->windowModality() != Qt::NonModal) { |
608 | q->accept(); |
609 | } |
610 | Q_EMIT q->okClicked(); |
611 | } |
612 | |
613 | void KFindDialogPrivate::slotReject() |
614 | { |
615 | Q_Q(KFindDialog); |
616 | |
617 | Q_EMIT q->cancelClicked(); |
618 | q->reject(); |
619 | } |
620 | |
621 | #include "moc_kfinddialog.cpp" |
622 | |