mirror of
https://github.com/fergalmoran/flameshot.git
synced 2026-03-26 09:59:52 +00:00
* Cppcheck and clazy warning fixes * Clang-format * Revert QRect call by value * Revert QPoint call by value * Revert complained renamings --------- Co-authored-by: Haris Gušić <harisgusic.dev@gmail.com>
567 lines
17 KiB
C++
567 lines
17 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
|
|
|
|
#include "selectionwidget.h"
|
|
#include "capturetool.h"
|
|
#include "capturetoolbutton.h"
|
|
#include "src/utils/globalvalues.h"
|
|
#include <QApplication>
|
|
#include <QEvent>
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QPropertyAnimation>
|
|
#include <QTimer>
|
|
#include <utility>
|
|
|
|
#define MARGIN (m_THandle.width())
|
|
|
|
SelectionWidget::SelectionWidget(QColor c, QWidget* parent)
|
|
: QWidget(parent)
|
|
, m_color(std::move(c))
|
|
, m_activeSide(NO_SIDE)
|
|
, m_ignoreMouse(false)
|
|
{
|
|
// prevents this widget from consuming CaptureToolButton mouse events
|
|
setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
parent->installEventFilter(this);
|
|
|
|
m_animation = new QPropertyAnimation(this, "geometry", this);
|
|
m_animation->setEasingCurve(QEasingCurve::InOutQuad);
|
|
m_animation->setDuration(200);
|
|
connect(m_animation, &QPropertyAnimation::finished, this, [this]() {
|
|
emit geometrySettled();
|
|
});
|
|
|
|
int sideVal = GlobalValues::buttonBaseSize() * 0.6;
|
|
int handleSide = sideVal / 2;
|
|
const QRect areaRect(0, 0, sideVal, sideVal);
|
|
|
|
const QRect handleRect(0, 0, handleSide, handleSide);
|
|
m_TLHandle = m_TRHandle = m_BLHandle = m_BRHandle = m_LHandle = m_THandle =
|
|
m_RHandle = m_BHandle = handleRect;
|
|
m_TLArea = m_TRArea = m_BLArea = m_BRArea = areaRect;
|
|
|
|
m_areaOffset = QPoint(-sideVal / 2, -sideVal / 2);
|
|
m_handleOffset = QPoint(-handleSide / 2, -handleSide / 2);
|
|
}
|
|
|
|
/**
|
|
* @brief Get the side where the mouse cursor is.
|
|
* @param mousePos Mouse cursor position relative to the parent widget.
|
|
*/
|
|
SelectionWidget::SideType SelectionWidget::getMouseSide(
|
|
const QPoint& mousePos) const
|
|
{
|
|
if (!isVisible()) {
|
|
return NO_SIDE;
|
|
}
|
|
QPoint localPos = mapFromParent(mousePos);
|
|
if (m_TLArea.contains(localPos)) {
|
|
return TOPLEFT_SIDE;
|
|
} else if (m_TRArea.contains(localPos)) {
|
|
return TOPRIGHT_SIDE;
|
|
} else if (m_BLArea.contains(localPos)) {
|
|
return BOTTOMLEFT_SIDE;
|
|
} else if (m_BRArea.contains(localPos)) {
|
|
return BOTTOMRIGHT_SIDE;
|
|
} else if (m_LArea.contains(localPos)) {
|
|
return LEFT_SIDE;
|
|
} else if (m_TArea.contains(localPos)) {
|
|
return TOP_SIDE;
|
|
} else if (m_RArea.contains(localPos)) {
|
|
return RIGHT_SIDE;
|
|
} else if (m_BArea.contains(localPos)) {
|
|
return BOTTOM_SIDE;
|
|
} else if (rect().contains(localPos)) {
|
|
return CENTER;
|
|
} else {
|
|
return NO_SIDE;
|
|
}
|
|
}
|
|
|
|
QVector<QRect> SelectionWidget::handlerAreas()
|
|
{
|
|
QVector<QRect> areas;
|
|
areas << m_TLHandle << m_TRHandle << m_BLHandle << m_BRHandle << m_LHandle
|
|
<< m_THandle << m_RHandle << m_BHandle;
|
|
return areas;
|
|
}
|
|
|
|
// helper function
|
|
SelectionWidget::SideType getProperSide(SelectionWidget::SideType side,
|
|
const QRect& r)
|
|
{
|
|
using SideType = SelectionWidget::SideType;
|
|
int intSide = side;
|
|
if (r.right() < r.left()) {
|
|
intSide ^= SideType::LEFT_SIDE;
|
|
intSide ^= SideType::RIGHT_SIDE;
|
|
}
|
|
if (r.bottom() < r.top()) {
|
|
intSide ^= SideType::TOP_SIDE;
|
|
intSide ^= SideType::BOTTOM_SIDE;
|
|
}
|
|
|
|
return (SideType)intSide;
|
|
}
|
|
|
|
void SelectionWidget::setIgnoreMouse(bool ignore)
|
|
{
|
|
m_ignoreMouse = ignore;
|
|
updateCursor();
|
|
}
|
|
|
|
/**
|
|
* Set the cursor that will be active when the mouse is inside the selection and
|
|
* the mouse is not clicked.
|
|
*/
|
|
void SelectionWidget::setIdleCentralCursor(const QCursor& cursor)
|
|
{
|
|
m_idleCentralCursor = cursor;
|
|
}
|
|
|
|
void SelectionWidget::setGeometryAnimated(const QRect& r)
|
|
{
|
|
if (isVisible()) {
|
|
m_animation->setStartValue(geometry());
|
|
m_animation->setEndValue(r);
|
|
m_animation->start();
|
|
}
|
|
}
|
|
|
|
void SelectionWidget::setGeometry(const QRect& r)
|
|
{
|
|
QWidget::setGeometry(r + QMargins(MARGIN, MARGIN, MARGIN, MARGIN));
|
|
updateCursor();
|
|
if (isVisible()) {
|
|
emit geometryChanged();
|
|
}
|
|
}
|
|
|
|
QRect SelectionWidget::geometry() const
|
|
{
|
|
return QWidget::geometry() - QMargins(MARGIN, MARGIN, MARGIN, MARGIN);
|
|
}
|
|
|
|
QRect SelectionWidget::fullGeometry() const
|
|
{
|
|
return QWidget::geometry();
|
|
}
|
|
|
|
QRect SelectionWidget::rect() const
|
|
{
|
|
return QWidget::rect() - QMargins(MARGIN, MARGIN, MARGIN, MARGIN);
|
|
}
|
|
|
|
bool SelectionWidget::eventFilter(QObject* obj, QEvent* event)
|
|
{
|
|
if (m_ignoreMouse && dynamic_cast<QMouseEvent*>(event)) {
|
|
m_activeSide = NO_SIDE;
|
|
unsetCursor();
|
|
} else if (event->type() == QEvent::MouseButtonRelease) {
|
|
parentMouseReleaseEvent(static_cast<QMouseEvent*>(event));
|
|
} else if (event->type() == QEvent::MouseButtonPress) {
|
|
parentMousePressEvent(static_cast<QMouseEvent*>(event));
|
|
} else if (event->type() == QEvent::MouseMove) {
|
|
parentMouseMoveEvent(static_cast<QMouseEvent*>(event));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SelectionWidget::parentMousePressEvent(QMouseEvent* e)
|
|
{
|
|
if (e->button() != Qt::LeftButton) {
|
|
return;
|
|
}
|
|
|
|
m_dragStartPos = e->pos();
|
|
m_activeSide = getMouseSide(e->pos());
|
|
}
|
|
|
|
void SelectionWidget::parentMouseReleaseEvent(QMouseEvent* e)
|
|
{
|
|
// released outside of the selection area
|
|
if (!getMouseSide(e->pos())) {
|
|
hide();
|
|
}
|
|
|
|
m_activeSide = NO_SIDE;
|
|
updateCursor();
|
|
emit geometrySettled();
|
|
}
|
|
|
|
void SelectionWidget::parentMouseMoveEvent(QMouseEvent* e)
|
|
{
|
|
updateCursor();
|
|
|
|
if (e->buttons() != Qt::LeftButton) {
|
|
return;
|
|
}
|
|
|
|
SideType mouseSide = m_activeSide;
|
|
if (!m_activeSide) {
|
|
mouseSide = getMouseSide(e->pos());
|
|
}
|
|
|
|
QPoint pos;
|
|
|
|
if (!isVisible() || !mouseSide) {
|
|
show();
|
|
m_activeSide = TOPLEFT_SIDE;
|
|
pos = m_dragStartPos;
|
|
setGeometry({ pos, pos });
|
|
} else {
|
|
pos = e->pos();
|
|
}
|
|
|
|
auto geom = geometry();
|
|
float aspectRatio = (float)geom.width() / (float)geom.height();
|
|
bool symmetryMod = qApp->keyboardModifiers() & Qt::ShiftModifier;
|
|
bool preserveAspect = qApp->keyboardModifiers() & Qt::ControlModifier;
|
|
|
|
QPoint newTopLeft = geom.topLeft(), newBottomRight = geom.bottomRight();
|
|
int oldLeft = newTopLeft.rx(), oldRight = newBottomRight.rx(),
|
|
oldTop = newTopLeft.ry(), oldBottom = newBottomRight.ry();
|
|
int &newLeft = newTopLeft.rx(), &newRight = newBottomRight.rx(),
|
|
&newTop = newTopLeft.ry(), &newBottom = newBottomRight.ry();
|
|
switch (mouseSide) {
|
|
case TOPLEFT_SIDE:
|
|
if (m_activeSide) {
|
|
if (preserveAspect) {
|
|
if ((float)(oldRight - pos.x()) /
|
|
(float)(oldBottom - pos.y()) >
|
|
aspectRatio) {
|
|
/* width longer than expected width, hence increase
|
|
* height to compensate for the aspect ratio */
|
|
newLeft = pos.x();
|
|
newTop =
|
|
oldBottom -
|
|
(int)(((float)(oldRight - pos.x())) / aspectRatio);
|
|
} else {
|
|
/* height longer than expected height, hence increase
|
|
* width to compensate for the aspect ratio */
|
|
newTop = pos.y();
|
|
newLeft =
|
|
oldRight -
|
|
(int)(((float)(oldBottom - pos.y())) * aspectRatio);
|
|
}
|
|
} else {
|
|
newTopLeft = pos;
|
|
}
|
|
}
|
|
break;
|
|
case BOTTOMRIGHT_SIDE:
|
|
if (m_activeSide) {
|
|
if (preserveAspect) {
|
|
if ((float)(pos.x() - oldLeft) / (float)(pos.y() - oldTop) >
|
|
aspectRatio) {
|
|
newRight = pos.x();
|
|
newBottom =
|
|
oldTop +
|
|
(int)(((float)(pos.x() - oldLeft)) / aspectRatio);
|
|
} else {
|
|
newBottom = pos.y();
|
|
newRight = oldLeft + (int)(((float)(pos.y() - oldTop)) *
|
|
aspectRatio);
|
|
}
|
|
} else {
|
|
newBottomRight = pos;
|
|
}
|
|
}
|
|
break;
|
|
case TOPRIGHT_SIDE:
|
|
if (m_activeSide) {
|
|
if (preserveAspect) {
|
|
if ((float)(pos.x() - oldLeft) /
|
|
(float)(oldBottom - pos.y()) >
|
|
aspectRatio) {
|
|
newRight = pos.x();
|
|
newTop =
|
|
oldBottom -
|
|
(int)(((float)(pos.x() - oldLeft)) / aspectRatio);
|
|
} else {
|
|
newTop = pos.y();
|
|
newRight =
|
|
oldLeft +
|
|
(int)(((float)(oldBottom - pos.y())) * aspectRatio);
|
|
}
|
|
} else {
|
|
newTop = pos.y();
|
|
newRight = pos.x();
|
|
}
|
|
}
|
|
break;
|
|
case BOTTOMLEFT_SIDE:
|
|
if (m_activeSide) {
|
|
if (preserveAspect) {
|
|
if ((float)(oldRight - pos.x()) /
|
|
(float)(pos.y() - oldTop) >
|
|
aspectRatio) {
|
|
newLeft = pos.x();
|
|
newBottom =
|
|
oldTop +
|
|
(int)(((float)(oldRight - pos.x())) / aspectRatio);
|
|
} else {
|
|
newBottom = pos.y();
|
|
newLeft = oldRight - (int)(((float)(pos.y() - oldTop)) *
|
|
aspectRatio);
|
|
}
|
|
} else {
|
|
newBottom = pos.y();
|
|
newLeft = pos.x();
|
|
}
|
|
}
|
|
break;
|
|
case LEFT_SIDE:
|
|
if (m_activeSide) {
|
|
newLeft = pos.x();
|
|
if (preserveAspect) {
|
|
/* By default bottom edge moves when dragging sides, this
|
|
* behavior feels natural */
|
|
newBottom = oldTop + (int)(((float)(oldRight - pos.x())) /
|
|
aspectRatio);
|
|
}
|
|
}
|
|
break;
|
|
case RIGHT_SIDE:
|
|
if (m_activeSide) {
|
|
newRight = pos.x();
|
|
if (preserveAspect) {
|
|
newBottom = oldTop + (int)(((float)(pos.x() - oldLeft)) /
|
|
aspectRatio);
|
|
}
|
|
}
|
|
break;
|
|
case TOP_SIDE:
|
|
if (m_activeSide) {
|
|
newTop = pos.y();
|
|
if (preserveAspect) {
|
|
/* By default right edge moves when dragging sides, this
|
|
* behavior feels natural */
|
|
newRight =
|
|
oldLeft +
|
|
(int)(((float)(oldBottom - pos.y()) * aspectRatio));
|
|
}
|
|
}
|
|
break;
|
|
case BOTTOM_SIDE:
|
|
if (m_activeSide) {
|
|
newBottom = pos.y();
|
|
if (preserveAspect) {
|
|
newRight = oldLeft +
|
|
(int)(((float)(pos.y() - oldTop) * aspectRatio));
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
if (m_activeSide) {
|
|
move(this->pos() + pos - m_dragStartPos);
|
|
m_dragStartPos = pos;
|
|
/* do nothing special in case of preserveAspect */
|
|
}
|
|
return;
|
|
}
|
|
// finalize geometry change
|
|
if (m_activeSide) {
|
|
if (symmetryMod) {
|
|
QPoint deltaTopLeft = newTopLeft - geom.topLeft();
|
|
QPoint deltaBottomRight = newBottomRight - geom.bottomRight();
|
|
newTopLeft = geom.topLeft() + deltaTopLeft - deltaBottomRight;
|
|
newBottomRight =
|
|
geom.bottomRight() + deltaBottomRight - deltaTopLeft;
|
|
}
|
|
geom = { newTopLeft, newBottomRight };
|
|
setGeometry(geom.normalized());
|
|
m_activeSide = getProperSide(m_activeSide, geom);
|
|
}
|
|
m_dragStartPos = e->pos();
|
|
}
|
|
|
|
void SelectionWidget::paintEvent(QPaintEvent*)
|
|
{
|
|
QPainter p(this);
|
|
p.setPen(m_color);
|
|
p.drawRect(rect() + QMargins(0, 0, -1, -1));
|
|
p.setRenderHint(QPainter::Antialiasing);
|
|
p.setBrush(m_color);
|
|
for (auto rectangle : handlerAreas()) {
|
|
p.drawEllipse(rectangle);
|
|
}
|
|
}
|
|
|
|
void SelectionWidget::resizeEvent(QResizeEvent*)
|
|
{
|
|
updateAreas();
|
|
if (isVisible()) {
|
|
emit geometryChanged();
|
|
}
|
|
}
|
|
|
|
void SelectionWidget::moveEvent(QMoveEvent*)
|
|
{
|
|
updateAreas();
|
|
if (isVisible()) {
|
|
emit geometryChanged();
|
|
}
|
|
}
|
|
|
|
void SelectionWidget::showEvent(QShowEvent*)
|
|
{
|
|
emit visibilityChanged();
|
|
}
|
|
|
|
void SelectionWidget::hideEvent(QHideEvent*)
|
|
{
|
|
emit visibilityChanged();
|
|
}
|
|
|
|
void SelectionWidget::updateColor(const QColor& c)
|
|
{
|
|
m_color = c;
|
|
}
|
|
|
|
void SelectionWidget::moveLeft()
|
|
{
|
|
setGeometryByKeyboard(geometry().adjusted(-1, 0, -1, 0));
|
|
}
|
|
|
|
void SelectionWidget::moveRight()
|
|
{
|
|
setGeometryByKeyboard(geometry().adjusted(1, 0, 1, 0));
|
|
}
|
|
|
|
void SelectionWidget::moveUp()
|
|
{
|
|
setGeometryByKeyboard(geometry().adjusted(0, -1, 0, -1));
|
|
}
|
|
|
|
void SelectionWidget::moveDown()
|
|
{
|
|
setGeometryByKeyboard(geometry().adjusted(0, 1, 0, 1));
|
|
}
|
|
|
|
void SelectionWidget::resizeLeft()
|
|
{
|
|
setGeometryByKeyboard(geometry().adjusted(0, 0, -1, 0));
|
|
}
|
|
|
|
void SelectionWidget::resizeRight()
|
|
{
|
|
setGeometryByKeyboard(geometry().adjusted(0, 0, 1, 0));
|
|
}
|
|
|
|
void SelectionWidget::resizeUp()
|
|
{
|
|
setGeometryByKeyboard(geometry().adjusted(0, 0, 0, -1));
|
|
}
|
|
|
|
void SelectionWidget::resizeDown()
|
|
{
|
|
setGeometryByKeyboard(geometry().adjusted(0, 0, 0, 1));
|
|
}
|
|
|
|
void SelectionWidget::symResizeLeft()
|
|
{
|
|
setGeometryByKeyboard(geometry().adjusted(1, 0, -1, 0));
|
|
}
|
|
|
|
void SelectionWidget::symResizeRight()
|
|
{
|
|
setGeometryByKeyboard(geometry().adjusted(-1, 0, 1, 0));
|
|
}
|
|
|
|
void SelectionWidget::symResizeUp()
|
|
{
|
|
setGeometryByKeyboard(geometry().adjusted(0, -1, 0, 1));
|
|
}
|
|
|
|
void SelectionWidget::symResizeDown()
|
|
{
|
|
setGeometryByKeyboard(geometry().adjusted(0, 1, 0, -1));
|
|
}
|
|
|
|
void SelectionWidget::updateAreas()
|
|
{
|
|
QRect r = rect();
|
|
m_TLArea.moveTo(r.topLeft() + m_areaOffset);
|
|
m_TRArea.moveTo(r.topRight() + m_areaOffset);
|
|
m_BLArea.moveTo(r.bottomLeft() + m_areaOffset);
|
|
m_BRArea.moveTo(r.bottomRight() + m_areaOffset);
|
|
|
|
m_LArea = QRect(m_TLArea.bottomLeft(), m_BLArea.topRight());
|
|
m_TArea = QRect(m_TLArea.topRight(), m_TRArea.bottomLeft());
|
|
m_RArea = QRect(m_TRArea.bottomLeft(), m_BRArea.topRight());
|
|
m_BArea = QRect(m_BLArea.topRight(), m_BRArea.bottomLeft());
|
|
|
|
m_TLHandle.moveTo(m_TLArea.center() + m_handleOffset);
|
|
m_BLHandle.moveTo(m_BLArea.center() + m_handleOffset);
|
|
m_TRHandle.moveTo(m_TRArea.center() + m_handleOffset);
|
|
m_BRHandle.moveTo(m_BRArea.center() + m_handleOffset);
|
|
m_LHandle.moveTo(m_LArea.center() + m_handleOffset);
|
|
m_THandle.moveTo(m_TArea.center() + m_handleOffset);
|
|
m_RHandle.moveTo(m_RArea.center() + m_handleOffset);
|
|
m_BHandle.moveTo(m_BArea.center() + m_handleOffset);
|
|
}
|
|
|
|
void SelectionWidget::updateCursor()
|
|
{
|
|
SideType mouseSide = m_activeSide;
|
|
if (!m_activeSide) {
|
|
mouseSide = getMouseSide(parentWidget()->mapFromGlobal(QCursor::pos()));
|
|
}
|
|
|
|
switch (mouseSide) {
|
|
case TOPLEFT_SIDE:
|
|
setCursor(Qt::SizeFDiagCursor);
|
|
break;
|
|
case BOTTOMRIGHT_SIDE:
|
|
setCursor(Qt::SizeFDiagCursor);
|
|
break;
|
|
case TOPRIGHT_SIDE:
|
|
setCursor(Qt::SizeBDiagCursor);
|
|
break;
|
|
case BOTTOMLEFT_SIDE:
|
|
setCursor(Qt::SizeBDiagCursor);
|
|
break;
|
|
case LEFT_SIDE:
|
|
setCursor(Qt::SizeHorCursor);
|
|
break;
|
|
case RIGHT_SIDE:
|
|
setCursor(Qt::SizeHorCursor);
|
|
break;
|
|
case TOP_SIDE:
|
|
setCursor(Qt::SizeVerCursor);
|
|
break;
|
|
case BOTTOM_SIDE:
|
|
setCursor(Qt::SizeVerCursor);
|
|
break;
|
|
default:
|
|
if (m_activeSide) {
|
|
setCursor(Qt::ClosedHandCursor);
|
|
} else {
|
|
setCursor(m_idleCentralCursor);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SelectionWidget::setGeometryByKeyboard(const QRect& r)
|
|
{
|
|
static QTimer timer;
|
|
QRect rectangle = r.intersected(parentWidget()->rect());
|
|
if (rectangle.width() <= 0) {
|
|
rectangle.setWidth(1);
|
|
}
|
|
if (rectangle.height() <= 0) {
|
|
rectangle.setHeight(1);
|
|
}
|
|
setGeometry(rectangle);
|
|
connect(&timer,
|
|
&QTimer::timeout,
|
|
this,
|
|
&SelectionWidget::geometrySettled,
|
|
Qt::UniqueConnection);
|
|
timer.start(400);
|
|
}
|