// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors // Based on Lightscreen areadialog.cpp, Copyright 2017 Christian Kaiser // released under the GNU GPL2 // // Based on KDE's KSnapshot regiongrabber.cpp, revision 796531, Copyright 2007 // Luca Gugelmann released under the GNU LGPL // #include "capturewidget.h" #include "abstractlogger.h" #include "copytool.h" #include "src/config/cacheutils.h" #include "src/core/flameshot.h" #include "src/core/qguiappcurrentscreen.h" #include "src/tools/toolfactory.h" #include "src/utils/colorutils.h" #include "src/utils/screengrabber.h" #include "src/utils/screenshotsaver.h" #include "src/utils/systemnotification.h" #include "src/widgets/capture/colorpicker.h" #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" #include "src/widgets/updatenotificationwidget.h" #include #include #include #include #include #include #include #include #include #include #include #define MOUSE_DISTANCE_TO_START_MOVING 3 // CaptureWidget is the main component used to capture the screen. It contains // an area of selection with its respective buttons. // enableSaveWindow CaptureWidget::CaptureWidget(const CaptureRequest& req, bool fullScreen, QWidget* parent) : QWidget(parent) , m_mouseIsClicked(false) , m_captureDone(false) , m_previewEnabled(true) , m_adjustmentButtonPressed(false) , m_configError(false) , m_configErrorResolved(false) , m_activeButton(nullptr) , m_activeTool(nullptr) , m_toolWidget(nullptr) , m_colorPicker(nullptr) , m_lastMouseWheel(0) , m_updateNotificationWidget(nullptr) , m_activeToolIsMoved(false) , m_panel(nullptr) , m_sidePanel(nullptr) , m_selection(nullptr) , m_magnifier(nullptr) , m_existingObjectIsChanged(false) , m_startMove(false) , m_toolSizeByKeyboard(0) { m_undoStack.setUndoLimit(ConfigHandler().undoLimit()); m_context.circleCount = 1; // Base config of the widget m_eventFilter = new HoverEventFilter(this); connect(m_eventFilter, &HoverEventFilter::hoverIn, this, &CaptureWidget::childEnter); connect(m_eventFilter, &HoverEventFilter::hoverOut, this, &CaptureWidget::childLeave); setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_QuitOnClose, false); m_opacity = m_config.contrastOpacity(); m_uiColor = m_config.uiColor(); m_contrastUiColor = m_config.contrastUiColor(); setMouseTracking(true); initContext(fullScreen, req); #if (defined(Q_OS_WIN) || defined(Q_OS_MACOS)) // Top left of the whole set of screens QPoint topLeft(0, 0); #endif if (fullScreen) { // Grab Screenshot bool ok = true; m_context.screenshot = ScreenGrabber().grabEntireDesktop(ok); if (!ok) { AbstractLogger::error() << tr("Unable to capture screen"); this->close(); } m_context.origScreenshot = m_context.screenshot; #if defined(Q_OS_WIN) setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::SubWindow // Hides the taskbar icon ); for (QScreen* const screen : QGuiApplication::screens()) { QPoint topLeftScreen = screen->geometry().topLeft(); if (topLeftScreen.x() < topLeft.x()) { topLeft.setX(topLeftScreen.x()); } if (topLeftScreen.y() < topLeft.y()) { topLeft.setY(topLeftScreen.y()); } } move(topLeft); resize(pixmap().size()); #elif defined(Q_OS_MACOS) // Emulate fullscreen mode // setWindowFlags(Qt::WindowStaysOnTopHint | // Qt::BypassWindowManagerHint | // Qt::FramelessWindowHint | // Qt::NoDropShadowWindowHint | Qt::ToolTip | // Qt::Popup // ); QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); move(currentScreen->geometry().x(), currentScreen->geometry().y()); resize(currentScreen->size()); #else // Call cmake with -DFLAMESHOT_DEBUG_CAPTURE=ON to enable easier debugging #if !defined(FLAMESHOT_DEBUG_CAPTURE) setWindowFlags(Qt::BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool); resize(pixmap().size()); #endif #endif } QVector areas; if (m_context.fullscreen) { QPoint topLeftOffset = QPoint(0, 0); #if defined(Q_OS_WIN) topLeftOffset = topLeft; #endif #if defined(Q_OS_MACOS) // MacOS works just with one active display, so we need to append // just one current display and keep multiple displays logic for // other OS QRect r; QScreen* screen = QGuiAppCurrentScreen().currentScreen(); r = screen->geometry(); // all calculations are processed according to (0, 0) start // point so we need to move current object to (0, 0) r.moveTo(0, 0); areas.append(r); #else for (QScreen* const screen : QGuiApplication::screens()) { QRect r = screen->geometry(); r.moveTo(r.x() / screen->devicePixelRatio(), r.y() / screen->devicePixelRatio()); r.moveTo(r.topLeft() - topLeftOffset); areas.append(r); } #endif } else { areas.append(rect()); } m_buttonHandler = new ButtonHandler(this); m_buttonHandler->updateScreenRegions(areas); m_buttonHandler->hide(); initButtons(); initSelection(); // button handler must be initialized before initShortcuts(); // must be called after initSelection // init magnify if (m_config.showMagnifier()) { m_magnifier = new MagnifierWidget( m_context.screenshot, m_uiColor, m_config.squareMagnifier(), this); } // Init color picker m_colorPicker = new ColorPicker(this); connect(m_colorPicker, &ColorPicker::colorSelected, this, [this](const QColor& c) { m_context.mousePos = mapFromGlobal(QCursor::pos()); setDrawColor(c); }); m_colorPicker->hide(); // Init tool size sigslots connect(this, &CaptureWidget::toolSizeChanged, this, &CaptureWidget::onToolSizeChanged); // Init notification widget m_notifierBox = new NotifierBox(this); m_notifierBox->hide(); connect(m_notifierBox, &NotifierBox::hidden, this, [this]() { // Show cursor if it was hidden while adjusting tool size updateCursor(); m_toolSizeByKeyboard = 0; onToolSizeChanged(m_context.toolSize); onToolSizeSettled(m_context.toolSize); }); initPanel(); m_config.checkAndHandleError(); if (m_config.hasError()) { m_configError = true; } connect(ConfigHandler::getInstance(), &ConfigHandler::error, this, [=]() { m_configError = true; m_configErrorResolved = false; OverlayMessage::instance()->update(); }); connect( ConfigHandler::getInstance(), &ConfigHandler::errorResolved, this, [=]() { m_configError = false; m_configErrorResolved = true; OverlayMessage::instance()->update(); }); OverlayMessage::init(this, QGuiAppCurrentScreen().currentScreen()->geometry()); if (m_config.showHelp()) { initHelpMessage(); OverlayMessage::push(m_helpMessage); } updateCursor(); } CaptureWidget::~CaptureWidget() { #if defined(Q_OS_MACOS) for (QWidget* widget : qApp->topLevelWidgets()) { QString className(widget->metaObject()->className()); if (0 == className.compare(CaptureWidget::staticMetaObject.className())) { widget->showNormal(); widget->hide(); break; } } #endif if (m_captureDone) { auto lastRegion = m_selection->geometry(); setLastRegion(lastRegion); QRect geometry(m_context.selection); geometry.setTopLeft(geometry.topLeft() + m_context.widgetOffset); Flameshot::instance()->exportCapture( pixmap(), geometry, m_context.request); } else { emit Flameshot::instance()->captureFailed(); } } void CaptureWidget::initButtons() { auto allButtonTypes = CaptureToolButton::getIterableButtonTypes(); auto visibleButtonTypes = m_config.buttons(); if ((m_context.request.tasks() == CaptureRequest::NO_TASK) || (m_context.request.tasks() == CaptureRequest::PRINT_GEOMETRY)) { allButtonTypes.removeOne(CaptureTool::TYPE_ACCEPT); visibleButtonTypes.removeOne(CaptureTool::TYPE_ACCEPT); } else { // Remove irrelevant buttons from both lists for (auto* buttonList : { &allButtonTypes, &visibleButtonTypes }) { buttonList->removeOne(CaptureTool::TYPE_SAVE); buttonList->removeOne(CaptureTool::TYPE_COPY); buttonList->removeOne(CaptureTool::TYPE_IMAGEUPLOADER); buttonList->removeOne(CaptureTool::TYPE_OPEN_APP); buttonList->removeOne(CaptureTool::TYPE_PIN); } } QVector vectorButtons; // Add all buttons but hide those that were disabled in the Interface config // This will allow keyboard shortcuts for those buttons to work for (CaptureTool::Type t : allButtonTypes) { auto* b = new CaptureToolButton(t, this); if (t == CaptureTool::TYPE_SELECTIONINDICATOR) { m_sizeIndButton = b; } b->setColor(m_uiColor); b->hide(); // must be enabled for SelectionWidget's eventFilter to work correctly b->setAttribute(Qt::WA_NoMousePropagation); makeChild(b); switch (t) { case CaptureTool::TYPE_UNDO: case CaptureTool::TYPE_REDO: // nothing to do, just skip non-dynamic buttons with existing // hard coded slots break; default: // Set shortcuts for a tool QString shortcut = ConfigHandler().shortcut(QVariant::fromValue(t).toString()); if (!shortcut.isNull()) { auto shortcuts = newShortcut(shortcut, this, nullptr); for (auto* shortcut : shortcuts) { connect(shortcut, &QShortcut::activated, this, [=]() { setState(b); }); } } break; } m_tools[t] = b->tool(); connect(b->tool(), &CaptureTool::requestAction, this, &CaptureWidget::handleToolSignal); if (visibleButtonTypes.contains(t)) { connect(b, &CaptureToolButton::pressedButtonLeftClick, this, &CaptureWidget::handleButtonLeftClick); if (b->tool()->isSelectable()) { connect(b, &CaptureToolButton::pressedButtonRightClick, this, &CaptureWidget::handleButtonRightClick); } vectorButtons << b; } } m_buttonHandler->setButtons(vectorButtons); } void CaptureWidget::handleButtonRightClick(CaptureToolButton* b) { if (!b) { return; } // if button already selected, do not deselect it on right click if (!m_activeButton || m_activeButton != b) { setState(b); } if (!m_panel->isVisible()) { m_panel->show(); } } void CaptureWidget::handleButtonLeftClick(CaptureToolButton* b) { if (!b) { return; } setState(b); } void CaptureWidget::initHelpMessage() { QList> keyMap; if (keyMap.isEmpty()) { keyMap << QPair(tr("Mouse"), tr("Select screenshot area")); using CT = CaptureTool; for (auto toolType : { CT::TYPE_ACCEPT, CT::TYPE_SAVE, CT::TYPE_COPY }) { if (!m_tools.contains(toolType)) { continue; } auto* tool = m_tools[toolType]; QString shortcut = ConfigHandler().shortcut( QVariant::fromValue(toolType).toString()); shortcut.replace("Return", "Enter"); if (!shortcut.isEmpty()) { keyMap << QPair(shortcut, tool->description()); } } keyMap << QPair(tr("Mouse Wheel"), tr("Change tool size")); keyMap << QPair(tr("Right Click"), tr("Show color picker")); keyMap << QPair(ConfigHandler().shortcut("TYPE_TOGGLE_PANEL"), tr("Open side panel")); keyMap << QPair(tr("Esc"), tr("Exit")); } m_helpMessage = OverlayMessage::compileFromKeyMap(keyMap); } QPixmap CaptureWidget::pixmap() { return m_context.selectedScreenshotArea(); } // Finish whatever the current tool is doing, if there is a current active // tool. bool CaptureWidget::commitCurrentTool() { if (m_activeTool) { processPixmapWithTool(&m_context.screenshot, m_activeTool); if (m_activeTool->isValid() && !m_activeTool->editMode() && m_toolWidget) { pushToolToStack(); } if (m_toolWidget) { m_toolWidget->update(); } releaseActiveTool(); return true; } return false; } void CaptureWidget::deleteToolWidgetOrClose() { if (m_activeButton != nullptr) { uncheckActiveTool(); } else if (m_panel->activeLayerIndex() >= 0) { // remove active tool selection m_panel->setActiveLayer(-1); } else if (m_panel->isVisible()) { // hide panel if visible m_panel->hide(); } else if (m_toolWidget) { // delete toolWidget if exists m_toolWidget->hide(); delete m_toolWidget; m_toolWidget = nullptr; } else if (m_colorPicker && m_colorPicker->isVisible()) { m_colorPicker->hide(); } else { // close CaptureWidget close(); } } void CaptureWidget::releaseActiveTool() { if (m_activeTool) { if (m_activeTool->editMode()) { // Object shouldn't be deleted here because it is in the undo/redo // stack, just set current pointer to null m_activeTool->setEditMode(false); if (m_activeTool->isChanged()) { pushObjectsStateToUndoStack(); } } else { delete m_activeTool; } m_activeTool = nullptr; } if (m_toolWidget) { m_toolWidget->hide(); delete m_toolWidget; m_toolWidget = nullptr; } } void CaptureWidget::uncheckActiveTool() { // uncheck active tool m_panel->setToolWidget(nullptr); m_activeButton->setColor(m_uiColor); updateTool(activeButtonTool()); m_activeButton = nullptr; releaseActiveTool(); updateSelectionState(); updateCursor(); } void CaptureWidget::paintEvent(QPaintEvent* paintEvent) { Q_UNUSED(paintEvent) QPainter painter(this); painter.drawPixmap(0, 0, m_context.screenshot); if (m_activeTool && m_mouseIsClicked) { painter.save(); m_activeTool->process(painter, m_context.screenshot); painter.restore(); } else if (m_previewEnabled && activeButtonTool() && m_activeButton->tool()->showMousePreview()) { painter.save(); m_activeButton->tool()->paintMousePreview(painter, m_context); painter.restore(); } // draw inactive region drawInactiveRegion(&painter); if (!isActiveWindow()) { drawErrorMessage( tr("Flameshot has lost focus. Keyboard shortcuts won't " "work until you click somewhere."), &painter); } else if (m_configError) { drawErrorMessage(ConfigHandler().errorMessage(), &painter); } else if (m_configErrorResolved) { drawErrorMessage(tr("Configuration error resolved. Launch `flameshot " "gui` again to apply it."), &painter); } } void CaptureWidget::showColorPicker(const QPoint& pos) { // Try to select new object if current pos out of active object auto toolItem = activeToolObject(); if (!toolItem || (toolItem && !toolItem->boundingRect().contains(pos))) { selectToolItemAtPos(pos); } // save current state for undo/redo stack if (m_panel->activeLayerIndex() >= 0) { m_captureToolObjectsBackup = m_captureToolObjects; } // Call color picker m_colorPicker->move(pos.x() - m_colorPicker->width() / 2, pos.y() - m_colorPicker->height() / 2); m_colorPicker->raise(); m_colorPicker->show(); } bool CaptureWidget::startDrawObjectTool(const QPoint& pos) { if (activeButtonToolType() != CaptureTool::NONE && activeButtonToolType() != CaptureTool::TYPE_MOVESELECTION) { if (commitCurrentTool()) { return false; } m_activeTool = m_activeButton->tool()->copy(this); connect(this, &CaptureWidget::colorChanged, m_activeTool, &CaptureTool::onColorChanged); connect(this, &CaptureWidget::toolSizeChanged, m_activeTool, &CaptureTool::onSizeChanged); connect(m_activeTool, &CaptureTool::requestAction, this, &CaptureWidget::handleToolSignal); m_context.mousePos = pos; m_activeTool->drawStart(m_context); // TODO this is the wrong place to do this if (m_activeTool->type() == CaptureTool::TYPE_CIRCLECOUNT) { m_activeTool->setCount(m_context.circleCount++); } return true; } return false; } void CaptureWidget::pushObjectsStateToUndoStack() { m_undoStack.push(new ModificationCommand( this, m_captureToolObjects, m_captureToolObjectsBackup)); m_captureToolObjectsBackup.clear(); } int CaptureWidget::selectToolItemAtPos(const QPoint& pos) { // Try to select existing tool, "-1" - no active tool int activeLayerIndex = -1; auto selectionMouseSide = m_selection->getMouseSide(pos); if (m_activeButton.isNull() && m_captureToolObjects.captureToolObjects().size() > 0 && (selectionMouseSide == SelectionWidget::NO_SIDE || selectionMouseSide == SelectionWidget::CENTER)) { auto toolItem = activeToolObject(); if (!toolItem || (toolItem && !toolItem->boundingRect().contains(pos))) { activeLayerIndex = m_captureToolObjects.find(pos, size()); int oldToolSize = m_context.toolSize; m_panel->setActiveLayer(activeLayerIndex); drawObjectSelection(); if (oldToolSize != m_context.toolSize) { emit toolSizeChanged(m_context.toolSize); } } } return activeLayerIndex; } void CaptureWidget::mousePressEvent(QMouseEvent* e) { activateWindow(); m_startMove = false; m_startMovePos = QPoint(); m_mousePressedPos = e->pos(); m_activeToolOffsetToMouseOnStart = QPoint(); if (m_colorPicker->isVisible()) { updateCursor(); return; } // reset object selection if capture area selection is active if (m_selection->getMouseSide(e->pos()) != SelectionWidget::CENTER) { m_panel->setActiveLayer(-1); } if (e->button() == Qt::RightButton) { if (m_activeTool && m_activeTool->editMode()) { return; } showColorPicker(m_mousePressedPos); return; } else if (e->button() == Qt::LeftButton) { m_mouseIsClicked = true; // Click using a tool excluding tool MOVE if (startDrawObjectTool(m_mousePressedPos)) { // return if success return; } } // Commit current tool if it has edit widget and mouse click is outside // of it if (m_toolWidget && !m_toolWidget->geometry().contains(e->pos())) { commitCurrentTool(); m_panel->setToolWidget(nullptr); drawToolsData(); updateLayersPanel(); } selectToolItemAtPos(m_mousePressedPos); updateSelectionState(); updateCursor(); } void CaptureWidget::mouseDoubleClickEvent(QMouseEvent* event) { int activeLayerIndex = m_panel->activeLayerIndex(); if (activeLayerIndex != -1) { // Start object editing auto activeTool = m_captureToolObjects.at(activeLayerIndex); if (activeTool && activeTool->type() == CaptureTool::TYPE_TEXT) { m_activeTool = activeTool; m_mouseIsClicked = false; m_context.mousePos = *m_activeTool->pos(); m_captureToolObjectsBackup = m_captureToolObjects; m_activeTool->setEditMode(true); drawToolsData(); updateLayersPanel(); handleToolSignal(CaptureTool::REQ_ADD_CHILD_WIDGET); m_panel->setToolWidget(m_activeTool->configurationWidget()); } } else if (m_selection->geometry().contains(event->pos())) { if ((event->button() == Qt::LeftButton) && (m_config.copyOnDoubleClick())) { CopyTool copyTool; connect(©Tool, &CopyTool::requestAction, this, &CaptureWidget::handleToolSignal); copyTool.pressed(m_context); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } } } void CaptureWidget::mouseMoveEvent(QMouseEvent* e) { if (m_magnifier) { if (!m_activeButton) { m_magnifier->show(); m_magnifier->update(); } else { m_magnifier->hide(); } } m_context.mousePos = e->pos(); if (e->buttons() != Qt::LeftButton) { updateTool(activeButtonTool()); updateCursor(); return; } // The rest assumes that left mouse button is clicked if (!m_activeButton && m_panel->activeLayerIndex() >= 0) { // Move existing object if (!m_startMove) { // Check for the minimal offset to start moving an object if (m_startMovePos.isNull()) { m_startMovePos = e->pos(); } if ((e->pos() - m_startMovePos).manhattanLength() > MOUSE_DISTANCE_TO_START_MOVING) { m_startMove = true; } } if (m_startMove) { QPointer activeTool = m_captureToolObjects.at(m_panel->activeLayerIndex()); if (m_activeToolOffsetToMouseOnStart.isNull()) { setCursor(Qt::ClosedHandCursor); m_activeToolOffsetToMouseOnStart = e->pos() - *activeTool->pos(); } if (!m_activeToolIsMoved) { // save state before movement for undo stack m_captureToolObjectsBackup = m_captureToolObjects; } m_activeToolIsMoved = true; // update the old region of the selection, margins are added to // ensure selection outline is updated too update(paddedUpdateRect(activeTool->boundingRect())); activeTool->move(e->pos() - m_activeToolOffsetToMouseOnStart); drawToolsData(); } } else if (m_activeTool) { // drawing with a tool if (m_adjustmentButtonPressed) { m_activeTool->drawMoveWithAdjustment(e->pos()); } else { m_activeTool->drawMove(e->pos()); } // update drawing object updateTool(m_activeTool); // Hides the buttons under the mouse. If the mouse leaves, it shows // them. if (m_buttonHandler->buttonsAreInside()) { const bool containsMouse = m_buttonHandler->contains(m_context.mousePos); if (containsMouse) { m_buttonHandler->hide(); } else if (m_selection->isVisible()) { m_buttonHandler->show(); } } } updateCursor(); } void CaptureWidget::mouseReleaseEvent(QMouseEvent* e) { if (e->button() == Qt::LeftButton && m_colorPicker->isVisible()) { // Color picker if (m_colorPicker->isVisible() && m_panel->activeLayerIndex() >= 0 && m_context.color.isValid()) { pushObjectsStateToUndoStack(); } m_colorPicker->hide(); if (!m_context.color.isValid()) { m_context.color = ConfigHandler().drawColor(); m_panel->show(); } } else if (m_mouseIsClicked) { if (m_activeTool) { // end draw/edit m_activeTool->drawEnd(m_context.mousePos); if (m_activeTool->isValid()) { pushToolToStack(); } else if (!m_toolWidget) { releaseActiveTool(); } } else { if (m_activeToolIsMoved) { m_activeToolIsMoved = false; pushObjectsStateToUndoStack(); } } } m_mouseIsClicked = false; m_activeToolIsMoved = false; updateSelectionState(); updateCursor(); } /** * Was updateThickness. * - Update tool mouse preview * - Show notifier box displaying the new thickness * - Update selected object thickness */ void CaptureWidget::setToolSize(int size) { int oldSize = m_context.toolSize; m_context.toolSize = qBound(1, size, maxToolSize); updateTool(activeButtonTool()); QPoint topLeft = QGuiAppCurrentScreen().currentScreen()->geometry().topLeft(); int offset = m_notifierBox->width() / 4; m_notifierBox->move(mapFromGlobal(topLeft) + QPoint(offset, offset)); m_notifierBox->showMessage(QString::number(m_context.toolSize)); if (m_context.toolSize != oldSize) { emit toolSizeChanged(m_context.toolSize); } } void CaptureWidget::keyPressEvent(QKeyEvent* e) { // If the key is a digit, change the tool size bool ok; int digit = e->text().toInt(&ok); if (ok && ((e->modifiers() == Qt::NoModifier) || e->modifiers() == Qt::KeypadModifier)) { // digit received m_toolSizeByKeyboard = 10 * m_toolSizeByKeyboard + digit; setToolSize(m_toolSizeByKeyboard); if (m_context.toolSize != m_toolSizeByKeyboard) { // The tool size was out of range and was clipped by setToolSize m_toolSizeByKeyboard = 0; } } else { m_toolSizeByKeyboard = 0; } if (!m_selection->isVisible()) { return; } else if (e->key() == Qt::Key_Control) { m_adjustmentButtonPressed = true; updateCursor(); } else if (e->key() == Qt::Key_Enter) { // Make no difference for Return and Enter keys QCoreApplication::postEvent( this, new QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier)); } } void CaptureWidget::keyReleaseEvent(QKeyEvent* e) { if (e->key() == Qt::Key_Control) { m_adjustmentButtonPressed = false; updateCursor(); } } void CaptureWidget::wheelEvent(QWheelEvent* e) { /* Mouse scroll usually gives value 120, not more or less, just how many * times. * Touchpad gives the value 2 or more (usually 2-8), it doesn't give * too big values like mouse wheel on normal scrolling, so it is almost * impossible to scroll. It's easier to calculate number of requests and do * not accept events faster that one in 200ms. * */ int toolSizeOffset = 0; if (e->angleDelta().y() >= 60) { // mouse scroll (wheel) increment toolSizeOffset = 1; } else if (e->angleDelta().y() <= -60) { // mouse scroll (wheel) decrement toolSizeOffset = -1; } else { // touchpad scroll qint64 current = QDateTime::currentMSecsSinceEpoch(); if ((current - m_lastMouseWheel) > 200) { if (e->angleDelta().y() > 0) { toolSizeOffset = 1; } else if (e->angleDelta().y() < 0) { toolSizeOffset = -1; } m_lastMouseWheel = current; } else { return; } } setToolSize(m_context.toolSize + toolSizeOffset); } void CaptureWidget::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); m_context.widgetOffset = mapToGlobal(QPoint(0, 0)); if (!m_context.fullscreen) { m_panel->setFixedHeight(height()); m_buttonHandler->updateScreenRegions(rect()); } } void CaptureWidget::moveEvent(QMoveEvent* e) { QWidget::moveEvent(e); m_context.widgetOffset = mapToGlobal(QPoint(0, 0)); } void CaptureWidget::changeEvent(QEvent* e) { if (e->type() == QEvent::ActivationChange) { QPoint bottomRight = rect().bottomRight(); // Update the message in the bottom right corner. A rough estimate is // used for the update rect update(QRect(bottomRight - QPoint(1000, 200), bottomRight)); } } void CaptureWidget::initContext(bool fullscreen, const CaptureRequest& req) { m_context.color = m_config.drawColor(); m_context.widgetOffset = mapToGlobal(QPoint(0, 0)); m_context.mousePos = mapFromGlobal(QCursor::pos()); m_context.toolSize = m_config.drawThickness(); m_context.fullscreen = fullscreen; // initialize m_context.request m_context.request = req; } void CaptureWidget::initPanel() { QRect panelRect = rect(); if (m_context.fullscreen) { #if (defined(Q_OS_MACOS) || defined(Q_OS_LINUX)) QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); panelRect = currentScreen->geometry(); auto devicePixelRatio = currentScreen->devicePixelRatio(); panelRect.moveTo(static_cast(panelRect.x() / devicePixelRatio), static_cast(panelRect.y() / devicePixelRatio)); #else panelRect = QGuiApplication::primaryScreen()->geometry(); auto devicePixelRatio = QGuiApplication::primaryScreen()->devicePixelRatio(); panelRect.moveTo(panelRect.x() / devicePixelRatio, panelRect.y() / devicePixelRatio); #endif } if (ConfigHandler().showSidePanelButton()) { auto* panelToggleButton = new OrientablePushButton(tr("Tool Settings"), this); makeChild(panelToggleButton); panelToggleButton->setColor(m_uiColor); panelToggleButton->setOrientation( OrientablePushButton::VerticalBottomToTop); #if defined(Q_OS_MACOS) panelToggleButton->move( 0, static_cast(panelRect.height() / 2) - static_cast(panelToggleButton->width() / 2)); #else panelToggleButton->move(panelRect.x(), panelRect.y() + panelRect.height() / 2 - panelToggleButton->width() / 2); #endif panelToggleButton->setCursor(Qt::ArrowCursor); (new DraggableWidgetMaker(this))->makeDraggable(panelToggleButton); connect(panelToggleButton, &QPushButton::clicked, this, &CaptureWidget::togglePanel); } m_panel = new UtilityPanel(this); m_panel->hide(); makeChild(m_panel); #if defined(Q_OS_MACOS) QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); panelRect.moveTo(mapFromGlobal(panelRect.topLeft())); m_panel->setFixedWidth(static_cast(m_colorPicker->width() * 1.5)); m_panel->setFixedHeight(currentScreen->geometry().height()); #else panelRect.moveTo(mapFromGlobal(panelRect.topLeft())); panelRect.setWidth(m_colorPicker->width() * 1.5); m_panel->setGeometry(panelRect); #endif connect(m_panel, &UtilityPanel::layerChanged, this, &CaptureWidget::updateActiveLayer); connect(m_panel, &UtilityPanel::moveUpClicked, this, &CaptureWidget::onMoveCaptureToolUp); connect(m_panel, &UtilityPanel::moveDownClicked, this, &CaptureWidget::onMoveCaptureToolDown); m_sidePanel = new SidePanelWidget(&m_context.screenshot, this); connect(m_sidePanel, &SidePanelWidget::colorChanged, this, &CaptureWidget::setDrawColor); connect(m_sidePanel, &SidePanelWidget::toolSizeChanged, this, &CaptureWidget::onToolSizeChanged); connect(this, &CaptureWidget::colorChanged, m_sidePanel, &SidePanelWidget::onColorChanged); connect(this, &CaptureWidget::toolSizeChanged, m_sidePanel, &SidePanelWidget::onToolSizeChanged); connect(m_sidePanel, &SidePanelWidget::togglePanel, m_panel, &UtilityPanel::toggle); // TODO replace with a CaptureWidget signal emit m_sidePanel->colorChanged(m_context.color); emit toolSizeChanged(m_context.toolSize); m_panel->pushWidget(m_sidePanel); // Fill undo/redo/history list widget m_panel->fillCaptureTools(m_captureToolObjects.captureToolObjects()); } void CaptureWidget::showAppUpdateNotification(const QString& appLatestVersion, const QString& appLatestUrl) { if (!ConfigHandler().checkForUpdates()) { // option check for updates disabled return; } if (nullptr == m_updateNotificationWidget) { m_updateNotificationWidget = new UpdateNotificationWidget(this, appLatestVersion, appLatestUrl); } #if defined(Q_OS_MACOS) int ax = (width() - m_updateNotificationWidget->width()) / 2; #elif (defined(Q_OS_LINUX) && QT_VERSION < QT_VERSION_CHECK(5, 10, 0)) QRect helpRect = QGuiApplication::primaryScreen()->geometry(); int ax = helpRect.left() + ((helpRect.width() - m_updateNotificationWidget->width()) / 2); #else QRect helpRect; QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); if (currentScreen) { helpRect = currentScreen->geometry(); } else { helpRect = QGuiApplication::primaryScreen()->geometry(); } int ax = helpRect.left() + ((helpRect.width() - m_updateNotificationWidget->width()) / 2); #endif m_updateNotificationWidget->move(ax, 0); makeChild(m_updateNotificationWidget); m_updateNotificationWidget->show(); } void CaptureWidget::initSelection() { // Be mindful of the order of statements, so that slots are called properly m_selection = new SelectionWidget(m_uiColor, this); QRect initialSelection = m_context.request.initialSelection(); connect(m_selection, &SelectionWidget::geometryChanged, this, [this]() { QRect constrainedToCaptureArea = m_selection->geometry().intersected(rect()); m_context.selection = extendedRect(constrainedToCaptureArea); updateSizeIndicator(); m_buttonHandler->hide(); updateCursor(); OverlayMessage::pop(); }); connect(m_selection, &SelectionWidget::geometrySettled, this, [this]() { if (m_selection->isVisibleTo(this)) { auto& req = m_context.request; if (req.tasks() & CaptureRequest::ACCEPT_ON_SELECT) { req.removeTask(CaptureRequest::ACCEPT_ON_SELECT); m_captureDone = true; close(); } m_buttonHandler->updatePosition(m_selection->geometry()); m_buttonHandler->show(); } else { m_buttonHandler->hide(); } }); connect(m_selection, &SelectionWidget::visibilityChanged, this, [this]() { if (!m_selection->isVisible() && !m_helpMessage.isEmpty()) { OverlayMessage::push(m_helpMessage); } }); if (!initialSelection.isNull()) { initialSelection.moveTopLeft(initialSelection.topLeft() - mapToGlobal({})); } m_selection->setGeometry(initialSelection); m_selection->setVisible(!initialSelection.isNull()); if (!initialSelection.isNull()) { m_context.selection = extendedRect(m_selection->geometry()); emit m_selection->geometrySettled(); } } void CaptureWidget::setState(CaptureToolButton* b) { if (!b) { return; } commitCurrentTool(); if (m_toolWidget && m_activeTool) { if (m_activeTool->isValid()) { pushToolToStack(); } else { releaseActiveTool(); } } if (m_activeButton != b) { auto backup = m_activeTool; // The tool is active during the pressed(). // This must be done in order to handle tool requests correctly. m_activeTool = b->tool(); m_activeTool->pressed(m_context); m_activeTool = backup; } if (b->tool()->isSelectable()) { if (m_activeButton != b) { if (m_activeButton) { m_activeButton->setColor(m_uiColor); } m_activeButton = b; m_activeButton->setColor(m_contrastUiColor); m_panel->setActiveLayer(-1); m_panel->setToolWidget(b->tool()->configurationWidget()); } else if (m_activeButton) { m_panel->clearToolWidget(); m_activeButton->setColor(m_uiColor); m_activeButton = nullptr; } m_context.toolSize = ConfigHandler().toolSize(activeButtonToolType()); emit toolSizeChanged(m_context.toolSize); updateCursor(); updateSelectionState(); updateTool(b->tool()); } } void CaptureWidget::handleToolSignal(CaptureTool::Request r) { switch (r) { case CaptureTool::REQ_CLOSE_GUI: close(); break; case CaptureTool::REQ_HIDE_GUI: hide(); break; case CaptureTool::REQ_UNDO_MODIFICATION: undo(); break; case CaptureTool::REQ_REDO_MODIFICATION: redo(); break; case CaptureTool::REQ_SHOW_COLOR_PICKER: // TODO break; case CaptureTool::REQ_CAPTURE_DONE_OK: m_captureDone = true; break; case CaptureTool::REQ_CLEAR_SELECTION: if (m_panel->activeLayerIndex() >= 0) { m_panel->setActiveLayer(-1); drawToolsData(false); } break; case CaptureTool::REQ_ADD_CHILD_WIDGET: if (!m_activeTool) { break; } if (m_toolWidget) { m_toolWidget->hide(); delete m_toolWidget; m_toolWidget = nullptr; } m_toolWidget = m_activeTool->widget(); if (m_toolWidget) { makeChild(m_toolWidget); m_toolWidget->move(m_context.mousePos); m_toolWidget->show(); m_toolWidget->setFocus(); } break; case CaptureTool::REQ_ADD_EXTERNAL_WIDGETS: if (!m_activeTool) { break; } else { QWidget* w = m_activeTool->widget(); w->setAttribute(Qt::WA_DeleteOnClose); w->activateWindow(); w->show(); } break; case CaptureTool::REQ_INCREASE_TOOL_SIZE: setToolSize(m_context.toolSize + 1); break; case CaptureTool::REQ_DECREASE_TOOL_SIZE: setToolSize(m_context.toolSize - 1); break; default: break; } } /** * Was setDrawThickness * - Update config options * - Update tool object thickness */ void CaptureWidget::onToolSizeChanged(int t) { m_context.toolSize = t; CaptureTool* tool = activeButtonTool(); if (tool && tool->showMousePreview()) { setCursor(Qt::BlankCursor); tool->onSizeChanged(t); } // update tool size of object being drawn if (m_activeTool != nullptr) { updateTool(m_activeTool); } // update tool size of selected object auto toolItem = activeToolObject(); if (toolItem) { // Change thickness toolItem->onSizeChanged(t); if (!m_existingObjectIsChanged) { m_captureToolObjectsBackup = m_captureToolObjects; m_existingObjectIsChanged = true; } drawToolsData(); updateTool(toolItem); } // Force a repaint to prevent artifacting this->repaint(); } void CaptureWidget::onToolSizeSettled(int size) { m_config.setToolSize(activeButtonToolType(), size); } void CaptureWidget::setDrawColor(const QColor& c) { m_context.color = c; if (m_context.color.isValid()) { ConfigHandler().setDrawColor(m_context.color); emit colorChanged(c); // Update mouse preview updateTool(activeButtonTool()); // change color for the active tool auto toolItem = activeToolObject(); if (toolItem) { // Change color toolItem->onColorChanged(c); drawToolsData(); } } } void CaptureWidget::updateActiveLayer(int layer) { // TODO - refactor this part, make all objects to work with // m_activeTool->isChanged() and remove m_existingObjectIsChanged if (m_activeTool && m_activeTool->type() == CaptureTool::TYPE_TEXT && m_activeTool->isChanged()) { commitCurrentTool(); } if (m_toolWidget) { // Release active tool if it is in the editing mode but not changed and // has editing widget (ex: text tool) releaseActiveTool(); } if (m_existingObjectIsChanged) { m_existingObjectIsChanged = false; pushObjectsStateToUndoStack(); } drawToolsData(); drawObjectSelection(); updateSelectionState(); } void CaptureWidget::onMoveCaptureToolUp(int captureToolIndex) { m_captureToolObjectsBackup = m_captureToolObjects; pushObjectsStateToUndoStack(); auto tool = m_captureToolObjects.at(captureToolIndex); m_captureToolObjects.removeAt(captureToolIndex); m_captureToolObjects.insert(captureToolIndex - 1, tool); updateLayersPanel(); } void CaptureWidget::onMoveCaptureToolDown(int captureToolIndex) { m_captureToolObjectsBackup = m_captureToolObjects; pushObjectsStateToUndoStack(); auto tool = m_captureToolObjects.at(captureToolIndex); m_captureToolObjects.removeAt(captureToolIndex); m_captureToolObjects.insert(captureToolIndex + 1, tool); updateLayersPanel(); } void CaptureWidget::selectAll() { m_selection->show(); m_selection->setGeometry(rect()); emit m_selection->geometrySettled(); m_buttonHandler->show(); updateSelectionState(); } void CaptureWidget::removeToolObject(int index) { --index; if (index >= 0 && index < m_captureToolObjects.size()) { // in case this tool is circle counter int removedCircleCount = -1; const CaptureTool::Type currentToolType = m_captureToolObjects.at(index)->type(); m_captureToolObjectsBackup = m_captureToolObjects; update( paddedUpdateRect(m_captureToolObjects.at(index)->boundingRect())); if (currentToolType == CaptureTool::TYPE_CIRCLECOUNT) { removedCircleCount = m_captureToolObjects.at(index)->count(); --m_context.circleCount; // Decrement circle counter numbers starting from deleted circle for (int cnt = 0; cnt < m_captureToolObjects.size(); cnt++) { auto toolItem = m_captureToolObjects.at(cnt); if (toolItem->type() != CaptureTool::TYPE_CIRCLECOUNT) { continue; } auto circleTool = m_captureToolObjects.at(cnt); if (circleTool->count() >= removedCircleCount) { circleTool->setCount(circleTool->count() - 1); } } } m_captureToolObjects.removeAt(index); pushObjectsStateToUndoStack(); drawToolsData(); updateLayersPanel(); } } void CaptureWidget::initShortcuts() { newShortcut( QKeySequence(ConfigHandler().shortcut("TYPE_UNDO")), this, SLOT(undo())); newShortcut( QKeySequence(ConfigHandler().shortcut("TYPE_REDO")), this, SLOT(redo())); newShortcut(QKeySequence(ConfigHandler().shortcut("TYPE_TOGGLE_PANEL")), this, SLOT(togglePanel())); newShortcut(QKeySequence(ConfigHandler().shortcut("TYPE_RESIZE_LEFT")), m_selection, SLOT(resizeLeft())); newShortcut(QKeySequence(ConfigHandler().shortcut("TYPE_RESIZE_RIGHT")), m_selection, SLOT(resizeRight())); newShortcut(QKeySequence(ConfigHandler().shortcut("TYPE_RESIZE_UP")), m_selection, SLOT(resizeUp())); newShortcut(QKeySequence(ConfigHandler().shortcut("TYPE_RESIZE_DOWN")), m_selection, SLOT(resizeDown())); newShortcut(QKeySequence(ConfigHandler().shortcut("TYPE_MOVE_LEFT")), m_selection, SLOT(moveLeft())); newShortcut(QKeySequence(ConfigHandler().shortcut("TYPE_MOVE_RIGHT")), m_selection, SLOT(moveRight())); newShortcut(QKeySequence(ConfigHandler().shortcut("TYPE_MOVE_UP")), m_selection, SLOT(moveUp())); newShortcut(QKeySequence(ConfigHandler().shortcut("TYPE_MOVE_DOWN")), m_selection, SLOT(moveDown())); newShortcut( QKeySequence(ConfigHandler().shortcut("TYPE_DELETE_CURRENT_TOOL")), this, SLOT(deleteCurrentTool())); newShortcut( QKeySequence(ConfigHandler().shortcut("TYPE_COMMIT_CURRENT_TOOL")), this, SLOT(commitCurrentTool())); newShortcut(QKeySequence(ConfigHandler().shortcut("TYPE_SELECT_ALL")), this, SLOT(selectAll())); newShortcut(Qt::Key_Escape, this, SLOT(deleteToolWidgetOrClose())); } void CaptureWidget::deleteCurrentTool() { int oldToolSize = m_context.toolSize; m_panel->slotButtonDelete(true); drawObjectSelection(); if (oldToolSize != m_context.toolSize) { emit toolSizeChanged(m_context.toolSize); } } void CaptureWidget::updateSizeIndicator() { if (m_sizeIndButton) { const QRect& selection = extendedSelection(); m_sizeIndButton->setText(QStringLiteral("%1\n%2") .arg(selection.width()) .arg(selection.height())); } } void CaptureWidget::updateCursor() { if (m_colorPicker && m_colorPicker->isVisible()) { setCursor(Qt::ArrowCursor); } else if (m_activeButton != nullptr && activeButtonToolType() != CaptureTool::TYPE_MOVESELECTION) { setCursor(Qt::CrossCursor); } else if (m_selection->getMouseSide(mapFromGlobal(QCursor::pos())) != SelectionWidget::NO_SIDE) { setCursor(m_selection->cursor()); } else if (activeButtonToolType() == CaptureTool::TYPE_MOVESELECTION) { setCursor(Qt::OpenHandCursor); } else { setCursor(Qt::CrossCursor); } } void CaptureWidget::updateSelectionState() { auto toolType = activeButtonToolType(); if (toolType == CaptureTool::TYPE_MOVESELECTION) { m_selection->setIdleCentralCursor(Qt::OpenHandCursor); m_selection->setIgnoreMouse(false); } else { m_selection->setIdleCentralCursor(Qt::ArrowCursor); if (toolType == CaptureTool::NONE) { m_selection->setIgnoreMouse(m_panel->activeLayerIndex() != -1); } else { m_selection->setIgnoreMouse(true); } } } void CaptureWidget::updateTool(CaptureTool* tool) { if (!tool || !tool->showMousePreview()) { return; } static QRect oldPreviewRect, oldToolObjectRect; QRect previewRect(tool->mousePreviewRect(m_context)); previewRect += QMargins(previewRect.width(), previewRect.height(), previewRect.width(), previewRect.height()); QRect toolObjectRect = paddedUpdateRect(tool->boundingRect()); // old rects are united with current rects to handle sudden mouse movement update(previewRect); update(toolObjectRect); update(oldPreviewRect); update(oldToolObjectRect); oldPreviewRect = previewRect; oldToolObjectRect = toolObjectRect; } void CaptureWidget::updateLayersPanel() { m_panel->fillCaptureTools(m_captureToolObjects.captureToolObjects()); } void CaptureWidget::pushToolToStack() { // append current tool to the new state if (m_activeTool && m_activeButton) { disconnect(this, &CaptureWidget::colorChanged, m_activeTool, &CaptureTool::onColorChanged); disconnect(this, &CaptureWidget::toolSizeChanged, m_activeTool, &CaptureTool::onSizeChanged); if (m_panel->toolWidget()) { disconnect(m_panel->toolWidget(), nullptr, m_activeTool, nullptr); } // disable signal connect for updating layer because it may call this // function again on text objects m_panel->blockSignals(true); m_captureToolObjectsBackup = m_captureToolObjects; m_captureToolObjects.append(m_activeTool); pushObjectsStateToUndoStack(); releaseActiveTool(); drawToolsData(); updateLayersPanel(); // restore signal connection for updating layer m_panel->blockSignals(false); } } void CaptureWidget::drawToolsData(bool drawSelection) { // TODO refactor this for performance. The objects should not all be updated // at once every time QPixmap pixmapItem = m_context.origScreenshot; for (auto toolItem : m_captureToolObjects.captureToolObjects()) { processPixmapWithTool(&pixmapItem, toolItem); update(paddedUpdateRect(toolItem->boundingRect())); } m_context.screenshot = pixmapItem; if (drawSelection) { drawObjectSelection(); } } void CaptureWidget::drawObjectSelection() { auto toolItem = activeToolObject(); if (toolItem && !toolItem->editMode()) { QPainter painter(&m_context.screenshot); toolItem->drawObjectSelection(painter); // TODO move this elsewhere if (m_context.toolSize != toolItem->size()) { m_context.toolSize = toolItem->size(); } if (activeToolObject() && m_activeButton) { uncheckActiveTool(); } } } void CaptureWidget::processPixmapWithTool(QPixmap* pixmap, CaptureTool* tool) { QPainter painter(pixmap); painter.setRenderHint(QPainter::Antialiasing); tool->process(painter, *pixmap); } CaptureTool* CaptureWidget::activeButtonTool() const { if (m_activeButton == nullptr) { return nullptr; } return m_activeButton->tool(); } CaptureTool::Type CaptureWidget::activeButtonToolType() const { auto* activeTool = activeButtonTool(); if (activeTool == nullptr) { return CaptureTool::NONE; } return activeTool->type(); } QPointer CaptureWidget::activeToolObject() { return m_captureToolObjects.at(m_panel->activeLayerIndex()); } void CaptureWidget::makeChild(QWidget* w) { w->setParent(this); w->installEventFilter(m_eventFilter); } void CaptureWidget::restoreCircleCountState() { int largest = 0; for (int cnt = 0; cnt < m_captureToolObjects.size(); cnt++) { auto toolItem = m_captureToolObjects.at(cnt); if (toolItem->type() != CaptureTool::TYPE_CIRCLECOUNT) { continue; } if (toolItem->count() > largest) { largest = toolItem->count(); } } m_context.circleCount = largest + 1; } /** * @brief Wrapper around `new QShortcut`, properly handling Enter/Return. */ QList CaptureWidget::newShortcut(const QKeySequence& key, QWidget* parent, const char* slot) { QList shortcuts; QString strKey = key.toString(); if (strKey.contains("Enter") || strKey.contains("Return")) { strKey.replace("Enter", "Return"); shortcuts << new QShortcut(strKey, parent, slot); strKey.replace("Return", "Enter"); shortcuts << new QShortcut(strKey, parent, slot); } else { shortcuts << new QShortcut(key, parent, slot); } return shortcuts; } void CaptureWidget::togglePanel() { m_panel->toggle(); } void CaptureWidget::childEnter() { m_previewEnabled = false; updateTool(activeButtonTool()); } void CaptureWidget::childLeave() { m_previewEnabled = true; updateTool(activeButtonTool()); } void CaptureWidget::setCaptureToolObjects( const CaptureToolObjects& captureToolObjects) { // Used for undo/redo m_captureToolObjects = captureToolObjects; drawToolsData(); updateLayersPanel(); drawObjectSelection(); } void CaptureWidget::undo() { if (m_activeTool && (m_activeTool->isChanged() || m_activeTool->editMode())) { // Remove selection on undo, at the same time commit current tool will // be called m_panel->setActiveLayer(-1); } // drawToolsData is called twice to update both previous and new regions // FIXME this is a temporary workaround drawToolsData(); m_undoStack.undo(); drawToolsData(); updateLayersPanel(); restoreCircleCountState(); } void CaptureWidget::redo() { // drawToolsData is called twice to update both previous and new regions // FIXME this is a temporary workaround drawToolsData(); m_undoStack.redo(); drawToolsData(); update(); updateLayersPanel(); restoreCircleCountState(); } QRect CaptureWidget::extendedSelection() const { if (m_selection == nullptr) { return {}; } QRect r = m_selection->geometry(); return extendedRect(r); } QRect CaptureWidget::extendedRect(const QRect& r) const { auto devicePixelRatio = m_context.screenshot.devicePixelRatio(); return { static_cast(r.left() * devicePixelRatio), static_cast(r.top() * devicePixelRatio), static_cast(r.width() * devicePixelRatio), static_cast(r.height() * devicePixelRatio) }; } QRect CaptureWidget::paddedUpdateRect(const QRect& r) const { if (r.isNull()) { return r; } else { return r + QMargins(20, 20, 20, 20); } } void CaptureWidget::drawErrorMessage(const QString& msg, QPainter* painter) { auto textRect = painter->fontMetrics().boundingRect(msg); int w = textRect.width(), h = textRect.height(); textRect = { size().width() - w - 10, size().height() - h - 5, w + 100, h + 100 }; QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); if (!textRect.contains(QCursor::pos(currentScreen))) { QColor textColor(Qt::white); painter->setPen(textColor); painter->drawText(textRect, msg); } } void CaptureWidget::drawInactiveRegion(QPainter* painter) { QColor overlayColor(0, 0, 0, m_opacity); painter->setBrush(overlayColor); QRect r; if (m_selection->isVisible()) { r = m_selection->geometry().normalized(); } QRegion grey(rect()); grey = grey.subtracted(r); painter->setClipRegion(grey); painter->drawRect(-1, -1, rect().width() + 1, rect().height() + 1); }