File indexing completed on 2024-06-16 04:15:59

0001 /*
0002  *  SPDX-FileCopyrightText: 2010 Adam Celarek <kdedev at xibo dot at>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_color_selector_base.h"
0008 
0009 #include <QMouseEvent>
0010 #include <QApplication>
0011 #include <QDesktopWidget>
0012 #include <QScreen>
0013 #include <QTimer>
0014 #include <QCursor>
0015 #include <QPainter>
0016 #include <QMimeData>
0017 
0018 #include <kconfig.h>
0019 #include <kconfiggroup.h>
0020 #include <ksharedconfig.h>
0021 
0022 #include "KoColorSpace.h"
0023 #include "KoColorSpaceRegistry.h"
0024 
0025 #include "kis_canvas2.h"
0026 #include "kis_canvas_resource_provider.h"
0027 #include "kis_node.h"
0028 #include "KisViewManager.h"
0029 #include <KisView.h>
0030 #include "kis_image.h"
0031 #include "kis_global.h"
0032 #include "kis_display_color_converter.h"
0033 
0034 #include <resources/KoGamutMask.h>
0035 
0036 class KisColorPreviewPopup : public QWidget {
0037 public:
0038     KisColorPreviewPopup(KisColorSelectorBase* parent)
0039         : QWidget(parent), m_parent(parent)
0040     {
0041         setWindowFlags(Qt::ToolTip | Qt::NoDropShadowWindowHint);
0042         setQColor(QColor(0,0,0));
0043         m_baseColor = QColor(0,0,0,0);
0044         m_previousColor = QColor(0,0,0,0);
0045         m_lastUsedColor = QColor(0,0,0,0);
0046     }
0047 
0048     void show()
0049     {
0050         updatePosition();
0051         QWidget::show();
0052     }
0053 
0054     void updatePosition()
0055     {
0056         QPoint parentPos = m_parent->mapToGlobal(QPoint(0,0));
0057         const QRect availRect = QApplication::desktop()->availableGeometry(this);
0058         QPoint targetPos;
0059         if ( parentPos.x() - 100 > availRect.x() ) {
0060             targetPos =  QPoint(parentPos.x() - 100, parentPos.y());
0061         } else if ( parentPos.x() + m_parent->width() + 100 < availRect.right()) {
0062             targetPos = m_parent->mapToGlobal(QPoint(m_parent->width(), 0));
0063         } else if ( parentPos.y() - 100 > availRect.y() ) {
0064             targetPos =  QPoint(parentPos.x(), parentPos.y() - 100);
0065         } else {
0066             targetPos =  QPoint(parentPos.x(), parentPos.y() + m_parent->height());
0067         }
0068         setGeometry(targetPos.x(), targetPos.y(), 100, 150);
0069         setAttribute(Qt::WA_TranslucentBackground);
0070     }
0071 
0072     void setQColor(const QColor& color)
0073     {
0074         m_color = color;
0075         update();
0076     }
0077 
0078     void setPreviousColor()
0079     {
0080         m_previousColor = m_baseColor;
0081     }
0082 
0083     void setBaseColor(const QColor& color)
0084     {
0085         m_baseColor = color;
0086         update();
0087     }
0088 
0089     void setLastUsedColor(const QColor& color)
0090     {
0091         m_lastUsedColor = color;
0092         update();
0093     }
0094 
0095 protected:
0096     void paintEvent(QPaintEvent *e) override {
0097         Q_UNUSED(e);
0098         QPainter p(this);
0099         p.fillRect(0, 0, width(), width(), m_color);
0100         p.fillRect(50, width(), width(), height(), m_previousColor);
0101         p.fillRect(0, width(), 50, height(), m_lastUsedColor);
0102     }
0103 
0104     void enterEvent(QEvent *e) override {
0105         QWidget::enterEvent(e);
0106         m_parent->tryHideAllPopups();
0107     }
0108 
0109     void leaveEvent(QEvent *e) override {
0110         QWidget::leaveEvent(e);
0111         m_parent->tryHideAllPopups();
0112     }
0113 
0114 private:
0115     KisColorSelectorBase* m_parent;
0116     QColor m_color;
0117     QColor m_baseColor;
0118     QColor m_previousColor;
0119     QColor m_lastUsedColor;
0120 };
0121 
0122 KisColorSelectorBase::KisColorSelectorBase(QWidget *parent) :
0123     QWidget(parent),
0124     m_canvas(0),
0125     m_popup(0),
0126     m_parent(0),
0127     m_colorUpdateAllowed(true),
0128     m_colorUpdateSelf(false),
0129     m_hideTimer(new QTimer(this)),
0130     m_popupOnMouseOver(false),
0131     m_popupOnMouseClick(true),
0132     m_colorSpace(0),
0133     m_isPopup(false),
0134     m_hideOnMouseClick(false),
0135     m_colorPreviewPopup(new KisColorPreviewPopup(this))
0136 {
0137     m_hideTimer->setInterval(0);
0138     m_hideTimer->setSingleShot(true);
0139     connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hidePopup()));
0140 
0141     using namespace std::placeholders; // For _1 placeholder
0142     auto function = std::bind(&KisColorSelectorBase::slotUpdateColorAndPreview, this, _1);
0143     m_updateColorCompressor.reset(new ColorCompressorType(20 /* ms */, function));
0144 }
0145 
0146 KisColorSelectorBase::~KisColorSelectorBase()
0147 {
0148     delete m_popup;
0149     delete m_colorPreviewPopup;
0150 }
0151 
0152 void KisColorSelectorBase::setPopupBehaviour(bool onMouseOver, bool onMouseClick)
0153 {
0154     m_popupOnMouseClick = onMouseClick;
0155     m_popupOnMouseOver = onMouseOver;
0156     if(onMouseClick) {
0157         m_popupOnMouseOver = false;
0158     }
0159 
0160     if(m_popupOnMouseOver) {
0161         setMouseTracking(true);
0162     }
0163 }
0164 
0165 void KisColorSelectorBase::setColorSpace(const KoColorSpace *colorSpace)
0166 {
0167     m_colorSpace = colorSpace;
0168 }
0169 
0170 void KisColorSelectorBase::setCanvas(KisCanvas2 *canvas)
0171 {
0172     if (m_canvas) {
0173         m_canvas->disconnectCanvasObserver(this);
0174     }
0175     m_canvas = canvas;
0176     if (m_canvas) {
0177         connect(m_canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
0178                 SLOT(canvasResourceChanged(int,QVariant)), Qt::UniqueConnection);
0179 
0180         connect(m_canvas->displayColorConverter(), SIGNAL(displayConfigurationChanged()),
0181                 SLOT(reset()), Qt::UniqueConnection);
0182 
0183         connect(canvas->imageView()->resourceProvider(), SIGNAL(sigFGColorUsed(KoColor)),
0184                 this,                               SLOT(updateLastUsedColorPreview(KoColor)), Qt::UniqueConnection);
0185 
0186         if (m_canvas->viewManager() && m_canvas->viewManager()->canvasResourceProvider()) {
0187             setColor(Acs::currentColor(m_canvas->viewManager()->canvasResourceProvider(), Acs::Foreground));
0188         }
0189     }
0190     if (m_popup) {
0191         m_popup->setCanvas(canvas);
0192     }
0193 
0194     reset();
0195 }
0196 
0197 void KisColorSelectorBase::unsetCanvas()
0198 {
0199     if (m_popup) {
0200         m_popup->unsetCanvas();
0201     }
0202     m_canvas = 0;
0203 }
0204 
0205 
0206 
0207 void KisColorSelectorBase::mousePressEvent(QMouseEvent* event)
0208 {
0209     event->accept();
0210 
0211     if(!m_isPopup && m_popupOnMouseClick &&
0212        event->button() == Qt::MiddleButton) {
0213 
0214         lazyCreatePopup();
0215 
0216         int x = event->globalX();
0217         int y = event->globalY();
0218         int popupsize = m_popup->width();
0219         x-=popupsize/2;
0220         y-=popupsize/2;
0221 
0222         const QRect availRect = QApplication::desktop()->availableGeometry(this);
0223 
0224         if(x<availRect.x())
0225             x = availRect.x();
0226         if(y<availRect.y())
0227             y = availRect.y();
0228         if(x+m_popup->width()>availRect.x()+availRect.width())
0229             x = availRect.x()+availRect.width()-m_popup->width();
0230         if(y+m_popup->height()>availRect.y()+availRect.height())
0231             y = availRect.y()+availRect.height()-m_popup->height();
0232 
0233         m_colorUpdateSelf=false;
0234         m_popup->move(x, y);
0235         m_popup->setHidingTime(200);
0236         showPopup(DontMove);
0237 
0238     } else if (m_isPopup && event->button() == Qt::MiddleButton) {
0239         if (m_colorPreviewPopup) {
0240             m_colorPreviewPopup->hide();
0241         }
0242         hide();
0243     } else {
0244         m_colorUpdateSelf=true;
0245         showColorPreview();
0246         event->ignore();
0247     }
0248 }
0249 
0250 void KisColorSelectorBase::mouseReleaseEvent(QMouseEvent *e) {
0251 
0252    Q_UNUSED(e);
0253 
0254     if (e->button() == Qt::MiddleButton) {
0255         e->accept();
0256     } else if (m_isPopup &&
0257                (m_hideOnMouseClick && !m_popupOnMouseOver) &&
0258                !m_hideTimer->isActive()) {
0259         if (m_colorPreviewPopup) {
0260             m_colorPreviewPopup->hide();
0261         }
0262         hide();
0263     }
0264 }
0265 
0266 void KisColorSelectorBase::enterEvent(QEvent *e)
0267 {
0268     if (m_popup && m_popup->isVisible()) {
0269         m_popup->m_hideTimer->stop();
0270     }
0271 
0272     if (m_isPopup && m_hideTimer->isActive()) {
0273         m_hideTimer->stop();
0274     }
0275 
0276     // do not show the popup when boxed in
0277     // the configuration dialog (m_canvas == 0)
0278 
0279     if (m_canvas &&
0280         !m_isPopup && m_popupOnMouseOver &&
0281         (!m_popup || m_popup->isHidden())) {
0282 
0283         lazyCreatePopup();
0284 
0285         const QRect availRect = QApplication::desktop()->availableGeometry(this);
0286 
0287         QPoint proposedTopLeft = rect().center() - m_popup->rect().center();
0288         proposedTopLeft = mapToGlobal(proposedTopLeft);
0289 
0290         QRect popupRect = QRect(proposedTopLeft, m_popup->size());
0291         popupRect = kisEnsureInRect(popupRect, availRect);
0292 
0293         m_popup->setGeometry(popupRect);
0294         m_popup->setHidingTime(200);
0295         showPopup(DontMove);
0296     }
0297 
0298     QWidget::enterEvent(e);
0299 }
0300 
0301 void KisColorSelectorBase::leaveEvent(QEvent *e)
0302 {
0303     tryHideAllPopups();
0304     QWidget::leaveEvent(e);
0305 }
0306 
0307 void KisColorSelectorBase::keyPressEvent(QKeyEvent *)
0308 {
0309     if (m_isPopup) {
0310         hidePopup();
0311     }
0312 }
0313 
0314 void KisColorSelectorBase::dragEnterEvent(QDragEnterEvent *e)
0315 {
0316     if(e->mimeData()->hasColor())
0317         e->acceptProposedAction();
0318     if(e->mimeData()->hasText() && QColor(e->mimeData()->text()).isValid())
0319         e->acceptProposedAction();
0320 }
0321 
0322 void KisColorSelectorBase::dropEvent(QDropEvent *e)
0323 {
0324     QColor color;
0325     if(e->mimeData()->hasColor()) {
0326         color = qvariant_cast<QColor>(e->mimeData()->colorData());
0327     }
0328     else if(e->mimeData()->hasText()) {
0329         color.setNamedColor(e->mimeData()->text());
0330         if(!color.isValid())
0331             return;
0332     }
0333 
0334     KoColor kocolor(color , KoColorSpaceRegistry::instance()->rgb8());
0335     updateColor(kocolor, Acs::Foreground, true);
0336 }
0337 
0338 void KisColorSelectorBase::updateColor(const KoColor &color, Acs::ColorRole role, bool needsExplicitColorReset)
0339 {
0340     commitColor(color, role);
0341 
0342     if (needsExplicitColorReset) {
0343         setColor(color);
0344     }
0345 }
0346 
0347 void KisColorSelectorBase::requestUpdateColorAndPreview(const KoColor &color, Acs::ColorRole role)
0348 {
0349     m_updateColorCompressor->start(qMakePair(color, role));
0350 }
0351 
0352 void KisColorSelectorBase::slotUpdateColorAndPreview(QPair<KoColor, Acs::ColorRole> color)
0353 {
0354     updateColorPreview(color.first);
0355     updateColor(color.first, color.second, false);
0356 }
0357 
0358 void KisColorSelectorBase::setColor(const KoColor& color)
0359 {
0360     Q_UNUSED(color);
0361 }
0362 
0363 void KisColorSelectorBase::setHidingTime(int time)
0364 {
0365     KIS_ASSERT_RECOVER_NOOP(m_isPopup);
0366 
0367     m_hideTimer->setInterval(time);
0368 }
0369 
0370 void KisColorSelectorBase::lazyCreatePopup()
0371 {
0372     if (!m_popup) {
0373         m_popup = createPopup();
0374         Q_ASSERT(m_popup);
0375         m_popup->setParent(this);
0376 
0377         // Setting Qt::BypassWindowManagerHint will prevent
0378         // the WM from showing another taskbar entry,
0379         // but will require that we handle window activation manually
0380         m_popup->setWindowFlags(Qt::FramelessWindowHint |
0381 #if defined(Q_OS_MACOS) || defined(Q_OS_ANDROID)
0382                                 Qt::Popup |
0383 #else
0384                                 Qt::Window |
0385 #endif
0386                                 Qt::NoDropShadowWindowHint |
0387                                 Qt::BypassWindowManagerHint);
0388         m_popup->m_parent = this;
0389         m_popup->m_isPopup = true;
0390     }
0391     m_popup->setCanvas(m_canvas);
0392     m_popup->updateSettings();
0393 }
0394 
0395 void KisColorSelectorBase::showPopup(Move move)
0396 {
0397     // This slot may be called by some action,
0398     // so we need to be able to handle it
0399     lazyCreatePopup();
0400 
0401     QPoint cursorPos = QCursor::pos();
0402     QScreen *activeScreen = 0;
0403 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
0404     activeScreen = QGuiApplication::screenAt(cursorPos);
0405 #endif
0406     const QRect availRect = (activeScreen)? activeScreen->availableGeometry() : QApplication::desktop()->availableGeometry(this);
0407 
0408     if (move == MoveToMousePosition) {
0409         m_popup->move(QPoint(cursorPos.x()-m_popup->width()/2, cursorPos.y()-m_popup->height()/2));
0410         QRect rc = m_popup->geometry();
0411         if (rc.x() < availRect.x()) rc.setX(availRect.x());
0412         if (rc.y() < availRect.y()) rc.setY(availRect.y());
0413         m_popup->setGeometry(rc);
0414     }
0415 
0416     if (m_colorPreviewPopup) {
0417         m_colorPreviewPopup->hide();
0418     }
0419 
0420     m_popup->show();
0421     m_popup->m_colorPreviewPopup->show();
0422 }
0423 
0424 void KisColorSelectorBase::hidePopup()
0425 {
0426     KIS_ASSERT_RECOVER_RETURN(m_isPopup);
0427 
0428     m_colorPreviewPopup->hide();
0429     hide();
0430 }
0431 
0432 void KisColorSelectorBase::commitColor(const KoColor& color, Acs::ColorRole role)
0433 {
0434     if (!m_canvas)
0435         return;
0436 
0437     m_colorUpdateAllowed=false;
0438 
0439     if (role == Acs::Foreground)
0440         m_canvas->resourceManager()->setForegroundColor(color);
0441     else
0442         m_canvas->resourceManager()->setBackgroundColor(color);
0443 
0444     m_colorUpdateAllowed=true;
0445 }
0446 
0447 void KisColorSelectorBase::showColorPreview()
0448 {
0449     if(m_colorPreviewPopup->isHidden()) {
0450         m_colorPreviewPopup->show();
0451     }
0452 }
0453 
0454 void KisColorSelectorBase::updateColorPreview(const KoColor &color)
0455 {
0456     m_colorPreviewPopup->setQColor(converter()->toQColor(color));
0457 }
0458 
0459 void KisColorSelectorBase::canvasResourceChanged(int key, const QVariant &v)
0460 {
0461     if (key == KoCanvasResource::ForegroundColor || key == KoCanvasResource::BackgroundColor) {
0462         KoColor realColor(v.value<KoColor>());
0463         updateColorPreview(realColor);
0464         if (m_colorUpdateAllowed && !m_colorUpdateSelf) {
0465             setColor(realColor);
0466         }
0467     }
0468 }
0469 
0470 const KoColorSpace* KisColorSelectorBase::colorSpace() const
0471 {
0472     return converter()->paintingColorSpace();
0473 }
0474 
0475 void KisColorSelectorBase::updateSettings()
0476 {
0477     if(m_popup) {
0478         m_popup->updateSettings();
0479     }
0480 
0481     KConfigGroup cfg =  KSharedConfig::openConfig()->group("advancedColorSelector");
0482 
0483 
0484    int zoomSelectorOptions =  (int) cfg.readEntry("zoomSelectorOptions", 0) ;
0485    if (zoomSelectorOptions == 0)   {
0486        setPopupBehaviour(false, true);   // middle mouse button click will open zoom selector
0487    } else if (zoomSelectorOptions == 1)   {
0488        setPopupBehaviour(true, false);   // move over will open the zoom selector
0489    }
0490    else
0491    {
0492         setPopupBehaviour(false, false); // do not show zoom selector
0493    }
0494 
0495 
0496     if(m_isPopup) {
0497         m_hideOnMouseClick = cfg.readEntry("hidePopupOnClickCheck", false);
0498         const int zoomSize = cfg.readEntry("zoomSize", 280);
0499         resize(zoomSize, zoomSize);
0500     }
0501 
0502     reset();
0503 }
0504 
0505 void KisColorSelectorBase::reset()
0506 {
0507     update();
0508 }
0509 
0510 void KisColorSelectorBase::updateBaseColorPreview(const KoColor &color)
0511 {
0512     m_colorPreviewPopup->setBaseColor(converter()->toQColor(color));
0513 }
0514 
0515 void KisColorSelectorBase::updatePreviousColorPreview()
0516 {
0517     m_colorPreviewPopup->setPreviousColor();
0518 }
0519 
0520 void KisColorSelectorBase::updateLastUsedColorPreview(const KoColor &color)
0521 {
0522     m_colorPreviewPopup->setLastUsedColor(converter()->toQColor(color));
0523 }
0524 
0525 KisDisplayColorConverter* KisColorSelectorBase::converter() const
0526 {
0527     return m_canvas ?
0528         m_canvas->displayColorConverter() :
0529                 KisDisplayColorConverter::dumbConverterInstance();
0530 }
0531 
0532 void KisColorSelectorBase::tryHideAllPopups()
0533 {
0534     if (m_colorPreviewPopup->isVisible()) {
0535         m_colorUpdateSelf=false; //this is for allowing advanced selector to listen to outside color-change events.
0536         m_colorPreviewPopup->hide();
0537     }
0538 
0539     if (m_popup && m_popup->isVisible()) {
0540         m_popup->m_hideTimer->start();
0541     }
0542 
0543     if (m_isPopup && !m_hideTimer->isActive()) {
0544         m_hideTimer->start();
0545     }
0546 }
0547 
0548 
0549 void KisColorSelectorBase::mouseMoveEvent(QMouseEvent *event)
0550 {
0551     event->accept();
0552 }
0553 
0554 
0555 void KisColorSelectorBase::changeEvent(QEvent *event)
0556 {
0557     // hide the popup when another window becomes active, e.g. due to alt+tab
0558     if(m_isPopup && event->type() == QEvent::ActivationChange && !isActiveWindow()) {
0559         hidePopup();
0560     }
0561 
0562     QWidget::changeEvent(event);
0563 }
0564 
0565 void KisColorSelectorBase::showEvent(QShowEvent *event)
0566 {
0567     QWidget::showEvent(event);
0568 
0569     // manual activation required due to Qt::BypassWindowManagerHint
0570     if(m_isPopup) {
0571         activateWindow();
0572     }
0573 }