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#ifndef KFIND_H
11#define KFIND_H
12
13#include "ktextwidgets_export.h"
14
15#include <QObject>
16#include <memory>
17
18class QDialog;
19class KFindPrivate;
20
21/*!
22 * \class KFind
23 * \inmodule KTextWidgets
24 *
25 * \brief A generic implementation of the "find" function.
26 *
27 * \b Detail:
28 *
29 * This class includes prompt handling etc. Also provides some
30 * static functions which can be used to create custom behavior
31 * instead of using the class directly.
32 *
33 * \b Example:
34 *
35 * To use the class to implement a complete find feature:
36 *
37 * In the slot connected to the find action, after using KFindDialog:
38 * \code
39 *
40 * // This creates a find-next-prompt dialog if needed.
41 * m_find = new KFind(pattern, options, this);
42 *
43 * // Connect textFound() signal to code which handles highlighting of found text.
44 * connect(m_find, &KFind::textFound, this, [this](const QString &text, int matchingIndex, int matchedLength) {
45 * slotHighlight(text, matchingIndex, matchedLength);
46 * }));
47 * // Connect findNext signal - called when pressing the button in the dialog
48 * connect(m_find, SIGNAL(findNext()),
49 * this, SLOT(slotFindNext()));
50 * \endcode
51 *
52 * Then initialize the variables determining the "current position"
53 * (to the cursor, if the option FromCursor is set,
54 * to the beginning of the selection if the option SelectedText is set,
55 * and to the beginning of the document otherwise).
56 * Initialize the "end of search" variables as well (end of doc or end of selection).
57 * Swap begin and end if FindBackwards.
58 * Finally, call slotFindNext();
59 *
60 * \code
61 * void slotFindNext()
62 * {
63 * KFind::Result res = KFind::NoMatch;
64 * while (res == KFind::NoMatch && <position not at end>) {
65 * if (m_find->needData())
66 * m_find->setData(<current text fragment>);
67 *
68 * // Let KFind inspect the text fragment, and display a dialog if a match is found
69 * res = m_find->find();
70 *
71 * if (res == KFind::NoMatch) {
72 * <Move to the next text fragment, honoring the FindBackwards setting for the direction>
73 * }
74 * }
75 *
76 * if (res == KFind::NoMatch) // i.e. at end
77 * <Call either m_find->displayFinalDialog(); m_find->deleteLater(); m_find = nullptr;
78 * or if (m_find->shouldRestart()) { reinit (w/o FromCursor); m_find->resetCounts(); slotFindNext(); }
79 * else { m_find->closeFindNextDialog(); }>
80 * }
81 * \endcode
82 *
83 * Don't forget to delete m_find in the destructor of your class,
84 * unless you gave it a parent widget on construction.
85 *
86 * This implementation allows to have a "Find Next" action, which resumes the
87 * search, even if the user closed the "Find Next" dialog.
88 *
89 * A "Find Previous" action can simply switch temporarily the value of
90 * FindBackwards and call slotFindNext() - and reset the value afterwards.
91 */
92class KTEXTWIDGETS_EXPORT KFind : public QObject
93{
94 Q_OBJECT
95
96public:
97 /*!
98 * \enum KFind::Options
99 *
100 * \value WholeWordsOnly
101 * Match whole words only.
102 * \value FromCursor
103 * Start from current cursor position.
104 * \value SelectedText
105 * Only search selected area.
106 * \value CaseSensitive
107 * Consider case when matching.
108 * \value FindBackwards
109 * Go backwards.
110 * \value RegularExpression
111 * Interpret the pattern as a regular expression.
112 * \value FindIncremental
113 * Find incremental.
114 * \value MinimumUserOption
115 * User options start with this bit
116 */
117 enum Options {
118 WholeWordsOnly = 1,
119 FromCursor = 2,
120 SelectedText = 4,
121 CaseSensitive = 8,
122 FindBackwards = 16,
123 RegularExpression = 32,
124 FindIncremental = 64,
125 MinimumUserOption = 65536,
126 };
127 Q_DECLARE_FLAGS(SearchOptions, Options)
128
129 /*!
130 * Only use this constructor if you don't use KFindDialog, or if
131 * you use it as a modal dialog.
132 */
133 KFind(const QString &pattern, long options, QWidget *parent);
134
135 /*!
136 * This is the recommended constructor if you also use KFindDialog (non-modal).
137 * You should pass the pointer to it here, so that when a message box
138 * appears it has the right parent. Don't worry about deletion, KFind
139 * will notice if the find dialog is closed.
140 */
141 KFind(const QString &pattern, long options, QWidget *parent, QWidget *findDialog);
142 ~KFind() override;
143
144 /*!
145 * \value NoMatch
146 * \value Match
147 */
148 enum Result {
149 NoMatch,
150 Match,
151 };
152
153 /*!
154 * Returns true if the application must supply a new text fragment
155 * It also means the last call returned "NoMatch". But by storing this here
156 * the application doesn't have to store it in a member variable (between
157 * calls to slotFindNext()).
158 */
159 bool needData() const;
160
161 /*!
162 * Call this when needData returns true, before calling find().
163 *
164 * \a data is the text fragment (line).
165 *
166 * \a startPos if set, the index at which the search should start.
167 *
168 * Usually \a startPos is only necessary for the very first call to setData,
169 * for the 'find in selection' feature. The default value of -1
170 * means "process all the data", i.e. either 0 or data.length()-1 depending
171 * on FindBackwards.
172 */
173 void setData(const QString &data, int startPos = -1);
174
175 /*!
176 * Call this when needData returns true, before calling find(). The use of
177 * ID's is especially useful if you're using the FindIncremental option.
178 *
179 * \a id the id of the text fragment.
180 *
181 * \a data the text fragment (line).
182 *
183 * \a startPos if set, the index at which the search should start.
184 * This is only necessary for the very first call to setData usually,
185 * for the 'find in selection' feature. A value of -1 (the default value)
186 * means "process all the data", i.e. either 0 or data.length()-1 depending
187 * on FindBackwards.
188 */
189 void setData(int id, const QString &data, int startPos = -1);
190
191 /*!
192 * Walk the text fragment (e.g. in a text-processor line or spreadsheet
193 * cell ...etc) looking for matches.
194 * For each match, emits the textFound() signal and displays the find-again
195 * dialog to ask if the user wants to find the same text again.
196 */
197 Result find();
198
199 /*!
200 * Return the current options.
201 *
202 * Warning: this is usually the same value as the one passed to the constructor,
203 * but options might change _during_ the replace operation:
204 * e.g. the "All" button resets the PromptOnReplace flag.
205 *
206 * \sa KFind::Options
207 */
208 long options() const;
209
210 /*!
211 * Set new \a options. Usually this is used for setting or clearing the
212 * FindBackwards options.
213 *
214 * \sa KFind::Options
215 */
216 virtual void setOptions(long options);
217
218 /*!
219 * Returns the pattern we're currently looking for
220 */
221 QString pattern() const;
222
223 /*!
224 * Sets the \a pattern to look for
225 */
226 void setPattern(const QString &pattern);
227
228 /*!
229 * Returns the number of matches found (i.e. the number of times the textFound()
230 * signal was emitted).
231 * If 0, can be used in a dialog box to tell the user "no match was found".
232 * The final dialog does so already, unless you used setDisplayFinalDialog(false).
233 */
234 int numMatches() const;
235
236 /*!
237 * Call this to reset the numMatches count
238 * (and the numReplacements count for a KReplace).
239 * Can be useful if reusing the same KReplace for different operations,
240 * or when restarting from the beginning of the document.
241 */
242 virtual void resetCounts();
243
244 /*!
245 * Virtual method, which allows applications to add extra checks for
246 * validating a candidate match. It's only necessary to reimplement this
247 * if the find dialog extension has been used to provide additional
248 * criteria.
249 *
250 * \a text The current text fragment
251 *
252 * \a index The starting index where the candidate match was found
253 *
254 * \a matchedlength The length of the candidate match
255 *
256 * Returns true if the candidate matches.
257 */
258 virtual bool validateMatch(const QString &text, int index, int matchedlength);
259
260 /*!
261 * Returns true if we should restart the search from scratch.
262 * Can ask the user, or return false (if we already searched the whole document).
263 *
264 * \a forceAsking set to true if the user modified the document during the
265 * search. In that case it makes sense to restart the search again.
266 *
267 * \a showNumMatches set to true if the dialog should show the number of
268 * matches. Set to false if the application provides a "find previous" action,
269 * in which case the match count will be erroneous when hitting the end,
270 * and we could even be hitting the beginning of the document (so not all
271 * matches have even been seen).
272 */
273 virtual bool shouldRestart(bool forceAsking = false, bool showNumMatches = true) const;
274
275 /*!
276 * Search \a text for \a pattern. If a match is found, the length of the matched
277 * string will be stored in \a matchedLength and the index of the matched string
278 * will be returned. If no match is found -1 is returned.
279 *
280 * If the KFind::RegularExpression flag is set, the \a pattern will be iterpreted
281 * as a regular expression (using QRegularExpression).
282 *
283 * \note Unicode support is always enabled (by setting the QRegularExpression::UseUnicodePropertiesOption flag).
284 *
285 * \a text The string to search in
286 *
287 * \a pattern The pattern to search for
288 *
289 * \a index The index in \a text from which to start the search
290 *
291 * \a options The options to use
292 *
293 * \a matchedLength If there is a match, its length will be stored in this parameter
294 *
295 * \a rmatch If there is a regular expression match (implies that the KFind::RegularExpression
296 * flag is set) and \a rmatch is not a nullptr the match result will be stored
297 * in this QRegularExpressionMatch object
298 *
299 * Returns The index at which a match was found otherwise -1
300 *
301 * \since 5.70
302 */
303 static int find(const QString &text, const QString &pattern, int index, long options, int *matchedLength, QRegularExpressionMatch *rmatch);
304
305 /*!
306 * Displays the final dialog saying "no match was found", if that was the case.
307 * Call either this or shouldRestart().
308 */
309 virtual void displayFinalDialog() const;
310
311 /*!
312 * Returns (or creates if \a create is true) the dialog that shows the "find next?" prompt.
313 * Usually you don't need to call this.
314 * One case where it can be useful, is when the user selects the "Find"
315 * menu item while a find operation is under way. In that case, the
316 * program may want to call setActiveWindow() on that dialog.
317 */
318 QDialog *findNextDialog(bool create = false);
319
320 /*!
321 * Close the "find next?" dialog. The application should do this when
322 * the last match was hit. If the application deletes the KFind, then
323 * "find previous" won't be possible anymore.
324 *
325 * IMPORTANT: you should also call this if you are using a non-modal
326 * find dialog, to tell KFind not to pop up its own dialog.
327 */
328 void closeFindNextDialog();
329
330 /*!
331 * Returns the current matching index (or -1).
332 * Same as the matchingIndex parameter passed to the textFound() signal.
333 * You usually don't need to use this, except maybe when updating the current data,
334 * so you need to call setData(newData, index()).
335 */
336 int index() const;
337
338Q_SIGNALS:
339 /*!
340 * Connect to this signal to implement highlighting of found text during the find
341 * operation.
342 *
343 * If you've set data with setData(id, text), use the textFoundAtId(int, int, int) signal.
344 *
345 * WARNING: If you're using the FindIncremental option, the text argument
346 * passed by this signal is not necessarily the data last set through
347 * setData(), but can also be an earlier set data block.
348 *
349 * \a text is the text that was found
350 *
351 * \a matchingIndex is the index of the start of the matched text
352 *
353 * \a matchedLength is the length of the matched text
354 *
355 * \sa setData()
356 *
357 * \since 5.81
358 */
359 void textFound(const QString &text, int matchingIndex, int matchedLength);
360
361 /*!
362 * Connect to this signal to implement highlighting of found text during
363 * the find operation.
364 *
365 * Use this signal if you've set your data with setData(id, text),
366 * otherwise use the textFound(text, matchingIndex, matchedLength) signal.
367 *
368 * WARNING: If you're using the FindIncremental option, the \a id
369 * passed by this signal is not necessarily the id of the data last set
370 * through setData(), but can also be of an earlier set data block.
371 *
372 * \a matchingIndex is the index of the start of the matched text
373 *
374 * \a matchedLength is the length of the matched text
375 *
376 * \sa setData()
377 *
378 * \since 5.81
379 */
380 void textFoundAtId(int id, int matchingIndex, int matchedLength);
381
382 // ## TODO docu
383 // findprevious will also emit findNext, after temporarily switching the value
384 // of FindBackwards
385 void findNext();
386
387 /*!
388 * Emitted when the options have changed.
389 * This can happen e.g. with "Replace All", or if our 'find next' dialog
390 * gets a "find previous" one day.
391 */
392 void optionsChanged();
393
394 /*!
395 * Emitted when the 'find next' dialog is being closed.
396 * Some apps might want to remove the highlighted text when this happens.
397 * Apps without support for "Find Next" can also do m_find->deleteLater()
398 * to terminate the find operation.
399 */
400 void dialogClosed();
401
402protected:
403 QWidget *parentWidget() const;
404 QWidget *dialogsParent() const;
405
406protected:
407 KTEXTWIDGETS_NO_EXPORT KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent);
408 KTEXTWIDGETS_NO_EXPORT KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent, QWidget *findDialog);
409
410protected:
411 std::unique_ptr<class KFindPrivate> const d_ptr;
412
413private:
414 Q_DECLARE_PRIVATE(KFind)
415};
416
417Q_DECLARE_OPERATORS_FOR_FLAGS(KFind::SearchOptions)
418
419#endif
420

source code of ktextwidgets/src/findreplace/kfind.h