File indexing completed on 2024-12-22 04:12:42
0001 /* This file is part of the KDE project 0002 * SPDX-FileCopyrightText: 2012 Arjen Hiemstra <ahiemstra@heimr.nl> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kis_rotate_canvas_action.h" 0008 0009 #include <QApplication> 0010 #include <QNativeGestureEvent> 0011 #include <klocalizedstring.h> 0012 0013 #include "kis_cursor.h" 0014 #include "kis_canvas_controller.h" 0015 #include <kis_canvas2.h> 0016 #include "kis_input_manager.h" 0017 0018 #include <math.h> 0019 0020 constexpr qreal DISCRETE_ANGLE_STEP = 15.0; // discrete rotation snapping angle 0021 0022 class KisRotateCanvasAction::Private 0023 { 0024 public: 0025 Private() {} 0026 0027 // Coverity requires sane defaults for all variables (CID 36429) 0028 Shortcut mode {RotateModeShortcut}; 0029 0030 qreal previousAngle {0.0}; 0031 qreal snapRotation {0.0}; 0032 qreal touchRotation {0.0}; 0033 bool allowRotation {false}; 0034 }; 0035 0036 0037 KisRotateCanvasAction::KisRotateCanvasAction() 0038 : KisAbstractInputAction("Rotate Canvas") 0039 , d(new Private()) 0040 { 0041 setName(i18n("Rotate Canvas")); 0042 setDescription(i18n("The <i>Rotate Canvas</i> action rotates the canvas.")); 0043 0044 QHash<QString, int> shortcuts; 0045 shortcuts.insert(i18n("Rotate Mode"), RotateModeShortcut); 0046 shortcuts.insert(i18n("Discrete Rotate Mode"), DiscreteRotateModeShortcut); 0047 shortcuts.insert(i18n("Rotate Left"), RotateLeftShortcut); 0048 shortcuts.insert(i18n("Rotate Right"), RotateRightShortcut); 0049 shortcuts.insert(i18n("Reset Rotation"), RotateResetShortcut); 0050 setShortcutIndexes(shortcuts); 0051 } 0052 0053 KisRotateCanvasAction::~KisRotateCanvasAction() 0054 { 0055 delete d; 0056 } 0057 0058 int KisRotateCanvasAction::priority() const 0059 { 0060 return 3; 0061 } 0062 0063 void KisRotateCanvasAction::activate(int shortcut) 0064 { 0065 if (shortcut == DiscreteRotateModeShortcut) { 0066 QApplication::setOverrideCursor(KisCursor::rotateCanvasDiscreteCursor()); 0067 } else /* if (shortcut == SmoothRotateModeShortcut) */ { 0068 QApplication::setOverrideCursor(KisCursor::rotateCanvasSmoothCursor()); 0069 } 0070 } 0071 0072 void KisRotateCanvasAction::deactivate(int shortcut) 0073 { 0074 Q_UNUSED(shortcut); 0075 QApplication::restoreOverrideCursor(); 0076 } 0077 0078 void KisRotateCanvasAction::begin(int shortcut, QEvent *event) 0079 { 0080 KisAbstractInputAction::begin(shortcut, event); 0081 d->allowRotation = false; 0082 d->previousAngle = 0; 0083 d->snapRotation = 0; 0084 d->touchRotation = 0; 0085 0086 KisCanvasController *canvasController = 0087 dynamic_cast<KisCanvasController*>(inputManager()->canvas()->canvasController()); 0088 KIS_SAFE_ASSERT_RECOVER_RETURN(canvasController); 0089 0090 d->mode = (Shortcut)shortcut; 0091 0092 switch(shortcut) { 0093 case RotateModeShortcut: 0094 case DiscreteRotateModeShortcut: { 0095 // If the canvas has been rotated to an angle that is not an exact multiple of DISCRETE_ANGLE_STEP, 0096 // we need to adjust the final discrete rotation by that angle difference. 0097 // trunc() is used to round the negative numbers towards zero. 0098 const qreal startRotation = inputManager()->canvas()->rotationAngle(); 0099 d->snapRotation = startRotation - std::trunc(startRotation / DISCRETE_ANGLE_STEP) * DISCRETE_ANGLE_STEP; 0100 canvasController->beginCanvasRotation(); 0101 break; 0102 } 0103 case RotateLeftShortcut: 0104 canvasController->rotateCanvasLeft15(); 0105 break; 0106 case RotateRightShortcut: 0107 canvasController->rotateCanvasRight15(); 0108 break; 0109 case RotateResetShortcut: 0110 canvasController->resetCanvasRotation(); 0111 break; 0112 } 0113 } 0114 0115 void KisRotateCanvasAction::end(QEvent *event) 0116 { 0117 Q_UNUSED(event); 0118 0119 KisCanvasController *canvasController = 0120 dynamic_cast<KisCanvasController*>(inputManager()->canvas()->canvasController()); 0121 KIS_SAFE_ASSERT_RECOVER_RETURN(canvasController); 0122 0123 switch(d->mode) { 0124 case RotateModeShortcut: 0125 case DiscreteRotateModeShortcut: 0126 canvasController->endCanvasRotation(); 0127 break; 0128 default: 0129 break; 0130 } 0131 } 0132 0133 void KisRotateCanvasAction::cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos) 0134 { 0135 if (d->mode == RotateResetShortcut) { 0136 return; 0137 } 0138 0139 const KisCoordinatesConverter *converter = inputManager()->canvas()->coordinatesConverter(); 0140 const QPointF centerPoint = converter->flakeToWidget(converter->flakeCenterPoint()); 0141 const QPointF startPoint = startPos - centerPoint; 0142 const QPointF newPoint = pos - centerPoint; 0143 0144 const qreal oldAngle = atan2(startPoint.y(), startPoint.x()); 0145 const qreal newAngle = atan2(newPoint.y(), newPoint.x()); 0146 0147 qreal newRotation = (180 / M_PI) * (newAngle - oldAngle); 0148 0149 if (d->mode == DiscreteRotateModeShortcut) { 0150 // Do not snap unless the user rotated half-way in the desired direction. 0151 if (qAbs(newRotation) > 0.5 * DISCRETE_ANGLE_STEP || d->allowRotation) { 0152 d->allowRotation = true; 0153 newRotation = qRound((newRotation + d->snapRotation) / DISCRETE_ANGLE_STEP) * DISCRETE_ANGLE_STEP - d->snapRotation; 0154 } else { 0155 newRotation = 0.0; 0156 } 0157 } 0158 0159 KisCanvasController *canvasController = 0160 dynamic_cast<KisCanvasController*>(inputManager()->canvas()->canvasController()); 0161 KIS_SAFE_ASSERT_RECOVER_RETURN(canvasController); 0162 canvasController->rotateCanvas(newRotation); 0163 } 0164 0165 void KisRotateCanvasAction::inputEvent(QEvent* event) 0166 { 0167 switch (event->type()) { 0168 case QEvent::NativeGesture: { 0169 QNativeGestureEvent *gevent = static_cast<QNativeGestureEvent*>(event); 0170 KisCanvas2 *canvas = inputManager()->canvas(); 0171 KisCanvasController *controller = static_cast<KisCanvasController*>(canvas->canvasController()); 0172 0173 const float angle = gevent->value(); 0174 QPoint widgetPos = canvas->canvasWidget()->mapFromGlobal(gevent->globalPos()); 0175 controller->rotateCanvas(angle, widgetPos, true); 0176 return; 0177 } 0178 case QEvent::TouchUpdate: { 0179 QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event); 0180 0181 if (touchEvent->touchPoints().count() != 2) 0182 break; 0183 0184 QTouchEvent::TouchPoint tp0 = touchEvent->touchPoints().at(0); 0185 QTouchEvent::TouchPoint tp1 = touchEvent->touchPoints().at(1); 0186 0187 if (tp0.state() == Qt::TouchPointReleased || 0188 tp1.state() == Qt::TouchPointReleased) 0189 { 0190 // Workaround: on some devices, the coordinates of TouchPoints 0191 // in state TouchPointReleased are not reliable, and can 0192 // "jump" by a significant distance. So we just stop handling 0193 // the rotation as soon as the user's finger leaves the tablet. 0194 break; 0195 } 0196 0197 QPointF p0 = tp0.pos(); 0198 QPointF p1 = tp1.pos(); 0199 0200 if ((p0-p1).manhattanLength() < 10) 0201 { 0202 // The TouchPoints are too close together. Don't update the 0203 // rotation as the angle will likely be off. This also deals 0204 // with a glitch where a newly pressed TouchPoint incorrectly 0205 // reports the existing TouchPoint's coordinates instead of its 0206 // own. 0207 break; 0208 } 0209 0210 // high school (y2 - y1) / (x2 - x1) 0211 QPointF slope = p1 - p0; 0212 qreal newAngle = atan2(slope.y(), slope.x()); 0213 0214 // We must have the previous angle measurement to calculate the delta. 0215 if (d->allowRotation) 0216 { 0217 qreal delta = (180 / M_PI) * (newAngle - d->previousAngle); 0218 0219 // Rotate by the effective angle from the beginning of the action. 0220 d->touchRotation += delta; 0221 0222 KisCanvas2 *canvas = inputManager()->canvas(); 0223 KisCanvasController *controller = static_cast<KisCanvasController*>(canvas->canvasController()); 0224 controller->rotateCanvas(d->touchRotation); 0225 } 0226 else 0227 { 0228 d->allowRotation = true; 0229 } 0230 0231 d->previousAngle = newAngle; 0232 return; 0233 } 0234 default: 0235 break; 0236 } 0237 KisAbstractInputAction::inputEvent(event); 0238 } 0239 0240 KisInputActionGroup KisRotateCanvasAction::inputActionGroup(int shortcut) const 0241 { 0242 Q_UNUSED(shortcut); 0243 return ViewTransformActionGroup; 0244 } 0245 0246 bool KisRotateCanvasAction::supportsHiResInputEvents(int shortcut) const 0247 { 0248 Q_UNUSED(shortcut); 0249 return true; 0250 }