| 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) && (e->pos() - d->mPos).manhattanLength() > QApplication::startDragDistance()) { |
| 283 | createDrag(color: color(), dragsource: this)->exec(); |
| 284 | setDown(false); |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | void KColorButtonPrivate::chooseColor() |
| 289 | { |
| 290 | QColorDialog *dialog = dialogPtr.data(); |
| 291 | if (dialog) { |
| 292 | dialog->show(); |
| 293 | dialog->raise(); |
| 294 | dialog->activateWindow(); |
| 295 | return; |
| 296 | } |
| 297 | |
| 298 | dialog = new QColorDialog(q); |
| 299 | dialog->setCurrentColor(q->color()); |
| 300 | dialog->setOption(option: QColorDialog::ShowAlphaChannel, on: m_alphaChannel); |
| 301 | dialog->setAttribute(Qt::WA_DeleteOnClose); |
| 302 | QObject::connect(sender: dialog, signal: &QDialog::accepted, context: q, slot: [this]() { |
| 303 | colorChosen(); |
| 304 | }); |
| 305 | dialogPtr = dialog; |
| 306 | dialog->show(); |
| 307 | } |
| 308 | |
| 309 | void KColorButtonPrivate::colorChosen() |
| 310 | { |
| 311 | QColorDialog *dialog = dialogPtr.data(); |
| 312 | if (!dialog) { |
| 313 | return; |
| 314 | } |
| 315 | |
| 316 | if (dialog->selectedColor().isValid()) { |
| 317 | q->setColor(dialog->selectedColor()); |
| 318 | } else if (m_bdefaultColor) { |
| 319 | q->setColor(m_defaultColor); |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | #include "moc_kcolorbutton.cpp" |
| 324 | |