File indexing completed on 2024-04-21 16:31:52

0001 /**
0002  * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût <slaout@linux62.org>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #ifndef KCOLORCOMBO2_H
0008 #define KCOLORCOMBO2_H
0009 
0010 #include <KComboBox>
0011 
0012 class QColor;
0013 class QPixmap;
0014 
0015 class QDragEnterEvent;
0016 class QDropEvent;
0017 class QMouseEvent;
0018 class QKeyEvent;
0019 class QPaintEvent;
0020 
0021 class KColorPopup;
0022 
0023 /**
0024  * @short A combobox to display or allow user selection of a color in a user-friendly way.
0025  *
0026  * A combobox widget that popup an array of colors for the user to easily pick a common color.\n
0027  * He/she can use the popup to quickly pick a reasonable color or open a color chooser dialog for a more precise choice.\n
0028  * The user can also choose a default color (the standard background color, text color, etc... it's to the programmer to make sense of this property).\n
0029  * \n
0030  * The user is also offered some facilities: like KColorButton he/she can copy a color or paste it
0031  * (with standard keyboard shortcuts, usually Ctrl+C and Ctrl+V), and he/she can drag or drop colors.
0032  *
0033  * @par Quick usage:
0034  * Just create a new KColorCombo2() with the initial color and eventually an allowed default color
0035  * (eg. palette().color(QPalette::Base) for a background color, palette().color(QPalette::Text)...).\n
0036  * You will be noticed of the color the user selects with the signal changed(), or you can use color() to get the color at any moment.\n
0037  * Note that they can return an invalid color (see QColor::isValid()) if the user chosen the default color (if he can choose that).\n
0038  * It's then easy to save in settings, but if you want the real color (even for the default), you can get it with effectiveColor().
0039  *
0040  * @par Notes about default color:
0041  * If you set a default color using Qt or KDE standard colors, the user can change them in the KDE Control Center,
0042  * but this widget willn't be update and will still show the old one.\n
0043  * To be noticed of such color change and then update the widget with the new standard color, you can use one of those two methods:
0044  * @code
0045  * void QWidgetDerivate::paletteChange(const QPalette &oldPalette) { // QWidgetDerivate is a parent or near custom widget
0046  *     theComboBox->setDefaultColor(theNewDefaultColor);
0047  *     QWidget::paletteChange(oldPalette);
0048  * }
0049  * @endcode
0050  * or connect the signal QApplication::kdisplayPaletteChanged() to a slot that will set the default color of this widget.
0051  *
0052  * @par Advanced usage:
0053  * By default, the combobox show a well balanced rainbow, OK for most usages, and you don't need to do anything for it to work.\n
0054  * You however can set your own color array by calling newColorArray() with the number of columns and rows.
0055  * Then, setColorAt() several times to fill the array.\n
0056  * This allow the most flexibility. But if you just want a rainbow with more or less colors, setRainbowPreset() is what you want.\n
0057  * If you worry about performance issues of creating a combobox with the default color array and then allocating another color array by yourself,
0058  * note that the default color array is not allocated in the constructor, but as soon as it is demanded (on first popup if no array has been
0059  * set before, or on first call of any accessors: colorAt(), columnCount(), setColorAt()...).
0060  * Finally, colorRectPixmap() and drawColorRect() allow to draw the color rounded-rectangle in other places for a consistent look.
0061  *
0062  * @see KGlobalSettings Use one of the static functions to get KDE standard colors for default values.
0063  * @see KColorButton    The same, but without the rainbow popup or the choice of a default color.
0064  * @see QColorDialog    The dialog that is shown when the user click the "Other..." entry.
0065  * @author Sébastien Laoût <slaout@linux62.org>
0066  *
0067  * @image html commoncolorselector.png "Common Color Selector ComboBox"
0068  */
0069 class KColorCombo2 : public KComboBox
0070 {
0071     Q_OBJECT
0072     Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
0073     Q_PROPERTY(QColor defaultColor READ defaultColor WRITE setDefaultColor NOTIFY defaultColorChanged)
0074 
0075 public Q_SLOTS:
0076     /**
0077      * Change the selected color.\n
0078      * If the popup is open, it will not reflect the change. FIXME: Should it?
0079      * @param color The new selected color. Can be invalid to select the default one.\n
0080      *              If @p color is invalid and no default color is allowed, the function will keep the old one.
0081      */
0082     void setColor(const QColor &color);
0083 
0084     /**
0085      * Change the default color.
0086      * @param color The color to return if the user choose the default one. If it is not valid, the user willn't be allowed to choose a default one.
0087      * @see defaultColor() to get it.
0088      */
0089     void setDefaultColor(const QColor &color);
0090 private Q_SLOTS:
0091     void popupClosed();
0092 Q_SIGNALS:
0093     /**
0094      * Emitted when the color of the widget is changed, either with setColor() or via user selection.
0095      * @see color() to know the content of @p newColor.
0096      */
0097     void colorChanged(const QColor &newColor);
0098 
0099     /**
0100      * Emitted when the default color of the widget is changed with setDefaultColor()
0101      * @see defaultColor() to know the content of @p newDefaultColor.
0102      */
0103     void defaultColorChanged(const QColor &newDefaultColor);
0104 
0105 public:
0106     /**
0107      * Constructs a color combobox with parent @p parent.
0108      * @param color         The initial selected color. If it is not valid, the default one will then be selected.\n
0109      *                      But if @p color is invalid and there is no default color, the result is undefined.
0110      * @param defaultColor  The color to return if the user choose the default one. If it is not valid, the user willn't be allowed to choose a default one.
0111      */
0112     KColorCombo2(const QColor &color, const QColor &defaultColor, QWidget *parent = nullptr);
0113 
0114     /**
0115      * Constructs a color combobox with parent @p parent.\n
0116      * The user is not allowed to choose a default color, unless you call setDefaultColor() later.
0117      * @param color         The initial selected color. If it is invalid, the result is undefined.
0118      */
0119     explicit KColorCombo2(const QColor &color, QWidget *parent = nullptr);
0120 
0121     /**
0122      * Destroys the combobox.
0123      */
0124     ~KColorCombo2() override;
0125 
0126     /**
0127      * Get the color chosen by the user.\n
0128      * Can be invalid, if the user chosen the default one.\n
0129      * Ideal to store it in settings for later recall.
0130      * @see effectiveColor() if you want the color to be always valid.
0131      */
0132     QColor color() const;
0133 
0134     /**
0135      * Return the color chosen by the user.\n
0136      * If the user chosen the default color, the default one is then returned, so the returned color is always valid.\n
0137      * Ideal to directly use to draw.
0138      * @see color() if you want to be notified of a default color choice.
0139      */
0140     QColor effectiveColor() const;
0141 
0142     /**
0143      * Returns the default color or an invalid color if no default color is set (if the user isn't allowed to choose a default color).
0144      * @see setDefaultColor() to change it.
0145      */
0146     QColor defaultColor() const;
0147 
0148     /**
0149      * Allocate a new color array of the specified dimension.\n
0150      * The new array will have invalid colors: you should then assign them one by one.\n
0151      * If one or both of the dimensions are negative or null, this function do nothing (both dimensions are always ensured to be at least equal to 1).
0152      * @param columnCount The number of columns of the array.
0153      * @param rowCount    The number of rows of the array.
0154      * @see setColorAt() to set all colors once the array have been created.
0155      */
0156     void newColorArray(int columnCount, int rowCount);
0157 
0158     /**
0159      * Get the number of columns in the array that the user can see to choose.
0160      * @see rowCount() for the number of rows, and colorAt() to get a color from the array.
0161      */
0162     int columnCount() const;
0163 
0164     /**
0165      * Get the number of rows in the array that the user can see to choose.
0166      * @see columnCount() for the number of columns, and colorAt() to get a color from the array.
0167      */
0168     int rowCount() const;
0169 
0170     /**
0171      * Set a color in the array at position (column,row).\n
0172      * If one or both of the indexes are out of range, this function do nothing.\n
0173      * @p column and @p row start from 0 to columnCount()-1 and columnRow()-1.
0174      *
0175      * @param column The x coordinate of the color to set or change.
0176      * @param row    The y coordinate of the color to set or change.
0177      * @param color  The color to assign at this position.
0178      */
0179     void setColorAt(int column, int row, const QColor &color);
0180 
0181     /**
0182      * Get a color in the array that the user can see to choose.\n
0183      * @p column and @p row start from 0 to columnCount()-1 and columnRow()-1.
0184      *
0185      * @return The asked color, or an invalid color if the index is out of limit of the array.
0186      * @see columnCount() and rowCount() to get the array dimensions.
0187      */
0188     QColor colorAt(int column, int row) /* const*/;
0189 
0190     /**
0191      * Fill the array of colors (that will be shown to the user in the popup that appears when he/she click the arrow) with a rainbow of different luminosity.\n
0192      * This rainbow representation have the advantage of being natural and well structured for a human to be able to select reasonable colors.\n
0193      * This function will allocate a color array by itself depending on the parameters (no need to call newColorArray()).
0194      * @param colorColumnCount The number of columns. The 360 possible colors of the rainbow will be split to take the wanted number of colors, equally separated.
0195      * @param lightRowCount    There is always at least 1 row of colors: the "pure" colors: pure red, pure blue, pure green, pure fuchsia...\n
0196      *                         Additionally, you can add row on top: they will contain the same colors, but lighter.\n
0197      *                         The parameter @p lightRowCount specify how many different lighting grades should be shown (from near to white, but not white, to "pure").
0198      * @param darkRowCount     Finally, on bottom of the row of "pure colors", you can put any variety of dark colors (from "pure", to near to black, but not black).\n
0199      *                         So, the number of rows is equal to @p lightRowCount + 1 + @p darkRowCount. On top are light colors, gradually going to dark ones on bottom.
0200      * @param withGray         If true, another column (so there will be @p colorColumnCount+1 columns) is added on the very-right of the popup
0201      *                         to show different gray values, matching the brightness of the sibling colors.
0202      *
0203      * The most acceptable parameters:
0204      * @li The default values are good to have the 7 colors of the rainbow + colors between them, and light/dark colors are well distinct.
0205      * @li If the color is a background color, you can set @p darkRowCount to 0, so only light colors are shown.
0206      * @li The inverse is true for text color choice: you can set @p lightRowCount to 0.
0207      * @li But be careful: some advanced users prefer white text on dark background, so you eg. can set @p lightRowCount to a big value and
0208      *     @p darkRowCount to a small one for a fewer choice of dark colors, but at least some ones.
0209      */
0210     void setRainbowPreset(int colorColumnCount = 12, int lightRowCount = 4, int darkRowCount = 4, bool withGray = true);
0211     // void setHsvPreset(QColor hue[], QColor saturation[], QColor value[], bool withGray = true);
0212 
0213     /**
0214      * Returns a pixmap of a colored rounded-rectangle. The four corners are transparent.\n
0215      * Useful if you want to set such a rectangle as an icon for a menu entry, or for drag and drop operation...
0216      * @param color     The color of the rectangle. If the color is invalid, a rainbow is then drawn (like for the "Other..." entry in the popup).
0217      * @param isDefault True if @p color is the default one and should then be draw with a diagonal line.
0218      * @param width     The width of the rectangle pixmap to return.
0219      * @param height    The height of the rectangle pixmap to return.
0220      *
0221      * @see drawColorRect() if you need to draw it directly: it's faster.
0222      */
0223     QPixmap colorRectPixmap(const QColor &color, bool isDefault, int width, int height);
0224 
0225     /**
0226      * Draw an image of a colored rounded-rectangle.\n
0227      * This is like colorRectPixmap() but significantly faster because there is nothing to copy, and no transparency mask to create and apply.
0228      * @param painter   The painter where to draw the image.
0229      * @param x         The x coordinate on the @p painter where to draw the rectangle.
0230      * @param y         The y coordinate on the @p painter where to draw the rectangle.
0231      * @param color     The color of the rectangle. If the color is invalid, a rainbow is then drawn (like for the "Other..." entry in the popup).
0232      * @param isDefault True if @p color is the default one and should then be draw with a diagonal line.
0233      * @param width     The width of the rectangle pixmap to return.
0234      * @param height    The height of the rectangle pixmap to return.
0235      *
0236      * @see colorRectPixmap() to get a transparent pixmap of the rectangle.
0237      */
0238     void drawColorRect(QPainter &painter, int x, int y, const QColor &color, bool isDefault, int width, int height);
0239 
0240     /**
0241      * Get the height of a color rectangle for this combobox.\n
0242      * This is equal to the text height, regarding to the current font of this combobox.
0243      */
0244     int colorRectHeight() const;
0245 
0246     /**
0247      * Get the width of a color rectangle, depending of the @p height of it.\n
0248      * It typically return 1.4 * @p height for decent rectangle proportions.
0249      */
0250     int colorRectWidthForHeight(int height) const;
0251 
0252 protected:
0253     void showPopup() override;
0254     void mouseMoveEvent(QMouseEvent *event) override;
0255     void dragEnterEvent(QDragEnterEvent *event) override;
0256     void dropEvent(QDropEvent *event) override;
0257     void keyPressEvent(QKeyEvent *event) override;
0258     virtual void fontChange(const QFont &oldFont);
0259 
0260 private:
0261     /**
0262      * Initialization routine common to every constructors.\n
0263      * Constructors just have to initialize the KComboBox, m_color and m_defaultColor
0264      * and this function do the rest to complete the creation of this widget.
0265      */
0266     void init();
0267 
0268     /**
0269      * Free up all memory allocated for the color array.\n
0270      * But only if an array have previously been allocated, of course.
0271      */
0272     void deleteColorArray();
0273 
0274     /**
0275      * Update the only item of the combobox to mirror the new selected color.\n
0276      * Mainly called on init() and setColor().
0277      */
0278     void updateComboBox();
0279 
0280     KColorPopup *m_popup;
0281     QColor m_color;
0282     QColor m_defaultColor;
0283     QColor **m_colorArray;
0284     int m_columnCount;
0285     int m_rowCount;
0286     QPoint m_dragStartPos;
0287 
0288 protected:
0289     /**
0290      * Keep place for future improvements without having to break binary compatibility.\n
0291      * Does nothing for the moment.
0292      */
0293     void virtual_hook(int id, void *data) override;
0294 
0295 private:
0296     /**
0297      * Keep place for future improvements without having to break binary compatibility.
0298      */
0299     class KColorCombo2Private;
0300 
0301     KColorCombo2Private *d;
0302 };
0303 
0304 // TODO: setColorArray(QColor **, int, int) and use signals/slots ??
0305 
0306 class KColorPopup : public QWidget
0307 {
0308     Q_OBJECT
0309 public:
0310     explicit KColorPopup(KColorCombo2 *parent);
0311     ~KColorPopup() override;
0312     void relayout(); // updateGeometry() ??
0313 Q_SIGNALS:
0314     void closed();
0315 
0316 protected:
0317     void paintEvent(QPaintEvent * /*event*/) override;
0318     void mouseMoveEvent(QMouseEvent *event) override;
0319     void mousePressEvent(QMouseEvent *event) override;
0320     void keyPressEvent(QKeyEvent *event) override;
0321     void doSelection();
0322     void validate();
0323     void updateCell(int column, int row);
0324 
0325     friend class KColorCombo2;
0326 
0327 private:
0328     KColorCombo2 *m_selector;
0329     QPixmap *m_pixmap;
0330     int m_selectedRow;
0331     int m_selectedColumn;
0332     int m_columnOther;
0333     QColor m_otherColor;
0334 
0335     static const int MARGIN;
0336     static const int FRAME_WIDTH;
0337 };
0338 
0339 #endif // KCOLORCOMBO2_H