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 |
47 | void populateMimeData(QMimeData *mimeData, const QColor &color) |
48 | { |
49 | mimeData->setColorData(color); |
50 | mimeData->setText(color.name()); |
51 | } |
52 | |
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 | |
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 | |
78 | QDrag *createDrag(const QColor &color, QObject *dragsource) |
79 | { |
80 | QDrag *drag = new QDrag(dragsource); |
81 | QMimeData *mime = new QMimeData; |
82 | populateMimeData(mimeData: mime, color); |
83 | drag->setMimeData(mime); |
84 | QPixmap colorpix(25, 20); |
85 | colorpix.fill(fillColor: color); |
86 | QPainter p(&colorpix); |
87 | p.setPen(Qt::black); |
88 | p.drawRect(x: 0, y: 0, w: 24, h: 19); |
89 | p.end(); |
90 | drag->setPixmap(colorpix); |
91 | drag->setHotSpot(QPoint(-5, -7)); |
92 | return drag; |
93 | } |
94 | ///////////////////////////////////////////////////////////////////// |
95 | |
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(sender: q, signal: &KColorButton::clicked, context: q, slot: [this]() { |
104 | chooseColor(); |
105 | }); |
106 | } |
107 | |
108 | KColorButton::KColorButton(QWidget *parent) |
109 | : QPushButton(parent) |
110 | , d(new KColorButtonPrivate(this)) |
111 | { |
112 | } |
113 | |
114 | KColorButton::KColorButton(const QColor &c, QWidget *parent) |
115 | : QPushButton(parent) |
116 | , d(new KColorButtonPrivate(this)) |
117 | { |
118 | d->col = c; |
119 | } |
120 | |
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 | |
131 | QColor KColorButton::color() const |
132 | { |
133 | return d->col; |
134 | } |
135 | |
136 | void KColorButton::setColor(const QColor &c) |
137 | { |
138 | if (d->col != c) { |
139 | d->col = c; |
140 | update(); |
141 | Q_EMIT changed(newColor: d->col); |
142 | } |
143 | } |
144 | |
145 | void KColorButton::setAlphaChannelEnabled(bool alpha) |
146 | { |
147 | d->m_alphaChannel = alpha; |
148 | } |
149 | |
150 | bool KColorButton::isAlphaChannelEnabled() const |
151 | { |
152 | return d->m_alphaChannel; |
153 | } |
154 | |
155 | QColor KColorButton::defaultColor() const |
156 | { |
157 | return d->m_defaultColor; |
158 | } |
159 | |
160 | void KColorButton::setDefaultColor(const QColor &c) |
161 | { |
162 | d->m_bdefaultColor = c.isValid(); |
163 | d->m_defaultColor = c; |
164 | } |
165 | |
166 | void KColorButtonPrivate::initStyleOption(QStyleOptionButton *opt) const |
167 | { |
168 | opt->initFrom(w: 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 | |
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(opt: &butOpt); |
186 | style->drawControl(element: QStyle::CE_PushButtonBevel, opt: &butOpt, p: &painter, w: 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(subElement: QStyle::SE_PushButtonContents, option: &butOpt, widget: this); |
191 | int shift = style->pixelMetric(metric: QStyle::PM_ButtonMargin, option: &butOpt, widget: this) / 2; |
192 | labelRect.adjust(dx1: shift, dy1: shift, dx2: -shift, dy2: -shift); |
193 | int x; |
194 | int y; |
195 | int w; |
196 | int h; |
197 | labelRect.getRect(ax: &x, ay: &y, aw: &w, ah: &h); |
198 | |
199 | if (isChecked() || isDown()) { |
200 | x += style->pixelMetric(metric: QStyle::PM_ButtonShiftHorizontal, option: &butOpt, widget: this); |
201 | y += style->pixelMetric(metric: QStyle::PM_ButtonShiftVertical, option: &butOpt, widget: this); |
202 | } |
203 | |
204 | QColor fillCol = isEnabled() ? d->col : palette().color(cr: backgroundRole()); |
205 | qDrawShadePanel(p: &painter, x, y, w, h, pal: palette(), sunken: true, lineWidth: 1, fill: 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(x: 0, y: 0, w: 8, h: 8, c: Qt::black); |
212 | patternPainter.fillRect(x: 8, y: 8, w: 8, h: 8, c: Qt::black); |
213 | patternPainter.fillRect(x: 0, y: 8, w: 8, h: 8, c: Qt::white); |
214 | patternPainter.fillRect(x: 8, y: 0, w: 8, h: 8, c: Qt::white); |
215 | patternPainter.end(); |
216 | painter.fillRect(rect, QBrush(chessboardPattern)); |
217 | } |
218 | painter.fillRect(rect, color: fillCol); |
219 | } |
220 | |
221 | if (hasFocus()) { |
222 | QRect focusRect = style->subElementRect(subElement: QStyle::SE_PushButtonFocusRect, option: &butOpt, widget: this); |
223 | QStyleOptionFocusRect focusOpt; |
224 | focusOpt.initFrom(w: this); |
225 | focusOpt.rect = focusRect; |
226 | focusOpt.backgroundColor = palette().window().color(); |
227 | style->drawPrimitive(pe: QStyle::PE_FrameFocusRect, opt: &focusOpt, p: &painter, w: this); |
228 | } |
229 | } |
230 | |
231 | QSize KColorButton::sizeHint() const |
232 | { |
233 | QStyleOptionButton opt; |
234 | d->initStyleOption(opt: &opt); |
235 | return style()->sizeFromContents(ct: QStyle::CT_PushButton, opt: &opt, contentsSize: QSize(40, 15), w: this); |
236 | } |
237 | |
238 | QSize KColorButton::minimumSizeHint() const |
239 | { |
240 | QStyleOptionButton opt; |
241 | d->initStyleOption(opt: &opt); |
242 | return style()->sizeFromContents(ct: QStyle::CT_PushButton, opt: &opt, contentsSize: QSize(3, 3), w: this); |
243 | } |
244 | |
245 | void KColorButton::dragEnterEvent(QDragEnterEvent *event) |
246 | { |
247 | event->setAccepted(canDecode(mimeData: event->mimeData()) && isEnabled()); |
248 | } |
249 | |
250 | void KColorButton::dropEvent(QDropEvent *event) |
251 | { |
252 | QColor c = fromMimeData(mimeData: event->mimeData()); |
253 | if (c.isValid()) { |
254 | setColor(c); |
255 | } |
256 | } |
257 | |
258 | void KColorButton::keyPressEvent(QKeyEvent *e) |
259 | { |
260 | int key = e->key() | e->modifiers(); |
261 | |
262 | if (QKeySequence::keyBindings(key: QKeySequence::Copy).contains(t: key)) { |
263 | QMimeData *mime = new QMimeData; |
264 | populateMimeData(mimeData: mime, color: color()); |
265 | QApplication::clipboard()->setMimeData(data: mime, mode: QClipboard::Clipboard); |
266 | } else if (QKeySequence::keyBindings(key: QKeySequence::Paste).contains(t: key)) { |
267 | QColor color = fromMimeData(mimeData: QApplication::clipboard()->mimeData(mode: QClipboard::Clipboard)); |
268 | setColor(color); |
269 | } else { |
270 | QPushButton::keyPressEvent(e); |
271 | } |
272 | } |
273 | |
274 | void KColorButton::mousePressEvent(QMouseEvent *e) |
275 | { |
276 | d->mPos = e->pos(); |
277 | QPushButton::mousePressEvent(e); |
278 | } |
279 | |
280 | void KColorButton::mouseMoveEvent(QMouseEvent *e) |
281 | { |
282 | if ((e->buttons() & Qt::LeftButton) && |
283 | (e->pos() - d->mPos).manhattanLength() > QApplication::startDragDistance()) { |
284 | createDrag(color: color(), dragsource: this)->exec(); |
285 | setDown(false); |
286 | } |
287 | } |
288 | |
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(option: QColorDialog::ShowAlphaChannel, on: m_alphaChannel); |
302 | dialog->setAttribute(Qt::WA_DeleteOnClose); |
303 | QObject::connect(sender: dialog, signal: &QDialog::accepted, context: q, slot: [this]() { |
304 | colorChosen(); |
305 | }); |
306 | dialogPtr = dialog; |
307 | dialog->show(); |
308 | } |
309 | |
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 | |