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 }