Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_rotate_canvas_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
8
9#include <QApplication>
10#include <QNativeGestureEvent>
11#include <klocalizedstring.h>
12
13#include "kis_cursor.h"
15#include <kis_canvas2.h>
16#include "kis_input_manager.h"
18
19#include <math.h>
20
21constexpr qreal DISCRETE_ANGLE_STEP = 15.0; // discrete rotation snapping angle
22
24{
25public:
27
28 // Coverity requires sane defaults for all variables (CID 36429)
30
31 qreal previousAngle {0.0};
32 qreal snapRotation {0.0};
33 qreal touchRotation {0.0};
34 bool allowRotation {false};
36};
37
38
40 : KisAbstractInputAction("Rotate Canvas")
41 , d(new Private())
42{
43 setName(i18n("Rotate Canvas"));
44 setDescription(i18n("The <i>Rotate Canvas</i> action rotates the canvas."));
45
46 QHash<QString, int> shortcuts;
47 shortcuts.insert(i18n("Rotate Mode"), RotateModeShortcut);
48 shortcuts.insert(i18n("Discrete Rotate Mode"), DiscreteRotateModeShortcut);
49 shortcuts.insert(i18n("Rotate Left"), RotateLeftShortcut);
50 shortcuts.insert(i18n("Rotate Right"), RotateRightShortcut);
51 shortcuts.insert(i18n("Reset Rotation"), RotateResetShortcut);
52 setShortcutIndexes(shortcuts);
53}
54
59
61{
62 return 3;
63}
64
66{
67 if (shortcut == DiscreteRotateModeShortcut) {
68 QApplication::setOverrideCursor(KisCursor::rotateCanvasDiscreteCursor());
69 } else /* if (shortcut == SmoothRotateModeShortcut) */ {
70 QApplication::setOverrideCursor(KisCursor::rotateCanvasSmoothCursor());
71 }
72}
73
75{
76 Q_UNUSED(shortcut);
77 QApplication::restoreOverrideCursor();
78}
79
80void KisRotateCanvasAction::begin(int shortcut, QEvent *event)
81{
82 KisAbstractInputAction::begin(shortcut, event);
83 d->allowRotation = false;
84 d->previousAngle = 0;
85 d->snapRotation = 0;
86 d->touchRotation = 0;
87
88 KisCanvasController *canvasController =
90 KIS_SAFE_ASSERT_RECOVER_RETURN(canvasController);
91
92 d->mode = (Shortcut)shortcut;
93
94 switch(shortcut) {
97 // If the canvas has been rotated to an angle that is not an exact multiple of DISCRETE_ANGLE_STEP,
98 // we need to adjust the final discrete rotation by that angle difference.
99 // trunc() is used to round the negative numbers towards zero.
100 const qreal startRotation = inputManager()->canvas()->rotationAngle();
101 d->snapRotation = startRotation - std::trunc(startRotation / DISCRETE_ANGLE_STEP) * DISCRETE_ANGLE_STEP;
102 canvasController->beginCanvasRotation();
104 break;
105 }
107 canvasController->rotateCanvasLeft15();
108 break;
110 canvasController->rotateCanvasRight15();
111 break;
113 canvasController->resetCanvasRotation();
114 break;
115 }
116}
117
118void KisRotateCanvasAction::end(QEvent *event)
119{
120 Q_UNUSED(event);
121
122 KisCanvasController *canvasController =
124 KIS_SAFE_ASSERT_RECOVER_RETURN(canvasController);
125
126 switch(d->mode) {
129 canvasController->endCanvasRotation();
130 break;
131 default:
132 break;
133 }
134}
135
136void KisRotateCanvasAction::cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos)
137{
138 if (d->mode == RotateResetShortcut) {
139 return;
140 }
141
143 const QPointF centerPoint = converter->flakeToWidget(converter->flakeCenterPoint());
144 const QPointF startPoint = startPos - centerPoint;
145 const QPointF newPoint = pos - centerPoint;
146
147 const qreal oldAngle = atan2(startPoint.y(), startPoint.x());
148 const qreal newAngle = atan2(newPoint.y(), newPoint.x());
149
150 qreal newRotation = (180 / M_PI) * (newAngle - oldAngle);
151
153 // Do not snap unless the user rotated half-way in the desired direction.
154 if (qAbs(newRotation) > 0.5 * DISCRETE_ANGLE_STEP || d->allowRotation) {
155 d->allowRotation = true;
156 newRotation = qRound((newRotation + d->snapRotation) / DISCRETE_ANGLE_STEP) * DISCRETE_ANGLE_STEP - d->snapRotation;
157 } else {
158 newRotation = 0.0;
159 }
160 }
161
162 KisCanvasController *canvasController =
164 KIS_SAFE_ASSERT_RECOVER_RETURN(canvasController);
165 canvasController->rotateCanvas(newRotation);
166}
167
169{
170 switch (event->type()) {
171 case QEvent::NativeGesture: {
172 QNativeGestureEvent *gevent = static_cast<QNativeGestureEvent*>(event);
174 KisCanvasController *controller = static_cast<KisCanvasController*>(canvas->canvasController());
175
176 const float angle = gevent->value();
177 QPoint widgetPos = canvas->canvasWidget()->mapFromGlobal(gevent->globalPos());
178
179 KoViewTransformStillPoint adjustedStillPoint = d->actionStillPoint;
180 adjustedStillPoint.second = widgetPos;
181
182 controller->rotateCanvas(angle, adjustedStillPoint, true);
183 return;
184 }
185 case QEvent::TouchUpdate: {
186 QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
187
188 if (touchEvent->touchPoints().count() != 2)
189 break;
190
191 QTouchEvent::TouchPoint tp0 = touchEvent->touchPoints().at(0);
192 QTouchEvent::TouchPoint tp1 = touchEvent->touchPoints().at(1);
193
194 if (tp0.state() == Qt::TouchPointReleased ||
195 tp1.state() == Qt::TouchPointReleased)
196 {
197 // Workaround: on some devices, the coordinates of TouchPoints
198 // in state TouchPointReleased are not reliable, and can
199 // "jump" by a significant distance. So we just stop handling
200 // the rotation as soon as the user's finger leaves the tablet.
201 break;
202 }
203
204 QPointF p0 = tp0.pos();
205 QPointF p1 = tp1.pos();
206
207 if ((p0-p1).manhattanLength() < 10)
208 {
209 // The TouchPoints are too close together. Don't update the
210 // rotation as the angle will likely be off. This also deals
211 // with a glitch where a newly pressed TouchPoint incorrectly
212 // reports the existing TouchPoint's coordinates instead of its
213 // own.
214 break;
215 }
216
217 // high school (y2 - y1) / (x2 - x1)
218 QPointF slope = p1 - p0;
219 qreal newAngle = atan2(slope.y(), slope.x());
220
221 // We must have the previous angle measurement to calculate the delta.
222 if (d->allowRotation)
223 {
224 qreal delta = (180 / M_PI) * (newAngle - d->previousAngle);
225
226 // Rotate by the effective angle from the beginning of the action.
227 d->touchRotation += delta;
228
230 KisCanvasController *controller = static_cast<KisCanvasController*>(canvas->canvasController());
231 controller->rotateCanvas(d->touchRotation);
232 }
233 else
234 {
235 d->allowRotation = true;
236 }
237
238 d->previousAngle = newAngle;
239 return;
240 }
241 default:
242 break;
243 }
245}
246
248{
249 Q_UNUSED(shortcut);
251}
252
254{
255 Q_UNUSED(shortcut);
256 return true;
257}
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)
QPointF eventPosF(const QEvent *event)
void setName(const QString &name)
void setDescription(const QString &description)
virtual void begin(int shortcut, QEvent *event)
qreal rotationAngle() const
canvas rotation in degrees
KisCoordinatesConverter * coordinatesConverter
KisAbstractCanvasWidget * canvasWidget
void rotateCanvas(qreal angle, const std::optional< KoViewTransformStillPoint > &stillPoint, bool isNativeGesture=false)
_Private::Traits< T >::Result flakeToWidget(const T &obj) const
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 rotateCanvasDiscreteCursor()
static QCursor rotateCanvasSmoothCursor()
KisCanvas2 * canvas() const
KoViewTransformStillPoint actionStillPoint
KisInputActionGroup inputActionGroup(int shortcut) const override
void deactivate(int shortcut) override
void inputEvent(QEvent *event) override
@ RotateLeftShortcut
Rotate left by a fixed amount.
@ RotateResetShortcut
Reset the rotation to 0.
@ DiscreteRotateModeShortcut
Toggle Discrete Rotate mode.
@ RotateRightShortcut
Rotate right by a fixed amount.
@ RotateModeShortcut
Toggle Rotate mode.
bool supportsHiResInputEvents(int shortcut) const override
void cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos) override
void activate(int shortcut) override
void end(QEvent *event) override
void begin(int shortcut, QEvent *event) override
KoCanvasController * canvasController() const
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define M_PI
Definition kis_global.h:111
constexpr qreal DISCRETE_ANGLE_STEP
KisCanvas2 * canvas