File indexing completed on 2024-06-23 04:28:11

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2006-2007 Thomas Zander <zander@kde.org>
0003  * SPDX-FileCopyrightText: 2007, 2011 Jan Hambrecht <jaham@gmx.net>
0004  * SPDX-FileCopyrightText: 2017 Boudewijn Rempt <boud@valdyas.org>
0005  *
0006  * SPDX-License-Identifier: LGPL-2.0-or-later
0007  */
0008 
0009 #include "ShapeResizeStrategy.h"
0010 #include "SelectionDecorator.h"
0011 
0012 #include <KoShapeManager.h>
0013 #include <KoPointerEvent.h>
0014 #include <KoCanvasBase.h>
0015 #include <commands/KoShapeResizeCommand.h>
0016 #include <kis_command_utils.h>
0017 #include <KoSnapGuide.h>
0018 #include <KoToolBase.h>
0019 #include <KoSelection.h>
0020 
0021 #include <klocalizedstring.h>
0022 #include <limits>
0023 #include <math.h>
0024 
0025 #include <kis_debug.h>
0026 #include "kis_algebra_2d.h"
0027 
0028 ShapeResizeStrategy::ShapeResizeStrategy(KoToolBase *tool, KoSelection *selection, const QPointF &clicked, KoFlake::SelectionHandle direction, bool forceUniformScalingMode)
0029     : KoInteractionStrategy(tool),
0030       m_forceUniformScalingMode(forceUniformScalingMode)
0031 {
0032     KIS_SAFE_ASSERT_RECOVER_RETURN(selection && selection->count() > 0);
0033 
0034     m_selectedShapes = selection->selectedEditableShapes();
0035     m_start = clicked;
0036 
0037     KoShape *shape = selection;
0038 
0039     const qreal w = shape->size().width();
0040     const qreal h = shape->size().height();
0041 
0042     switch (direction) {
0043     case KoFlake::TopMiddleHandle:
0044         m_start = 0.5 * (shape->absolutePosition(KoFlake::TopLeft) + shape->absolutePosition(KoFlake::TopRight));
0045         m_top = true; m_bottom = false; m_left = false; m_right = false;
0046         m_globalStillPoint = QPointF(0.5 * w, h);
0047         break;
0048     case KoFlake::TopRightHandle:
0049         m_start = shape->absolutePosition(KoFlake::TopRight);
0050         m_top = true; m_bottom = false; m_left = false; m_right = true;
0051         m_globalStillPoint = QPointF(0, h);
0052         break;
0053     case KoFlake::RightMiddleHandle:
0054         m_start = 0.5 * (shape->absolutePosition(KoFlake::TopRight) + shape->absolutePosition(KoFlake::BottomRight));
0055         m_top = false; m_bottom = false; m_left = false; m_right = true;
0056         m_globalStillPoint = QPointF(0, 0.5 * h);
0057         break;
0058     case KoFlake::BottomRightHandle:
0059         m_start = shape->absolutePosition(KoFlake::BottomRight);
0060         m_top = false; m_bottom = true; m_left = false; m_right = true;
0061         m_globalStillPoint = QPointF(0, 0);
0062         break;
0063     case KoFlake::BottomMiddleHandle:
0064         m_start = 0.5 * (shape->absolutePosition(KoFlake::BottomRight) + shape->absolutePosition(KoFlake::BottomLeft));
0065         m_top = false; m_bottom = true; m_left = false; m_right = false;
0066         m_globalStillPoint = QPointF(0.5 * w, 0);
0067         break;
0068     case KoFlake::BottomLeftHandle:
0069         m_start = shape->absolutePosition(KoFlake::BottomLeft);
0070         m_top = false; m_bottom = true; m_left = true; m_right = false;
0071         m_globalStillPoint = QPointF(w, 0);
0072         break;
0073     case KoFlake::LeftMiddleHandle:
0074         m_start = 0.5 * (shape->absolutePosition(KoFlake::BottomLeft) + shape->absolutePosition(KoFlake::TopLeft));
0075         m_top = false; m_bottom = false; m_left = true; m_right = false;
0076         m_globalStillPoint = QPointF(w, 0.5 * h);
0077         break;
0078     case KoFlake::TopLeftHandle:
0079         m_start = shape->absolutePosition(KoFlake::TopLeft);
0080         m_top = true; m_bottom = false; m_left = true; m_right = false;
0081         m_globalStillPoint = QPointF(w, h);
0082         break;
0083     default:
0084         Q_ASSERT(0); // illegal 'corner'
0085     }
0086 
0087     const QPointF p0 = shape->outlineRect().topLeft();
0088     m_globalStillPoint = shape->absoluteTransformation().map(p0 + m_globalStillPoint);
0089     m_globalCenterPoint = shape->absolutePosition(KoFlake::Center);
0090 
0091     m_unwindMatrix = shape->absoluteTransformation().inverted();
0092     m_initialSelectionSize = shape->size();
0093     m_postScalingCoveringTransform = shape->transformation();
0094 
0095 
0096     tool->setStatusText(i18n("Press CTRL to resize from center."));
0097     tool->canvas()->snapGuide()->setIgnoredShapes(KoShape::linearizeSubtree(m_selectedShapes));
0098 }
0099 
0100 ShapeResizeStrategy::~ShapeResizeStrategy()
0101 {
0102 
0103 }
0104 
0105 void ShapeResizeStrategy::handleMouseMove(const QPointF &point, Qt::KeyboardModifiers modifiers)
0106 {
0107     QPointF newPos = tool()->canvas()->snapGuide()->snap(point, modifiers);
0108 
0109     bool keepAspect = modifiers & Qt::ShiftModifier;
0110     Q_FOREACH (KoShape *shape, m_selectedShapes) {
0111         keepAspect = keepAspect || shape->keepAspectRatio();
0112     }
0113 
0114     qreal startWidth = m_initialSelectionSize.width();
0115     if (startWidth < std::numeric_limits<qreal>::epsilon()) {
0116         startWidth = std::numeric_limits<qreal>::epsilon();
0117     }
0118     qreal startHeight = m_initialSelectionSize.height();
0119     if (startHeight < std::numeric_limits<qreal>::epsilon()) {
0120         startHeight = std::numeric_limits<qreal>::epsilon();
0121     }
0122 
0123     QPointF distance = m_unwindMatrix.map(newPos) - m_unwindMatrix.map(m_start);
0124 
0125     // guard against resizing zero width shapes, which would result in huge zoom factors
0126     if (m_initialSelectionSize.width() < std::numeric_limits<qreal>::epsilon()) {
0127         distance.rx() = 0.0;
0128     }
0129     // guard against resizing zero height shapes, which would result in huge zoom factors
0130     if (m_initialSelectionSize.height() < std::numeric_limits<qreal>::epsilon()) {
0131         distance.ry() = 0.0;
0132     }
0133 
0134     const bool scaleFromCenter = modifiers & Qt::ControlModifier;
0135     if (scaleFromCenter) {
0136         distance *= 2.0;
0137     }
0138 
0139     qreal newWidth = startWidth;
0140     qreal newHeight = startHeight;
0141 
0142     if (m_left) {
0143         newWidth = startWidth - distance.x();
0144     } else if (m_right) {
0145         newWidth = startWidth + distance.x();
0146     }
0147 
0148     if (m_top) {
0149         newHeight = startHeight - distance.y();
0150     } else if (m_bottom) {
0151         newHeight = startHeight + distance.y();
0152     }
0153 
0154     /**
0155      * Do not let a shape be less than 1px in size in current view
0156      * coordinates.  If the user wants it to be smaller, he can just
0157      * zoom-in a bit.
0158      */
0159     QSizeF minViewSize(1.0, 1.0);
0160     QSizeF minDocSize = tool()->canvas()->viewConverter()->viewToDocument(minViewSize);
0161 
0162     if (qAbs(newWidth) < minDocSize.width()) {
0163         newWidth = KisAlgebra2D::signPZ(newWidth) * minDocSize.width();
0164     }
0165 
0166     if (qAbs(newHeight) < minDocSize.height()) {
0167         newHeight = KisAlgebra2D::signPZ(newHeight) * minDocSize.height();
0168     }
0169 
0170     qreal zoomX = qAbs(startWidth) >= minDocSize.width() ? newWidth / startWidth : 1.0;
0171     qreal zoomY = qAbs(startHeight) >= minDocSize.height() ? newHeight / startHeight : 1.0;
0172 
0173     if (keepAspect) {
0174         const bool cornerUsed = m_bottom + m_top + m_left + m_right == 2;
0175         if (cornerUsed) {
0176             if (startWidth < startHeight) {
0177                 zoomY = zoomX;
0178             } else {
0179                 zoomX = zoomY;
0180             }
0181         } else {
0182             if (m_left || m_right) {
0183                zoomY = qAbs(zoomX);
0184             } else {
0185                zoomX = qAbs(zoomY);
0186             }
0187         }
0188     }
0189 
0190     resizeBy(scaleFromCenter ? m_globalCenterPoint : m_globalStillPoint, zoomX, zoomY);
0191 }
0192 
0193 void ShapeResizeStrategy::resizeBy(const QPointF &stillPoint, qreal zoomX, qreal zoomY)
0194 {
0195     if (!m_executedCommand) {
0196         const bool usePostScaling = m_selectedShapes.size() > 1 || m_forceUniformScalingMode;
0197 
0198         m_executedCommand.reset(
0199                     new KoShapeResizeCommand(
0200                         m_selectedShapes,
0201                         zoomX, zoomY,
0202                         stillPoint,
0203                         false, usePostScaling, m_postScalingCoveringTransform));
0204         m_executedCommand->redo();
0205     } else {
0206         m_executedCommand->replaceResizeAction(zoomX, zoomY, stillPoint);
0207     }
0208 }
0209 
0210 KUndo2Command *ShapeResizeStrategy::createCommand()
0211 {
0212     tool()->canvas()->snapGuide()->reset();
0213 
0214     if (m_executedCommand) {
0215         m_executedCommand->setSkipOneRedo(true);
0216     }
0217 
0218     return m_executedCommand.take();
0219 }
0220 
0221 void ShapeResizeStrategy::finishInteraction(Qt::KeyboardModifiers modifiers)
0222 {
0223     Q_UNUSED(modifiers);
0224 }
0225 
0226 void ShapeResizeStrategy::paint(QPainter &painter, const KoViewConverter &converter)
0227 {
0228     Q_UNUSED(painter);
0229     Q_UNUSED(converter);
0230 }