/*
    KmPlot - a math. function plotter for the KDE-Desktop

    SPDX-FileCopyrightText: 2006 David Saxton <david@bluehaze.org>

    This file is part of the KDE Project.
    KmPlot is part of the KDE-EDU Project.

    SPDX-License-Identifier: GPL-2.0-or-later

*/

#ifndef KGRADIENTDIALOG_H
#define KGRADIENTDIALOG_H

#include <QDialog>
#include <QGradient>
#include <QPushButton>
#include <QWidget>

class QMouseEvent;
class QPaintEvent;
class QStyleOptionButton;

/**
 * \short A color-gradient strip with arrows to change the stops.
 *
 * Displays a color gradient for editing. Color stops can be added, removed or
 * repositioned by the user via the mouse.
 *
 * This widget can't change the colors used in the gradient however. For that,
 * you should either:
 *
 * \li Use KGradientDialog, or
 * \li Connect to the colorChanged signal, provide your own color editing
 *     widgets, and call setColor as appropriate.
 *
 * \see QGradient
 */
class KGradientEditor : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation)
    Q_PROPERTY(QGradient gradient READ gradient WRITE setGradient USER true)

public:
    explicit KGradientEditor(QWidget *parent);
    ~KGradientEditor();

    /**
     * \return the current color-gradient.
     */
    QGradient gradient() const
    {
        return m_gradient;
    }
    /**
     * \return the currently selected color.
     */
    QColor color() const
    {
        return m_currentStop.second;
    }
    /**
     * Set the orientation of the gradient strip.
     */
    void setOrientation(Qt::Orientation orientation);
    /**
     * \return the orientation of the gradient strip.
     */
    Qt::Orientation orientation() const
    {
        return m_orientation;
    }

    QSize minimumSizeHint() const override;

public Q_SLOTS:
    /**
     * Set the current gradient being edited.
     */
    void setGradient(const QGradient &gradient);
    /**
     * Set the color of the currently selected color-stop.
     */
    void setColor(const QColor &color);
    /**
     * Removes the currently selected stop.
     */
    void removeStop();

Q_SIGNALS:
    /**
     * Emitted when a color-stop is selected (e.g. when it is clicked on
     * or the previously selected color is removed).
     */
    void colorSelected(const QColor &color);
    /**
     * Emitted when the gradient changes.
     */
    void gradientChanged(const QGradient &gradient);

protected:
    void paintEvent(QPaintEvent *e) override;
    void mousePressEvent(QMouseEvent *e) override;
    void mouseMoveEvent(QMouseEvent *e) override;
    void mouseReleaseEvent(QMouseEvent *e) override;
    void mouseDoubleClickEvent(QMouseEvent *e) override;
    void contextMenuEvent(QContextMenuEvent *e) override;

private:
    /**
     * Attempts to get the arrow under the mouse, updating m_currentStop
     * to the corresponding stop if an arrow was found.
     * \return true if an arrow was under \p mousePos
     * \return false if an arrow wasn't under \p mousePos
     */
    bool getGradientStop(const QPoint &mousePos);
    /**
     * Updates the current stop, redraws the widget and emits
     * colorSelected.
     */
    void setCurrentStop(const QGradientStop &stop);
    /**
     * Updates m_gradient, redraws the widget and emits gradientChanged.
     */
    void setGradient(const QGradientStops &stops);
    /**
     * Draws the given gradient stop.
     */
    void drawArrow(QPainter *painter, const QGradientStop &stop);
    /**
     * Converts from \p stop (ranging from 0 to 1) to a horizontal or
     * vertical (depending on the current orientation) widget coordinate.
     */
    double toArrowPos(double stop) const;
    /**
     * Inverse of toArrowPos; converts from the widget coordinate \p pos
     * to a stop position, which is guaranteed to be between 0 and 1.
     */
    double fromArrowPos(double pos) const;
    /**
     * Find a gradient stop to be selected. This is called after e.g. the
     * currently selected stop is deleted.
     */
    void findGradientStop();

    double m_clickOffset; // from the center of the arrow
    bool m_haveArrow; // true when an arrow has been clicked on
    QGradientStop m_currentStop;
    QGradient m_gradient;
    Qt::Orientation m_orientation;
};

/**
 * \short A dialog for getting a QGradient.
 *
 * This dialog contains a KGradientEditor for editing the gradient stops and
 * a selection of widgets for changing the current color.
 *
 * Example:
 *
 * \code
 *  QGradient gradient;
 *  int result = KGradientDialog::getColor( gradient );
 *  if ( result == KGradientDialog::Accepted )
 *      ...
 * \endcode
 */
class KGradientDialog : public QDialog
{
    Q_OBJECT

public:
    KGradientDialog(QWidget *parent, bool modal);
    ~KGradientDialog();

    /**
     * Creates a modal gradient dialog, lets the user choose a gradient,
     * and returns when the dialog is closed.
     *
     * The initial gradient will be the one passed in \p gradient, and the
     * chosen gradient is returned in this argument. If the user Cancels
     * the dialog instead of Ok'ing it, then \p gradient will remain
     * unchanged.
     *
     * \returns QDialog::result()
     */
    static int getGradient(QGradient &gradient, QWidget *parent = nullptr);

    /**
     * \return the current gradient.
     */
    QGradient gradient() const;

public Q_SLOTS:
    /**
     * Sets the current gradient.
     */
    void setGradient(const QGradient &gradient);

Q_SIGNALS:
    /**
     * Emitted when the current gradient changes.
     */
    void gradientChanged(const QGradient &gradient);

private:
    class QColorDialog *m_colorDialog;
    KGradientEditor *m_gradient;
};

/**
 * \short A button to display and edit color gradients
 *
 * The button contains and displays a color gradient (see QGradient). When
 * clicked on, it creates a KGradientDialog for editing the gradient.
 */
class KGradientButton : public QPushButton
{
    Q_OBJECT
    Q_PROPERTY(QGradient gradient READ gradient WRITE setGradient USER true)

public:
    explicit KGradientButton(QWidget *parent = nullptr);
    ~KGradientButton();

    /**
     * \return the current gradient.
     */
    QGradient gradient() const
    {
        return m_gradient;
    }

    QSize sizeHint() const override;

Q_SIGNALS:
    /**
     * Emitted when the current gradient changes.
     */
    void gradientChanged(const QGradient &gradient);

public Q_SLOTS:
    /**
     * Sets the current gradient.
     */
    void setGradient(const QGradient &gradient);

protected Q_SLOTS:
    /**
     * Invokes the KGradientDialog for selecting a gradient.
     */
    void chooseGradient();

protected:
    void paintEvent(QPaintEvent *pe) override;

private:
    void initStyleOption(QStyleOptionButton *opt) const override;
    QGradient m_gradient;
};

#endif // KGRADIENTEDITOR_H
