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

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