File indexing completed on 2024-06-23 04:28:12
0001 /* This file is part of the KDE project 0002 * SPDX-FileCopyrightText: 2006-2007 Thomas Zander <zander@kde.org> 0003 * SPDX-FileCopyrightText: 2006 C. Boemann <cbo@boemann.dk> 0004 * SPDX-FileCopyrightText: 2008 Jan Hambrecht <jaham@gmx.net> 0005 * 0006 * SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "ShapeShearStrategy.h" 0010 #include "SelectionDecorator.h" 0011 0012 #include <KoToolBase.h> 0013 #include <KoCanvasBase.h> 0014 #include <KoPointerEvent.h> 0015 #include <KoShapeManager.h> 0016 #include <commands/KoShapeShearCommand.h> 0017 #include <commands/KoShapeMoveCommand.h> 0018 #include <commands/KoShapeTransformCommand.h> 0019 0020 #include <KoSelection.h> 0021 #include <QPointF> 0022 0023 #include <math.h> 0024 #include <QDebug> 0025 #include <klocalizedstring.h> 0026 #include "kis_algebra_2d.h" 0027 0028 ShapeShearStrategy::ShapeShearStrategy(KoToolBase *tool, KoSelection *selection, const QPointF &clicked, KoFlake::SelectionHandle direction) 0029 : KoInteractionStrategy(tool) 0030 , m_start(clicked) 0031 { 0032 /** 0033 * The outline of the selection should look as if it is also sheared, so we 0034 * add it to the transformed shapes list. 0035 */ 0036 m_transformedShapesAndSelection = selection->selectedEditableShapes(); 0037 m_transformedShapesAndSelection << selection; 0038 0039 Q_FOREACH (KoShape *shape, m_transformedShapesAndSelection) { 0040 m_oldTransforms << shape->transformation(); 0041 } 0042 0043 // Even though we aren't currently activated by the corner handles we might as well code like it 0044 switch (direction) { 0045 case KoFlake::TopMiddleHandle: 0046 m_top = true; m_bottom = false; m_left = false; m_right = false; break; 0047 case KoFlake::TopRightHandle: 0048 m_top = true; m_bottom = false; m_left = false; m_right = true; break; 0049 case KoFlake::RightMiddleHandle: 0050 m_top = false; m_bottom = false; m_left = false; m_right = true; break; 0051 case KoFlake::BottomRightHandle: 0052 m_top = false; m_bottom = true; m_left = false; m_right = true; break; 0053 case KoFlake::BottomMiddleHandle: 0054 m_top = false; m_bottom = true; m_left = false; m_right = false; break; 0055 case KoFlake::BottomLeftHandle: 0056 m_top = false; m_bottom = true; m_left = true; m_right = false; break; 0057 case KoFlake::LeftMiddleHandle: 0058 m_top = false; m_bottom = false; m_left = true; m_right = false; break; 0059 case KoFlake::TopLeftHandle: 0060 m_top = true; m_bottom = false; m_left = true; m_right = false; break; 0061 default: 0062 Q_UNREACHABLE(); 0063 ;// throw exception ? TODO 0064 } 0065 m_initialSize = selection->size(); 0066 m_solidPoint = QPointF(m_initialSize.width() / 2, m_initialSize.height() / 2); 0067 0068 if (m_top) { 0069 m_solidPoint += QPointF(0, m_initialSize.height() / 2); 0070 } else if (m_bottom) { 0071 m_solidPoint -= QPointF(0, m_initialSize.height() / 2); 0072 } 0073 if (m_left) { 0074 m_solidPoint += QPointF(m_initialSize.width() / 2, 0); 0075 } else if (m_right) { 0076 m_solidPoint -= QPointF(m_initialSize.width() / 2, 0); 0077 } 0078 0079 m_solidPoint = selection->absoluteTransformation().map(selection->outlineRect().topLeft() + m_solidPoint); 0080 0081 QPointF edge; 0082 qreal angle = 0.0; 0083 if (m_top) { 0084 edge = selection->absolutePosition(KoFlake::BottomLeft) - selection->absolutePosition(KoFlake::BottomRight); 0085 angle = 180.0; 0086 } else if (m_bottom) { 0087 edge = selection->absolutePosition(KoFlake::TopRight) - selection->absolutePosition(KoFlake::TopLeft); 0088 angle = 0.0; 0089 } else if (m_left) { 0090 edge = selection->absolutePosition(KoFlake::BottomLeft) - selection->absolutePosition(KoFlake::TopLeft); 0091 angle = 90.0; 0092 } else if (m_right) { 0093 edge = selection->absolutePosition(KoFlake::TopRight) - selection->absolutePosition(KoFlake::BottomRight); 0094 angle = 270.0; 0095 } 0096 qreal currentAngle = atan2(edge.y(), edge.x()) / M_PI * 180; 0097 m_initialSelectionAngle = currentAngle - angle; 0098 0099 // use cross product of top edge and left edge of selection bounding rect 0100 // to determine if the selection is mirrored 0101 QPointF top = selection->absolutePosition(KoFlake::TopRight) - selection->absolutePosition(KoFlake::TopLeft); 0102 QPointF left = selection->absolutePosition(KoFlake::BottomLeft) - selection->absolutePosition(KoFlake::TopLeft); 0103 m_isMirrored = (top.x() * left.y() - top.y() * left.x()) < 0.0; 0104 } 0105 0106 void ShapeShearStrategy::handleMouseMove(const QPointF &point, Qt::KeyboardModifiers modifiers) 0107 { 0108 Q_UNUSED(modifiers); 0109 QPointF shearVector = point - m_start; 0110 0111 QTransform m; 0112 m.rotate(-m_initialSelectionAngle); 0113 shearVector = m.map(shearVector); 0114 0115 qreal shearX = 0, shearY = 0; 0116 0117 if (m_top || m_left) { 0118 shearVector = - shearVector; 0119 } 0120 if (m_top || m_bottom) { 0121 shearX = m_initialSize.height() > 0 ? shearVector.x() / m_initialSize.height() : 0; 0122 } 0123 if (m_left || m_right) { 0124 shearY = m_initialSize.width() > 0 ? shearVector.y() / m_initialSize.width() : 0; 0125 } 0126 0127 // if selection is mirrored invert the shear values 0128 if (m_isMirrored) { 0129 shearX *= -1.0; 0130 shearY *= -1.0; 0131 } 0132 0133 const qreal maxSaneShear = 1e6; 0134 if ((qAbs(shearX) == 0.0 && qAbs(shearY) == 0.0) || 0135 qAbs(shearX) > maxSaneShear || 0136 qAbs(shearY) > maxSaneShear) { 0137 0138 return; 0139 } 0140 0141 QTransform matrix; 0142 matrix.translate(m_solidPoint.x(), m_solidPoint.y()); 0143 matrix.rotate(m_initialSelectionAngle); 0144 matrix.shear(shearX, shearY); 0145 matrix.rotate(-m_initialSelectionAngle); 0146 matrix.translate(-m_solidPoint.x(), -m_solidPoint.y()); 0147 0148 QTransform applyMatrix = matrix * m_shearMatrix.inverted(); 0149 0150 Q_FOREACH (KoShape *shape, m_transformedShapesAndSelection) { 0151 const QRectF oldDirtyRect = shape->boundingRect(); 0152 shape->applyAbsoluteTransformation(applyMatrix); 0153 shape->updateAbsolute(oldDirtyRect | shape->boundingRect()); 0154 } 0155 m_shearMatrix = matrix; 0156 } 0157 0158 void ShapeShearStrategy::paint(QPainter &painter, const KoViewConverter &converter) 0159 { 0160 Q_UNUSED(painter); 0161 Q_UNUSED(converter); 0162 } 0163 0164 KUndo2Command *ShapeShearStrategy::createCommand() 0165 { 0166 QList<QTransform> newTransforms; 0167 Q_FOREACH (KoShape *shape, m_transformedShapesAndSelection) { 0168 newTransforms << shape->transformation(); 0169 } 0170 const bool nothingChanged = 0171 std::equal(m_oldTransforms.begin(), m_oldTransforms.end(), 0172 newTransforms.begin(), 0173 [] (const QTransform &t1, const QTransform &t2) { 0174 return KisAlgebra2D::fuzzyMatrixCompare(t1, t2, 1e-6); 0175 }); 0176 0177 KoShapeTransformCommand *cmd = 0; 0178 if (!nothingChanged) { 0179 cmd = new KoShapeTransformCommand(m_transformedShapesAndSelection, m_oldTransforms, newTransforms); 0180 cmd->setText(kundo2_i18n("Shear")); 0181 } 0182 return cmd; 0183 }