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 }