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 KREPLACE_H |
10 | #define KREPLACE_H |
11 | |
12 | #include "kfind.h" |
13 | |
14 | #include "ktextwidgets_export.h" |
15 | |
16 | class KReplacePrivate; |
17 | |
18 | /** |
19 | * @class KReplace kreplace.h <KReplace> |
20 | * |
21 | * @brief A generic implementation of the "replace" function. |
22 | * |
23 | * @author S.R.Haque <srhaque@iee.org>, David Faure <faure@kde.org> |
24 | * |
25 | * \b Detail: |
26 | * |
27 | * This class includes prompt handling etc. Also provides some |
28 | * static functions which can be used to create custom behavior |
29 | * instead of using the class directly. |
30 | * |
31 | * \b Example: |
32 | * |
33 | * To use the class to implement a complete replace feature: |
34 | * |
35 | * In the slot connect to the replace action, after using KReplaceDialog: |
36 | * \code |
37 | * |
38 | * // This creates a replace-on-prompt dialog if needed. |
39 | * m_replace = new KReplace(pattern, replacement, options, this); |
40 | * |
41 | * // Connect signals to code which handles highlighting of found text, and |
42 | * // on-the-fly replacement. |
43 | * connect(m_replace, &KFind::textFound, this, [this](const QString &text, int matchingIndex, int matchedLength) { |
44 | * slotHighlight(text, matchingIndex, matchedLength); |
45 | * }); |
46 | * // Connect findNext signal - called when pressing the button in the dialog |
47 | * connect( m_replace, SIGNAL( findNext() ), |
48 | * this, SLOT( slotReplaceNext() ) ); |
49 | * // Connect to the textReplaced() signal - emitted when a replacement is done |
50 | * connect( m_replace, &KReplace::textReplaced, this, [this](const QString &text, int replacementIndex, int replacedLength, int matchedLength) { |
51 | * slotReplace(text, replacementIndex, replacedLength, matchedLength); |
52 | * }); |
53 | * \endcode |
54 | * Then initialize the variables determining the "current position" |
55 | * (to the cursor, if the option FromCursor is set, |
56 | * to the beginning of the selection if the option SelectedText is set, |
57 | * and to the beginning of the document otherwise). |
58 | * Initialize the "end of search" variables as well (end of doc or end of selection). |
59 | * Swap begin and end if FindBackwards. |
60 | * Finally, call slotReplaceNext(); |
61 | * |
62 | * \code |
63 | * void slotReplaceNext() |
64 | * { |
65 | * KFind::Result res = KFind::NoMatch; |
66 | * while ( res == KFind::NoMatch && <position not at end> ) { |
67 | * if ( m_replace->needData() ) |
68 | * m_replace->setData( <current text fragment> ); |
69 | * |
70 | * // Let KReplace inspect the text fragment, and display a dialog if a match is found |
71 | * res = m_replace->replace(); |
72 | * |
73 | * if ( res == KFind::NoMatch ) { |
74 | * <Move to the next text fragment, honoring the FindBackwards setting for the direction> |
75 | * } |
76 | * } |
77 | * |
78 | * if ( res == KFind::NoMatch ) // i.e. at end |
79 | * <Call either m_replace->displayFinalDialog(); delete m_replace; m_replace = nullptr; |
80 | * or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); } |
81 | * else { m_replace->closeReplaceNextDialog(); }> |
82 | * } |
83 | * \endcode |
84 | * |
85 | * Don't forget delete m_find in the destructor of your class, |
86 | * unless you gave it a parent widget on construction. |
87 | * |
88 | */ |
89 | class KTEXTWIDGETS_EXPORT KReplace : public KFind |
90 | { |
91 | Q_OBJECT |
92 | |
93 | public: |
94 | /** |
95 | * Only use this constructor if you don't use KFindDialog, or if |
96 | * you use it as a modal dialog. |
97 | */ |
98 | KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent = nullptr); |
99 | /** |
100 | * This is the recommended constructor if you also use KReplaceDialog (non-modal). |
101 | * You should pass the pointer to it here, so that when a message box |
102 | * appears it has the right parent. Don't worry about deletion, KReplace |
103 | * will notice if the find dialog is closed. |
104 | */ |
105 | KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent, QWidget *replaceDialog); |
106 | |
107 | ~KReplace() override; |
108 | |
109 | /** |
110 | * Return the number of replacements made (i.e. the number of times |
111 | * the textReplaced() signal was emitted). |
112 | * |
113 | * Can be used in a dialog box to tell the user how many replacements were made. |
114 | * The final dialog does so already, unless you used setDisplayFinalDialog(false). |
115 | */ |
116 | int numReplacements() const; |
117 | |
118 | /** |
119 | * Call this to reset the numMatches & numReplacements counts. |
120 | * Can be useful if reusing the same KReplace for different operations, |
121 | * or when restarting from the beginning of the document. |
122 | */ |
123 | void resetCounts() override; |
124 | |
125 | /** |
126 | * Walk the text fragment (e.g. kwrite line, kspread cell) looking for matches. |
127 | * For each match, if prompt-on-replace is specified, emits the textFound() signal |
128 | * and displays the prompt-for-replace dialog before doing the replace. |
129 | */ |
130 | Result replace(); |
131 | |
132 | /** |
133 | * Return (or create) the dialog that shows the "find next?" prompt. |
134 | * Usually you don't need to call this. |
135 | * One case where it can be useful, is when the user selects the "Find" |
136 | * menu item while a find operation is under way. In that case, the |
137 | * program may want to call setActiveWindow() on that dialog. |
138 | */ |
139 | QDialog *replaceNextDialog(bool create = false); |
140 | |
141 | /** |
142 | * Close the "replace next?" dialog. The application should do this when |
143 | * the last match was hit. If the application deletes the KReplace, then |
144 | * "find previous" won't be possible anymore. |
145 | */ |
146 | void closeReplaceNextDialog(); |
147 | |
148 | /** |
149 | * Searches the given @p text for @p pattern; if a match is found it is replaced |
150 | * with @p replacement and the index of the replacement string is returned. |
151 | * |
152 | * @param text The string to search |
153 | * @param pattern The pattern to search for |
154 | * @param replacement The replacement string to insert into the text |
155 | * @param index The starting index into the string |
156 | * @param options The options to use |
157 | * @param replacedLength Output parameter, contains the length of the replaced string |
158 | * Not always the same as replacement.length(), when backreferences are used |
159 | * @return The index at which a match was found, or -1 otherwise |
160 | */ |
161 | static int replace(QString &text, const QString &pattern, const QString &replacement, int index, long options, int *replacedLength); |
162 | |
163 | /** |
164 | * Returns true if we should restart the search from scratch. |
165 | * Can ask the user, or return false (if we already searched/replaced the |
166 | * whole document without the PromptOnReplace option). |
167 | * |
168 | * @param forceAsking set to true if the user modified the document during the |
169 | * search. In that case it makes sense to restart the search again. |
170 | * |
171 | * @param showNumMatches set to true if the dialog should show the number of |
172 | * matches. Set to false if the application provides a "find previous" action, |
173 | * in which case the match count will be erroneous when hitting the end, |
174 | * and we could even be hitting the beginning of the document (so not all |
175 | * matches have even been seen). |
176 | */ |
177 | bool shouldRestart(bool forceAsking = false, bool showNumMatches = true) const override; |
178 | |
179 | /** |
180 | * Displays the final dialog telling the user how many replacements were made. |
181 | * Call either this or shouldRestart(). |
182 | */ |
183 | void displayFinalDialog() const override; |
184 | |
185 | Q_SIGNALS: |
186 | /** |
187 | * Connect to this signal to implement updating of replaced text during the replace |
188 | * operation. |
189 | * |
190 | * Extra care must be taken to properly implement the "no prompt-on-replace" case. |
191 | * For instance, the textFound() signal isn't emitted in that case (some code |
192 | * might rely on it), and for performance reasons one should repaint after |
193 | * replace() ONLY if prompt-on-replace was selected. |
194 | * |
195 | * @param text The text, in which the replacement has already been done |
196 | * @param replacementIndex Starting index of the matched substring |
197 | * @param replacedLength Length of the replacement string |
198 | * @param matchedLength Length of the matched string |
199 | * |
200 | * @since 5.83 |
201 | */ |
202 | void textReplaced(const QString &text, int replacementIndex, int replacedLength, int matchedLength); |
203 | |
204 | private: |
205 | Q_DECLARE_PRIVATE(KReplace) |
206 | }; |
207 | #endif |
208 | |