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