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 | |
17 | class QDialog; |
18 | class 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 | */ |
93 | class KTEXTWIDGETS_EXPORT KFind : public QObject |
94 | { |
95 | Q_OBJECT |
96 | |
97 | public: |
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 | |
307 | Q_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 | |
361 | protected: |
362 | QWidget *parentWidget() const; |
363 | QWidget *dialogsParent() const; |
364 | |
365 | protected: |
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 | |
369 | protected: |
370 | std::unique_ptr<class KFindPrivate> const d_ptr; |
371 | |
372 | private: |
373 | Q_DECLARE_PRIVATE(KFind) |
374 | }; |
375 | |
376 | Q_DECLARE_OPERATORS_FOR_FLAGS(KFind::SearchOptions) |
377 | |
378 | #endif |
379 | |