diff --git a/src/widgets/capture/CMakeLists.txt b/src/widgets/capture/CMakeLists.txt index 90612425..3bbafb65 100644 --- a/src/widgets/capture/CMakeLists.txt +++ b/src/widgets/capture/CMakeLists.txt @@ -7,6 +7,7 @@ target_sources( capturewidget.h colorpicker.h hovereventfilter.h + overlaymessage.h selectionwidget.h notifierbox.h modificationcommand.h) @@ -19,6 +20,7 @@ target_sources( capturewidget.cpp colorpicker.cpp hovereventfilter.cpp + overlaymessage.cpp notifierbox.cpp selectionwidget.cpp modificationcommand.cpp) diff --git a/src/widgets/capture/capturewidget.cpp b/src/widgets/capture/capturewidget.cpp index 162dcb29..c7cd1b95 100644 --- a/src/widgets/capture/capturewidget.cpp +++ b/src/widgets/capture/capturewidget.cpp @@ -21,6 +21,7 @@ #include "src/widgets/capture/hovereventfilter.h" #include "src/widgets/capture/modificationcommand.h" #include "src/widgets/capture/notifierbox.h" +#include "src/widgets/capture/overlaymessage.h" #include "src/widgets/orientablepushbutton.h" #include "src/widgets/panel/sidepanelwidget.h" #include "src/widgets/panel/utilitypanel.h" @@ -84,7 +85,6 @@ CaptureWidget::CaptureWidget(uint id, this, &CaptureWidget::childLeave); setAttribute(Qt::WA_DeleteOnClose); - m_showInitialMsg = m_config.showHelpValue(); m_opacity = m_config.contrastOpacityValue(); m_uiColor = m_config.uiMainColorValue(); m_contrastUiColor = m_config.uiContrastColorValue(); @@ -199,6 +199,18 @@ CaptureWidget::CaptureWidget(uint id, }); initPanel(); + + OverlayMessage::init(this, + QGuiAppCurrentScreen().currentScreen()->geometry()); + + if (m_config.showHelpValue()) { + OverlayMessage::push( + tr("Select an area with the mouse, or press Esc to exit." + "\nPress Enter to capture the screen." + "\nPress Right Click to show the color picker." + "\nUse the Mouse Wheel to change the thickness of your tool." + "\nPress Space to open the side panel.")); + } } CaptureWidget::~CaptureWidget() @@ -364,12 +376,6 @@ void CaptureWidget::paintEvent(QPaintEvent* paintEvent) // draw inactive region drawInactiveRegion(&painter); - - // show initial message on screen capture call if required (before selecting - // area) - if (m_showInitialMsg) { - drawInitialMessage(&painter); - } } void CaptureWidget::showColorPicker(const QPoint& pos) @@ -485,7 +491,7 @@ void CaptureWidget::mousePressEvent(QMouseEvent* e) showColorPicker(m_mousePressedPos); return; } else if (e->button() == Qt::LeftButton) { - m_showInitialMsg = false; + OverlayMessage::pop(); m_mouseIsClicked = true; // Click using a tool excluding tool MOVE @@ -972,7 +978,7 @@ void CaptureWidget::initPanel() this, &CaptureWidget::updateActiveLayer); - m_sidePanel = new SidePanelWidget(&m_context.screenshot); + m_sidePanel = new SidePanelWidget(&m_context.screenshot, this); connect(m_sidePanel, &SidePanelWidget::colorChanged, this, @@ -1325,7 +1331,6 @@ void CaptureWidget::selectAll() m_selection->setGeometry(newGeometry); m_context.selection = extendedRect(newGeometry); m_selection->setVisible(true); - m_showInitialMsg = false; m_buttonHandler->updatePosition(m_selection->geometry()); updateSizeIndicator(); m_buttonHandler->show(); @@ -1664,56 +1669,6 @@ QRect CaptureWidget::extendedRect(const QRect& r) const r.height() * devicePixelRatio); } -void CaptureWidget::drawInitialMessage(QPainter* painter) -{ - if (nullptr == painter) { - return; - } -#if (defined(Q_OS_MACOS) || defined(Q_OS_LINUX)) - QRect helpRect; - QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); - if (currentScreen) { - helpRect = currentScreen->geometry(); - } else { - helpRect = QGuiApplication::primaryScreen()->geometry(); - } -#else - QRect helpRect = QGuiApplication::primaryScreen()->geometry(); -#endif - - helpRect.moveTo(mapFromGlobal(helpRect.topLeft())); - - QString helpTxt = - tr("Select an area with the mouse, or press Esc to exit." - "\nPress Enter to capture the screen." - "\nPress Right Click to show the color picker." - "\nUse the Mouse Wheel to change the thickness of your tool." - "\nPress Space to open the side panel."); - - // We draw the white contrasting background for the text, using the - // same text and options to get the boundingRect that the text will - // have. - QRectF bRect = painter->boundingRect(helpRect, Qt::AlignCenter, helpTxt); - - // These four calls provide padding for the rect - const int margin = QApplication::fontMetrics().height() / 2; - bRect.setWidth(bRect.width() + margin); - bRect.setHeight(bRect.height() + margin); - bRect.setX(bRect.x() - margin); - bRect.setY(bRect.y() - margin); - - QColor rectColor(m_uiColor); - rectColor.setAlpha(180); - QColor textColor( - (ColorUtils::colorIsDark(rectColor) ? Qt::white : Qt::black)); - - painter->setBrush(QBrush(rectColor, Qt::SolidPattern)); - painter->setPen(QPen(textColor)); - - painter->drawRect(bRect); - painter->drawText(helpRect, Qt::AlignCenter, helpTxt); -} - void CaptureWidget::drawInactiveRegion(QPainter* painter) { QColor overlayColor(0, 0, 0, m_opacity); diff --git a/src/widgets/capture/capturewidget.h b/src/widgets/capture/capturewidget.h index 6eea5ee2..3d9b4796 100644 --- a/src/widgets/capture/capturewidget.h +++ b/src/widgets/capture/capturewidget.h @@ -131,7 +131,6 @@ private: QRect extendedSelection() const; QRect extendedRect(const QRect& r) const; - void drawInitialMessage(QPainter* painter); void drawInactiveRegion(QPainter* painter); void drawToolsData(bool updateLayersPanel = true, bool drawSelection = false); @@ -159,7 +158,6 @@ private: bool m_newSelection; bool m_grabbing; bool m_movingSelection; - bool m_showInitialMsg; bool m_captureDone; bool m_previewEnabled; bool m_adjustmentButtonPressed; diff --git a/src/widgets/capture/overlaymessage.cpp b/src/widgets/capture/overlaymessage.cpp new file mode 100644 index 00000000..0fa6a5df --- /dev/null +++ b/src/widgets/capture/overlaymessage.cpp @@ -0,0 +1,110 @@ +#include "overlaymessage.h" +#include "colorutils.h" +#include "confighandler.h" +#include "qguiappcurrentscreen.h" + +#include +#include +#include +#include +#include +#include + +OverlayMessage::OverlayMessage(QWidget* parent, const QRect& targetArea) + : QWidget(parent) + , m_targetArea(targetArea) +{ + // NOTE: do not call the static functions from the constructor + m_instance = this; + m_messageStack.push(QString()); // Default message is empty + setAttribute(Qt::WA_TransparentForMouseEvents); + setAttribute(Qt::WA_AlwaysStackOnTop); + QWidget::hide(); +} + +void OverlayMessage::init(QWidget* parent, const QRect& targetArea) +{ + new OverlayMessage(parent, targetArea); +} + +void OverlayMessage::push(const QString& msg) +{ + m_instance->m_messageStack.push(msg); + setVisibility(true); +} + +void OverlayMessage::pop() +{ + if (m_instance->m_messageStack.size() > 1) { + m_instance->m_messageStack.pop(); + } + + if (m_instance->m_messageStack.size() == 1) { + // Only empty message left (don't show it) + m_instance->QWidget::hide(); + } else { + // Still visible, resize for new message + m_instance->updateGeometry(); + } +} + +void OverlayMessage::setVisibility(bool visible) +{ + m_instance->updateGeometry(); + m_instance->setVisible(visible); +} + +OverlayMessage* OverlayMessage::instance() +{ + return m_instance; +} + +void OverlayMessage::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + + QRectF bRect = boundingRect(); + bRect.moveTo(0, 0); + + QColor rectColor(ConfigHandler().uiMainColorValue()); + rectColor.setAlpha(180); + QColor textColor( + (ColorUtils::colorIsDark(rectColor) ? Qt::white : Qt::black)); + + painter.setBrush(QBrush(rectColor, Qt::SolidPattern)); + painter.setPen(QPen(textColor)); + + float margin = painter.pen().widthF(); + painter.drawRect(bRect - QMarginsF(margin, margin, margin, margin)); + painter.drawText(bRect, Qt::AlignCenter, m_messageStack.top()); +} + +void OverlayMessage::showEvent(QShowEvent*) +{ + update(); +} + +QRectF OverlayMessage::boundingRect() const +{ + // We draw the white contrasting background for the text, using the + // same text and options to get the boundingRect that the text will + // have. + QRectF bRect = QApplication::fontMetrics().boundingRect( + m_targetArea, Qt::AlignCenter, m_messageStack.top()); + + // These four calls provide padding for the rect + const int margin = QApplication::fontMetrics().height() / 2; + bRect.setWidth(bRect.width() + margin); + bRect.setHeight(bRect.height() + margin); + bRect.setX(bRect.x() - margin); + bRect.setY(bRect.y() - margin); + return bRect; +} + +void OverlayMessage::updateGeometry() +{ + m_instance->setGeometry(m_instance->boundingRect().toRect()); + QWidget::updateGeometry(); +} + +OverlayMessage* OverlayMessage::m_instance = nullptr; diff --git a/src/widgets/capture/overlaymessage.h b/src/widgets/capture/overlaymessage.h new file mode 100644 index 00000000..e6dd6e69 --- /dev/null +++ b/src/widgets/capture/overlaymessage.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +/** + * @brief Overlay a message in capture mode. + * + * The message must be initialized by calling `init` before it can be used. That + * can be done once per capture session. The class is a singleton. + * + * To change the active message call `push`. This will automatically show the + * widget. Previous messages won't be forgotten and will be reactivated after + * you call `pop`. You can show/hide the message using `setVisibility` (this + * won't push/pop anything). + * + * @note You have to make sure that widgets pop the messages they pushed when + * they are closed, to avoid potential bugs and resource leaks. + */ +class OverlayMessage : public QWidget +{ +public: + OverlayMessage() = delete; + + static void init(QWidget* parent, const QRect& targetArea); + static void push(const QString& msg); + static void pop(); + static void setVisibility(bool visible); + static OverlayMessage* instance(); + +private: + QStack m_messageStack; + QRect m_targetArea; + static OverlayMessage* m_instance; + + OverlayMessage(QWidget* parent, const QRect& center); + + void paintEvent(QPaintEvent*) override; + void showEvent(QShowEvent*) override; + + QRectF boundingRect() const; + void updateGeometry(); +}; diff --git a/src/widgets/panel/CMakeLists.txt b/src/widgets/panel/CMakeLists.txt index eeb4de27..5c4a468b 100644 --- a/src/widgets/panel/CMakeLists.txt +++ b/src/widgets/panel/CMakeLists.txt @@ -1,4 +1,4 @@ # Required to generate MOC -target_sources(flameshot PRIVATE sidepanelwidget.h utilitypanel.h) +target_sources(flameshot PRIVATE sidepanelwidget.h utilitypanel.h colorgrabwidget.h) -target_sources(flameshot PRIVATE sidepanelwidget.cpp utilitypanel.cpp) +target_sources(flameshot PRIVATE sidepanelwidget.cpp utilitypanel.cpp colorgrabwidget.cpp) diff --git a/src/widgets/panel/colorgrabwidget.cpp b/src/widgets/panel/colorgrabwidget.cpp new file mode 100644 index 00000000..844195ea --- /dev/null +++ b/src/widgets/panel/colorgrabwidget.cpp @@ -0,0 +1,219 @@ +#include "colorgrabwidget.h" +#include "sidepanelwidget.h" + +#include "colorutils.h" +#include "confighandler.h" +#include "overlaymessage.h" +#include "src/core/qguiappcurrentscreen.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// Width (= height) and zoom level of the widget before the user clicks +#define WIDTH1 77 +#define ZOOM1 11 +// Width (= height) and zoom level of the widget after the user clicks +#define WIDTH2 165 +#define ZOOM2 15 + +// NOTE: WIDTH1(2) should be divisible by ZOOM1(2) for best precision. +// WIDTH1 should be odd so the cursor can be centered on a pixel. + +ColorGrabWidget::ColorGrabWidget(QPixmap* p, QWidget* parent) + : QWidget(parent) + , m_pixmap(p) + , m_mousePressReceived(false) + , m_extraZoomActive(false) + , m_magnifierActive(false) +{ + if (p == nullptr) { + throw std::logic_error("Pixmap must not be null"); + } + setAttribute(Qt::WA_DeleteOnClose); + // We don't need this widget to receive mouse events because we use + // eventFilter on other objects that do + setAttribute(Qt::WA_TransparentForMouseEvents); + setWindowFlags(Qt::BypassWindowManagerHint | Qt::FramelessWindowHint); + setMouseTracking(true); +} + +void ColorGrabWidget::startGrabbing() +{ + // NOTE: grabMouse() would prevent move events being received + // With this method we just need to make sure that mouse press and release + // events get consumed before they reach their target widget. + // This is undone in the destructor. + qApp->setOverrideCursor(Qt::CrossCursor); + qApp->installEventFilter(this); + OverlayMessage::push( + "Press Enter or Left Mouse Button to accept color\n" + "Press and hold Left Mouse Button to precisely select color\n" + "Press Space or Right Mouse Button to toggle magnifier\n" + "Press ESC to cancel"); +} + +QColor ColorGrabWidget::color() +{ + return m_color; +} + +bool ColorGrabWidget::eventFilter(QObject*, QEvent* event) +{ + // Consume shortcut events and handle key presses from whole app + if (event->type() == QEvent::KeyPress || + event->type() == QEvent::Shortcut) { + QKeySequence key = event->type() == QEvent::KeyPress + ? static_cast(event)->key() + : static_cast(event)->key(); + if (key == Qt::Key_Escape) { + emit grabAborted(); + finalize(); + } else if (key == Qt::Key_Return || key == Qt::Key_Enter) { + emit colorGrabbed(m_color); + finalize(); + } else if (key == Qt::Key_Space && !m_extraZoomActive) { + setMagnifierActive(!m_magnifierActive); + } + return true; + } else if (event->type() == QEvent::MouseMove) { + // NOTE: This relies on the fact that CaptureWidget tracks mouse moves + + if (m_extraZoomActive && !geometry().contains(cursorPos())) { + setExtraZoomActive(false); + return true; + } + if (!m_extraZoomActive && !m_magnifierActive) { + // This fixes an issue when the mouse leaves the zoom area before + // the widget even appears. + hide(); + } + if (!m_extraZoomActive) { + // Update only before the user clicks the mouse, after the mouse + // press the widget remains static. + updateWidget(); + } + + // Hide overlay message when cursor is over it + OverlayMessage* overlayMsg = OverlayMessage::instance(); + overlayMsg->setVisibility( + !overlayMsg->geometry().contains(cursorPos())); + + m_color = getColorAtPoint(cursorPos()); + emit colorUpdated(m_color); + return true; + } else if (event->type() == QEvent::MouseButtonPress) { + m_mousePressReceived = true; + auto* e = static_cast(event); + if (e->buttons() == Qt::RightButton) { + setMagnifierActive(!m_magnifierActive); + } else if (e->buttons() == Qt::LeftButton) { + setExtraZoomActive(true); + } + return true; + } else if (event->type() == QEvent::MouseButtonRelease) { + if (!m_mousePressReceived) { + // Do not consume event if it corresponds to the mouse press that + // triggered the color grabbing in the first place. This prevents + // focus issues in the capture widget when the color grabber is + // closed. + return false; + } + auto* e = static_cast(event); + if (e->button() == Qt::LeftButton && m_extraZoomActive) { + emit colorGrabbed(getColorAtPoint(cursorPos())); + finalize(); + } + return true; + } else if (event->type() == QEvent::MouseButtonDblClick) { + return true; + } + return false; +} + +void ColorGrabWidget::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + painter.drawImage(QRectF(0, 0, width(), height()), m_previewImage); +} + +void ColorGrabWidget::showEvent(QShowEvent*) +{ + updateWidget(); +} + +QPoint ColorGrabWidget::cursorPos() const +{ + return QCursor::pos(QGuiAppCurrentScreen().currentScreen()); +} + +/// @note The point is in screen coordinates. +QColor ColorGrabWidget::getColorAtPoint(const QPoint& p) const +{ + if (m_extraZoomActive && geometry().contains(p)) { + QPoint point = mapFromGlobal(p); + // we divide coordinate-wise to avoid rounding to nearest + return m_previewImage.pixel( + QPoint(point.x() / ZOOM2, point.y() / ZOOM2)); + } + QPoint point = p; +#if defined(Q_OS_MACOS) + QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); + if (currentScreen) { + point = QPoint((p.x() - currentScreen->geometry().x()) * + currentScreen->devicePixelRatio(), + (p.y() - currentScreen->geometry().y()) * + currentScreen->devicePixelRatio()); + } +#endif + QPixmap pixel = m_pixmap->copy(QRect(point, point)); + return pixel.toImage().pixel(0, 0); +} + +void ColorGrabWidget::setExtraZoomActive(bool active) +{ + m_extraZoomActive = active; + if (!active && !m_magnifierActive) { + hide(); + } else { + if (!isVisible()) { + QTimer::singleShot(250, this, [this]() { show(); }); + } else { + QTimer::singleShot(250, this, [this]() { updateWidget(); }); + } + } +} + +void ColorGrabWidget::setMagnifierActive(bool active) +{ + m_magnifierActive = active; + setVisible(active); +} + +void ColorGrabWidget::updateWidget() +{ + int width = m_extraZoomActive ? WIDTH2 : WIDTH1; + float zoom = m_extraZoomActive ? ZOOM2 : ZOOM1; + // Set window size and move its center to the mouse cursor + QRect rect(0, 0, width, width); + rect.moveCenter(cursorPos()); + setGeometry(rect); + // Store a pixmap containing the zoomed-in section around the cursor + QRect sourceRect(0, 0, width / zoom, width / zoom); + sourceRect.moveCenter(rect.center()); + m_previewImage = m_pixmap->copy(sourceRect).toImage(); + // Repaint + update(); +} + +void ColorGrabWidget::finalize() +{ + qApp->removeEventFilter(this); + qApp->restoreOverrideCursor(); + OverlayMessage::pop(); + close(); +} diff --git a/src/widgets/panel/colorgrabwidget.h b/src/widgets/panel/colorgrabwidget.h new file mode 100644 index 00000000..c2cf4a04 --- /dev/null +++ b/src/widgets/panel/colorgrabwidget.h @@ -0,0 +1,45 @@ +#ifndef COLORGRABWIDGET_H +#define COLORGRABWIDGET_H + +#include + +class SidePanelWidget; +class OverlayMessage; + +class ColorGrabWidget : public QWidget +{ + Q_OBJECT +public: + ColorGrabWidget(QPixmap* p, QWidget* parent = nullptr); + + void startGrabbing(); + + QColor color(); + +signals: + void colorUpdated(const QColor& color); + void colorGrabbed(const QColor& color); + void grabAborted(); + +private: + bool eventFilter(QObject* obj, QEvent* event) override; + void paintEvent(QPaintEvent* e); + void showEvent(QShowEvent* event) override; + + QPoint cursorPos() const; + QColor getColorAtPoint(const QPoint& point) const; + void setExtraZoomActive(bool active); + void setMagnifierActive(bool active); + void updateWidget(); + void finalize(); + + QPixmap* m_pixmap; + QImage m_previewImage; + QColor m_color; + + bool m_mousePressReceived; + bool m_extraZoomActive; + bool m_magnifierActive; +}; + +#endif // COLORGRABWIDGET_H diff --git a/src/widgets/panel/sidepanelwidget.cpp b/src/widgets/panel/sidepanelwidget.cpp index a1e55d8c..017b2c6f 100644 --- a/src/widgets/panel/sidepanelwidget.cpp +++ b/src/widgets/panel/sidepanelwidget.cpp @@ -2,57 +2,33 @@ // SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors #include "sidepanelwidget.h" +#include "colorgrabwidget.h" #include "src/core/qguiappcurrentscreen.h" #include "src/utils/colorutils.h" #include "src/utils/pathinfo.h" +#include "utilitypanel.h" +#include +#include // TODO remove #include #include #include +#include #include +#include #include #include #if defined(Q_OS_MACOS) #include #endif -class QColorPickingEventFilter : public QObject -{ -public: - explicit QColorPickingEventFilter(SidePanelWidget* pw, - QObject* parent = nullptr) - : QObject(parent) - , m_pw(pw) - {} - - bool eventFilter(QObject*, QEvent* event) override - { - event->accept(); - switch (event->type()) { - case QEvent::MouseMove: - return m_pw->handleMouseMove(static_cast(event)); - case QEvent::MouseButtonPress: - return m_pw->handleMouseButtonPressed( - static_cast(event)); - case QEvent::KeyPress: - return m_pw->handleKeyPress(static_cast(event)); - default: - break; - } - return false; - } - -private: - SidePanelWidget* m_pw; -}; - -//////////////////////// - SidePanelWidget::SidePanelWidget(QPixmap* p, QWidget* parent) : QWidget(parent) , m_pixmap(p) - , m_eventFilter(nullptr) { m_layout = new QVBoxLayout(this); + if (parent) { + parent->installEventFilter(this); + } QFormLayout* colorForm = new QFormLayout(); m_thicknessSlider = new QSlider(Qt::Horizontal); @@ -64,6 +40,23 @@ SidePanelWidget::SidePanelWidget(QPixmap* p, QWidget* parent) colorForm->addRow(tr("Active color:"), m_colorLabel); m_layout->addLayout(colorForm); + m_colorWheel = new color_widgets::ColorWheel(this); + m_colorWheel->setColor(m_color); + m_colorHex = new QLineEdit(this); + m_colorHex->setAlignment(Qt::AlignCenter); + + QColor background = this->palette().window().color(); + bool isDark = ColorUtils::colorIsDark(background); + QString modifier = + isDark ? PathInfo::whiteIconPath() : PathInfo::blackIconPath(); + QIcon grabIcon(modifier + "colorize.svg"); + m_colorGrabButton = new QPushButton(grabIcon, tr("Grab Color")); + + m_layout->addWidget(m_colorGrabButton); + m_layout->addWidget(m_colorWheel); + m_layout->addWidget(m_colorHex); + + // thickness sigslots connect(m_thicknessSlider, &QSlider::sliderMoved, this, @@ -72,22 +65,20 @@ SidePanelWidget::SidePanelWidget(QPixmap* p, QWidget* parent) &SidePanelWidget::thicknessChanged, this, &SidePanelWidget::updateThickness); - - QColor background = this->palette().window().color(); - bool isDark = ColorUtils::colorIsDark(background); - QString modifier = - isDark ? PathInfo::whiteIconPath() : PathInfo::blackIconPath(); - QIcon grabIcon(modifier + "colorize.svg"); - m_colorGrabButton = new QPushButton(grabIcon, QLatin1String("")); - updateGrabButton(false); + // color hex editor sigslots + connect(m_colorHex, &QLineEdit::editingFinished, this, [=]() { + if (!QColor::isValidColor(m_colorHex->text())) { + m_colorHex->setText(m_color.name(QColor::HexRgb)); + } else { + updateColor(m_colorHex->text()); + } + }); + // color grab button sigslots connect(m_colorGrabButton, &QPushButton::pressed, this, - &SidePanelWidget::colorGrabberActivated); - m_layout->addWidget(m_colorGrabButton); - - m_colorWheel = new color_widgets::ColorWheel(this); - m_colorWheel->setColor(m_color); + &SidePanelWidget::startColorGrab); + // color wheel sigslots connect(m_colorWheel, &color_widgets::ColorWheel::mouseReleaseOnColor, this, @@ -96,15 +87,20 @@ SidePanelWidget::SidePanelWidget(QPixmap* p, QWidget* parent) &color_widgets::ColorWheel::colorChanged, this, &SidePanelWidget::updateColorNoWheel); - m_layout->addWidget(m_colorWheel); } void SidePanelWidget::updateColor(const QColor& c) { m_color = c; + updateColorNoWheel(c); + m_colorWheel->setColor(c); +} + +void SidePanelWidget::updateColorNoWheel(const QColor& c) +{ m_colorLabel->setStyleSheet( QStringLiteral("QLabel { background-color : %1; }").arg(c.name())); - m_colorWheel->setColor(m_color); + m_colorHex->setText(c.name(QColor::HexRgb)); } void SidePanelWidget::updateThickness(const int& t) @@ -113,95 +109,70 @@ void SidePanelWidget::updateThickness(const int& t) m_thicknessSlider->setValue(m_thickness); } -void SidePanelWidget::updateColorNoWheel(const QColor& c) +void SidePanelWidget::startColorGrab() { - m_color = c; - m_colorLabel->setStyleSheet( - QStringLiteral("QLabel { background-color : %1; }").arg(c.name())); + m_revertColor = m_color; + m_colorGrabber = new ColorGrabWidget(m_pixmap); + connect(m_colorGrabber, + &ColorGrabWidget::colorUpdated, + this, + &SidePanelWidget::onColorUpdated); + connect(m_colorGrabber, + &ColorGrabWidget::colorGrabbed, + this, + &SidePanelWidget::onColorGrabFinished); + connect(m_colorGrabber, + &ColorGrabWidget::grabAborted, + this, + &SidePanelWidget::onColorGrabAborted); + + emit togglePanel(); + m_colorGrabber->startGrabbing(); } -void SidePanelWidget::colorGrabberActivated() +void SidePanelWidget::onColorGrabFinished() { - grabKeyboard(); - grabMouse(Qt::CrossCursor); - setMouseTracking(true); - m_colorBackup = m_color; - if (!m_eventFilter) { - m_eventFilter = new QColorPickingEventFilter(this, this); - } - installEventFilter(m_eventFilter); - updateGrabButton(true); -} - -void SidePanelWidget::releaseColorGrab() -{ - setMouseTracking(false); - removeEventFilter(m_eventFilter); - releaseMouse(); - releaseKeyboard(); - setFocus(); - updateGrabButton(false); -} - -QColor SidePanelWidget::grabPixmapColor(const QPoint& p) -{ - QColor c; - if (m_pixmap) { -#if defined(Q_OS_MACOS) - QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); - QPoint point = p; - if (currentScreen) { - point = QPoint((p.x() - currentScreen->geometry().x()) * - currentScreen->devicePixelRatio(), - (p.y() - currentScreen->geometry().y()) * - currentScreen->devicePixelRatio()); - } - QPixmap pixel = m_pixmap->copy(QRect(point, point)); -#else - QPixmap pixel = m_pixmap->copy(QRect(p, p)); -#endif - c = pixel.toImage().pixel(0, 0); - } - return c; -} - -bool SidePanelWidget::handleKeyPress(QKeyEvent* e) -{ - if (e->key() == Qt::Key_Escape) { - releaseColorGrab(); - updateColor(m_colorBackup); - } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { - updateColor(grabPixmapColor(QCursor::pos())); - releaseColorGrab(); - emit colorChanged(m_color); - } - return true; -} - -bool SidePanelWidget::handleMouseButtonPressed(QMouseEvent* e) -{ - if (m_colorGrabButton->geometry().contains(e->pos()) || - e->button() == Qt::RightButton) { - updateColorNoWheel(m_colorBackup); - } else if (e->button() == Qt::LeftButton) { - updateColor(grabPixmapColor(QCursor::pos())); - } - releaseColorGrab(); + finalizeGrab(); + m_color = m_colorGrabber->color(); emit colorChanged(m_color); - return true; } -bool SidePanelWidget::handleMouseMove(QMouseEvent* e) +void SidePanelWidget::onColorGrabAborted() { - updateColorNoWheel(grabPixmapColor(e->globalPos())); - return true; + finalizeGrab(); + // Restore color that was selected before we started grabbing + updateColor(m_revertColor); } -void SidePanelWidget::updateGrabButton(const bool activated) +void SidePanelWidget::onColorUpdated(const QColor& color) { - if (activated) { - m_colorGrabButton->setText(tr("Press ESC to cancel")); - } else { - m_colorGrabButton->setText(tr("Grab Color")); + updateColorNoWheel(color); +} + +void SidePanelWidget::finalizeGrab() +{ + emit togglePanel(); +} + +bool SidePanelWidget::eventFilter(QObject* obj, QEvent* event) +{ + if (event->type() == QEvent::ShortcutOverride) { + // Override Escape shortcut from CaptureWidget + auto* e = static_cast(event); + if (e->key() == Qt::Key_Escape && m_colorHex->hasFocus()) { + m_colorHex->clearFocus(); + e->accept(); + return true; + } + } else if (event->type() == QEvent::MouseButtonPress) { + // Clicks outside of the Color Hex editor + m_colorHex->clearFocus(); } + return QWidget::eventFilter(obj, event); +} + +void SidePanelWidget::hideEvent(QHideEvent* event) +{ + QWidget::hideEvent(event); + m_colorHex->clearFocus(); } diff --git a/src/widgets/panel/sidepanelwidget.h b/src/widgets/panel/sidepanelwidget.h index d726afe1..b8f00f51 100644 --- a/src/widgets/panel/sidepanelwidget.h +++ b/src/widgets/panel/sidepanelwidget.h @@ -9,6 +9,8 @@ class QVBoxLayout; class QPushButton; class QLabel; +class QLineEdit; +class ColorGrabWidget; class QColorPickingEventFilter; class QSlider; @@ -28,32 +30,30 @@ signals: public slots: void updateColor(const QColor& c); + void updateColorNoWheel(const QColor& c); void updateThickness(const int& t); private slots: - void updateColorNoWheel(const QColor& c); - -private slots: - void colorGrabberActivated(); - void releaseColorGrab(); + void startColorGrab(); + void onColorGrabFinished(); + void onColorGrabAborted(); + void onColorUpdated(const QColor& color); private: - QColor grabPixmapColor(const QPoint& p); + void finalizeGrab(); - bool handleKeyPress(QKeyEvent* e); - bool handleMouseButtonPressed(QMouseEvent* e); - bool handleMouseMove(QMouseEvent* e); - - void updateGrabButton(const bool activated); + bool eventFilter(QObject* obj, QEvent* event) override; + void hideEvent(QHideEvent* event) override; QVBoxLayout* m_layout; QPushButton* m_colorGrabButton; + ColorGrabWidget* m_colorGrabber; color_widgets::ColorWheel* m_colorWheel; QLabel* m_colorLabel; + QLineEdit* m_colorHex; QPixmap* m_pixmap; - QColor m_colorBackup; QColor m_color; + QColor m_revertColor; QSlider* m_thicknessSlider; int m_thickness; - QColorPickingEventFilter* m_eventFilter; };