1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 1997 Martin Jones <mjones@kde.org>
4     SPDX-FileCopyrightText: 1999 Cristian Tibirna <ctibirna@kde.org>
5 
6     SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "kcolorbutton.h"
10 
11 #include <QApplication>
12 #include <QClipboard>
13 #include <QColorDialog>
14 #include <QDrag>
15 #include <QMimeData>
16 #include <QMouseEvent>
17 #include <QPainter>
18 #include <QPointer>
19 #include <QStyle>
20 #include <QStyleOptionButton>
21 #include <qdrawutil.h>
22 
23 class KColorButtonPrivate
24 {
25 public:
26     KColorButtonPrivate(KColorButton *qq);
27 
28     void chooseColor();
29     void colorChosen();
30 
31     KColorButton *q;
32     QColor m_defaultColor;
33     bool m_bdefaultColor : 1;
34     bool m_alphaChannel : 1;
35 
36     QColor col;
37     QPoint mPos;
38 
39     QPointer<QColorDialog> dialogPtr;
40 
41     void initStyleOption(QStyleOptionButton *opt) const;
42 };
43 
44 /////////////////////////////////////////////////////////////////////
45 // Functions duplicated from KColorMimeData
46 // Should be kept in sync
populateMimeData(QMimeData * mimeData,const QColor & color)47 void populateMimeData(QMimeData *mimeData, const QColor &color)
48 {
49     mimeData->setColorData(color);
50     mimeData->setText(color.name());
51 }
52 
canDecode(const QMimeData * mimeData)53 bool canDecode(const QMimeData *mimeData)
54 {
55     if (mimeData->hasColor()) {
56         return true;
57     }
58     if (mimeData->hasText()) {
59         const QString colorName = mimeData->text();
60         if ((colorName.length() >= 4) && (colorName[0] == QLatin1Char('#'))) {
61             return true;
62         }
63     }
64     return false;
65 }
66 
fromMimeData(const QMimeData * mimeData)67 QColor fromMimeData(const QMimeData *mimeData)
68 {
69     if (mimeData->hasColor()) {
70         return mimeData->colorData().value<QColor>();
71     }
72     if (canDecode(mimeData)) {
73         return QColor(mimeData->text());
74     }
75     return QColor();
76 }
77 
createDrag(const QColor & color,QObject * dragsource)78 QDrag *createDrag(const QColor &color, QObject *dragsource)
79 {
80     QDrag *drag = new QDrag(dragsource);
81     QMimeData *mime = new QMimeData;
82     populateMimeData(mime, color);
83     drag->setMimeData(mime);
84     QPixmap colorpix(25, 20);
85     colorpix.fill(color);
86     QPainter p(&colorpix);
87     p.setPen(Qt::black);
88     p.drawRect(0, 0, 24, 19);
89     p.end();
90     drag->setPixmap(colorpix);
91     drag->setHotSpot(QPoint(-5, -7));
92     return drag;
93 }
94 /////////////////////////////////////////////////////////////////////
95 
KColorButtonPrivate(KColorButton * qq)96 KColorButtonPrivate::KColorButtonPrivate(KColorButton *qq)
97     : q(qq)
98 {
99     m_bdefaultColor = false;
100     m_alphaChannel = false;
101     q->setAcceptDrops(true);
102 
103     QObject::connect(q, &KColorButton::clicked, q, [this]() {
104         chooseColor();
105     });
106 }
107 
KColorButton(QWidget * parent)108 KColorButton::KColorButton(QWidget *parent)
109     : QPushButton(parent)
110     , d(new KColorButtonPrivate(this))
111 {
112 }
113 
KColorButton(const QColor & c,QWidget * parent)114 KColorButton::KColorButton(const QColor &c, QWidget *parent)
115     : QPushButton(parent)
116     , d(new KColorButtonPrivate(this))
117 {
118     d->col = c;
119 }
120 
KColorButton(const QColor & c,const QColor & defaultColor,QWidget * parent)121 KColorButton::KColorButton(const QColor &c, const QColor &defaultColor, QWidget *parent)
122     : QPushButton(parent)
123     , d(new KColorButtonPrivate(this))
124 {
125     d->col = c;
126     setDefaultColor(defaultColor);
127 }
128 
129 KColorButton::~KColorButton() = default;
130 
color() const131 QColor KColorButton::color() const
132 {
133     return d->col;
134 }
135 
setColor(const QColor & c)136 void KColorButton::setColor(const QColor &c)
137 {
138     if (d->col != c) {
139         d->col = c;
140         update();
141         Q_EMIT changed(d->col);
142     }
143 }
144 
setAlphaChannelEnabled(bool alpha)145 void KColorButton::setAlphaChannelEnabled(bool alpha)
146 {
147     d->m_alphaChannel = alpha;
148 }
149 
isAlphaChannelEnabled() const150 bool KColorButton::isAlphaChannelEnabled() const
151 {
152     return d->m_alphaChannel;
153 }
154 
defaultColor() const155 QColor KColorButton::defaultColor() const
156 {
157     return d->m_defaultColor;
158 }
159 
setDefaultColor(const QColor & c)160 void KColorButton::setDefaultColor(const QColor &c)
161 {
162     d->m_bdefaultColor = c.isValid();
163     d->m_defaultColor = c;
164 }
165 
initStyleOption(QStyleOptionButton * opt) const166 void KColorButtonPrivate::initStyleOption(QStyleOptionButton *opt) const
167 {
168     opt->initFrom(q);
169     opt->state |= q->isDown() ? QStyle::State_Sunken : QStyle::State_Raised;
170     opt->features = QStyleOptionButton::None;
171     if (q->isDefault()) {
172         opt->features |= QStyleOptionButton::DefaultButton;
173     }
174     opt->text.clear();
175     opt->icon = QIcon();
176 }
177 
paintEvent(QPaintEvent *)178 void KColorButton::paintEvent(QPaintEvent *)
179 {
180     QPainter painter(this);
181     QStyle *style = QWidget::style();
182 
183     // First, we need to draw the bevel.
184     QStyleOptionButton butOpt;
185     d->initStyleOption(&butOpt);
186     style->drawControl(QStyle::CE_PushButtonBevel, &butOpt, &painter, this);
187 
188     // OK, now we can muck around with drawing out pretty little color box
189     // First, sort out where it goes
190     QRect labelRect = style->subElementRect(QStyle::SE_PushButtonContents, &butOpt, this);
191     int shift = style->pixelMetric(QStyle::PM_ButtonMargin, &butOpt, this) / 2;
192     labelRect.adjust(shift, shift, -shift, -shift);
193     int x;
194     int y;
195     int w;
196     int h;
197     labelRect.getRect(&x, &y, &w, &h);
198 
199     if (isChecked() || isDown()) {
200         x += style->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &butOpt, this);
201         y += style->pixelMetric(QStyle::PM_ButtonShiftVertical, &butOpt, this);
202     }
203 
204     QColor fillCol = isEnabled() ? d->col : palette().color(backgroundRole());
205     qDrawShadePanel(&painter, x, y, w, h, palette(), true, 1, nullptr);
206     if (fillCol.isValid()) {
207         const QRect rect(x + 1, y + 1, w - 2, h - 2);
208         if (fillCol.alpha() < 255) {
209             QPixmap chessboardPattern(16, 16);
210             QPainter patternPainter(&chessboardPattern);
211             patternPainter.fillRect(0, 0, 8, 8, Qt::black);
212             patternPainter.fillRect(8, 8, 8, 8, Qt::black);
213             patternPainter.fillRect(0, 8, 8, 8, Qt::white);
214             patternPainter.fillRect(8, 0, 8, 8, Qt::white);
215             patternPainter.end();
216             painter.fillRect(rect, QBrush(chessboardPattern));
217         }
218         painter.fillRect(rect, fillCol);
219     }
220 
221     if (hasFocus()) {
222         QRect focusRect = style->subElementRect(QStyle::SE_PushButtonFocusRect, &butOpt, this);
223         QStyleOptionFocusRect focusOpt;
224         focusOpt.initFrom(this);
225         focusOpt.rect = focusRect;
226         focusOpt.backgroundColor = palette().window().color();
227         style->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, &painter, this);
228     }
229 }
230 
sizeHint() const231 QSize KColorButton::sizeHint() const
232 {
233     QStyleOptionButton opt;
234     d->initStyleOption(&opt);
235     return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(40, 15), this);
236 }
237 
minimumSizeHint() const238 QSize KColorButton::minimumSizeHint() const
239 {
240     QStyleOptionButton opt;
241     d->initStyleOption(&opt);
242     return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(3, 3), this);
243 }
244 
dragEnterEvent(QDragEnterEvent * event)245 void KColorButton::dragEnterEvent(QDragEnterEvent *event)
246 {
247     event->setAccepted(canDecode(event->mimeData()) && isEnabled());
248 }
249 
dropEvent(QDropEvent * event)250 void KColorButton::dropEvent(QDropEvent *event)
251 {
252     QColor c = fromMimeData(event->mimeData());
253     if (c.isValid()) {
254         setColor(c);
255     }
256 }
257 
keyPressEvent(QKeyEvent * e)258 void KColorButton::keyPressEvent(QKeyEvent *e)
259 {
260     int key = e->key() | e->modifiers();
261 
262     if (QKeySequence::keyBindings(QKeySequence::Copy).contains(key)) {
263         QMimeData *mime = new QMimeData;
264         populateMimeData(mime, color());
265         QApplication::clipboard()->setMimeData(mime, QClipboard::Clipboard);
266     } else if (QKeySequence::keyBindings(QKeySequence::Paste).contains(key)) {
267         QColor color = fromMimeData(QApplication::clipboard()->mimeData(QClipboard::Clipboard));
268         setColor(color);
269     } else {
270         QPushButton::keyPressEvent(e);
271     }
272 }
273 
mousePressEvent(QMouseEvent * e)274 void KColorButton::mousePressEvent(QMouseEvent *e)
275 {
276     d->mPos = e->pos();
277     QPushButton::mousePressEvent(e);
278 }
279 
mouseMoveEvent(QMouseEvent * e)280 void KColorButton::mouseMoveEvent(QMouseEvent *e)
281 {
282     if ((e->buttons() & Qt::LeftButton) &&
283             (e->pos() - d->mPos).manhattanLength() > QApplication::startDragDistance()) {
284         createDrag(color(), this)->exec();
285         setDown(false);
286     }
287 }
288 
chooseColor()289 void KColorButtonPrivate::chooseColor()
290 {
291     QColorDialog *dialog = dialogPtr.data();
292     if (dialog) {
293         dialog->show();
294         dialog->raise();
295         dialog->activateWindow();
296         return;
297     }
298 
299     dialog = new QColorDialog(q);
300     dialog->setCurrentColor(q->color());
301     dialog->setOption(QColorDialog::ShowAlphaChannel, m_alphaChannel);
302     dialog->setAttribute(Qt::WA_DeleteOnClose);
303     QObject::connect(dialog, &QDialog::accepted, q, [this]() {
304         colorChosen();
305     });
306     dialogPtr = dialog;
307     dialog->show();
308 }
309 
colorChosen()310 void KColorButtonPrivate::colorChosen()
311 {
312     QColorDialog *dialog = dialogPtr.data();
313     if (!dialog) {
314         return;
315     }
316 
317     if (dialog->selectedColor().isValid()) {
318         q->setColor(dialog->selectedColor());
319     } else if (m_bdefaultColor) {
320         q->setColor(m_defaultColor);
321     }
322 }
323 
324 #include "moc_kcolorbutton.cpp"
325