File indexing completed on 2024-05-12 11:58:41

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2001 S.R. Haque <srhaque@iee.org>.
0004     SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #ifndef KFIND_H
0010 #define KFIND_H
0011 
0012 #include "ktextwidgets_export.h"
0013 
0014 #include <QObject>
0015 #include <memory>
0016 
0017 class QDialog;
0018 class KFindPrivate;
0019 
0020 /**
0021  * @class KFind kfind.h <KFind>
0022  *
0023  * @brief A generic implementation of the "find" function.
0024  *
0025  * @author S.R.Haque <srhaque@iee.org>, David Faure <faure@kde.org>,
0026  *         Arend van Beelen jr. <arend@auton.nl>
0027  *
0028  * \b Detail:
0029  *
0030  * This class includes prompt handling etc. Also provides some
0031  * static functions which can be used to create custom behavior
0032  * instead of using the class directly.
0033  *
0034  * \b Example:
0035  *
0036  * To use the class to implement a complete find feature:
0037  *
0038  * In the slot connected to the find action, after using KFindDialog:
0039  * \code
0040  *
0041  *  // This creates a find-next-prompt dialog if needed.
0042  *  m_find = new KFind(pattern, options, this);
0043  *
0044  *  // Connect textFound() signal to code which handles highlighting of found text.
0045  *  connect(m_find, &KFind::textFound, this, [this](const QString &text, int matchingIndex, int matchedLength) {
0046  *      slotHighlight(text, matchingIndex, matchedLength);
0047  *  }));
0048  *  // Connect findNext signal - called when pressing the button in the dialog
0049  *  connect(m_find, SIGNAL(findNext()),
0050  *          this, SLOT(slotFindNext()));
0051  * \endcode
0052  *
0053  *  Then initialize the variables determining the "current position"
0054  *  (to the cursor, if the option FromCursor is set,
0055  *   to the beginning of the selection if the option SelectedText is set,
0056  *   and to the beginning of the document otherwise).
0057  *  Initialize the "end of search" variables as well (end of doc or end of selection).
0058  *  Swap begin and end if FindBackwards.
0059  *  Finally, call slotFindNext();
0060  *
0061  * \code
0062  *  void slotFindNext()
0063  *  {
0064  *      KFind::Result res = KFind::NoMatch;
0065  *      while (res == KFind::NoMatch && <position not at end>) {
0066  *          if (m_find->needData())
0067  *              m_find->setData(<current text fragment>);
0068  *
0069  *          // Let KFind inspect the text fragment, and display a dialog if a match is found
0070  *          res = m_find->find();
0071  *
0072  *          if (res == KFind::NoMatch) {
0073  *              <Move to the next text fragment, honoring the FindBackwards setting for the direction>
0074  *          }
0075  *      }
0076  *
0077  *      if (res == KFind::NoMatch) // i.e. at end
0078  *          <Call either  m_find->displayFinalDialog(); m_find->deleteLater(); m_find = nullptr;
0079  *           or           if (m_find->shouldRestart()) { reinit (w/o FromCursor); m_find->resetCounts(); slotFindNext(); }
0080  *                        else { m_find->closeFindNextDialog(); }>
0081  *  }
0082  * \endcode
0083  *
0084  *  Don't forget to delete m_find in the destructor of your class,
0085  *  unless you gave it a parent widget on construction.
0086  *
0087  *  This implementation allows to have a "Find Next" action, which resumes the
0088  *  search, even if the user closed the "Find Next" dialog.
0089  *
0090  *  A "Find Previous" action can simply switch temporarily the value of
0091  *  FindBackwards and call slotFindNext() - and reset the value afterwards.
0092  */
0093 class KTEXTWIDGETS_EXPORT KFind : public QObject
0094 {
0095     Q_OBJECT
0096 
0097 public:
0098     /**
0099      * @see SearchOptions
0100      */
0101     enum Options {
0102         WholeWordsOnly = 1, ///< Match whole words only.
0103         FromCursor = 2, ///< Start from current cursor position.
0104         SelectedText = 4, ///< Only search selected area.
0105         CaseSensitive = 8, ///< Consider case when matching.
0106         FindBackwards = 16, ///< Go backwards.
0107         RegularExpression = 32, ///< Interpret the pattern as a regular expression.
0108         FindIncremental = 64, ///< Find incremental.
0109         // Note that KReplaceDialog uses 256 and 512
0110         // User extensions can use boolean options above this value.
0111         MinimumUserOption = 65536, ///< user options start with this bit
0112     };
0113     /**
0114      * Stores a combination of #Options values.
0115      */
0116     Q_DECLARE_FLAGS(SearchOptions, Options)
0117 
0118     /**
0119      * Only use this constructor if you don't use KFindDialog, or if
0120      * you use it as a modal dialog.
0121      */
0122     KFind(const QString &pattern, long options, QWidget *parent);
0123 
0124     /**
0125      * This is the recommended constructor if you also use KFindDialog (non-modal).
0126      * You should pass the pointer to it here, so that when a message box
0127      * appears it has the right parent. Don't worry about deletion, KFind
0128      * will notice if the find dialog is closed.
0129      */
0130     KFind(const QString &pattern, long options, QWidget *parent, QWidget *findDialog);
0131     ~KFind() override;
0132 
0133     enum Result {
0134         NoMatch,
0135         Match,
0136     };
0137 
0138     /**
0139      * @return true if the application must supply a new text fragment
0140      * It also means the last call returned "NoMatch". But by storing this here
0141      * the application doesn't have to store it in a member variable (between
0142      * calls to slotFindNext()).
0143      */
0144     bool needData() const;
0145 
0146     /**
0147      * Call this when needData returns true, before calling find().
0148      * @param data the text fragment (line)
0149      * @param startPos if set, the index at which the search should start.
0150      * This is only necessary for the very first call to setData usually,
0151      * for the 'find in selection' feature. A value of -1 (the default value)
0152      * means "process all the data", i.e. either 0 or data.length()-1 depending
0153      * on FindBackwards.
0154      */
0155     void setData(const QString &data, int startPos = -1);
0156 
0157     /**
0158      * Call this when needData returns true, before calling find(). The use of
0159      * ID's is especially useful if you're using the FindIncremental option.
0160      * @param id the id of the text fragment
0161      * @param data the text fragment (line)
0162      * @param startPos if set, the index at which the search should start.
0163      * This is only necessary for the very first call to setData usually,
0164      * for the 'find in selection' feature. A value of -1 (the default value)
0165      * means "process all the data", i.e. either 0 or data.length()-1 depending
0166      * on FindBackwards.
0167      */
0168     void setData(int id, const QString &data, int startPos = -1);
0169 
0170     /**
0171      * Walk the text fragment (e.g. in a text-processor line or spreadsheet
0172      * cell ...etc) looking for matches.
0173      * For each match, emits the textFound() signal and displays the find-again
0174      * dialog to ask if the user wants to find the same text again.
0175      */
0176     Result find();
0177 
0178     /**
0179      * Return the current options.
0180      *
0181      * Warning: this is usually the same value as the one passed to the constructor,
0182      * but options might change _during_ the replace operation:
0183      * e.g. the "All" button resets the PromptOnReplace flag.
0184      *
0185      * @see KFind::Options
0186      */
0187     long options() const;
0188 
0189     /**
0190      * Set new options. Usually this is used for setting or clearing the
0191      * FindBackwards options.
0192      *
0193      * @see KFind::Options
0194      */
0195     virtual void setOptions(long options);
0196 
0197     /**
0198      * @return the pattern we're currently looking for
0199      */
0200     QString pattern() const;
0201 
0202     /**
0203      * Change the pattern we're looking for
0204      */
0205     void setPattern(const QString &pattern);
0206 
0207     /**
0208      * Returns the number of matches found (i.e. the number of times the textFound()
0209      * signal was emitted).
0210      * If 0, can be used in a dialog box to tell the user "no match was found".
0211      * The final dialog does so already, unless you used setDisplayFinalDialog(false).
0212      */
0213     int numMatches() const;
0214 
0215     /**
0216      * Call this to reset the numMatches count
0217      * (and the numReplacements count for a KReplace).
0218      * Can be useful if reusing the same KReplace for different operations,
0219      * or when restarting from the beginning of the document.
0220      */
0221     virtual void resetCounts();
0222 
0223     /**
0224      * Virtual method, which allows applications to add extra checks for
0225      * validating a candidate match. It's only necessary to reimplement this
0226      * if the find dialog extension has been used to provide additional
0227      * criteria.
0228      *
0229      * @param text  The current text fragment
0230      * @param index The starting index where the candidate match was found
0231      * @param matchedlength The length of the candidate match
0232      */
0233     virtual bool validateMatch(const QString &text, int index, int matchedlength);
0234 
0235     /**
0236      * Returns true if we should restart the search from scratch.
0237      * Can ask the user, or return false (if we already searched the whole document).
0238      *
0239      * @param forceAsking set to true if the user modified the document during the
0240      * search. In that case it makes sense to restart the search again.
0241      *
0242      * @param showNumMatches set to true if the dialog should show the number of
0243      * matches. Set to false if the application provides a "find previous" action,
0244      * in which case the match count will be erroneous when hitting the end,
0245      * and we could even be hitting the beginning of the document (so not all
0246      * matches have even been seen).
0247      */
0248     virtual bool shouldRestart(bool forceAsking = false, bool showNumMatches = true) const;
0249 
0250 #if KTEXTWIDGETS_ENABLE_DEPRECATED_SINCE(5, 70)
0251     /**
0252      * Search the given string, and returns whether a match was found. If one is,
0253      * the length of the string matched is also returned.
0254      *
0255      * A performance optimised version of the function is provided for use
0256      * with regular expressions.
0257      *
0258      * @param text The string to search.
0259      * @param pattern The pattern to look for.
0260      * @param index The starting index into the string.
0261      * @param options The options to use.
0262      * @param matchedlength The length of the string that was matched
0263      * @return The index at which a match was found, or -1 if no match was found.
0264      *
0265      * @deprecated Since 5.70
0266      */
0267     KTEXTWIDGETS_DEPRECATED_VERSION(5,
0268                                     70,
0269                                     "Use find(const QString &text, const QString &pattern, int index, long options, \
0270                    int *matchedLength, QRegularExpressionMatch *rmatch).")
0271     static int find(const QString &text, const QString &pattern, int index, long options, int *matchedlength);
0272 #endif
0273 
0274 #if KTEXTWIDGETS_ENABLE_DEPRECATED_SINCE(5, 70)
0275     /**
0276      * @deprecated Since 5.70, for lack of direct use
0277      */
0278     KTEXTWIDGETS_DEPRECATED_VERSION_BELATED(5,
0279                                             71,
0280                                             5,
0281                                             70,
0282                                             "Use find(const QString &, const QString &, int, long, \
0283                    int *, QRegularExpressionMatch *).")
0284     static int find(const QString &text, const QRegExp &pattern, int index, long options, int *matchedlength);
0285 #endif
0286 
0287     /**
0288      * Search @p text for @p pattern. If a match is found, the length of the matched
0289      * string will be stored in @p matchedLength and the index of the matched string
0290      * will be returned. If no match is found -1 is returned.
0291      *
0292      * If the KFind::RegularExpression flag is set, the @p pattern will be iterpreted
0293      * as a regular expression (using QRegularExpression).
0294      *
0295      * @note Unicode support is always enabled (by setting the QRegularExpression::UseUnicodePropertiesOption flag).
0296      *
0297      * @param text The string to search in
0298      * @param pattern The pattern to search for
0299      * @param index  The index in @p text from which to start the search
0300      * @param options The options to use
0301      * @param matchedlength If there is a match, its length will be stored in this parameter
0302      * @param rmatch If there is a regular expression match (implies that the KFind::RegularExpression
0303      *               flag is set) and @p rmatch is not a nullptr the match result will be stored
0304      *               in this QRegularExpressionMatch object
0305      * @return The index at which a match was found otherwise -1
0306      *
0307      * @since 5.70
0308      */
0309     static int find(const QString &text, const QString &pattern, int index, long options, int *matchedLength, QRegularExpressionMatch *rmatch);
0310 
0311     /**
0312      * Displays the final dialog saying "no match was found", if that was the case.
0313      * Call either this or shouldRestart().
0314      */
0315     virtual void displayFinalDialog() const;
0316 
0317     /**
0318      * Return (or create) the dialog that shows the "find next?" prompt.
0319      * Usually you don't need to call this.
0320      * One case where it can be useful, is when the user selects the "Find"
0321      * menu item while a find operation is under way. In that case, the
0322      * program may want to call setActiveWindow() on that dialog.
0323      */
0324     QDialog *findNextDialog(bool create = false);
0325 
0326     /**
0327      * Close the "find next?" dialog. The application should do this when
0328      * the last match was hit. If the application deletes the KFind, then
0329      * "find previous" won't be possible anymore.
0330      *
0331      * IMPORTANT: you should also call this if you are using a non-modal
0332      * find dialog, to tell KFind not to pop up its own dialog.
0333      */
0334     void closeFindNextDialog();
0335 
0336     /**
0337      * @return the current matching index (or -1).
0338      * Same as the matchingIndex parameter passed to the textFound() signal.
0339      * You usually don't need to use this, except maybe when updating the current data,
0340      * so you need to call setData(newData, index()).
0341      */
0342     int index() const;
0343 
0344 Q_SIGNALS:
0345 #if KTEXTWIDGETS_ENABLE_DEPRECATED_SINCE(5, 81)
0346     /**
0347      * Connect to this signal to implement highlighting of found text during the find
0348      * operation.
0349      *
0350      * If you've set data with setData(id, text), use the signal highlight(id,
0351      * matchingIndex, matchedLength)
0352      *
0353      * WARNING: If you're using the FindIncremental option, the text argument
0354      * passed by this signal is not necessarily the data last set through
0355      * setData(), but can also be an earlier set data block.
0356      *
0357      * @see setData()
0358      *
0359      * @deprecated since 5.81, use the KFind::textFound(const QString &, int, int) signal instead.
0360      */
0361     KTEXTWIDGETS_DEPRECATED_VERSION(5, 81, "Use the KFind::textFound(const QString &, int, int) signal instead.")
0362     void highlight(const QString &text, int matchingIndex, int matchedLength); // clazy:exclude=overloaded-signal
0363 #endif
0364 
0365     /**
0366      * Connect to this signal to implement highlighting of found text during the find
0367      * operation.
0368      *
0369      * If you've set data with setData(id, text), use the textFoundAtId(int, int, int) signal.
0370      *
0371      * WARNING: If you're using the FindIncremental option, the text argument
0372      * passed by this signal is not necessarily the data last set through
0373      * setData(), but can also be an earlier set data block.
0374      *
0375      * @see setData()
0376      *
0377      * @since 5.81
0378      */
0379     void textFound(const QString &text, int matchingIndex, int matchedLength);
0380 
0381 #if KTEXTWIDGETS_ENABLE_DEPRECATED_SINCE(5, 81)
0382     /**
0383      * Connect to this signal to implement highlighting of found text during the find
0384      * operation.
0385      *
0386      * Use this signal if you've set your data with setData(id, text), otherwise
0387      * use the textFound(text, matchingIndex, matchedLength) signal.
0388      *
0389      * WARNING: If you're using the FindIncremental option, the id argument
0390      * passed by this signal is not necessarily the id of the data last set
0391      * through setData(), but can also be of an earlier set data block.
0392      *
0393      * @see setData()
0394      *
0395      * @deprecated since 5.81, use the KFind::textFoundAtId(int id, int matchingIndex, int matchedLength) signal instead.
0396      */
0397     KTEXTWIDGETS_DEPRECATED_VERSION(5, 81, "Use the KFind::textFoundAtId(int id, int matchingIndex, int matchedLength) signal instead.")
0398     void highlight(int id, int matchingIndex, int matchedLength); // clazy:exclude=overloaded-signal
0399 #endif
0400 
0401     /**
0402      * Connect to this signal to implement highlighting of found text during
0403      * the find operation.
0404      *
0405      * Use this signal if you've set your data with setData(id, text),
0406      * otherwise use the textFound(text, matchingIndex, matchedLength) signal.
0407      *
0408      * WARNING: If you're using the FindIncremental option, the id argument
0409      * passed by this signal is not necessarily the id of the data last set
0410      * through setData(), but can also be of an earlier set data block.
0411      *
0412      * @see setData()
0413      *
0414      * @since 5.81
0415      */
0416     void textFoundAtId(int id, int matchingIndex, int matchedLength);
0417 
0418     // ## TODO docu
0419     // findprevious will also emit findNext, after temporarily switching the value
0420     // of FindBackwards
0421     void findNext();
0422 
0423     /**
0424      * Emitted when the options have changed.
0425      * This can happen e.g. with "Replace All", or if our 'find next' dialog
0426      * gets a "find previous" one day.
0427      */
0428     void optionsChanged();
0429 
0430     /**
0431      * Emitted when the 'find next' dialog is being closed.
0432      * Some apps might want to remove the highlighted text when this happens.
0433      * Apps without support for "Find Next" can also do m_find->deleteLater()
0434      * to terminate the find operation.
0435      */
0436     void dialogClosed();
0437 
0438 protected:
0439     QWidget *parentWidget() const;
0440     QWidget *dialogsParent() const;
0441 
0442 protected:
0443     KTEXTWIDGETS_NO_EXPORT KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent);
0444     KTEXTWIDGETS_NO_EXPORT KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent, QWidget *findDialog);
0445 
0446 private:
0447     friend class KReplace;
0448     Q_DECLARE_PRIVATE_D(d, KFind)
0449     std::unique_ptr<class KFindPrivate> const d;
0450     // KF6 TODO: change private d to protected d_ptr, use normal Q_DECLARE_PRIVATE, remove friend
0451 };
0452 
0453 Q_DECLARE_OPERATORS_FOR_FLAGS(KFind::SearchOptions)
0454 
0455 #endif