/*
    Copyright (C) 2001, S.R.Haque <srhaque@iee.org>.
    This file is part of the KDE project

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
*/

#ifndef KOFIND_H
#define KOFIND_H

#include <kdialogbase.h>

class KHistoryCombo;
class QCheckBox;
class QGroupBox;
class QLabel;
class QPopupMenu;
class QPushButton;
class QRect;
class QGridLayout;

/**
 * @short A generic "find" widget.
 *
 * @author S.R.Haque <srhaque@iee.org>
 *
 * @sect Detail
 *
 * This widget inherits from @ref KDialogBase and implements
 * the following additional functionalities:  a find string
 * object and an area for a user-defined widget to extend the dialog.
 *
 * @sect Example
 *
 * To use the basic find dialog:
 *
 * <pre>
 *  KoFindDialog dlg(....)
 *  if ( dlg.exec() != QDialog::Accepted )
 *      return;
 *
 *  // proceed with KoFind from here
 * </pre>
 *
 * To use your own extensions:
 *
 * <pre>
 * </pre>
 */
class KoFindDialog:
    public KDialogBase
{
    Q_OBJECT

public:

    // Options.

    typedef enum
    {
        WholeWordsOnly = 1,     // Match whole words only.
        FromCursor = 2,         // Start from current cursor position.
        SelectedText = 4,       // Only search selected area.
        CaseSensitive = 8,      // Consider case when matching.
        FindBackwards = 16,     // Go backwards.
        RegularExpression = 32, // Interpret the pattern as a regular expression.
        // User extensions can use boolean options above this value.
        MinimumUserOption = 65536
    } Options;

    /**
     * Construct a find dialog with a parent object and a name.
     *
     * @param parent The parent object of this widget.
     * @param name The name of this widget.
     * @param options A bitfield of the @ref Options to be enabled.
     * @param findStrings The find history, @see findHistory()
     * @param hasSelection Whether a selection exists
     */
    KoFindDialog( QWidget *parent = 0, const char *name = 0, long options = 0,
                  const QStringList &findStrings = QStringList(), bool hasSelection = true );

    /**
     * Destructor.
     */
    virtual ~KoFindDialog();

    /**
     * Provide the list of @p strings to be displayed as the history
     * of find strings. @p strings might get truncated if it is
     * too long.
     *
     * @param history The find history.
     * @see #findHistory
     */
    void setFindHistory( const QStringList &strings );

    /**
     * Returns the list of history items.
     *
     * @see #setFindHistory
     */
    QStringList findHistory() const;

    /**
     * Enable/disable the 'search in selection' option, depending
     * on whether there actually is a selection.
     *
     * @param hasSelection true if a selection exists
     */
    void setHasSelection( bool hasSelection );

    /**
     * Set the options which are enabled.
     *
     * @param options The setting of the @ref Options.
     */
    void setOptions( long options );

    /**
     * Returns the state of the options. Disabled options may be returned in
     * an indeterminate state.
     *
     * @see #setOptions
     */
    long options() const;

    /**
     * Returns the pattern to find.
     */
    QString pattern() const;

    /**
     * Returns an empty widget which the user may fill with additional UI
     * elements as required. The widget occupies the width of the dialog,
     * and is positioned immediately below the regular expression support
     * widgets for the pattern string.
     */
    QWidget *findExtension();

signals:

    /**
     * The OK button was pressed, the pattern was not empty, and if it is
     * supposed to be a regular expression, it is known to be valid.
     */
    void okClicked();

protected slots:

    void slotOk();
    void slotSelectedTextToggled(bool);
    void showPatterns();
    void showPlaceholders();
    void textSearchChanged( const QString &);
private:

    QGroupBox *m_findGrp;
    QLabel *m_findLabel;
    KHistoryCombo *m_find;
    QCheckBox *m_regExp;
    QPushButton *m_regExpItem;
    QGridLayout *m_findLayout;
    QWidget *m_findExtension;

    QGroupBox *m_optionGrp;
    QCheckBox *m_wholeWordsOnly;
    QCheckBox *m_fromCursor;
    QCheckBox *m_selectedText;
    QCheckBox *m_caseSensitive;
    QCheckBox *m_findBackwards;

    QPopupMenu *m_patterns;

    // Our dirty little secret is that we also implement the "replace" dialog. But we
    // keep that fact hidden from all but our friends.

    friend class KoReplaceDialog;

    /**
     * Construct a find dialog with a parent object and a name. This version of the
     * constructor is for use by friends only!
     *
     * @param forReplace Is this a replace dialog?
     */
    KoFindDialog( QWidget *parent, const char *name, bool forReplace );
    void init( bool forReplace, const QStringList &findStrings, bool hasSelection );

    QGroupBox *m_replaceGrp;
    QLabel *m_replaceLabel;
    KHistoryCombo *m_replace;
    QCheckBox* m_backRef;
    QPushButton* m_backRefItem;
    QGridLayout *m_replaceLayout;
    QWidget *m_replaceExtension;

    QCheckBox* m_promptOnReplace;

    QPopupMenu *m_placeholders;

    // Binary compatible extensibility.
    class KoFindDialogPrivate;
    KoFindDialogPrivate *d;
};

/**
 * @short A generic implementation of the "find" function.
 *
 * @author S.R.Haque <srhaque@iee.org>
 *
 * @sect Detail
 *
 * This class includes prompt handling etc. Also provides some
 * static functions which can be used to create custom behaviour
 * instead of using the class directly.
 *
 * @sect Example
 *
 * To use the class to implement a complete find feature:
 *
 * <pre>
 *
 *  // This creates a find-next-prompt dialog if needed.
 *  dialog = new KoFind(find, options);
 *
 *  // Connect signals to code which handles highlighting
 *  // of found text.
 *  connect(dialog, SIGNAL( highlight( const QString &, int, int, const QRect & ) ),
 *          this, SLOT( highlight( const QString &, int, int, const QRect & ) ) );
 *
 *  // Loop over all the text fragments of our document or selection
 *  for (text chosen by option SelectedText and in a direction set by FindBackwards)
 *       // don't forget to honour FromCursor too
 *  {
 *      // Let KoFind inspect the text fragment, and display a dialog if a match is found
 *      if ( !dialog->find( text_fragment, region_to_expose ) )
 *          break; // if cancelled by user
 *  }
 *  delete dialog;
 *
 * </pre>
 */
class KoFind :
    public KDialogBase
{
    Q_OBJECT

public:

    /** Will create a prompt dialog and use it as needed. */
    KoFind(const QString &pattern, long options, QWidget *parent = 0);
    virtual ~KoFind();

    /**
     * Walk the text fragment (e.g. kwrite line, kspread cell) looking for matches.
     * For each match, emits the expose() signal and displays the find-again dialog
     * proceeding.
     *
     * @param text The text fragment to modify.
     * @param expose The region to expose
     * @return false if the user elected to discontinue the find.
     */
    bool find(const QString &text, const QRect &expose);

    /**
     * Return the current options.
     *
     * Warning: this is usually the same value as the one passed to the constructor,
     * but options might change _during_ the replace operation:
     * e.g. the "All" button resets the PromptOnReplace flag.
     *
     */
    long options() const { return m_options; }

    /**
     * Return the number of matches found (i.e. the number of times
     * the @ref highlight signal was emitted).
     * If 0, can be used in a dialog box to tell the user "no match was found".
     * The final dialog does so already, unless you used setDisplayFinalDialog(false).
     */
    int numMatches() const { return m_matches; }

    /**
     * Call this to reset the numMatches count
     * (and the numReplacements count for a KoReplace).
     * Can be useful if reusing the same KoReplace for different operations,
     * or when restarting from the beginning of the document.
     */
    virtual void resetCounts() { m_matches = 0; }

    /**
     * Virtual method, which allows applications to add extra checks for
     * validating a candidate match. It's only necessary to reimplement this
     * if the find dialog extension has been used to provide additional
     * criterias.
     *
     * @param text  The current text fragment
     * @param index The starting index where the candidate match was found
     * @param matchedlength The length of the candidate match
     */
    virtual bool validateMatch( const QString &/*text*/, int /*index*/, int /*matchedlength*/ ) { return true; }

    /**
     * Returns true if we should restart the search from scratch.
     * Can ask the user, or return false (if we already searched the whole document).
     *
     * @param forceAsking set to true if the user modified the document during the
     * search. In that case it makes sense to restart the search again.
     */
    virtual bool shouldRestart( bool forceAsking = false ) const;

    /**
     * Search the given string, and returns whether a match was found. If one is,
     * the length of the string matched is also returned.
     *
     * A performance optimised version of the function is provided for use
     * with regular expressions.
     *
     * @param text The string to search.
     * @param pattern The pattern to look for.
     * @param index The starting index into the string.
     * @param options. The options to use.
     * @return The index at which a match was found, or -1 if no match was found.
     */
    static int find( const QString &text, const QString &pattern, int index, long options, int *matchedlength );
    static int find( const QString &text, const QRegExp &pattern, int index, long options, int *matchedlength );

    /**
     * Abort the current find process. Call this when the parent widget
     * is getting destroyed.
     */
    void abort();

    /**
     * Sets whether the final dialog saying "no match was found"
     * or "N replacements were made" should be displayed.
     * This is true by default, but some apps might want to deactivate this
     * to display a dialog that includes "do you want to start again"?
     *
     * The final dialog is displayed by the destructor (when abort() wasn't called
     * and the user didn't press Cancel).
     */
    void setDisplayFinalDialog( bool b ) { m_displayFinalDialog = b; }
    bool displayFinalDialog() const { return m_displayFinalDialog; }

protected:
    /**
     * @internal Constructor for KoReplace
     */
    KoFind(const QString &pattern, const QString &replacement, long options, QWidget *parent);

signals:

    /**
     * Connect to this signal to implement highlighting of found text during the find
     * operation.
     */
    void highlight(const QString &text, int matchingIndex, int matchedLength, const QRect &expose);

private:
    void init( const QString& pattern );


    QString m_pattern;
    QRegExp *m_regExp;
    long m_options;
    unsigned m_matches;
    QString m_text;
    QRect m_expose;
    int m_index;
    int m_matchedLength;
    bool m_cancelled;
    bool m_displayFinalDialog;

    static bool isInWord( QChar ch );
    static bool isWholeWords( const QString &text, int starts, int matchedLength );

    friend class KoReplace;

    // Binary compatible extensibility.
    class KoFindPrivate;
    KoFindPrivate *d;

private slots:

    virtual void slotUser1();   // Yes
    virtual void slotClose();
};

#endif
