File indexing completed on 2024-04-21 03:58:27

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 KREPLACE_H
0010 #define KREPLACE_H
0011 
0012 #include "kfind.h"
0013 
0014 #include "ktextwidgets_export.h"
0015 
0016 class KReplacePrivate;
0017 
0018 /**
0019  * @class KReplace kreplace.h <KReplace>
0020  *
0021  * @brief A generic implementation of the "replace" function.
0022  *
0023  * @author S.R.Haque <srhaque@iee.org>, David Faure <faure@kde.org>
0024  *
0025  * \b Detail:
0026  *
0027  * This class includes prompt handling etc. Also provides some
0028  * static functions which can be used to create custom behavior
0029  * instead of using the class directly.
0030  *
0031  * \b Example:
0032  *
0033  * To use the class to implement a complete replace feature:
0034  *
0035  * In the slot connect to the replace action, after using KReplaceDialog:
0036  * \code
0037  *
0038  *  // This creates a replace-on-prompt dialog if needed.
0039  *  m_replace = new KReplace(pattern, replacement, options, this);
0040  *
0041  *  // Connect signals to code which handles highlighting of found text, and
0042  *  // on-the-fly replacement.
0043  *  connect(m_replace, &KFind::textFound, this, [this](const QString &text, int matchingIndex, int matchedLength) {
0044  *      slotHighlight(text, matchingIndex, matchedLength);
0045  *  });
0046  *  // Connect findNext signal - called when pressing the button in the dialog
0047  *  connect( m_replace, SIGNAL( findNext() ),
0048  *          this, SLOT( slotReplaceNext() ) );
0049  *  // Connect to the textReplaced() signal - emitted when a replacement is done
0050  *  connect( m_replace, &KReplace::textReplaced, this, [this](const QString &text, int replacementIndex, int replacedLength, int matchedLength) {
0051  *      slotReplace(text, replacementIndex, replacedLength, matchedLength);
0052  *  });
0053  * \endcode
0054  *  Then initialize the variables determining the "current position"
0055  *  (to the cursor, if the option FromCursor is set,
0056  *   to the beginning of the selection if the option SelectedText is set,
0057  *   and to the beginning of the document otherwise).
0058  *  Initialize the "end of search" variables as well (end of doc or end of selection).
0059  *  Swap begin and end if FindBackwards.
0060  *  Finally, call slotReplaceNext();
0061  *
0062  * \code
0063  *  void slotReplaceNext()
0064  *  {
0065  *      KFind::Result res = KFind::NoMatch;
0066  *      while ( res == KFind::NoMatch && <position not at end> ) {
0067  *          if ( m_replace->needData() )
0068  *              m_replace->setData( <current text fragment> );
0069  *
0070  *          // Let KReplace inspect the text fragment, and display a dialog if a match is found
0071  *          res = m_replace->replace();
0072  *
0073  *          if ( res == KFind::NoMatch ) {
0074  *              <Move to the next text fragment, honoring the FindBackwards setting for the direction>
0075  *          }
0076  *      }
0077  *
0078  *      if ( res == KFind::NoMatch ) // i.e. at end
0079  *          <Call either  m_replace->displayFinalDialog(); delete m_replace; m_replace = nullptr;
0080  *           or           if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); }
0081  *                        else { m_replace->closeReplaceNextDialog(); }>
0082  *  }
0083  * \endcode
0084  *
0085  *  Don't forget delete m_find in the destructor of your class,
0086  *  unless you gave it a parent widget on construction.
0087  *
0088  */
0089 class KTEXTWIDGETS_EXPORT KReplace : public KFind
0090 {
0091     Q_OBJECT
0092 
0093 public:
0094     /**
0095      * Only use this constructor if you don't use KFindDialog, or if
0096      * you use it as a modal dialog.
0097      */
0098     KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent = nullptr);
0099     /**
0100      * This is the recommended constructor if you also use KReplaceDialog (non-modal).
0101      * You should pass the pointer to it here, so that when a message box
0102      * appears it has the right parent. Don't worry about deletion, KReplace
0103      * will notice if the find dialog is closed.
0104      */
0105     KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent, QWidget *replaceDialog);
0106 
0107     ~KReplace() override;
0108 
0109     /**
0110      * Return the number of replacements made (i.e. the number of times
0111      * the textReplaced() signal was emitted).
0112      *
0113      * Can be used in a dialog box to tell the user how many replacements were made.
0114      * The final dialog does so already, unless you used setDisplayFinalDialog(false).
0115      */
0116     int numReplacements() const;
0117 
0118     /**
0119      * Call this to reset the numMatches & numReplacements counts.
0120      * Can be useful if reusing the same KReplace for different operations,
0121      * or when restarting from the beginning of the document.
0122      */
0123     void resetCounts() override;
0124 
0125     /**
0126      * Walk the text fragment (e.g. kwrite line, kspread cell) looking for matches.
0127      * For each match, if prompt-on-replace is specified, emits the textFound() signal
0128      * and displays the prompt-for-replace dialog before doing the replace.
0129      */
0130     Result replace();
0131 
0132     /**
0133      * Return (or create) the dialog that shows the "find next?" prompt.
0134      * Usually you don't need to call this.
0135      * One case where it can be useful, is when the user selects the "Find"
0136      * menu item while a find operation is under way. In that case, the
0137      * program may want to call setActiveWindow() on that dialog.
0138      */
0139     QDialog *replaceNextDialog(bool create = false);
0140 
0141     /**
0142      * Close the "replace next?" dialog. The application should do this when
0143      * the last match was hit. If the application deletes the KReplace, then
0144      * "find previous" won't be possible anymore.
0145      */
0146     void closeReplaceNextDialog();
0147 
0148     /**
0149      * Searches the given @p text for @p pattern; if a match is found it is replaced
0150      * with @p replacement and the index of the replacement string is returned.
0151      *
0152      * @param text The string to search
0153      * @param pattern The pattern to search for
0154      * @param replacement The replacement string to insert into the text
0155      * @param index The starting index into the string
0156      * @param options The options to use
0157      * @param replacedLength Output parameter, contains the length of the replaced string
0158      *        Not always the same as replacement.length(), when backreferences are used
0159      * @return The index at which a match was found, or -1 otherwise
0160      */
0161     static int replace(QString &text, const QString &pattern, const QString &replacement, int index, long options, int *replacedLength);
0162 
0163     /**
0164      * Returns true if we should restart the search from scratch.
0165      * Can ask the user, or return false (if we already searched/replaced the
0166      * whole document without the PromptOnReplace option).
0167      *
0168      * @param forceAsking set to true if the user modified the document during the
0169      * search. In that case it makes sense to restart the search again.
0170      *
0171      * @param showNumMatches set to true if the dialog should show the number of
0172      * matches. Set to false if the application provides a "find previous" action,
0173      * in which case the match count will be erroneous when hitting the end,
0174      * and we could even be hitting the beginning of the document (so not all
0175      * matches have even been seen).
0176      */
0177     bool shouldRestart(bool forceAsking = false, bool showNumMatches = true) const override;
0178 
0179     /**
0180      * Displays the final dialog telling the user how many replacements were made.
0181      * Call either this or shouldRestart().
0182      */
0183     void displayFinalDialog() const override;
0184 
0185 Q_SIGNALS:
0186     /**
0187      * Connect to this signal to implement updating of replaced text during the replace
0188      * operation.
0189      *
0190      * Extra care must be taken to properly implement the "no prompt-on-replace" case.
0191      * For instance, the textFound() signal isn't emitted in that case (some code
0192      * might rely on it), and for performance reasons one should repaint after
0193      * replace() ONLY if prompt-on-replace was selected.
0194      *
0195      * @param text The text, in which the replacement has already been done
0196      * @param replacementIndex Starting index of the matched substring
0197      * @param replacedLength Length of the replacement string
0198      * @param matchedLength Length of the matched string
0199      *
0200      * @since 5.83
0201      */
0202     void textReplaced(const QString &text, int replacementIndex, int replacedLength, int matchedLength);
0203 
0204 private:
0205     Q_DECLARE_PRIVATE(KReplace)
0206 };
0207 #endif