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