Skip to content

Commit cf08755

Browse files
SilasDoSilas Dohm
and
Silas Dohm
authored
Added magnifier for more precise selections (flameshot-org#2219)
* added a magnifierwidget * added option to show magnifier and added option to switch to square shaped magnifier * integrated magnifierwidget into capture this could probably be done in a nicer way. right now the magnifier wont show if you select via the move tool. Co-authored-by: Silas Dohm <[email protected]>
1 parent 4affa92 commit cf08755

9 files changed

+252
-0
lines changed

src/config/generalconf.cpp

+22
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ GeneralConf::GeneralConf(QWidget* parent)
5757

5858
m_layout->addStretch();
5959

60+
initShowMagnifier();
61+
initSquareMagnifier();
6062
// this has to be at the end
6163
initConfigButtons();
6264
updateComponents();
@@ -79,6 +81,8 @@ void GeneralConf::_updateComponents(bool allowEmptySavePath)
7981
config.historyConfirmationToDelete());
8082
m_checkForUpdates->setChecked(config.checkForUpdates());
8183
m_allowMultipleGuiInstances->setChecked(config.allowMultipleGuiInstances());
84+
m_showMagnifier->setChecked(config.showMagnifier());
85+
m_squareMagnifier->setChecked(config.squareMagnifier());
8286

8387
#if !defined(Q_OS_WIN)
8488
m_autoCloseIdleDaemon->setChecked(config.autoCloseIdleDaemon());
@@ -622,6 +626,24 @@ const QString GeneralConf::chooseFolder(const QString pathDefault)
622626
return path;
623627
}
624628

629+
void GeneralConf::initShowMagnifier()
630+
{
631+
m_showMagnifier = new QCheckBox(tr("Show magnifier"), this);
632+
m_scrollAreaLayout->addWidget(m_showMagnifier);
633+
connect(m_showMagnifier, &QCheckBox::clicked, [](bool checked) {
634+
ConfigHandler().setShowMagnifier(checked);
635+
});
636+
}
637+
638+
void GeneralConf::initSquareMagnifier()
639+
{
640+
m_squareMagnifier = new QCheckBox(tr("Square shaped magnifier"), this);
641+
m_scrollAreaLayout->addWidget(m_squareMagnifier);
642+
connect(m_squareMagnifier, &QCheckBox::clicked, [](bool checked) {
643+
ConfigHandler().setSquareMagnifier(checked);
644+
});
645+
}
646+
625647
void GeneralConf::togglePathFixed()
626648
{
627649
ConfigHandler().setSavePathFixed(m_screenshotPathFixedCheck->isChecked());

src/config/generalconf.h

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ private slots:
6767
void initUseJpgForClipboard();
6868
void initUploadWithoutConfirmation();
6969
void initPredefinedColorPaletteLarge();
70+
void initShowMagnifier();
71+
void initSquareMagnifier();
7072

7173
void _updateComponents(bool allowEmptySavePath);
7274

@@ -100,4 +102,6 @@ private slots:
100102
QSpinBox* m_undoLimit;
101103
QComboBox* m_setSaveAsFileExtension;
102104
QCheckBox* m_predefinedColorPaletteLarge;
105+
QCheckBox* m_showMagnifier;
106+
QCheckBox* m_squareMagnifier;
103107
};

src/utils/confighandler.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ static QMap<class QString, QSharedPointer<ValueHandler>>
8181
OPTION("historyConfirmationToDelete" ,Bool ( true )),
8282
OPTION("checkForUpdates" ,Bool ( true )),
8383
OPTION("allowMultipleGuiInstances" ,Bool ( false )),
84+
OPTION("showMagnifier" ,Bool ( false )),
85+
OPTION("squareMagnifier" ,Bool ( false )),
8486
#if !defined(Q_OS_WIN)
8587
OPTION("autoCloseIdleDaemon" ,Bool ( false )),
8688
#endif

src/utils/confighandler.h

+2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ class ConfigHandler : public QObject
115115
QString)
116116
CONFIG_GETTER_SETTER(undoLimit, setUndoLimit, int)
117117
CONFIG_GETTER_SETTER(buttons, setButtons, QList<CaptureTool::Type>)
118+
CONFIG_GETTER_SETTER(showMagnifier, setShowMagnifier, bool)
119+
CONFIG_GETTER_SETTER(squareMagnifier, setSquareMagnifier, bool)
118120

119121
// SPECIAL CASES
120122
bool startupLaunch();

src/widgets/capture/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ target_sources(
99
hovereventfilter.h
1010
overlaymessage.h
1111
selectionwidget.h
12+
magnifierwidget.h
1213
notifierbox.h
1314
modificationcommand.h)
1415

@@ -23,4 +24,5 @@ target_sources(
2324
overlaymessage.cpp
2425
notifierbox.cpp
2526
selectionwidget.cpp
27+
magnifierwidget.cpp
2628
modificationcommand.cpp)

src/widgets/capture/capturewidget.cpp

+14
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ CaptureWidget::CaptureWidget(const CaptureRequest& req,
6767
, m_panel(nullptr)
6868
, m_sidePanel(nullptr)
6969
, m_selection(nullptr)
70+
, m_magnifier(nullptr)
7071
, m_existingObjectIsChanged(false)
7172
, m_startMove(false)
7273
, m_toolSizeByKeyboard(0)
@@ -181,6 +182,10 @@ CaptureWidget::CaptureWidget(const CaptureRequest& req,
181182
initButtons();
182183
initSelection(); // button handler must be initialized before
183184
initShortcuts(); // must be called after initSelection
185+
// init magnify
186+
if (m_config.showMagnifier())
187+
m_magnifier = new MagnifierWidget(
188+
m_context.screenshot, m_uiColor, m_config.squareMagnifier(), this);
184189

185190
// Init color picker
186191
m_colorPicker = new ColorPicker(this);
@@ -679,6 +684,15 @@ void CaptureWidget::mouseDoubleClickEvent(QMouseEvent* event)
679684

680685
void CaptureWidget::mouseMoveEvent(QMouseEvent* e)
681686
{
687+
if (m_magnifier) {
688+
if (!m_activeButton) {
689+
m_magnifier->show();
690+
m_magnifier->update();
691+
} else {
692+
m_magnifier->hide();
693+
}
694+
}
695+
682696
m_context.mousePos = e->pos();
683697
if (e->buttons() != Qt::LeftButton) {
684698
updateTool(activeButtonTool());

src/widgets/capture/capturewidget.h

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "src/tools/capturecontext.h"
1818
#include "src/tools/capturetool.h"
1919
#include "src/utils/confighandler.h"
20+
#include "src/widgets/capture/magnifierwidget.h"
2021
#include "src/widgets/capture/selectionwidget.h"
2122
#include <QPointer>
2223
#include <QUndoStack>
@@ -180,6 +181,7 @@ private slots:
180181
NotifierBox* m_notifierBox;
181182
HoverEventFilter* m_eventFilter;
182183
SelectionWidget* m_selection;
184+
MagnifierWidget* m_magnifier;
183185
QString m_helpMessage;
184186

185187
SelectionWidget::SideType m_mouseOverHandle;
+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
3+
4+
#include "magnifierwidget.h"
5+
#include <QApplication>
6+
#include <QEvent>
7+
#include <QMouseEvent>
8+
#include <QPainter>
9+
#include <QPainterPath>
10+
#include <QPen>
11+
#include <QPixmap>
12+
13+
MagnifierWidget::MagnifierWidget(const QPixmap& p,
14+
const QColor& c,
15+
bool isSquare,
16+
QWidget* parent)
17+
: QWidget(parent)
18+
, m_color(c)
19+
, m_borderColor(c)
20+
, m_screenshot(p)
21+
, m_square(isSquare)
22+
{
23+
setFixedSize(parent->width(), parent->height());
24+
setAttribute(Qt::WA_TransparentForMouseEvents);
25+
m_color.setAlpha(130);
26+
// add padding for circular magnifier
27+
QImage padded(p.width() + 2 * m_magPixels,
28+
p.height() + 2 * m_magPixels,
29+
QImage::Format_ARGB32);
30+
padded.fill(Qt::black);
31+
QPainter painter(&padded);
32+
painter.drawPixmap(m_magPixels, m_magPixels, p);
33+
m_paddedScreenshot.convertFromImage(padded);
34+
}
35+
void MagnifierWidget::paintEvent(QPaintEvent*)
36+
{
37+
QPainter p(this);
38+
if (m_square)
39+
drawMagnifier(p);
40+
else
41+
drawMagnifierCircle(p);
42+
}
43+
44+
void MagnifierWidget::drawMagnifierCircle(QPainter& painter)
45+
{
46+
int x = QCursor::pos().x() + m_magPixels;
47+
int y = QCursor::pos().y() + m_magPixels;
48+
int magX = static_cast<int>(x * m_devicePixelRatio - m_magPixels);
49+
int magY = static_cast<int>(y * m_devicePixelRatio - m_magPixels);
50+
QRectF magniRect(magX, magY, m_pixels, m_pixels);
51+
52+
qreal drawPosX = x + m_magOffset + m_pixels * magZoom / 2;
53+
if (drawPosX > width() - m_pixels * magZoom / 2) {
54+
drawPosX = x - m_magOffset - m_pixels * magZoom / 2;
55+
}
56+
qreal drawPosY = y + m_magOffset + m_pixels * magZoom / 2;
57+
if (drawPosY > height() - m_pixels * magZoom / 2) {
58+
drawPosY = y - m_magOffset - m_pixels * magZoom / 2;
59+
}
60+
QPointF drawPos(drawPosX, drawPosY);
61+
QRectF crossHairTop(drawPos.x() + magZoom * (-0.5),
62+
drawPos.y() - magZoom * (m_magPixels + 0.5),
63+
magZoom,
64+
magZoom * (m_magPixels));
65+
QRectF crossHairRight(drawPos.x() + magZoom * (0.5),
66+
drawPos.y() + magZoom * (-0.5),
67+
magZoom * (m_magPixels),
68+
magZoom);
69+
QRectF crossHairBottom(drawPos.x() + magZoom * (-0.5),
70+
drawPos.y() + magZoom * (0.5),
71+
magZoom,
72+
magZoom * (m_magPixels));
73+
QRectF crossHairLeft(drawPos.x() - magZoom * (m_magPixels + 0.5),
74+
drawPos.y() + magZoom * (-0.5),
75+
magZoom * (m_magPixels),
76+
magZoom);
77+
QRectF crossHairBorder(drawPos.x() - magZoom * (m_magPixels + 0.5) - 1,
78+
drawPos.y() - magZoom * (m_magPixels + 0.5) - 1,
79+
m_pixels * magZoom + 2,
80+
m_pixels * magZoom + 2);
81+
const auto frag =
82+
QPainter::PixmapFragment::create(drawPos, magniRect, magZoom, magZoom);
83+
84+
painter.setRenderHint(QPainter::Antialiasing, true);
85+
QPainterPath path = QPainterPath();
86+
path.addEllipse(drawPos, m_pixels * magZoom / 2, m_pixels * magZoom / 2);
87+
painter.setClipPath(path);
88+
89+
painter.drawPixmapFragments(
90+
&frag, 1, m_paddedScreenshot, QPainter::OpaqueHint);
91+
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
92+
for (auto& rect :
93+
{ crossHairTop, crossHairRight, crossHairBottom, crossHairLeft }) {
94+
painter.fillRect(rect, m_color);
95+
}
96+
QPen pen(m_borderColor);
97+
pen.setWidth(4);
98+
painter.setPen(pen);
99+
painter.drawEllipse(
100+
drawPos, m_pixels * magZoom / 2, m_pixels * magZoom / 2);
101+
}
102+
// https://invent.kde.org/graphics/spectacle/-/blob/master/src/QuickEditor/QuickEditor.cpp#L841
103+
void MagnifierWidget::drawMagnifier(QPainter& painter)
104+
{
105+
int x = QCursor::pos().x();
106+
int y = QCursor::pos().y();
107+
int magX = static_cast<int>(x * m_devicePixelRatio - m_magPixels);
108+
int offsetX = 0;
109+
if (magX < 0) {
110+
offsetX = magX;
111+
magX = 0;
112+
} else {
113+
const int maxX = m_screenshot.width() - m_pixels;
114+
if (magX > maxX) {
115+
offsetX = magX - maxX;
116+
magX = maxX;
117+
}
118+
}
119+
int magY = static_cast<int>(y * m_devicePixelRatio - m_magPixels);
120+
int offsetY = 0;
121+
if (magY < 0) {
122+
offsetY = magY;
123+
magY = 0;
124+
} else {
125+
const int maxY = m_screenshot.height() - m_pixels;
126+
if (magY > maxY) {
127+
offsetY = magY - maxY;
128+
magY = maxY;
129+
}
130+
}
131+
QRectF magniRect(magX, magY, m_pixels, m_pixels);
132+
133+
qreal drawPosX = x + m_magOffset + m_pixels * magZoom / 2;
134+
if (drawPosX > width() - m_pixels * magZoom / 2) {
135+
drawPosX = x - m_magOffset - m_pixels * magZoom / 2;
136+
}
137+
qreal drawPosY = y + m_magOffset + m_pixels * magZoom / 2;
138+
if (drawPosY > height() - m_pixels * magZoom / 2) {
139+
drawPosY = y - m_magOffset - m_pixels * magZoom / 2;
140+
}
141+
QPointF drawPos(drawPosX, drawPosY);
142+
QRectF crossHairTop(drawPos.x() + magZoom * (offsetX - 0.5),
143+
drawPos.y() - magZoom * (m_magPixels + 0.5),
144+
magZoom,
145+
magZoom * (m_magPixels + offsetY));
146+
QRectF crossHairRight(drawPos.x() + magZoom * (0.5 + offsetX),
147+
drawPos.y() + magZoom * (offsetY - 0.5),
148+
magZoom * (m_magPixels - offsetX),
149+
magZoom);
150+
QRectF crossHairBottom(drawPos.x() + magZoom * (offsetX - 0.5),
151+
drawPos.y() + magZoom * (0.5 + offsetY),
152+
magZoom,
153+
magZoom * (m_magPixels - offsetY));
154+
QRectF crossHairLeft(drawPos.x() - magZoom * (m_magPixels + 0.5),
155+
drawPos.y() + magZoom * (offsetY - 0.5),
156+
magZoom * (m_magPixels + offsetX),
157+
magZoom);
158+
QRectF crossHairBorder(drawPos.x() - magZoom * (m_magPixels + 0.5) - 1,
159+
drawPos.y() - magZoom * (m_magPixels + 0.5) - 1,
160+
m_pixels * magZoom + 2,
161+
m_pixels * magZoom + 2);
162+
const auto frag =
163+
QPainter::PixmapFragment::create(drawPos, magniRect, magZoom, magZoom);
164+
165+
painter.fillRect(crossHairBorder, m_borderColor);
166+
painter.drawPixmapFragments(&frag, 1, m_screenshot, QPainter::OpaqueHint);
167+
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
168+
for (auto& rect :
169+
{ crossHairTop, crossHairRight, crossHairBottom, crossHairLeft }) {
170+
painter.fillRect(rect, m_color);
171+
}
172+
}

src/widgets/capture/magnifierwidget.h

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#pragma once
2+
3+
#include <QWidget>
4+
5+
class QPropertyAnimation;
6+
7+
class MagnifierWidget : public QWidget
8+
{
9+
Q_OBJECT
10+
public:
11+
explicit MagnifierWidget(const QPixmap& p,
12+
const QColor& c,
13+
bool isSquare,
14+
QWidget* parent = nullptr);
15+
16+
protected:
17+
void paintEvent(QPaintEvent*) override;
18+
19+
private:
20+
const int m_magPixels = 8;
21+
const int m_magOffset = 16;
22+
const int magZoom = 10;
23+
const int m_pixels = 2 * m_magPixels + 1;
24+
const int m_devicePixelRatio = 1;
25+
bool m_square;
26+
QColor m_color;
27+
QColor m_borderColor;
28+
QPixmap m_screenshot;
29+
QPixmap m_paddedScreenshot;
30+
void drawMagnifier(QPainter& painter);
31+
void drawMagnifierCircle(QPainter& painter);
32+
};

0 commit comments

Comments
 (0)