[frameworks/kwidgetsaddons] src: KColorButton, KColorCombo: add contextmenu for Copy & Paste of color

Friedrich W. H. Kossebau <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 00866bc50fa32c280db443fc14e5294dcabfdeb4 by Friedrich W. H. Kossebau.
Committed on 24/07/2026 at 20:47.
Pushed by kossebau into branch 'master'.

KColorButton, KColorCombo: add contextmenu for Copy & Paste of color

For KColorCombo also adds keyboard shortcuts on the widget itself.

Implementation is shared from both classes by introducing a new internal
class KAbstractColorEditPrivate, which provides the common features.

M  +2    -0    src/CMakeLists.txt
A  +108  -0    src/kabstractcoloredit.cpp     [License: LGPL(v2.0+)]
A  +40   -0    src/kabstractcoloredit_p.h     [License: LGPL(v2.0+)]
M  +40   -16   src/kcolorbutton.cpp
M  +3    -0    src/kcolorbutton.h
M  +39   -3    src/kcolorcombo.cpp
M  +1    -0    src/kcolorcombo.h

https://invent.kde.org/frameworks/kwidgetsaddons/-/commit/00866bc50fa32c280db443fc14e5294dcabfdeb4

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b8ebf8b7..c1936a29 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -17,6 +17,8 @@ target_sources(KF6WidgetsAddons PRIVATE
     fonthelpers_p.h
     highcontrasthelper.cpp
     highcontrasthelper_p.h
+    kabstractcoloredit.cpp
+    kabstractcoloredit_p.h
     kacceleratormanager.cpp
     kacceleratormanager.h
     kacceleratormanager_p.h
diff --git a/src/kabstractcoloredit.cpp b/src/kabstractcoloredit.cpp
new file mode 100644
index 00000000..badea602
--- /dev/null
+++ b/src/kabstractcoloredit.cpp
@@ -0,0 +1,108 @@
+/*
+    This file is part of the KDE libraries
+    SPDX-FileCopyrightText: 2026 Friedrich W. H. Kossebau <[email protected]>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "kabstractcoloredit_p.h"
+
+#include "kcolormimedata_p.h"
+
+#include <QAction>
+#include <QApplication>
+#include <QClipboard>
+#include <QGlobalStatic>
+#include <QIcon>
+#include <QMenu>
+#include <QMimeData>
+
+class KColorClipboardWatcher : public QObject
+{
+    Q_OBJECT
+
+public:
+    KColorClipboardWatcher();
+
+    bool isColorOnClipboardAvailable() const;
+
+Q_SIGNALS:
+    void colorOnClipboardAvailableChanged(bool available);
+
+private:
+    void updateState();
+
+private:
+    bool m_colorOnClipboardAvailable = false;
+};
+
+KColorClipboardWatcher::KColorClipboardWatcher()
+{
+    connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &KColorClipboardWatcher::updateState);
+    const QMimeData *const mimeData = QApplication::clipboard()->mimeData(QClipboard::Clipboard);
+    m_colorOnClipboardAvailable = KColorMimeData::canDecode(mimeData);
+}
+
+bool KColorClipboardWatcher::isColorOnClipboardAvailable() const
+{
+    return m_colorOnClipboardAvailable;
+}
+
+void KColorClipboardWatcher::updateState()
+{
+    const QMimeData *const mimeData = QApplication::clipboard()->mimeData(QClipboard::Clipboard);
+    const bool colorOnClipboardAvailable = KColorMimeData::canDecode(mimeData);
+    if (colorOnClipboardAvailable == m_colorOnClipboardAvailable) {
+        return;
+    }
+    m_colorOnClipboardAvailable = colorOnClipboardAvailable;
+    Q_EMIT colorOnClipboardAvailableChanged(m_colorOnClipboardAvailable);
+}
+
+Q_GLOBAL_STATIC(KColorClipboardWatcher, globalClipboardWatcher)
+
+KAbstractColorEditPrivate::KAbstractColorEditPrivate(QWidget *qq)
+    : q_ptr(qq)
+{
+    Q_Q(QWidget);
+
+    copyAction =
+        new QAction(QIcon::fromTheme(QIcon::ThemeIcon::EditCopy), QCoreApplication::translate("KAbstractColorEdit", "Copy Color", "@action:inmenu"), q);
+    copyAction->setShortcut(QKeySequence::Copy);
+    copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
+    QObject::connect(copyAction, &QAction::triggered, q, [this]() {
+        auto *const mimeData = new QMimeData;
+        KColorMimeData::populateMimeData(mimeData, color());
+        QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
+    });
+    q->addAction(copyAction); // register shortcuts
+
+    pasteAction =
+        new QAction(QIcon::fromTheme(QIcon::ThemeIcon::EditPaste), QCoreApplication::translate("KAbstractColorEdit", "Paste Color", "@action:inmenu"), q);
+    pasteAction->setShortcut(QKeySequence::Paste);
+    pasteAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
+    QObject::connect(pasteAction, &QAction::triggered, q, [this]() {
+        const QColor color = KColorMimeData::fromMimeData(QApplication::clipboard()->mimeData(QClipboard::Clipboard));
+        if (color.isValid()) {
+            setColor(color);
+        }
+    });
+    pasteAction->setEnabled(globalClipboardWatcher()->isColorOnClipboardAvailable());
+    QObject::connect(globalClipboardWatcher(), &KColorClipboardWatcher::colorOnClipboardAvailableChanged, pasteAction, &QAction::setEnabled);
+    q->addAction(pasteAction); // register shortcuts
+}
+
+void KAbstractColorEditPrivate::showContextMenu(QPoint globalPos)
+{
+    Q_Q(QWidget);
+
+    auto menu = new QMenu(q);
+    menu->setAttribute(Qt::WA_DeleteOnClose);
+
+    menu->addAction(copyAction);
+    menu->addAction(pasteAction);
+
+    menu->popup(globalPos);
+}
+
+#include "kabstractcoloredit.moc"
diff --git a/src/kabstractcoloredit_p.h b/src/kabstractcoloredit_p.h
new file mode 100644
index 00000000..71223b0d
--- /dev/null
+++ b/src/kabstractcoloredit_p.h
@@ -0,0 +1,40 @@
+/*
+    This file is part of the KDE libraries
+    SPDX-FileCopyrightText: 2026 Friedrich W. H. Kossebau <[email protected]>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef KABSTRACTCOLOREDIT_P_H
+#define KABSTRACTCOLOREDIT_P_H
+
+#include <QtClassHelperMacros>
+
+class QWidget;
+class QAction;
+class QColor;
+class QPoint;
+
+class KAbstractColorEditPrivate
+{
+    Q_DECLARE_PUBLIC(QWidget)
+
+public:
+    explicit KAbstractColorEditPrivate(QWidget *qq);
+    virtual ~KAbstractColorEditPrivate() = default;
+
+public: // API to implement
+    virtual QColor color() const = 0;
+    virtual void setColor(const QColor &color) = 0;
+
+public:
+    void showContextMenu(QPoint globalPos);
+
+public:
+    QWidget *const q_ptr;
+
+    QAction *copyAction;
+    QAction *pasteAction;
+};
+
+#endif
diff --git a/src/kcolorbutton.cpp b/src/kcolorbutton.cpp
index 5be3142f..56ad0b5e 100644
--- a/src/kcolorbutton.cpp
+++ b/src/kcolorbutton.cpp
@@ -8,10 +8,10 @@
 
 #include "kcolorbutton.h"
 
+#include "kabstractcoloredit_p.h"
 #include "kcolormimedata_p.h"
 
 #include <QApplication>
-#include <QClipboard>
 #include <QColorDialog>
 #include <QDrag>
 #include <QMimeData>
@@ -22,15 +22,19 @@
 #include <QStyleOptionButton>
 #include <qdrawutil.h>
 
-class KColorButtonPrivate
+class KColorButtonPrivate : public KAbstractColorEditPrivate
 {
+    Q_DECLARE_PUBLIC(KColorButton)
+
 public:
     KColorButtonPrivate(KColorButton *qq);
 
+    QColor color() const override;
+    void setColor(const QColor &color) override;
+
     void chooseColor();
     void colorChosen();
 
-    KColorButton *q;
     QColor m_defaultColor;
     bool m_bdefaultColor : 1;
     bool m_alphaChannel : 1;
@@ -44,8 +48,10 @@ public:
 };
 
 KColorButtonPrivate::KColorButtonPrivate(KColorButton *qq)
-    : q(qq)
+    : KAbstractColorEditPrivate(qq)
 {
+    Q_Q(KColorButton);
+
     m_bdefaultColor = false;
     m_alphaChannel = false;
     q->setAcceptDrops(true);
@@ -55,6 +61,20 @@ KColorButtonPrivate::KColorButtonPrivate(KColorButton *qq)
     });
 }
 
+QColor KColorButtonPrivate::color() const
+{
+    Q_Q(const KColorButton);
+
+    return q->color();
+}
+
+void KColorButtonPrivate::setColor(const QColor &color)
+{
+    Q_Q(KColorButton);
+
+    q->setColor(color);
+}
+
 KColorButton::KColorButton(QWidget *parent)
     : QPushButton(parent)
     , d(new KColorButtonPrivate(this))
@@ -112,6 +132,8 @@ void KColorButton::setDefaultColor(const QColor &c)
 
 void KColorButtonPrivate::initStyleOption(QStyleOptionButton *opt) const
 {
+    Q_Q(const KColorButton);
+
     opt->initFrom(q);
     opt->state |= q->isDown() ? QStyle::State_Sunken : QStyle::State_Raised;
     opt->features = QStyleOptionButton::None;
@@ -202,21 +224,12 @@ void KColorButton::dropEvent(QDropEvent *event)
     }
 }
 
+#if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(6, 29)
 void KColorButton::keyPressEvent(QKeyEvent *e)
 {
-    int key = e->key() | e->modifiers();
-
-    if (QKeySequence::keyBindings(QKeySequence::Copy).contains(key)) {
-        QMimeData *mime = new QMimeData;
-        KColorMimeData::populateMimeData(mime, color());
-        QApplication::clipboard()->setMimeData(mime, QClipboard::Clipboard);
-    } else if (QKeySequence::keyBindings(QKeySequence::Paste).contains(key)) {
-        QColor color = KColorMimeData::fromMimeData(QApplication::clipboard()->mimeData(QClipboard::Clipboard));
-        setColor(color);
-    } else {
-        QPushButton::keyPressEvent(e);
-    }
+    QPushButton::keyPressEvent(e);
 }
+#endif
 
 void KColorButton::mousePressEvent(QMouseEvent *e)
 {
@@ -232,8 +245,17 @@ void KColorButton::mouseMoveEvent(QMouseEvent *e)
     }
 }
 
+void KColorButton::contextMenuEvent(QContextMenuEvent *ev)
+{
+    ev->accept();
+
+    d->showContextMenu(ev->globalPos());
+}
+
 void KColorButtonPrivate::chooseColor()
 {
+    Q_Q(KColorButton);
+
     QColorDialog *dialog = dialogPtr.data();
     if (dialog) {
         dialog->show();
@@ -255,6 +277,8 @@ void KColorButtonPrivate::chooseColor()
 
 void KColorButtonPrivate::colorChosen()
 {
+    Q_Q(KColorButton);
+
     QColorDialog *dialog = dialogPtr.data();
     if (!dialog) {
         return;
diff --git a/src/kcolorbutton.h b/src/kcolorbutton.h
index 0ef7bb03..9f52cb53 100644
--- a/src/kcolorbutton.h
+++ b/src/kcolorbutton.h
@@ -113,7 +113,10 @@ protected:
     void dropEvent(QDropEvent *) override;
     void mousePressEvent(QMouseEvent *e) override;
     void mouseMoveEvent(QMouseEvent *e) override;
+#if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(6, 29)
     void keyPressEvent(QKeyEvent *e) override;
+#endif
+    void contextMenuEvent(QContextMenuEvent *) override;
 
 private:
     std::unique_ptr<class KColorButtonPrivate> const d;
diff --git a/src/kcolorcombo.cpp b/src/kcolorcombo.cpp
index 39012597..af457c60 100644
--- a/src/kcolorcombo.cpp
+++ b/src/kcolorcombo.cpp
@@ -9,9 +9,12 @@
 
 #include "kcolorcombo.h"
 
+#include "kabstractcoloredit_p.h"
+
 #include <QAbstractItemDelegate>
 #include <QApplication>
 #include <QColorDialog>
+#include <QContextMenuEvent>
 #include <QStylePainter>
 
 class KColorComboDelegate : public QAbstractItemDelegate
@@ -159,11 +162,16 @@ static inline QColor standardColor(int i)
     return QColor(entry[0], entry[1], entry[2]);
 }
 
-class KColorComboPrivate
+class KColorComboPrivate : public KAbstractColorEditPrivate
 {
+    Q_DECLARE_PUBLIC(KColorCombo)
+
 public:
     KColorComboPrivate(KColorCombo *qq);
 
+    QColor color() const override;
+    void setColor(const QColor &color) override;
+
     void addColors();
     void setCustomColor(const QColor &color, bool lookupInPresets = true);
 
@@ -171,20 +179,35 @@ public:
     void slotActivated(int index);
     void slotHighlighted(int index);
 
-    KColorCombo *q;
     QList<QColor> colorList;
     QColor customColor;
     QColor internalcolor;
 };
 
 KColorComboPrivate::KColorComboPrivate(KColorCombo *qq)
-    : q(qq)
+    : KAbstractColorEditPrivate(qq)
     , customColor(Qt::white)
 {
 }
 
+QColor KColorComboPrivate::color() const
+{
+    Q_Q(const KColorCombo);
+
+    return q->color();
+}
+
+void KColorComboPrivate::setColor(const QColor &color)
+{
+    Q_Q(KColorCombo);
+
+    q->setColor(color);
+}
+
 void KColorComboPrivate::setCustomColor(const QColor &color, bool lookupInPresets)
 {
+    Q_Q(KColorCombo);
+
     if (lookupInPresets) {
         if (colorList.isEmpty()) {
             for (int i = 0; i < STANDARD_PALETTE_SIZE; ++i) {
@@ -294,6 +317,13 @@ void KColorCombo::paintEvent(QPaintEvent *event)
     painter.drawRoundedRect(frame.adjusted(1, 1, -1, -1), 2, 2);
 }
 
+void KColorCombo::contextMenuEvent(QContextMenuEvent *ev)
+{
+    ev->accept();
+
+    d->showContextMenu(ev->globalPos());
+}
+
 void KColorCombo::showEmptyList()
 {
     clear();
@@ -301,6 +331,8 @@ void KColorCombo::showEmptyList()
 
 void KColorComboPrivate::slotActivated(int index)
 {
+    Q_Q(KColorCombo);
+
     if (index == 0) {
         QColor c = QColorDialog::getColor(customColor, q);
         if (c.isValid()) {
@@ -318,6 +350,8 @@ void KColorComboPrivate::slotActivated(int index)
 
 void KColorComboPrivate::slotHighlighted(int index)
 {
+    Q_Q(KColorCombo);
+
     if (index == 0) {
         internalcolor = customColor;
     } else if (colorList.isEmpty()) {
@@ -331,6 +365,8 @@ void KColorComboPrivate::slotHighlighted(int index)
 
 void KColorComboPrivate::addColors()
 {
+    Q_Q(KColorCombo);
+
     q->addItem(KColorCombo::tr("Custom…", "@item:inlistbox Custom color"));
 
     if (colorList.isEmpty()) {
diff --git a/src/kcolorcombo.h b/src/kcolorcombo.h
index 505c3b7f..bf6f7dc6 100644
--- a/src/kcolorcombo.h
+++ b/src/kcolorcombo.h
@@ -100,6 +100,7 @@ Q_SIGNALS:
 
 protected:
     void paintEvent(QPaintEvent *event) override;
+    void contextMenuEvent(QContextMenuEvent *) override;
 
 private:
     friend class KColorComboPrivate;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.