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 if(!event) {
171 return;
172 }
173
174 switch (event->type()) {
175 case QEvent::NativeGesture: {
176 QNativeGestureEvent *gevent = static_cast<QNativeGestureEvent*>(event);
178 KisCanvasController *controller = static_cast<KisCanvasController*>(canvas->canvasController());
179
180 const float angle = gevent->value();
181 QPoint widgetPos = canvas->canvasWidget()->mapFromGlobal(gevent->globalPos());
182
183 KoViewTransformStillPoint adjustedStillPoint = d->actionStillPoint;
184 adjustedStillPoint.second = widgetPos;
185
186 controller->rotateCanvas(angle, adjustedStillPoint, true);
187 return;
188 }
189 case QEvent::TouchUpdate: {
190 QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
191
192 if (touchEvent->touchPoints().count() != 2)
193 break;
194
195 QTouchEvent::TouchPoint tp0 = touchEvent->touchPoints().at(0);
196 QTouchEvent::TouchPoint tp1 = touchEvent->touchPoints().at(1);
197
198 if (tp0.state() == Qt::TouchPointReleased ||
199 tp1.state() == Qt::TouchPointReleased)
200 {
201 // Workaround: on some devices, the coordinates of TouchPoints
202 // in state TouchPointReleased are not reliable, and can
203 // "jump" by a significant distance. So we just stop handling
204 // the rotation as soon as the user's finger leaves the tablet.
205 break;
206 }
207
208 QPointF p0 = tp0.pos();
209 QPointF p1 = tp1.pos();
210
211 if ((p0-p1).manhattanLength() < 10)
212 {
213 // The TouchPoints are too close together. Don't update the
214 // rotation as the angle will likely be off. This also deals
215 // with a glitch where a newly pressed TouchPoint incorrectly
216 // reports the existing TouchPoint's coordinates instead of its
217 // own.
218 break;
219 }
220
221 // high school (y2 - y1) / (x2 - x1)
222 QPointF slope = p1 - p0;
223 qreal newAngle = atan2(slope.y(), slope.x());
224
225 // We must have the previous angle measurement to calculate the delta.
226 if (d->allowRotation)
227 {
228 qreal delta = (180 / M_PI) * (newAngle - d->previousAngle);
229
230 // Rotate by the effective angle from the beginning of the action.
231 d->touchRotation += delta;
232
234 KisCanvasController *controller = static_cast<KisCanvasController*>(canvas->canvasController());
235 controller->rotateCanvas(d->touchRotation);
236 }
237 else
238 {
239 d->allowRotation = true;
240 }
241
242 d->previousAngle = newAngle;
243 return;
244 }
245 default:
246 break;
247 }
249}
250
252{
253 Q_UNUSED(shortcut);
255}
256
258{
259 Q_UNUSED(shortcut);
260 return true;
261}
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