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 | SPDX-FileCopyrightText: 2004 Arend van Beelen jr. <arend@auton.nl> |
6 | |
7 | SPDX-License-Identifier: LGPL-2.0-only |
8 | */ |
9 | |
10 | #include "kfind.h" |
11 | #include "kfind_p.h" |
12 | |
13 | #include "kfinddialog.h" |
14 | |
15 | #include <KGuiItem> |
16 | #include <KLocalizedString> |
17 | #include <KMessageBox> |
18 | |
19 | #include <QDialog> |
20 | #include <QDialogButtonBox> |
21 | #include <QHash> |
22 | #include <QLabel> |
23 | #include <QPushButton> |
24 | #include <QRegularExpression> |
25 | #include <QVBoxLayout> |
26 | |
27 | // #define DEBUG_FIND |
28 | |
29 | static const int INDEX_NOMATCH = -1; |
30 | |
31 | class KFindNextDialog : public QDialog |
32 | { |
33 | Q_OBJECT |
34 | public: |
35 | explicit KFindNextDialog(const QString &pattern, QWidget *parent); |
36 | |
37 | QPushButton *findButton() const; |
38 | |
39 | private: |
40 | QPushButton *m_findButton = nullptr; |
41 | }; |
42 | |
43 | // Create the dialog. |
44 | KFindNextDialog::KFindNextDialog(const QString &pattern, QWidget *parent) |
45 | : QDialog(parent) |
46 | { |
47 | setModal(false); |
48 | setWindowTitle(i18n("Find Next" )); |
49 | |
50 | QVBoxLayout *layout = new QVBoxLayout(this); |
51 | |
52 | layout->addWidget(new QLabel(i18n("<qt>Find next occurrence of '<b>%1</b>'?</qt>" , pattern), this)); |
53 | |
54 | m_findButton = new QPushButton; |
55 | KGuiItem::assign(button: m_findButton, item: KStandardGuiItem::find()); |
56 | m_findButton->setDefault(true); |
57 | |
58 | QDialogButtonBox *buttonBox = new QDialogButtonBox(this); |
59 | buttonBox->addButton(button: m_findButton, role: QDialogButtonBox::ActionRole); |
60 | buttonBox->setStandardButtons(QDialogButtonBox::Close); |
61 | layout->addWidget(buttonBox); |
62 | |
63 | connect(sender: buttonBox, signal: &QDialogButtonBox::accepted, context: this, slot: &QDialog::accept); |
64 | connect(sender: buttonBox, signal: &QDialogButtonBox::rejected, context: this, slot: &QDialog::reject); |
65 | } |
66 | |
67 | QPushButton *KFindNextDialog::findButton() const |
68 | { |
69 | return m_findButton; |
70 | } |
71 | |
72 | //// |
73 | |
74 | KFind::KFind(const QString &pattern, long options, QWidget *parent) |
75 | : KFind(*new KFindPrivate(this), pattern, options, parent) |
76 | { |
77 | } |
78 | |
79 | KFind::KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent) |
80 | : QObject(parent) |
81 | , d_ptr(&dd) |
82 | { |
83 | Q_D(KFind); |
84 | |
85 | d->options = options; |
86 | d->init(pattern); |
87 | } |
88 | |
89 | KFind::KFind(const QString &pattern, long options, QWidget *parent, QWidget *findDialog) |
90 | : KFind(*new KFindPrivate(this), pattern, options, parent, findDialog) |
91 | { |
92 | } |
93 | |
94 | KFind::KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent, QWidget *findDialog) |
95 | : QObject(parent) |
96 | , d_ptr(&dd) |
97 | { |
98 | Q_D(KFind); |
99 | |
100 | d->findDialog = findDialog; |
101 | d->options = options; |
102 | d->init(pattern); |
103 | } |
104 | |
105 | void KFindPrivate::init(const QString &_pattern) |
106 | { |
107 | Q_Q(KFind); |
108 | |
109 | matches = 0; |
110 | pattern = _pattern; |
111 | dialog = nullptr; |
112 | dialogClosed = false; |
113 | index = INDEX_NOMATCH; |
114 | lastResult = KFind::NoMatch; |
115 | |
116 | // TODO: KF6 change this comment once d->regExp is removed |
117 | // set options and create d->regExp with the right options |
118 | q->setOptions(options); |
119 | } |
120 | |
121 | KFind::~KFind() = default; |
122 | |
123 | bool KFind::needData() const |
124 | { |
125 | Q_D(const KFind); |
126 | |
127 | // always true when d->text is empty. |
128 | if (d->options & KFind::FindBackwards) |
129 | // d->index==-1 and d->lastResult==Match means we haven't answered nomatch yet |
130 | // This is important in the "replace with a prompt" case. |
131 | { |
132 | return (d->index < 0 && d->lastResult != Match); |
133 | } else |
134 | // "index over length" test removed: we want to get a nomatch before we set data again |
135 | // This is important in the "replace with a prompt" case. |
136 | { |
137 | return d->index == INDEX_NOMATCH; |
138 | } |
139 | } |
140 | |
141 | void KFind::setData(const QString &data, int startPos) |
142 | { |
143 | setData(id: -1, data, startPos); |
144 | } |
145 | |
146 | void KFind::setData(int id, const QString &data, int startPos) |
147 | { |
148 | Q_D(KFind); |
149 | |
150 | // cache the data for incremental find |
151 | if (d->options & KFind::FindIncremental) { |
152 | if (id != -1) { |
153 | d->customIds = true; |
154 | } else { |
155 | id = d->currentId + 1; |
156 | } |
157 | |
158 | Q_ASSERT(id <= d->data.size()); |
159 | |
160 | if (id == d->data.size()) { |
161 | d->data.append(t: KFindPrivate::Data(id, data, true)); |
162 | } else { |
163 | d->data.replace(i: id, t: KFindPrivate::Data(id, data, true)); |
164 | } |
165 | Q_ASSERT(d->data.at(id).text == data); |
166 | } |
167 | |
168 | if (!(d->options & KFind::FindIncremental) || needData()) { |
169 | d->text = data; |
170 | |
171 | if (startPos != -1) { |
172 | d->index = startPos; |
173 | } else if (d->options & KFind::FindBackwards) { |
174 | d->index = d->text.length(); |
175 | } else { |
176 | d->index = 0; |
177 | } |
178 | #ifdef DEBUG_FIND |
179 | // qDebug() << "setData: '" << d->text << "' d->index=" << d->index; |
180 | #endif |
181 | Q_ASSERT(d->index != INDEX_NOMATCH); |
182 | d->lastResult = NoMatch; |
183 | |
184 | d->currentId = id; |
185 | } |
186 | } |
187 | |
188 | QDialog *KFind::findNextDialog(bool create) |
189 | { |
190 | Q_D(KFind); |
191 | |
192 | if (!d->dialog && create) { |
193 | KFindNextDialog *dialog = new KFindNextDialog(d->pattern, parentWidget()); |
194 | connect(sender: dialog->findButton(), signal: &QPushButton::clicked, context: this, slot: [d]() { |
195 | d->slotFindNext(); |
196 | }); |
197 | connect(sender: dialog, signal: &QDialog::finished, context: this, slot: [d]() { |
198 | d->slotDialogClosed(); |
199 | }); |
200 | d->dialog = dialog; |
201 | } |
202 | return d->dialog; |
203 | } |
204 | |
205 | KFind::Result KFind::find() |
206 | { |
207 | Q_D(KFind); |
208 | |
209 | Q_ASSERT(d->index != INDEX_NOMATCH || d->patternChanged); |
210 | |
211 | if (d->lastResult == Match && !d->patternChanged) { |
212 | // Move on before looking for the next match, _if_ we just found a match |
213 | if (d->options & KFind::FindBackwards) { |
214 | d->index--; |
215 | if (d->index == -1) { // don't call KFind::find with -1, it has a special meaning |
216 | d->lastResult = NoMatch; |
217 | return NoMatch; |
218 | } |
219 | } else { |
220 | d->index++; |
221 | } |
222 | } |
223 | d->patternChanged = false; |
224 | |
225 | if (d->options & KFind::FindIncremental) { |
226 | // if the current pattern is shorter than the matchedPattern we can |
227 | // probably look up the match in the incrementalPath |
228 | if (d->pattern.length() < d->matchedPattern.length()) { |
229 | KFindPrivate::Match match; |
230 | if (!d->pattern.isEmpty()) { |
231 | match = d->incrementalPath.value(key: d->pattern); |
232 | } else if (d->emptyMatch) { |
233 | match = *d->emptyMatch; |
234 | } |
235 | QString previousPattern(d->matchedPattern); |
236 | d->matchedPattern = d->pattern; |
237 | if (!match.isNull()) { |
238 | bool clean = true; |
239 | |
240 | // find the first result backwards on the path that isn't dirty |
241 | while (d->data.at(i: match.dataId).dirty == true && !d->pattern.isEmpty()) { |
242 | d->pattern.truncate(pos: d->pattern.length() - 1); |
243 | |
244 | match = d->incrementalPath.value(key: d->pattern); |
245 | |
246 | clean = false; |
247 | } |
248 | |
249 | // remove all matches that lie after the current match |
250 | while (d->pattern.length() < previousPattern.length()) { |
251 | d->incrementalPath.remove(key: previousPattern); |
252 | previousPattern.truncate(pos: previousPattern.length() - 1); |
253 | } |
254 | |
255 | // set the current text, index, etc. to the found match |
256 | d->text = d->data.at(i: match.dataId).text; |
257 | d->index = match.index; |
258 | d->matchedLength = match.matchedLength; |
259 | d->currentId = match.dataId; |
260 | |
261 | // if the result is clean we can return it now |
262 | if (clean) { |
263 | if (d->customIds) { |
264 | Q_EMIT textFoundAtId(id: d->currentId, matchingIndex: d->index, matchedLength: d->matchedLength); |
265 | } else { |
266 | Q_EMIT textFound(text: d->text, matchingIndex: d->index, matchedLength: d->matchedLength); |
267 | } |
268 | |
269 | d->lastResult = Match; |
270 | d->matchedPattern = d->pattern; |
271 | return Match; |
272 | } |
273 | } |
274 | // if we couldn't look up the match, the new pattern isn't a |
275 | // substring of the matchedPattern, so we start a new search |
276 | else { |
277 | d->startNewIncrementalSearch(); |
278 | } |
279 | } |
280 | // if the new pattern is longer than the matchedPattern we might be |
281 | // able to proceed from the last search |
282 | else if (d->pattern.length() > d->matchedPattern.length()) { |
283 | // continue from the previous pattern |
284 | if (d->pattern.startsWith(s: d->matchedPattern)) { |
285 | // we can't proceed from the previous position if the previous |
286 | // position already failed |
287 | if (d->index == INDEX_NOMATCH) { |
288 | return NoMatch; |
289 | } |
290 | |
291 | QString temp(d->pattern); |
292 | d->pattern.truncate(pos: d->matchedPattern.length() + 1); |
293 | d->matchedPattern = temp; |
294 | } |
295 | // start a new search |
296 | else { |
297 | d->startNewIncrementalSearch(); |
298 | } |
299 | } |
300 | // if the new pattern is as long as the matchedPattern, we reset if |
301 | // they are not equal |
302 | else if (d->pattern != d->matchedPattern) { |
303 | d->startNewIncrementalSearch(); |
304 | } |
305 | } |
306 | |
307 | #ifdef DEBUG_FIND |
308 | // qDebug() << "d->index=" << d->index; |
309 | #endif |
310 | do { |
311 | // if we have multiple data blocks in our cache, walk through these |
312 | // blocks till we either searched all blocks or we find a match |
313 | do { |
314 | // Find the next candidate match. |
315 | d->index = KFind::find(text: d->text, pattern: d->pattern, index: d->index, options: d->options, matchedLength: &d->matchedLength, rmatch: nullptr); |
316 | |
317 | if (d->options & KFind::FindIncremental) { |
318 | d->data[d->currentId].dirty = false; |
319 | } |
320 | |
321 | if (d->index == -1 && d->currentId < d->data.count() - 1) { |
322 | d->text = d->data.at(i: ++d->currentId).text; |
323 | |
324 | if (d->options & KFind::FindBackwards) { |
325 | d->index = d->text.length(); |
326 | } else { |
327 | d->index = 0; |
328 | } |
329 | } else { |
330 | break; |
331 | } |
332 | } while (!(d->options & KFind::RegularExpression)); |
333 | |
334 | if (d->index != -1) { |
335 | // Flexibility: the app can add more rules to validate a possible match |
336 | if (validateMatch(text: d->text, index: d->index, matchedlength: d->matchedLength)) { |
337 | bool done = true; |
338 | |
339 | if (d->options & KFind::FindIncremental) { |
340 | if (d->pattern.isEmpty()) { |
341 | delete d->emptyMatch; |
342 | d->emptyMatch = new KFindPrivate::Match(d->currentId, d->index, d->matchedLength); |
343 | } else { |
344 | d->incrementalPath.insert(key: d->pattern, value: KFindPrivate::Match(d->currentId, d->index, d->matchedLength)); |
345 | } |
346 | |
347 | if (d->pattern.length() < d->matchedPattern.length()) { |
348 | d->pattern += QStringView(d->matchedPattern).mid(pos: d->pattern.length(), n: 1); |
349 | done = false; |
350 | } |
351 | } |
352 | |
353 | if (done) { |
354 | d->matches++; |
355 | // Tell the world about the match we found, in case someone wants to |
356 | // highlight it. |
357 | if (d->customIds) { |
358 | Q_EMIT textFoundAtId(id: d->currentId, matchingIndex: d->index, matchedLength: d->matchedLength); |
359 | } else { |
360 | Q_EMIT textFound(text: d->text, matchingIndex: d->index, matchedLength: d->matchedLength); |
361 | } |
362 | |
363 | if (!d->dialogClosed) { |
364 | findNextDialog(create: true)->show(); |
365 | } |
366 | |
367 | #ifdef DEBUG_FIND |
368 | // qDebug() << "Match. Next d->index=" << d->index; |
369 | #endif |
370 | d->lastResult = Match; |
371 | return Match; |
372 | } |
373 | } else { // Skip match |
374 | if (d->options & KFind::FindBackwards) { |
375 | d->index--; |
376 | } else { |
377 | d->index++; |
378 | } |
379 | } |
380 | } else { |
381 | if (d->options & KFind::FindIncremental) { |
382 | QString temp(d->pattern); |
383 | temp.truncate(pos: temp.length() - 1); |
384 | d->pattern = d->matchedPattern; |
385 | d->matchedPattern = temp; |
386 | } |
387 | |
388 | d->index = INDEX_NOMATCH; |
389 | } |
390 | } while (d->index != INDEX_NOMATCH); |
391 | |
392 | #ifdef DEBUG_FIND |
393 | // qDebug() << "NoMatch. d->index=" << d->index; |
394 | #endif |
395 | d->lastResult = NoMatch; |
396 | return NoMatch; |
397 | } |
398 | |
399 | void KFindPrivate::startNewIncrementalSearch() |
400 | { |
401 | KFindPrivate::Match *match = emptyMatch; |
402 | if (match == nullptr) { |
403 | text.clear(); |
404 | index = 0; |
405 | currentId = 0; |
406 | } else { |
407 | text = data.at(i: match->dataId).text; |
408 | index = match->index; |
409 | currentId = match->dataId; |
410 | } |
411 | matchedLength = 0; |
412 | incrementalPath.clear(); |
413 | delete emptyMatch; |
414 | emptyMatch = nullptr; |
415 | matchedPattern = pattern; |
416 | pattern.clear(); |
417 | } |
418 | |
419 | static bool isInWord(QChar ch) |
420 | { |
421 | return ch.isLetter() || ch.isDigit() || ch == QLatin1Char('_'); |
422 | } |
423 | |
424 | static bool isWholeWords(const QString &text, int starts, int matchedLength) |
425 | { |
426 | if (starts == 0 || !isInWord(ch: text.at(i: starts - 1))) { |
427 | const int ends = starts + matchedLength; |
428 | if (ends == text.length() || !isInWord(ch: text.at(i: ends))) { |
429 | return true; |
430 | } |
431 | } |
432 | return false; |
433 | } |
434 | |
435 | static bool matchOk(const QString &text, int index, int matchedLength, long options) |
436 | { |
437 | if (options & KFind::WholeWordsOnly) { |
438 | // Is the match delimited correctly? |
439 | if (isWholeWords(text, starts: index, matchedLength)) { |
440 | return true; |
441 | } |
442 | } else { |
443 | // Non-whole-word search: this match is good |
444 | return true; |
445 | } |
446 | return false; |
447 | } |
448 | |
449 | static int findRegex(const QString &text, const QString &pattern, int index, long options, int *matchedLength, QRegularExpressionMatch *rmatch) |
450 | { |
451 | QString _pattern = pattern; |
452 | |
453 | // Always enable Unicode support in QRegularExpression |
454 | QRegularExpression::PatternOptions opts = QRegularExpression::UseUnicodePropertiesOption; |
455 | // instead of this rudimentary test, add a checkbox to toggle MultilineOption ? |
456 | if (pattern.startsWith(c: QLatin1Char('^')) || pattern.endsWith(c: QLatin1Char('$'))) { |
457 | opts |= QRegularExpression::MultilineOption; |
458 | } else if (options & KFind::WholeWordsOnly) { // WholeWordsOnly makes no sense with multiline |
459 | _pattern = QLatin1String("\\b" ) + pattern + QLatin1String("\\b" ); |
460 | } |
461 | |
462 | if (!(options & KFind::CaseSensitive)) { |
463 | opts |= QRegularExpression::CaseInsensitiveOption; |
464 | } |
465 | |
466 | QRegularExpression re(_pattern, opts); |
467 | QRegularExpressionMatch match; |
468 | if (options & KFind::FindBackwards) { |
469 | // Backward search, until the beginning of the line... |
470 | (void)text.lastIndexOf(re, from: index, rmatch: &match); |
471 | } else { |
472 | // Forward search, until the end of the line... |
473 | match = re.match(subject: text, offset: index); |
474 | } |
475 | |
476 | // index is -1 if no match is found |
477 | index = match.capturedStart(nth: 0); |
478 | // matchedLength is 0 if no match is found |
479 | *matchedLength = match.capturedLength(nth: 0); |
480 | |
481 | if (rmatch) { |
482 | *rmatch = match; |
483 | } |
484 | |
485 | return index; |
486 | } |
487 | |
488 | // static |
489 | int KFind::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength, QRegularExpressionMatch *rmatch) |
490 | { |
491 | // Handle regular expressions in the appropriate way. |
492 | if (options & KFind::RegularExpression) { |
493 | return findRegex(text, pattern, index, options, matchedLength, rmatch); |
494 | } |
495 | |
496 | // In Qt4 QString("aaaaaa").lastIndexOf("a",6) returns -1; we need |
497 | // to start at text.length() - pattern.length() to give a valid index to QString. |
498 | if (options & KFind::FindBackwards) { |
499 | index = qMin(a: qMax(a: 0, b: text.length() - pattern.length()), b: index); |
500 | } |
501 | |
502 | Qt::CaseSensitivity caseSensitive = (options & KFind::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive; |
503 | |
504 | if (options & KFind::FindBackwards) { |
505 | // Backward search, until the beginning of the line... |
506 | while (index >= 0) { |
507 | // ...find the next match. |
508 | index = text.lastIndexOf(s: pattern, from: index, cs: caseSensitive); |
509 | if (index == -1) { |
510 | break; |
511 | } |
512 | |
513 | if (matchOk(text, index, matchedLength: pattern.length(), options)) { |
514 | break; |
515 | } |
516 | index--; |
517 | // qDebug() << "decrementing:" << index; |
518 | } |
519 | } else { |
520 | // Forward search, until the end of the line... |
521 | while (index <= text.length()) { |
522 | // ...find the next match. |
523 | index = text.indexOf(s: pattern, from: index, cs: caseSensitive); |
524 | if (index == -1) { |
525 | break; |
526 | } |
527 | |
528 | if (matchOk(text, index, matchedLength: pattern.length(), options)) { |
529 | break; |
530 | } |
531 | index++; |
532 | } |
533 | if (index > text.length()) { // end of line |
534 | // qDebug() << "at" << index << "-> not found"; |
535 | index = -1; // not found |
536 | } |
537 | } |
538 | if (index <= -1) { |
539 | *matchedLength = 0; |
540 | } else { |
541 | *matchedLength = pattern.length(); |
542 | } |
543 | return index; |
544 | } |
545 | |
546 | void KFindPrivate::slotFindNext() |
547 | { |
548 | Q_Q(KFind); |
549 | |
550 | Q_EMIT q->findNext(); |
551 | } |
552 | |
553 | void KFindPrivate::slotDialogClosed() |
554 | { |
555 | Q_Q(KFind); |
556 | |
557 | #ifdef DEBUG_FIND |
558 | // qDebug() << " Begin"; |
559 | #endif |
560 | Q_EMIT q->dialogClosed(); |
561 | dialogClosed = true; |
562 | #ifdef DEBUG_FIND |
563 | // qDebug() << " End"; |
564 | #endif |
565 | } |
566 | |
567 | void KFind::displayFinalDialog() const |
568 | { |
569 | Q_D(const KFind); |
570 | |
571 | QString message; |
572 | if (numMatches()) { |
573 | message = i18np("1 match found." , "%1 matches found." , numMatches()); |
574 | } else { |
575 | message = i18n("<qt>No matches found for '<b>%1</b>'.</qt>" , d->pattern.toHtmlEscaped()); |
576 | } |
577 | KMessageBox::information(parent: dialogsParent(), text: message); |
578 | } |
579 | |
580 | bool KFind::shouldRestart(bool forceAsking, bool showNumMatches) const |
581 | { |
582 | Q_D(const KFind); |
583 | |
584 | // Only ask if we did a "find from cursor", otherwise it's pointless. |
585 | // Well, unless the user can modify the document during a search operation, |
586 | // hence the force boolean. |
587 | if (!forceAsking && (d->options & KFind::FromCursor) == 0) { |
588 | displayFinalDialog(); |
589 | return false; |
590 | } |
591 | QString message; |
592 | if (showNumMatches) { |
593 | if (numMatches()) { |
594 | message = i18np("1 match found." , "%1 matches found." , numMatches()); |
595 | } else { |
596 | message = i18n("No matches found for '<b>%1</b>'." , d->pattern.toHtmlEscaped()); |
597 | } |
598 | } else { |
599 | if (d->options & KFind::FindBackwards) { |
600 | message = i18n("Beginning of document reached." ); |
601 | } else { |
602 | message = i18n("End of document reached." ); |
603 | } |
604 | } |
605 | |
606 | message += QLatin1String("<br><br>" ); // can't be in the i18n() of the first if() because of the plural form. |
607 | // Hope this word puzzle is ok, it's a different sentence |
608 | message += (d->options & KFind::FindBackwards) ? i18n("Continue from the end?" ) : i18n("Continue from the beginning?" ); |
609 | |
610 | int ret = KMessageBox::questionTwoActions(parent: dialogsParent(), |
611 | QStringLiteral("<qt>%1</qt>" ).arg(a: message), |
612 | title: QString(), |
613 | primaryAction: KStandardGuiItem::cont(), |
614 | secondaryAction: KStandardGuiItem::stop()); |
615 | bool yes = (ret == KMessageBox::PrimaryAction); |
616 | if (yes) { |
617 | const_cast<KFindPrivate *>(d)->options &= ~KFind::FromCursor; // clear FromCursor option |
618 | } |
619 | return yes; |
620 | } |
621 | |
622 | long KFind::options() const |
623 | { |
624 | Q_D(const KFind); |
625 | |
626 | return d->options; |
627 | } |
628 | |
629 | void KFind::setOptions(long options) |
630 | { |
631 | Q_D(KFind); |
632 | |
633 | d->options = options; |
634 | } |
635 | |
636 | void KFind::closeFindNextDialog() |
637 | { |
638 | Q_D(KFind); |
639 | |
640 | if (d->dialog) { |
641 | d->dialog->deleteLater(); |
642 | d->dialog = nullptr; |
643 | } |
644 | d->dialogClosed = true; |
645 | } |
646 | |
647 | int KFind::index() const |
648 | { |
649 | Q_D(const KFind); |
650 | |
651 | return d->index; |
652 | } |
653 | |
654 | QString KFind::pattern() const |
655 | { |
656 | Q_D(const KFind); |
657 | |
658 | return d->pattern; |
659 | } |
660 | |
661 | void KFind::setPattern(const QString &pattern) |
662 | { |
663 | Q_D(KFind); |
664 | |
665 | if (d->pattern != pattern) { |
666 | d->patternChanged = true; |
667 | d->matches = 0; |
668 | } |
669 | |
670 | d->pattern = pattern; |
671 | |
672 | // TODO: KF6 change this comment once d->regExp is removed |
673 | // set the options and rebuild d->regeExp if necessary |
674 | setOptions(options()); |
675 | } |
676 | |
677 | int KFind::numMatches() const |
678 | { |
679 | Q_D(const KFind); |
680 | |
681 | return d->matches; |
682 | } |
683 | |
684 | void KFind::resetCounts() |
685 | { |
686 | Q_D(KFind); |
687 | |
688 | d->matches = 0; |
689 | } |
690 | |
691 | bool KFind::validateMatch(const QString &, int, int) |
692 | { |
693 | return true; |
694 | } |
695 | |
696 | QWidget *KFind::parentWidget() const |
697 | { |
698 | return static_cast<QWidget *>(parent()); |
699 | } |
700 | |
701 | QWidget *KFind::dialogsParent() const |
702 | { |
703 | Q_D(const KFind); |
704 | |
705 | // If the find dialog is still up, it should get the focus when closing a message box |
706 | // Otherwise, maybe the "find next?" dialog is up |
707 | // Otherwise, the "view" is the parent. |
708 | return d->findDialog ? static_cast<QWidget *>(d->findDialog) : (d->dialog ? d->dialog : parentWidget()); |
709 | } |
710 | |
711 | #include "kfind.moc" |
712 | #include "moc_kfind.cpp" |
713 | |