Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_color_selector_base.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2010 Adam Celarek <kdedev at xibo dot at>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <QMouseEvent>
10#include <QApplication>
11#include <QScreen>
12#include <QScreen>
13#include <QTimer>
14#include <QCursor>
15#include <QPainter>
16#include <QMimeData>
17
18#include <kconfig.h>
19#include <kconfiggroup.h>
20#include <ksharedconfig.h>
21
22#include "KoColorSpace.h"
24
25#include "kis_canvas2.h"
27#include "kis_node.h"
28#include "KisViewManager.h"
29#include <KisView.h>
30#include "kis_image.h"
31#include "kis_global.h"
33
35
36// for HAVE_WAYLAND
37#include <KoConfig.h>
38
39#if defined HAVE_WAYLAND && QT_VERSION >= QT_VERSION_CHECK(6, 11, 0)
40#define HAVE_QT_PRIVATE_TOOLTIP_POSITION_API
41#endif
42
43#ifdef HAVE_QT_PRIVATE_TOOLTIP_POSITION_API
44#include <QtWaylandClient/private/qwaylandwindow_p.h>
45#endif
46
47
48namespace {
49 QWidget* findNearestDockerOrNativeParent(QWidget *w) {
50 if (!w || w->inherits("QDockWidget") || w->windowHandle()) {
51 return w;
52 }
53
54 return findNearestDockerOrNativeParent(w->parentWidget());
55 }
56}
57
58class KisColorPreviewPopup : public QWidget {
59public:
61 : QWidget(parent), m_parent(parent)
62 {
63 setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint | Qt::NoDropShadowWindowHint);
64 setAttribute(Qt::WA_TranslucentBackground);
65 setQColor(QColor(0,0,0));
66 m_baseColor = QColor(0,0,0,0);
67 m_previousColor = QColor(0,0,0,0);
68 m_lastUsedColor = QColor(0,0,0,0);
69 }
70
72 {
74 show();
75 }
76
78 {
79 resize(100, 150);
80
81 // make sure that all the platform-specific structures for the window
82 // are created (i.e. QWaylandWindow) before trying to request them
83 // via `windowHandle()->handle()`
84 create();
85
86#ifdef HAVE_QT_PRIVATE_TOOLTIP_POSITION_API
87 if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow *>(windowHandle()->handle())) {
88 QWidget *referenceWidget = findNearestDockerOrNativeParent(parentWidget());
89 if (!referenceWidget) {
90 referenceWidget = parentWidget();
91 }
92
93 const QPoint parentPos = parentWidget()->window()->mapFromGlobal(referenceWidget->mapToGlobal(QPoint()));
94 const QRect gravityRect(QRect(parentPos, QSize(referenceWidget->width(), 10)));
95
96 waylandWindow->setParentControlGeometry(gravityRect);
97
98 // well, it is technically not a "submenu", but this is the way
99 // to make qt position it "around" the docker widget
100 waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::SubMenu);
101 } else
102#endif /* HAVE_QT_PRIVATE_TOOLTIP_POSITION_API */
103 {
104 QPoint parentPos = m_parent->mapToGlobal(QPoint(0,0));
105 const QRect availRect = this->screen()->availableGeometry();
106 QPoint targetPos;
107 if ( parentPos.x() - 100 > availRect.x() ) {
108 targetPos = QPoint(parentPos.x() - 100, parentPos.y());
109 } else if ( parentPos.x() + m_parent->width() + 100 < availRect.right()) {
110 targetPos = m_parent->mapToGlobal(QPoint(m_parent->width(), 0));
111 } else if ( parentPos.y() - 100 > availRect.y() ) {
112 targetPos = QPoint(parentPos.x(), parentPos.y() - 100);
113 } else {
114 targetPos = QPoint(parentPos.x(), parentPos.y() + m_parent->height());
115 }
116 move(targetPos.x(), targetPos.y());
117 }
118 }
119
120 void initializeColors(const QColor& color, const QColor& lastUsedColor) {
121 m_color = color;
122 m_baseColor = color;
123 m_previousColor = color;
124 m_lastUsedColor = lastUsedColor;
125 update();
126 }
127
128 void setQColor(const QColor& color)
129 {
130 m_color = color;
131 update();
132 }
133
138
139 void setBaseColor(const QColor& color)
140 {
141 m_baseColor = color;
142 update();
143 }
144
145 void setLastUsedColor(const QColor& color)
146 {
147 m_lastUsedColor = color;
148 update();
149 }
150
151protected:
152 void paintEvent(QPaintEvent *e) override {
153 Q_UNUSED(e);
154 QPainter p(this);
155 p.fillRect(0, 0, width(), width(), m_color);
156 p.fillRect(50, width(), width(), height(), m_previousColor);
157 p.fillRect(0, width(), 50, height(), m_lastUsedColor);
158 }
159
160#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
161 void enterEvent(QEvent *e) override
162#else
163 void enterEvent(QEnterEvent *e) override
164#endif
165 {
167 QWidget::enterEvent(e);
168 }
169
170 void leaveEvent(QEvent *e) override {
172 QWidget::leaveEvent(e);
173 }
174
175private:
177 QColor m_color;
181};
182
184 QWidget(parent),
185 m_canvas(0),
186 m_popup(0),
187 m_parent(0),
188 m_colorUpdateAllowed(true),
189 m_colorUpdateSelf(false),
190 m_hideTimer(new QTimer(this)),
191 m_popupOnMouseOver(false),
192 m_popupOnMouseClick(true),
193 m_colorSpace(0),
194 m_isPopup(false),
195 m_hideOnMouseClick(false),
196 m_colorPreviewPopup(new KisColorPreviewPopup(this))
197{
198 m_hideTimer->setInterval(200);
199 m_hideTimer->setSingleShot(true);
200 connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(tryHideMyself()));
201
202 using namespace std::placeholders; // For _1 placeholder
203 auto function = std::bind(&KisColorSelectorBase::slotUpdateColorAndPreview, this, _1);
204 m_updateColorCompressor.reset(new ColorCompressorType(25 /* ms */, function));
205
206 // Color selectors don't have context menus. Setting this prevents any
207 // long-presses from delaying inputs, see KisLongPressEventFilter.cpp.
208 setContextMenuPolicy(Qt::PreventContextMenu);
209}
210
216
217void KisColorSelectorBase::setPopupBehaviour(bool onMouseOver, bool onMouseClick)
218{
219 m_popupOnMouseClick = onMouseClick;
220 m_popupOnMouseOver = onMouseOver;
221 if(onMouseClick) {
222 m_popupOnMouseOver = false;
223 }
224
226 setMouseTracking(true);
227 }
228}
229
234
236{
237 if (m_canvas) {
238 m_canvas->disconnectCanvasObserver(this);
239 }
240 m_canvas = canvas;
241 if (m_canvas) {
242 connect(m_canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
243 SLOT(canvasResourceChanged(int,QVariant)), Qt::UniqueConnection);
244
245 connect(m_canvas->displayColorConverter(), SIGNAL(displayConfigurationChanged()),
246 SLOT(reset()), Qt::UniqueConnection);
247
248 connect(canvas->imageView()->resourceProvider(), SIGNAL(sigFGColorUsed(KoColor)),
249 this, SLOT(updateLastUsedColorPreview(KoColor)), Qt::UniqueConnection);
250
251 if (m_canvas->viewManager() && m_canvas->viewManager()->canvasResourceProvider()) {
252 const KoColor currentColor = Acs::currentColor(m_canvas->viewManager()->canvasResourceProvider(), Acs::Foreground);
253 setColor(currentColor);
254
255 auto colorHistory = m_canvas->viewManager()->canvasResourceProvider()->colorHistoryColors();
256 const KoColor lastUsedColor = !colorHistory.isEmpty() ? colorHistory.first() : currentColor;
257
258 const QColor currentQColor = converter()->toQColor(currentColor);
259 const QColor lastUsedQColor = converter()->toQColor(lastUsedColor);
260
261 m_colorPreviewPopup->initializeColors(currentQColor, lastUsedQColor);
262 }
263 }
264 if (m_popup) {
265 m_popup->setCanvas(canvas);
266 }
267
268 reset();
269}
270
272{
273 if (m_popup) {
275 }
276 m_canvas = 0;
277}
278
279
280
282{
283 event->accept();
284
286 event->button() == Qt::MiddleButton) {
287
289
290 m_colorUpdateSelf=false;
291 showPopup(event->globalPos());
292
293 } else if (m_isPopup && event->button() == Qt::MiddleButton) {
295 m_colorPreviewPopup->hide();
296 }
297 hide();
298 } else {
301 event->ignore();
302 }
303}
304
306
307 Q_UNUSED(e);
308
309 if (e->button() == Qt::MiddleButton) {
310 e->accept();
311 } else if (m_isPopup &&
313 !m_hideTimer->isActive()) {
315 m_colorPreviewPopup->hide();
316 }
317 hide();
318 }
319}
320#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
322#else
324#endif
325{
326
327 if (m_hideTimer->isActive()) {
328 m_hideTimer->stop();
329 }
330
331 // do not show the popup when boxed in
332 // the configuration dialog (m_canvas == 0)
333
334 if (m_canvas &&
335 !m_isPopup && m_popupOnMouseOver &&
336 (!m_popup || m_popup->isHidden())) {
337
338 lazyCreatePopup();
339
340 const QPoint preferredCenter = mapToGlobal(rect().center());
341 showPopup(preferredCenter);
342 }
343
344#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
345 QWidget::enterEvent(e);
346#else
347 QEnterEvent *enterEvent = dynamic_cast<QEnterEvent*>(e);
348 QWidget::enterEvent(enterEvent);
349#endif
350
351}
352
354{
355 m_hideTimer->start();
356 QWidget::leaveEvent(e);
357}
358
360{
362}
363
365{
366 if(e->mimeData()->hasColor())
367 e->acceptProposedAction();
368 if(e->mimeData()->hasText() && QColor(e->mimeData()->text()).isValid())
369 e->acceptProposedAction();
370}
371
373{
374 QColor color;
375 if(e->mimeData()->hasColor()) {
376 color = qvariant_cast<QColor>(e->mimeData()->colorData());
377 }
378 else if(e->mimeData()->hasText()) {
379#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
380 color.setNamedColor(e->mimeData()->text());
381#else
382 color.fromString(e->mimeData()->text());
383#endif
384 if(!color.isValid())
385 return;
386 }
387
388 KoColor kocolor(color , KoColorSpaceRegistry::instance()->rgb8());
389 updateColor(kocolor, Acs::Foreground, true);
390}
391
392void KisColorSelectorBase::updateColor(const KoColor &color, Acs::ColorRole role, bool needsExplicitColorReset)
393{
394 commitColor(color, role);
395
396 if (needsExplicitColorReset) {
397 setColor(color);
398 }
399}
400
402{
403 m_updateColorCompressor->start(qMakePair(color, role));
404}
405
406void KisColorSelectorBase::slotUpdateColorAndPreview(QPair<KoColor, Acs::ColorRole> color)
407{
408 updateColorPreview(color.first);
409 updateColor(color.first, color.second, false);
410}
411
413{
414 Q_UNUSED(color);
415}
416
418{
419 if (!m_popup) {
421 Q_ASSERT(m_popup);
422 m_popup->setParent(this);
423
424 // Setting Qt::BypassWindowManagerHint will prevent
425 // the WM from showing another taskbar entry,
426 // but will require that we handle window activation manually
427 m_popup->setWindowFlags(Qt::FramelessWindowHint |
428 Qt::Popup |
429 Qt::NoDropShadowWindowHint |
430 Qt::BypassWindowManagerHint);
431 m_popup->m_parent = this;
432 m_popup->m_isPopup = true;
433 }
436}
437
438void KisColorSelectorBase::showPopup(std::optional<QPoint> preferredCenter)
439{
440 // This slot may be called by some action,
441 // so we need to be able to handle it
443
444 const QPoint preferredGlobalCenterPos = preferredCenter ? *preferredCenter : QCursor::pos();
445
446 m_popup->create();
447
448 const QPoint preferredGlobalTopLeft =
449 QPoint(preferredGlobalCenterPos.x() - m_popup->width() / 2, preferredGlobalCenterPos.y() - m_popup->height() / 2);
450
451#ifdef HAVE_WAYLAND
452 if (qApp->platformName() == "wayland") {
453 m_popup->move(preferredGlobalTopLeft);
454 } else
455#endif
456 {
457 const QRect availRect = this->screen()->availableGeometry();
458 QRect rc(preferredGlobalTopLeft, m_popup->size());
459 rc = kisEnsureInRect(rc, availRect);
460 m_popup->move(rc.topLeft());
461 }
462
464 m_colorPreviewPopup->hide();
465 }
466
467 m_popup->show();
469}
470
475
477{
478 m_colorPreviewPopup->hide();
479
480 if (m_isPopup) {
481 hide();
482 }
483}
484
486{
487 if (!m_canvas)
488 return;
489
491
492 if (role == Acs::Foreground)
493 m_canvas->resourceManager()->setForegroundColor(color);
494 else
495 m_canvas->resourceManager()->setBackgroundColor(color);
496
498}
499
506
508{
509 m_colorPreviewPopup->setQColor(converter()->toQColor(color));
510}
511
512void KisColorSelectorBase::canvasResourceChanged(int key, const QVariant &v)
513{
515 KoColor realColor(v.value<KoColor>());
516 updateColorPreview(realColor);
518 setColor(realColor);
519 }
520 }
521}
522
527
529{
530 if(m_popup) {
532 }
533
534 KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
535
536
537 int zoomSelectorOptions = (int) cfg.readEntry("zoomSelectorOptions", 0) ;
538 if (zoomSelectorOptions == 0) {
539 setPopupBehaviour(false, true); // middle mouse button click will open zoom selector
540 } else if (zoomSelectorOptions == 1) {
541 setPopupBehaviour(true, false); // move over will open the zoom selector
542 }
543 else
544 {
545 setPopupBehaviour(false, false); // do not show zoom selector
546 }
547
548 if(m_isPopup) {
549 m_hideOnMouseClick = cfg.readEntry("hidePopupOnClickCheck", false);
550 const int zoomSize = cfg.readEntry("zoomSize", 280);
551 resize(zoomSize, zoomSize);
552 }
553
554 reset();
555}
556
558{
559 update();
560}
561
566
571
576
583
585{
586 event->accept();
587}
588
590{
591 // hide the popup when another window becomes active, e.g. due to alt+tab
592 if(m_isPopup && event->type() == QEvent::ActivationChange && !isActiveWindow()) {
594 }
595
596 QWidget::changeEvent(event);
597}
598
599void KisColorSelectorBase::showEvent(QShowEvent *event)
600{
601 QWidget::showEvent(event);
602
603 // manual activation required due to Qt::BypassWindowManagerHint
604 if(m_isPopup) {
605 activateWindow();
606 }
607}
const Params2D p
qreal v
QPointer< KisView > imageView() const
void paintEvent(QPaintEvent *e) override
KisColorPreviewPopup(KisColorSelectorBase *parent)
void initializeColors(const QColor &color, const QColor &lastUsedColor)
void setLastUsedColor(const QColor &color)
void leaveEvent(QEvent *e) override
void enterEvent(QEnterEvent *e) override
KisColorSelectorBase * m_parent
void setQColor(const QColor &color)
void setBaseColor(const QColor &color)
Base class for all color selectors, that should support color management and zooming.
KisColorSelectorBase * m_popup
void showPopup(std::optional< QPoint > preferredCenter=std::nullopt)
void requestUpdateColorAndPreview(const KoColor &color, Acs::ColorRole role)
const KoColorSpace * m_colorSpace
void dropEvent(QDropEvent *) override
void updateColorPreview(const KoColor &color)
virtual void canvasResourceChanged(int key, const QVariant &v)
if you overwrite this, keep in mind, that you should set the color only, if m_colorUpdateAllowed is t...
void dragEnterEvent(QDragEnterEvent *) override
void updateLastUsedColorPreview(const KoColor &color)
void slotUpdateColorAndPreview(QPair< KoColor, Acs::ColorRole > color)
void changeEvent(QEvent *event) override
virtual void setCanvas(KisCanvas2 *canvas)
KisDisplayColorConverter * converter() const
const KoColorSpace * colorSpace() const
virtual void setColor(const KoColor &color)
void mouseReleaseEvent(QMouseEvent *) override
KisColorSelectorBase(QWidget *parent=0)
void mousePressEvent(QMouseEvent *) override
void enterEvent(QEnterEvent *e) override
KisColorPreviewPopup * m_colorPreviewPopup
void showEvent(QShowEvent *event) override
void mouseMoveEvent(QMouseEvent *event) override
void commitColor(const KoColor &koColor, Acs::ColorRole role)
QScopedPointer< ColorCompressorType > m_updateColorCompressor
QPointer< KisCanvas2 > m_canvas
KisSignalCompressorWithParam< QPair< KoColor, Acs::ColorRole > > ColorCompressorType
void updateBaseColorPreview(const KoColor &color)
void setPopupBehaviour(bool onMouseOver, bool onMouseClick)
void setColorSpace(const KoColorSpace *colorSpace)
void leaveEvent(QEvent *e) override
virtual KisColorSelectorBase * createPopup() const =0
void updateColor(const KoColor &color, Acs::ColorRole role, bool needsExplicitColorReset)
void keyPressEvent(QKeyEvent *) override
const KoColorSpace * paintingColorSpace() const
static KisDisplayColorConverter * dumbConverterInstance()
QColor toQColor(const KoColor &c, bool proofToPaintColors=false) const
QRect kisEnsureInRect(QRect rc, const QRect &bounds)
Definition kis_global.h:291
KoColor currentColor(ResourceProvider *provider, ColorRole role)
@ Foreground
@ BackgroundColor
The active background color selected for this canvas.
@ ForegroundColor
The active foreground color selected for this canvas.
static KoColorSpaceRegistry * instance()