Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_zoom_action.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include "kis_zoom_action.h"
8
9#include <QApplication>
10#include <QNativeGestureEvent>
11
12#include <klocalizedstring.h>
13
15
16#include <kis_canvas2.h>
19#include "kis_cursor.h"
20#include "KisViewManager.h"
21#include "kis_input_manager.h"
22#include "kis_config.h"
23
24
26{
27public:
28 Private(KisZoomAction *qq) : q(qq), lastDistance(0.f) {}
29
30 QPointF centerPoint(QTouchEvent* event);
31
32 KisZoomAction *q {nullptr};
33 // Coverity requires sane defaults for all variables (CID 36380)
35
36 QPointF lastPosition;
37 float lastDistance {0.0};
38
40
41 qreal startZoom {1.0};
43};
44
45QPointF KisZoomAction::Private::centerPoint(QTouchEvent* event)
46{
47 QPointF result;
48 int count = 0;
49
50 Q_FOREACH (QTouchEvent::TouchPoint point, event->touchPoints()) {
51 if (point.state() != Qt::TouchPointReleased) {
52 result += point.pos();
53 count++;
54 }
55 }
56
57 if (count > 0) {
58 return result / count;
59 } else {
60 return QPointF();
61 }
62}
63
65 : KisAbstractInputAction("Zoom Canvas")
66 , d(new Private(this))
67{
68 setName(i18n("Zoom Canvas"));
69 setDescription(i18n("The <i>Zoom Canvas</i> action zooms the canvas."));
70
71 QHash< QString, int > shortcuts;
72 shortcuts.insert(i18n("Zoom Mode"), ZoomModeShortcut);
73 shortcuts.insert(i18n("Discrete Zoom Mode"), DiscreteZoomModeShortcut);
74 shortcuts.insert(i18n("Relative Zoom Mode"), RelativeZoomModeShortcut);
75 shortcuts.insert(i18n("Relative Discrete Zoom Mode"), RelativeDiscreteZoomModeShortcut);
76 shortcuts.insert(i18n("Zoom In"), ZoomInShortcut);
77 shortcuts.insert(i18n("Zoom Out"), ZoomOutShortcut);
78 shortcuts.insert(i18n("Zoom In To Cursor"), ZoomInToCursorShortcut);
79 shortcuts.insert(i18n("Zoom Out From Cursor"), ZoomOutFromCursorShortcut);
80 shortcuts.insert(i18n("Zoom to 100%"), Zoom100PctShortcut);
81 shortcuts.insert(i18n("Fit to View"), FitToViewShortcut);
82 shortcuts.insert(i18n("Fit to View Width"), FitToWidthShortcut);
83 shortcuts.insert(i18n("Fit to View Height"), FitToHeightShortcut);
84 setShortcutIndexes(shortcuts);
85}
86
88{
89 delete d;
90}
91
93{
94 return 4;
95}
96
97void KisZoomAction::activate(int shortcut)
98{
99 if (shortcut == DiscreteZoomModeShortcut ||
101 QApplication::setOverrideCursor(KisCursor::zoomDiscreteCursor());
102 } else /* if (shortcut == SmoothZoomModeShortcut) */ {
103 QApplication::setOverrideCursor(KisCursor::zoomSmoothCursor());
104 }
105}
106
108{
109 Q_UNUSED(shortcut);
110 QApplication::restoreOverrideCursor();
111}
112
113void KisZoomAction::begin(int shortcut, QEvent *event)
114{
115 KisAbstractInputAction::begin(shortcut, event);
116
117 d->lastDistance = 0.f;
118
119 switch(shortcut) {
120 case ZoomModeShortcut:
123 d->mode = (Shortcuts)shortcut;
124 d->lastPosition = QPoint();
126 break;
127 }
132 d->mode = (Shortcuts)shortcut;
134 break;
135 case ZoomInShortcut:
136 case ZoomOutShortcut:
139 KoCanvasControllerWidget *controller =
142
143 QPoint pt;
144 if (shortcut == ZoomInToCursorShortcut || shortcut == ZoomOutFromCursorShortcut) {
145 pt = eventPos(event);
146 if (pt.isNull()) {
147 pt = controller->mapFromGlobal(QCursor::pos());
148 }
149 }
150
151 if (shortcut == ZoomInToCursorShortcut || shortcut == ZoomInShortcut) {
152 if (pt.isNull()) {
153 controller->zoomIn();
154 } else {
155 controller->zoomIn(inputManager()->canvas()->coordinatesConverter()->makeWidgetStillPoint(pt));
156 }
157 } else {
158 if (pt.isNull()) {
159 controller->zoomOut();
160 } else {
161 controller->zoomOut(inputManager()->canvas()->coordinatesConverter()->makeWidgetStillPoint(pt));
162 }
163 }
164 break;
165 }
166 case Zoom100PctShortcut: {
167 KoCanvasControllerWidget *controller =
169 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
170 break;
171 }
172 case FitToViewShortcut: {
173 KoCanvasControllerWidget *controller =
175 controller->setZoom(KoZoomMode::ZOOM_PAGE, 1.0);
176 break;
177 }
178 case FitToWidthShortcut: {
179 KoCanvasControllerWidget *controller =
181 controller->setZoom(KoZoomMode::ZOOM_WIDTH, 1.0);
182 break;
183 }
184 case FitToHeightShortcut: {
185 KoCanvasControllerWidget *controller =
187 controller->setZoom(KoZoomMode::ZOOM_HEIGHT, 1.0);
188 break;
189 }
190 }
191}
192
193void KisZoomAction::inputEvent( QEvent* event )
194{
195 switch (event->type()) {
196 case QEvent::TouchUpdate: {
197 QTouchEvent *tevent = static_cast<QTouchEvent*>(event);
198
199 if (tevent->touchPoints().count() != 2) {
200 // Sanity check. The input state machine should only invoke
201 // this action if there are 2 TouchPoints in the event.
202 return;
203 }
204
205 // First, let's determine if we want to handle this event. Sadly
206 // the coordinates of TouchPoints reported by Qt are not always
207 // dependable. TouchPoints that are just getting released can be
208 // off by a significant amount. So we stop the zoom as soon as the
209 // user lifts a finger.
210
211 QTouchEvent::TouchPoint tp0 = tevent->touchPoints().at(0);
212 QTouchEvent::TouchPoint tp1 = tevent->touchPoints().at(1);
213 if (tp0.state() == Qt::TouchPointReleased ||
214 tp1.state() == Qt::TouchPointReleased) {
215 // Force a recomputation of the position on the next event.
216 d->lastPosition = QPoint();
217 return;
218 }
219
220 QPointF p0 = tp0.pos();
221 QPointF p1 = tp1.pos();
222
223 // Make sure none of the TouchPoints are too close together, which
224 // throws off the zoom calculations. This also addresses a glitch
225 // where a newly pressed TouchPoint can incorrectly report another
226 // existing TouchPoint's coordinates instead of its own.
227
228 if ((p0-p1).manhattanLength() < 10) {
229 d->lastPosition = QPointF();
230 return;
231 }
232
233 // If this is the first valid set of points that we are getting,
234 // then use that as the reference for the zoom.
235
236 if (d->lastPosition.isNull()) {
237 d->lastPosition = p0;
238 d->lastDistance = 0;
239 return;
240 }
241
242 float dist = QLineF(p0, p1).length();
243 float delta = qFuzzyCompare(1.0f, 1.0f + d->lastDistance) ? 1.f : dist / d->lastDistance;
244
245 // Workaround: only apply the zoom delta if it's not too
246 // outlandish. As explained above, TouchPoint coordinates are not
247 // always 100% reliable.
248
249 if(qAbs(delta) < 0.8f || qAbs(delta) > 1.2f) {
250 // TouchPoint coordinates tend to converge toward correct
251 // values over time, so assume that the new position is
252 // likelier to be correct than the last and use that as the new
253 // reference.
254 d->lastPosition = p0;
255 return;
256 }
257
258 KisCanvasController *controller = static_cast<KisCanvasController *>(inputManager()->canvas()->canvasController());
259 const qreal newZoom = controller->canvas()->viewConverter()->zoom() * delta;
260 KoViewTransformStillPoint adjustedStillPoint = d->actionStillPoint;
261 adjustedStillPoint.second = p0;
262 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom, adjustedStillPoint);
263 d->lastDistance = dist;
264 d->lastPosition = p0;
265 return; // Don't try to update the cursor during a pinch-zoom
266 }
267 case QEvent::NativeGesture: {
268 QNativeGestureEvent *gevent = static_cast<QNativeGestureEvent*>(event);
269 if (gevent->gestureType() == Qt::ZoomNativeGesture) {
271 KisCanvasController *controller = static_cast<KisCanvasController*>(canvas->canvasController());
272 const qreal delta = 1.0f + gevent->value();
273 const qreal newZoom = controller->canvas()->viewConverter()->zoom() * delta;
274 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom, d->actionStillPoint);
275 } else if (gevent->gestureType() == Qt::SmartZoomNativeGesture) {
278
279 if (controller->zoomState().mode != KoZoomMode::ZOOM_WIDTH) {
280 controller->setZoom(KoZoomMode::ZOOM_WIDTH, 1.0);
281 } else {
282 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
283 }
284 }
285 return;
286 }
287 default:
288 break;
289 }
291}
292
293void KisZoomAction::cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos)
294{
295 QPointF diff = -(pos - startPos);
296
297 const int stepCont = 100;
298 const int stepDisc = 50;
299
300 if (d->mode == ZoomModeShortcut ||
302
303 KisConfig cfg(true);
304
305 const qreal logDistance = std::pow(2.0, qreal(cfg.zoomHorizontal() ? -diff.x() : diff.y()) / qreal(stepCont));
306
307 qreal newZoom = 1.0;
308 if (cfg.readEntry<bool>("InvertMiddleClickZoom", false)) {
309 newZoom = d->startZoom / logDistance;
310 } else {
311 newZoom = d->startZoom * logDistance;
312 }
313
314 KoCanvasControllerWidget *controller =
317
318 if (d->mode == ZoomModeShortcut) {
319 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom);
320 } else {
321 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom, d->actionStillPoint);
322 }
323
324 } else if (d->mode == DiscreteZoomModeShortcut ||
326
327 KoCanvasControllerWidget *controller =
330
331 KisConfig cfg(true);
332
333 qreal axisDiff = qreal(cfg.zoomHorizontal() ? -diff.x() : diff.y());
334 qreal currentDiff = axisDiff / stepDisc - d->lastDiscreteZoomDistance;
335
336 const bool zoomIn = currentDiff > 0;
337 while (qAbs(currentDiff) > 1.0) {
338 if (zoomIn) {
340 controller->zoomIn(d->actionStillPoint);
341 } else {
342 controller->zoomIn();
343 }
344 } else {
346 controller->zoomOut(d->actionStillPoint);
347 } else {
348 controller->zoomOut();
349 }
350 }
351 d->lastDiscreteZoomDistance += zoomIn ? 1.0 : -1.0;
352 currentDiff = axisDiff / stepDisc - d->lastDiscreteZoomDistance;
353 }
354 }
355}
356
357bool KisZoomAction::isShortcutRequired(int shortcut) const
358{
359 return shortcut == ZoomModeShortcut;
360}
361
363{
364 Q_UNUSED(shortcut);
365 return true;
366}
367
369{
370 Q_UNUSED(shortcut);
372}
373
QPointF p0
QPointF p1
KisInputActionGroup
@ ViewTransformActionGroup
Abstract base class for input actions.
virtual void inputEvent(QEvent *event)
static KisInputManager * inputManager
void setShortcutIndexes(const QHash< QString, int > &indexes)
QPoint eventPos(const QEvent *event)
QPointF eventPosF(const QEvent *event)
void setName(const QString &name)
void setDescription(const QString &description)
virtual void begin(int shortcut, QEvent *event)
KisCoordinatesConverter * coordinatesConverter
bool zoomHorizontal(bool defaultValue=false) const
T readEntry(const QString &name, const T &defaultValue=T())
Definition kis_config.h:789
KoViewTransformStillPoint makeWidgetStillPoint(const QPointF &viewPoint) const override
Creates a still point that links the viewPoint of the widget to the corresponding point of the image.
static QCursor zoomSmoothCursor()
static QCursor zoomDiscreteCursor()
KisCanvas2 * canvas() const
KoViewTransformStillPoint actionStillPoint
QPointF centerPoint(QTouchEvent *event)
Private(KisZoomAction *qq)
Zoom Canvas implementation of KisAbstractInputAction.
bool isShortcutRequired(int shortcut) const override
~KisZoomAction() override
void deactivate(int shortcut) override
bool supportsHiResInputEvents(int shortcut) const override
void cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos) override
int priority() const override
@ FitToHeightShortcut
Zoom fit to height.
@ ZoomInShortcut
Zoom in by a fixed amount.
@ RelativeDiscreteZoomModeShortcut
Toggle discrete zoom mode relative to cursor.
@ ZoomOutFromCursorShortcut
Zoom out from cursor.
@ ZoomInToCursorShortcut
Zoom in to cursor.
@ ZoomOutShortcut
Zoom out by a fixed amount.
@ DiscreteZoomModeShortcut
Toggle discrete zoom mode.
@ FitToViewShortcut
Zoom fit to page.
@ ZoomModeShortcut
Toggle zoom mode.
@ Zoom100PctShortcut
Reset zoom to 100%.
@ RelativeZoomModeShortcut
Toggle zoom mode relative to cursor.
@ FitToWidthShortcut
Zoom fit to width.
void activate(int shortcut) override
void inputEvent(QEvent *event) override
KisInputActionGroup inputActionGroup(int shortcut) const override
Private *const d
void begin(int shortcut, QEvent *event=0) override
KoCanvasController * canvasController() const
void setZoom(KoZoomMode::Mode mode, qreal zoom) override
QPointer< KoCanvasBase > canvas
virtual void zoomIn(const KoViewTransformStillPoint &stillPoint)=0
zooms in keeping stillPoint not moved.
virtual KoZoomState zoomState() const =0
virtual void setZoom(KoZoomMode::Mode mode, qreal zoom)=0
virtual void zoomOut(const KoViewTransformStillPoint &stillPoint)=0
zooms out keeping stillPoint not moved.
void zoom(qreal *zoomX, qreal *zoomY) const override
@ ZOOM_CONSTANT
zoom x %
Definition KoZoomMode.h:24
@ ZOOM_PAGE
zoom page
Definition KoZoomMode.h:25
@ ZOOM_WIDTH
zoom pagewidth
Definition KoZoomMode.h:26
@ ZOOM_HEIGHT
zoom pageheight
Definition KoZoomMode.h:27
KoZoomMode::Mode mode
Definition KoZoomState.h:24
static bool qFuzzyCompare(half p1, half p2)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
KisCanvas2 * canvas