Files
flameshot/src/widgets/capture/capturewidget.cpp
2022-06-20 09:13:45 -05:00

1753 lines
55 KiB
C++

// 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
// <info@ckaiser.com.ar> released under the GNU GPL2
// <https://www.gnu.org/licenses/gpl-2.0.txt>
// Based on KDE's KSnapshot regiongrabber.cpp, revision 796531, Copyright 2007
// Luca Gugelmann <lucag@student.ethz.ch> released under the GNU LGPL
// <http://www.gnu.org/licenses/old-licenses/library.txt>
#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 <QApplication>
#include <QDateTime>
#include <QDebug>
#include <QDesktopWidget>
#include <QFontMetrics>
#include <QLabel>
#include <QPaintEvent>
#include <QPainter>
#include <QScreen>
#include <QShortcut>
#include <draggablewidgetmaker.h>
#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<QRect> 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<CaptureToolButton*> 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<QPair<QString, QString>> 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(&copyTool,
&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<CaptureTool> 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<int>(panelRect.x() / devicePixelRatio),
static_cast<int>(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<int>(panelRect.height() / 2) -
static_cast<int>(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<int>(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<CaptureTool> 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<QShortcut*> CaptureWidget::newShortcut(const QKeySequence& key,
QWidget* parent,
const char* slot)
{
QList<QShortcut*> 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<int>(r.left() * devicePixelRatio),
static_cast<int>(r.top() * devicePixelRatio),
static_cast<int>(r.width() * devicePixelRatio),
static_cast<int>(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);
}