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 if(!event) {
196 return;
197 }
198 switch (event->type()) {
199 case QEvent::TouchUpdate: {
200 QTouchEvent *tevent = static_cast<QTouchEvent*>(event);
201
202 if (tevent->touchPoints().count() != 2) {
203 // Sanity check. The input state machine should only invoke
204 // this action if there are 2 TouchPoints in the event.
205 return;
206 }
207
208 // First, let's determine if we want to handle this event. Sadly
209 // the coordinates of TouchPoints reported by Qt are not always
210 // dependable. TouchPoints that are just getting released can be
211 // off by a significant amount. So we stop the zoom as soon as the
212 // user lifts a finger.
213
214 QTouchEvent::TouchPoint tp0 = tevent->touchPoints().at(0);
215 QTouchEvent::TouchPoint tp1 = tevent->touchPoints().at(1);
216 if (tp0.state() == Qt::TouchPointReleased ||
217 tp1.state() == Qt::TouchPointReleased) {
218 // Force a recomputation of the position on the next event.
219 d->lastPosition = QPoint();
220 return;
221 }
222
223 QPointF p0 = tp0.pos();
224 QPointF p1 = tp1.pos();
225
226 // Make sure none of the TouchPoints are too close together, which
227 // throws off the zoom calculations. This also addresses a glitch
228 // where a newly pressed TouchPoint can incorrectly report another
229 // existing TouchPoint's coordinates instead of its own.
230
231 if ((p0-p1).manhattanLength() < 10) {
232 d->lastPosition = QPointF();
233 return;
234 }
235
236 // If this is the first valid set of points that we are getting,
237 // then use that as the reference for the zoom.
238
239 if (d->lastPosition.isNull()) {
240 d->lastPosition = p0;
241 d->lastDistance = 0;
242 return;
243 }
244
245 float dist = QLineF(p0, p1).length();
246 float delta = qFuzzyCompare(1.0f, 1.0f + d->lastDistance) ? 1.f : dist / d->lastDistance;
247
248 // Workaround: only apply the zoom delta if it's not too
249 // outlandish. As explained above, TouchPoint coordinates are not
250 // always 100% reliable.
251
252 if(qAbs(delta) < 0.8f || qAbs(delta) > 1.2f) {
253 // TouchPoint coordinates tend to converge toward correct
254 // values over time, so assume that the new position is
255 // likelier to be correct than the last and use that as the new
256 // reference.
257 d->lastPosition = p0;
258 return;
259 }
260
261 KisCanvasController *controller = static_cast<KisCanvasController *>(inputManager()->canvas()->canvasController());
262 const qreal newZoom = controller->canvas()->viewConverter()->zoom() * delta;
263 KoViewTransformStillPoint adjustedStillPoint = d->actionStillPoint;
264 adjustedStillPoint.second = p0;
265 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom, adjustedStillPoint);
266 d->lastDistance = dist;
267 d->lastPosition = p0;
268 return; // Don't try to update the cursor during a pinch-zoom
269 }
270 case QEvent::NativeGesture: {
271 QNativeGestureEvent *gevent = static_cast<QNativeGestureEvent*>(event);
272 if (gevent->gestureType() == Qt::ZoomNativeGesture) {
274 KisCanvasController *controller = static_cast<KisCanvasController*>(canvas->canvasController());
275 const qreal delta = 1.0f + gevent->value();
276 const qreal newZoom = controller->canvas()->viewConverter()->zoom() * delta;
277 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom, d->actionStillPoint);
278 } else if (gevent->gestureType() == Qt::SmartZoomNativeGesture) {
281
282 if (controller->zoomState().mode != KoZoomMode::ZOOM_WIDTH) {
283 controller->setZoom(KoZoomMode::ZOOM_WIDTH, 1.0);
284 } else {
285 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
286 }
287 }
288 return;
289 }
290 default:
291 break;
292 }
294}
295
296void KisZoomAction::cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos)
297{
298 QPointF diff = -(pos - startPos);
299
300 const int stepCont = 100;
301 const int stepDisc = 50;
302
303 if (d->mode == ZoomModeShortcut ||
305
306 KisConfig cfg(true);
307
308 const qreal logDistance = std::pow(2.0, qreal(cfg.zoomHorizontal() ? -diff.x() : diff.y()) / qreal(stepCont));
309
310 qreal newZoom = 1.0;
311 if (cfg.readEntry<bool>("InvertMiddleClickZoom", false)) {
312 newZoom = d->startZoom / logDistance;
313 } else {
314 newZoom = d->startZoom * logDistance;
315 }
316
317 KoCanvasControllerWidget *controller =
320
321 if (d->mode == ZoomModeShortcut) {
322 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom);
323 } else {
324 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom, d->actionStillPoint);
325 }
326
327 } else if (d->mode == DiscreteZoomModeShortcut ||
329
330 KoCanvasControllerWidget *controller =
333
334 KisConfig cfg(true);
335
336 qreal axisDiff = qreal(cfg.zoomHorizontal() ? -diff.x() : diff.y());
337 qreal currentDiff = axisDiff / stepDisc - d->lastDiscreteZoomDistance;
338
339 const bool zoomIn = currentDiff > 0;
340 while (qAbs(currentDiff) > 1.0) {
341 if (zoomIn) {
343 controller->zoomIn(d->actionStillPoint);
344 } else {
345 controller->zoomIn();
346 }
347 } else {
349 controller->zoomOut(d->actionStillPoint);
350 } else {
351 controller->zoomOut();
352 }
353 }
354 d->lastDiscreteZoomDistance += zoomIn ? 1.0 : -1.0;
355 currentDiff = axisDiff / stepDisc - d->lastDiscreteZoomDistance;
356 }
357 }
358}
359
360bool KisZoomAction::isShortcutRequired(int shortcut) const
361{
362 return shortcut == ZoomModeShortcut;
363}
364
366{
367 Q_UNUSED(shortcut);
368 return true;
369}
370
372{
373 Q_UNUSED(shortcut);
375}
376
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:875
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