File indexing completed on 2024-06-16 04:18:01

0001 /*
0002  *  kis_tool_transform.cc -- part of Krita
0003  *
0004  *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
0005  *  SPDX-FileCopyrightText: 2005 C. Boemann <cbo@boemann.dk>
0006  *  SPDX-FileCopyrightText: 2010 Marc Pegon <pe.marc@free.fr>
0007  *  SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73@gmail.com>
0008  *
0009  *  SPDX-License-Identifier: GPL-2.0-or-later
0010  */
0011 
0012 #include "kis_tool_transform.h"
0013 
0014 
0015 #include <math.h>
0016 #include <limits>
0017 
0018 #include <QPainter>
0019 #include <QPen>
0020 #include <QPushButton>
0021 #include <QObject>
0022 #include <QLabel>
0023 #include <QComboBox>
0024 #include <QApplication>
0025 #include <QMatrix4x4>
0026 #include <QMenu>
0027 
0028 #include <kis_debug.h>
0029 #include <klocalizedstring.h>
0030 
0031 #include <KoPointerEvent.h>
0032 #include <KoID.h>
0033 #include <KoCanvasBase.h>
0034 #include <KoViewConverter.h>
0035 #include <KoSelection.h>
0036 #include <KoCompositeOp.h>
0037 #include <KisCursorOverrideLock.h>
0038 
0039 #include <kis_global.h>
0040 #include <canvas/kis_canvas2.h>
0041 #include <KisViewManager.h>
0042 #include <kis_painter.h>
0043 #include <kis_cursor.h>
0044 #include <kis_image.h>
0045 #include <kis_undo_adapter.h>
0046 #include <kis_transaction.h>
0047 #include <kis_selection.h>
0048 #include <kis_filter_strategy.h>
0049 #include <widgets/kis_cmb_idlist.h>
0050 #include <kis_statusbar.h>
0051 #include <kis_transform_worker.h>
0052 #include <kis_perspectivetransform_worker.h>
0053 #include <kis_warptransform_worker.h>
0054 #include <kis_pixel_selection.h>
0055 #include <kis_shape_selection.h>
0056 #include <kis_selection_manager.h>
0057 #include <krita_utils.h>
0058 #include <kis_resources_snapshot.h>
0059 #include <KisOptimizedBrushOutline.h>
0060 
0061 #include <KoShapeTransformCommand.h>
0062 
0063 #include "kis_action_registry.h"
0064 
0065 #include "widgets/kis_progress_widget.h"
0066 
0067 #include "kis_transform_utils.h"
0068 #include "kis_warp_transform_strategy.h"
0069 #include "kis_cage_transform_strategy.h"
0070 #include "kis_liquify_transform_strategy.h"
0071 #include "kis_free_transform_strategy.h"
0072 #include "kis_perspective_transform_strategy.h"
0073 #include "kis_mesh_transform_strategy.h"
0074 
0075 #include "kis_transform_mask.h"
0076 #include "kis_transform_mask_adapter.h"
0077 
0078 #include "krita_container_utils.h"
0079 #include "kis_layer_utils.h"
0080 #include <KisDelayedUpdateNodeInterface.h>
0081 #include "kis_config_notifier.h"
0082 
0083 #include "strokes/transform_stroke_strategy.h"
0084 #include "strokes/inplace_transform_stroke_strategy.h"
0085 
0086 KisToolTransform::KisToolTransform(KoCanvasBase * canvas)
0087     : KisTool(canvas, KisCursor::rotateCursor())
0088     , m_warpStrategy(
0089         new KisWarpTransformStrategy(
0090             dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
0091             dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
0092             m_currentArgs, m_transaction))
0093     , m_cageStrategy(
0094         new KisCageTransformStrategy(
0095             dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
0096             dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
0097             m_currentArgs, m_transaction))
0098     , m_liquifyStrategy(
0099         new KisLiquifyTransformStrategy(
0100             dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
0101             m_currentArgs, m_transaction, canvas->resourceManager()))
0102     , m_meshStrategy(
0103         new KisMeshTransformStrategy(
0104             dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
0105             dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
0106             m_currentArgs, m_transaction))
0107     , m_freeStrategy(
0108         new KisFreeTransformStrategy(
0109             dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
0110             dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
0111             m_currentArgs, m_transaction))
0112     , m_perspectiveStrategy(
0113         new KisPerspectiveTransformStrategy(
0114             dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
0115             dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
0116             m_currentArgs, m_transaction))
0117 {
0118     m_canvas = dynamic_cast<KisCanvas2*>(canvas);
0119     Q_ASSERT(m_canvas);
0120 
0121     setObjectName("tool_transform");
0122     m_optionsWidget = 0;
0123 
0124     warpAction = new KisAction(i18nc("Warp Transform Tab Label", "Warp"));
0125     liquifyAction = new KisAction(i18nc("Liquify Transform Tab Label", "Liquify"));
0126     meshAction = new KisAction(i18nc("Mesh Transform Tab Label", "Mesh"));
0127     cageAction = new KisAction(i18nc("Cage Transform Tab Label", "Cage"));
0128     freeTransformAction = new KisAction(i18nc("Free Transform Tab Label", "Free"));
0129     perspectiveAction = new KisAction(i18nc("Perspective Transform Tab Label", "Perspective"));
0130 
0131     // extra actions for free transform that are in the tool options
0132     mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal"));
0133     mirrorVerticalAction = new KisAction(i18n("Mirror Vertical"));
0134     rotateNinetyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise"));
0135     rotateNinetyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise"));
0136 
0137     applyTransformation = new KisAction(i18n("Apply"));
0138     resetTransformation = new KisAction(i18n("Reset"));
0139 
0140     m_contextMenu.reset(new QMenu());
0141 
0142     connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
0143     connect(m_warpStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
0144     connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
0145     connect(m_cageStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
0146     connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
0147     connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(QPointF)), SLOT(cursorOutlineUpdateRequested(QPointF)));
0148     connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget()));
0149     connect(m_liquifyStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
0150     connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
0151     connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested()));
0152     connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
0153     connect(m_freeStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
0154     connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
0155     connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
0156     connect(m_perspectiveStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
0157     connect(m_meshStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
0158     connect(m_meshStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
0159 
0160     connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)),
0161             this, SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP)));
0162 
0163     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotGlobalConfigChanged()));
0164 }
0165 
0166 KisToolTransform::~KisToolTransform()
0167 {
0168     cancelStroke();
0169 
0170     delete warpAction;
0171     delete meshAction;
0172     delete liquifyAction;
0173     delete cageAction;
0174     delete freeTransformAction;
0175     delete perspectiveAction;
0176     delete applyTransformation;
0177     delete resetTransformation;
0178     delete mirrorHorizontalAction;
0179     delete mirrorVerticalAction;
0180     delete rotateNinetyCWAction;
0181     delete rotateNinetyCCWAction;
0182 }
0183 
0184 void KisToolTransform::outlineChanged()
0185 {
0186     emit freeTransformChanged();
0187     m_canvas->updateCanvas();
0188 }
0189 
0190 void KisToolTransform::canvasUpdateRequested()
0191 {
0192     m_canvas->updateCanvas();
0193 }
0194 
0195 void KisToolTransform::resetCursorStyle()
0196 {
0197     setFunctionalCursor();
0198 }
0199 
0200 void KisToolTransform::slotGlobalConfigChanged()
0201 {
0202     KConfigGroup group = KSharedConfig::openConfig()->group(toolId());
0203     m_preferOverlayPreviewStyle = group.readEntry("useOverlayPreviewStyle", false);
0204     m_forceLodMode = group.readEntry("forceLodMode", true);
0205 }
0206 
0207 void KisToolTransform::resetRotationCenterButtonsRequested()
0208 {
0209     if (!m_optionsWidget) return;
0210     m_optionsWidget->resetRotationCenterButtons();
0211 }
0212 
0213 void KisToolTransform::imageTooBigRequested(bool value)
0214 {
0215     if (!m_optionsWidget) return;
0216     m_optionsWidget->setTooBigLabelVisible(value);
0217 }
0218 
0219 KisTransformStrategyBase* KisToolTransform::currentStrategy() const
0220 {
0221     if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) {
0222         return m_freeStrategy.data();
0223     } else if (m_currentArgs.mode() == ToolTransformArgs::WARP) {
0224         return m_warpStrategy.data();
0225     } else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) {
0226         return m_cageStrategy.data();
0227     } else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) {
0228         return m_liquifyStrategy.data();
0229     } else if (m_currentArgs.mode() == ToolTransformArgs::MESH) {
0230         return m_meshStrategy.data();
0231     } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ {
0232         return m_perspectiveStrategy.data();
0233     }
0234 }
0235 
0236 void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter)
0237 {
0238     Q_UNUSED(converter);
0239 
0240     if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
0241 
0242     QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0));
0243     if (m_refRect != newRefRect) {
0244         m_refRect = newRefRect;
0245         currentStrategy()->externalConfigChanged();
0246     }
0247     currentStrategy()->setDecorationThickness(decorationThickness());
0248     currentStrategy()->paint(gc);
0249 
0250 
0251     if (!m_cursorOutline.isEmpty()) {
0252         QPainterPath mappedOutline =
0253             KisTransformUtils::imageToFlakeTransform(
0254                 m_canvas->coordinatesConverter()).map(m_cursorOutline);
0255         paintToolOutline(&gc, mappedOutline);
0256     }
0257 }
0258 
0259 void KisToolTransform::setFunctionalCursor()
0260 {
0261     if (overrideCursorIfNotEditable()) {
0262         return;
0263     }
0264 
0265     if (!m_strokeId) {
0266         useCursor(KisCursor::pointingHandCursor());
0267     } else if (m_strokeId && m_transaction.rootNodes().isEmpty()) {
0268         // we are in the middle of stroke initialization
0269         useCursor(KisCursor::waitCursor());
0270     } else {
0271         useCursor(currentStrategy()->getCurrentCursor());
0272     }
0273 }
0274 
0275 void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos)
0276 {
0277     QRect canvasUpdateRect;
0278 
0279     if (!m_cursorOutline.isEmpty()) {
0280         canvasUpdateRect = m_canvas->coordinatesConverter()->
0281             imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
0282     }
0283 
0284     m_cursorOutline = currentStrategy()->
0285         getCursorOutline().translated(imagePos);
0286 
0287     if (!m_cursorOutline.isEmpty()) {
0288         canvasUpdateRect |=
0289             m_canvas->coordinatesConverter()->
0290             imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
0291     }
0292 
0293     if (!canvasUpdateRect.isEmpty()) {
0294         // grow rect a bit to follow interpolation fuzziness
0295         canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2);
0296         m_canvas->updateCanvas(canvasUpdateRect);
0297     }
0298 }
0299 
0300 void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
0301 {
0302     if (!nodeEditable()) {
0303         event->ignore();
0304         return;
0305     }
0306 
0307     if (!m_strokeId) {
0308         startStroke(m_currentArgs.mode(), action == KisTool::ChangeSize);
0309     } else if (!m_transaction.rootNodes().isEmpty()) {
0310         bool result = false;
0311 
0312         if (usePrimaryAction) {
0313             result = currentStrategy()->beginPrimaryAction(event);
0314         } else {
0315             result = currentStrategy()->beginAlternateAction(event, action);
0316         }
0317 
0318         if (result) {
0319             setMode(KisTool::PAINT_MODE);
0320         }
0321     }
0322 
0323     m_actuallyMoveWhileSelected = false;
0324 
0325     outlineChanged();
0326 }
0327 
0328 void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
0329 {
0330     if (mode() != KisTool::PAINT_MODE) return;
0331     if (m_transaction.rootNodes().isEmpty()) return;
0332 
0333     m_actuallyMoveWhileSelected = true;
0334 
0335     if (usePrimaryAction) {
0336         currentStrategy()->continuePrimaryAction(event);
0337     } else {
0338         currentStrategy()->continueAlternateAction(event, action);
0339     }
0340 
0341     updateOptionWidget();
0342     outlineChanged();
0343 }
0344 
0345 void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
0346 {
0347     if (mode() != KisTool::PAINT_MODE) return;
0348 
0349     setMode(KisTool::HOVER_MODE);
0350 
0351     if (m_actuallyMoveWhileSelected ||
0352         currentStrategy()->acceptsClicks()) {
0353 
0354         bool result = false;
0355 
0356         if (usePrimaryAction) {
0357             result = currentStrategy()->endPrimaryAction(event);
0358         } else {
0359             result = currentStrategy()->endAlternateAction(event, action);
0360         }
0361 
0362         if (result) {
0363             commitChanges();
0364         }
0365 
0366         outlineChanged();
0367     }
0368 
0369     updateOptionWidget();
0370     updateApplyResetAvailability();
0371 }
0372 
0373 QMenu* KisToolTransform::popupActionsMenu()
0374 {
0375     if (m_contextMenu) {
0376         m_contextMenu->clear();
0377 
0378         m_contextMenu->addSection(i18n("Transform Tool Actions"));
0379         // add a quick switch to different transform types
0380         m_contextMenu->addAction(freeTransformAction);
0381         m_contextMenu->addAction(perspectiveAction);
0382         m_contextMenu->addAction(warpAction);
0383         m_contextMenu->addAction(cageAction);
0384         m_contextMenu->addAction(liquifyAction);
0385         m_contextMenu->addAction(meshAction);
0386 
0387         // extra options if free transform is selected
0388         if (transformMode() == FreeTransformMode) {
0389             m_contextMenu->addSeparator();
0390             m_contextMenu->addAction(mirrorHorizontalAction);
0391             m_contextMenu->addAction(mirrorVerticalAction);
0392             m_contextMenu->addAction(rotateNinetyCWAction);
0393             m_contextMenu->addAction(rotateNinetyCCWAction);
0394         }
0395 
0396         m_contextMenu->addSeparator();
0397         m_contextMenu->addAction(applyTransformation);
0398         m_contextMenu->addAction(resetTransformation);
0399     }
0400 
0401     return m_contextMenu.data();
0402 }
0403 
0404 void KisToolTransform::beginPrimaryAction(KoPointerEvent *event)
0405 {
0406     beginActionImpl(event, true, KisTool::NONE);
0407 }
0408 
0409 void KisToolTransform::continuePrimaryAction(KoPointerEvent *event)
0410 {
0411     continueActionImpl(event, true, KisTool::NONE);
0412 }
0413 
0414 void KisToolTransform::endPrimaryAction(KoPointerEvent *event)
0415 {
0416     endActionImpl(event, true, KisTool::NONE);
0417 }
0418 
0419 void KisToolTransform::activatePrimaryAction()
0420 {
0421     currentStrategy()->activatePrimaryAction();
0422     setFunctionalCursor();
0423 }
0424 
0425 void KisToolTransform::deactivatePrimaryAction()
0426 {
0427     currentStrategy()->deactivatePrimaryAction();
0428 }
0429 
0430 void KisToolTransform::activateAlternateAction(AlternateAction action)
0431 {
0432     currentStrategy()->activateAlternateAction(action);
0433     setFunctionalCursor();
0434 }
0435 
0436 void KisToolTransform::deactivateAlternateAction(AlternateAction action)
0437 {
0438     currentStrategy()->deactivateAlternateAction(action);
0439 }
0440 
0441 void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action)
0442 {
0443     beginActionImpl(event, false, action);
0444 }
0445 
0446 void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action)
0447 {
0448     continueActionImpl(event, false, action);
0449 }
0450 
0451 void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action)
0452 {
0453     endActionImpl(event, false, action);
0454 }
0455 
0456 void KisToolTransform::mousePressEvent(KoPointerEvent *event)
0457 {
0458     KisTool::mousePressEvent(event);
0459 }
0460 
0461 void KisToolTransform::mouseMoveEvent(KoPointerEvent *event)
0462 {
0463     QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point);
0464 
0465     cursorOutlineUpdateRequested(mousePos);
0466 
0467     if (this->mode() != KisTool::PAINT_MODE) {
0468         currentStrategy()->hoverActionCommon(event);
0469         setFunctionalCursor();
0470         KisTool::mouseMoveEvent(event);
0471         return;
0472     }
0473 }
0474 
0475 void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event)
0476 {
0477     KisTool::mouseReleaseEvent(event);
0478 }
0479 
0480 void KisToolTransform::applyTransform()
0481 {
0482     slotApplyTransform();
0483 }
0484 
0485 KisToolTransform::TransformToolMode KisToolTransform::transformMode() const
0486 {
0487     TransformToolMode mode = FreeTransformMode;
0488 
0489     switch (m_currentArgs.mode())
0490     {
0491     case ToolTransformArgs::FREE_TRANSFORM:
0492         mode = FreeTransformMode;
0493         break;
0494     case ToolTransformArgs::WARP:
0495         mode = WarpTransformMode;
0496         break;
0497     case ToolTransformArgs::CAGE:
0498         mode = CageTransformMode;
0499         break;
0500     case ToolTransformArgs::LIQUIFY:
0501         mode = LiquifyTransformMode;
0502         break;
0503     case ToolTransformArgs::PERSPECTIVE_4POINT:
0504         mode = PerspectiveTransformMode;
0505         break;
0506     case ToolTransformArgs::MESH:
0507         mode = MeshTransformMode;
0508         break;
0509     default:
0510         KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
0511     }
0512 
0513     return mode;
0514 }
0515 
0516 double KisToolTransform::translateX() const
0517 {
0518     return m_currentArgs.transformedCenter().x();
0519 }
0520 
0521 double KisToolTransform::translateY() const
0522 {
0523     return m_currentArgs.transformedCenter().y();
0524 }
0525 
0526 double KisToolTransform::rotateX() const
0527 {
0528     return m_currentArgs.aX();
0529 }
0530 
0531 double KisToolTransform::rotateY() const
0532 {
0533     return m_currentArgs.aY();
0534 }
0535 
0536 double KisToolTransform::rotateZ() const
0537 {
0538     return m_currentArgs.aZ();
0539 }
0540 
0541 double KisToolTransform::scaleX() const
0542 {
0543     return m_currentArgs.scaleX();
0544 }
0545 
0546 double KisToolTransform::scaleY() const
0547 {
0548     return m_currentArgs.scaleY();
0549 }
0550 
0551 double KisToolTransform::shearX() const
0552 {
0553     return m_currentArgs.shearX();
0554 }
0555 
0556 double KisToolTransform::shearY() const
0557 {
0558     return m_currentArgs.shearY();
0559 }
0560 
0561 KisToolTransform::WarpType KisToolTransform::warpType() const
0562 {
0563     switch(m_currentArgs.warpType()) {
0564     case KisWarpTransformWorker::AFFINE_TRANSFORM:
0565         return AffineWarpType;
0566     case KisWarpTransformWorker::RIGID_TRANSFORM:
0567         return RigidWarpType;
0568     case KisWarpTransformWorker::SIMILITUDE_TRANSFORM:
0569         return SimilitudeWarpType;
0570     default:
0571         return RigidWarpType;
0572     }
0573 }
0574 
0575 double KisToolTransform::warpFlexibility() const
0576 {
0577     return m_currentArgs.alpha();
0578 }
0579 
0580 int KisToolTransform::warpPointDensity() const
0581 {
0582     return m_currentArgs.numPoints();
0583 }
0584 
0585 void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode)
0586 {
0587     ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM;
0588 
0589     switch (newMode) {
0590     case FreeTransformMode:
0591         mode = ToolTransformArgs::FREE_TRANSFORM;
0592         break;
0593     case WarpTransformMode:
0594         mode = ToolTransformArgs::WARP;
0595         break;
0596     case CageTransformMode:
0597         mode = ToolTransformArgs::CAGE;
0598         break;
0599     case LiquifyTransformMode:
0600         mode = ToolTransformArgs::LIQUIFY;
0601         break;
0602     case PerspectiveTransformMode:
0603         mode = ToolTransformArgs::PERSPECTIVE_4POINT;
0604         break;
0605     case MeshTransformMode:
0606         mode = ToolTransformArgs::MESH;
0607         break;
0608     default:
0609         KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
0610     }
0611 
0612     if( mode != m_currentArgs.mode() ) {
0613         if( newMode == FreeTransformMode ) {
0614             m_optionsWidget->slotSetFreeTransformModeButtonClicked( true );
0615         } else if( newMode == WarpTransformMode ) {
0616             m_optionsWidget->slotSetWarpModeButtonClicked( true );
0617         } else if( newMode == CageTransformMode ) {
0618             m_optionsWidget->slotSetCageModeButtonClicked( true );
0619         } else if( newMode == LiquifyTransformMode ) {
0620             m_optionsWidget->slotSetLiquifyModeButtonClicked( true );
0621         } else if( newMode == PerspectiveTransformMode ) {
0622             m_optionsWidget->slotSetPerspectiveModeButtonClicked( true );
0623         } else if( newMode == MeshTransformMode ) {
0624             m_optionsWidget->slotSetMeshModeButtonClicked( true );
0625         }
0626 
0627         emit transformModeChanged();
0628     }
0629 }
0630 
0631 void KisToolTransform::setRotateX( double rotation )
0632 {
0633     m_currentArgs.setAX( rotation );
0634 }
0635 
0636 void KisToolTransform::setRotateY( double rotation )
0637 {
0638     m_currentArgs.setAY( rotation );
0639 }
0640 
0641 void KisToolTransform::setRotateZ( double rotation )
0642 {
0643     m_currentArgs.setAZ( rotation );
0644 }
0645 
0646 void KisToolTransform::setWarpType( KisToolTransform::WarpType type )
0647 {
0648     switch( type ) {
0649     case RigidWarpType:
0650         m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM);
0651         break;
0652     case AffineWarpType:
0653         m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM);
0654         break;
0655     case SimilitudeWarpType:
0656         m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM);
0657         break;
0658     default:
0659         break;
0660     }
0661 }
0662 
0663 void KisToolTransform::setWarpFlexibility( double flexibility )
0664 {
0665     m_currentArgs.setAlpha( flexibility );
0666 }
0667 
0668 void KisToolTransform::setWarpPointDensity( int density )
0669 {
0670     m_optionsWidget->slotSetWarpDensity(density);
0671 }
0672 
0673 void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode)
0674 {
0675     m_currentArgs = KisTransformUtils::resetArgsForMode(mode, m_currentArgs.filterId(), m_transaction, m_currentArgs.externalSource());
0676     initGuiAfterTransformMode();
0677 }
0678 
0679 void KisToolTransform::initGuiAfterTransformMode()
0680 {
0681     currentStrategy()->externalConfigChanged();
0682     outlineChanged();
0683     updateOptionWidget();
0684     updateApplyResetAvailability();
0685     setFunctionalCursor();
0686 }
0687 
0688 void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice)
0689 {
0690     QImage origImg;
0691     m_selectedPortionCache = previewDevice;
0692 
0693     QTransform thumbToImageTransform;
0694 
0695     const int maxSize = 2000;
0696 
0697     QRect srcRect(m_transaction.originalRect().toAlignedRect());
0698     int x, y, w, h;
0699     srcRect.getRect(&x, &y, &w, &h);
0700 
0701     if (m_selectedPortionCache) {
0702         if (w > maxSize || h > maxSize) {
0703             qreal scale = qreal(maxSize) / (w > h ? w : h);
0704             QTransform scaleTransform = QTransform::fromScale(scale, scale);
0705 
0706             QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect();
0707 
0708             origImg = m_selectedPortionCache->
0709                     createThumbnail(thumbRect.width(),
0710                                     thumbRect.height(),
0711                                     srcRect, 1,
0712                                     KoColorConversionTransformation::internalRenderingIntent(),
0713                                     KoColorConversionTransformation::internalConversionFlags());
0714             thumbToImageTransform = scaleTransform.inverted();
0715 
0716         } else {
0717             origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h,
0718                                                               KoColorConversionTransformation::internalRenderingIntent(),
0719                                                               KoColorConversionTransformation::internalConversionFlags());
0720             thumbToImageTransform = QTransform();
0721         }
0722     }
0723 
0724     // init both strokes since the thumbnail is initialized only once
0725     // during the stroke
0726     m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform);
0727     m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform);
0728     m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform);
0729     m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform);
0730     m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform);
0731     m_meshStrategy->setThumbnailImage(origImg, thumbToImageTransform);
0732 }
0733 
0734 void KisToolTransform::newActivationWithExternalSource(KisPaintDeviceSP externalSource)
0735 {
0736     m_externalSourceForNextActivation = externalSource;
0737     if (isActive()) {
0738         QSet<KoShape*> dummy;
0739         deactivate();
0740         activate(dummy);
0741     } else {
0742         KoToolManager::instance()->switchToolRequested("KisToolTransform");
0743     }
0744 }
0745 
0746 void KisToolTransform::activate(const QSet<KoShape*> &shapes)
0747 {
0748     KisTool::activate(shapes);
0749 
0750     /// we cannot initialize the setting in the constructor, because
0751     /// factory() is not yet initialized, so we cannot get toolId()
0752     slotGlobalConfigChanged();
0753 
0754     m_actionConnections.addConnection(action("movetool-move-up"), SIGNAL(triggered(bool)),
0755                                       this, SLOT(slotMoveDiscreteUp()));
0756     m_actionConnections.addConnection(action("movetool-move-up-more"), SIGNAL(triggered(bool)),
0757                                       this, SLOT(slotMoveDiscreteUpMore()));
0758     m_actionConnections.addConnection(action("movetool-move-down"), SIGNAL(triggered(bool)),
0759                                       this, SLOT(slotMoveDiscreteDown()));
0760     m_actionConnections.addConnection(action("movetool-move-down-more"), SIGNAL(triggered(bool)),
0761                                       this, SLOT(slotMoveDiscreteDownMore()));
0762     m_actionConnections.addConnection(action("movetool-move-left"), SIGNAL(triggered(bool)),
0763                                       this, SLOT(slotMoveDiscreteLeft()));
0764     m_actionConnections.addConnection(action("movetool-move-left-more"), SIGNAL(triggered(bool)),
0765                                       this, SLOT(slotMoveDiscreteLeftMore()));
0766     m_actionConnections.addConnection(action("movetool-move-right"), SIGNAL(triggered(bool)),
0767                                       this, SLOT(slotMoveDiscreteRight()));
0768     m_actionConnections.addConnection(action("movetool-move-right-more"), SIGNAL(triggered(bool)),
0769                                       this, SLOT(slotMoveDiscreteRightMore()));
0770 
0771     if (currentNode()) {
0772         m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeList(), {});
0773     }
0774 
0775     startStroke(ToolTransformArgs::FREE_TRANSFORM, false);
0776 }
0777 
0778 void KisToolTransform::deactivate()
0779 {
0780     endStroke();
0781     m_canvas->updateCanvas();
0782     m_actionConnections.clear();
0783     KisTool::deactivate();
0784 }
0785 
0786 void KisToolTransform::requestUndoDuringStroke()
0787 {
0788     if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
0789 
0790     if (!m_changesTracker.canUndo()) {
0791         cancelStroke();
0792     } else {
0793         m_changesTracker.requestUndo();
0794     }
0795 }
0796 
0797 void KisToolTransform::requestRedoDuringStroke()
0798 {
0799     if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
0800 
0801     if (m_changesTracker.canRedo()) {
0802         m_changesTracker.requestRedo();
0803     }
0804 }
0805 
0806 void KisToolTransform::requestStrokeEnd()
0807 {
0808     endStroke();
0809 }
0810 
0811 void KisToolTransform::requestStrokeCancellation()
0812 {
0813     if (m_transaction.rootNodes().isEmpty() || m_currentArgs.isIdentity()) {
0814         cancelStroke();
0815     } else {
0816         slotCancelTransform();
0817     }
0818 }
0819 
0820 void KisToolTransform::requestImageRecalculation()
0821 {
0822     if (!m_currentlyUsingOverlayPreviewStyle && m_strokeId && !m_transaction.rootNodes().isEmpty()) {
0823         image()->addJob(
0824             m_strokeId,
0825             new InplaceTransformStrokeStrategy::UpdateTransformData(
0826                 m_currentArgs,
0827                 InplaceTransformStrokeStrategy::UpdateTransformData::PAINT_DEVICE));
0828     }
0829 }
0830 
0831 void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset)
0832 {
0833     Q_ASSERT(!m_strokeId);
0834 
0835     KisPaintDeviceSP externalSource = m_externalSourceForNextActivation;
0836     m_externalSourceForNextActivation.clear();
0837 
0838     // set up and null checks before we do anything
0839     KisResourcesSnapshotSP resources =
0840             new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager(), 0, selectedNodes(), 0);
0841     KisNodeList rootNodes = resources->selectedNodes();
0842     //Filter out any nodes that might be children of other selected nodes so they aren't used twice
0843     KisLayerUtils::filterMergeableNodes(rootNodes, true);
0844     KisSelectionSP selection = resources->activeSelection();
0845 
0846     m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeList(), {});
0847     m_currentArgs = ToolTransformArgs();
0848 
0849     Q_FOREACH (KisNodeSP currentNode, resources->selectedNodes()) {
0850         if (!currentNode || !currentNode->isEditable()) return;
0851 
0852         // some layer types cannot be transformed. Give a message and return if a user tries it
0853         if (currentNode->inherits("KisColorizeMask") ||
0854             currentNode->inherits("KisFileLayer") ||
0855             currentNode->inherits("KisCloneLayer")) {
0856 
0857             KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0858             KIS_ASSERT(kisCanvas);
0859 
0860             if(currentNode->inherits("KisColorizeMask")){
0861                 kisCanvas->viewManager()->
0862                     showFloatingMessage(
0863                         i18nc("floating message in transformation tool",
0864                               "Layer type cannot use the transform tool"),
0865                         koIcon("object-locked"), 4000, KisFloatingMessage::High);
0866             }
0867             else{
0868                 kisCanvas->viewManager()->
0869                     showFloatingMessage(
0870                         i18nc("floating message in transformation tool",
0871                               "Layer type cannot use the transform tool. Use transform mask instead."),
0872                         koIcon("object-locked"), 4000, KisFloatingMessage::High);
0873             }
0874             return;
0875         }
0876 
0877         KisNodeSP impossibleMask =
0878             KisLayerUtils::recursiveFindNode(currentNode,
0879             [currentNode] (KisNodeSP node) {
0880                 // we can process transform masks of the first level
0881                 if (node == currentNode || node->parent() == currentNode) return false;
0882 
0883                 return node->inherits("KisTransformMask") && node->visible(true);
0884             });
0885 
0886         if (impossibleMask) {
0887             KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0888             kisCanvas->viewManager()->
0889                 showFloatingMessage(
0890                     i18nc("floating message in transformation tool",
0891                           "Layer has children with transform masks. Please disable them before doing transformation."),
0892                     koIcon("object-locked"), 8000, KisFloatingMessage::High);
0893             return;
0894         }
0895 
0896         /**
0897          * When working with transform mask, selections are not
0898          * taken into account.
0899          */
0900         if (selection && dynamic_cast<KisTransformMask*>(currentNode.data())) {
0901             KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0902             kisCanvas->viewManager()->
0903                 showFloatingMessage(
0904                     i18nc("floating message in transformation tool",
0905                           "Selections are not used when editing transform masks "),
0906                     QIcon(), 4000, KisFloatingMessage::Low);
0907 
0908             selection = 0;
0909         }
0910     }
0911     // Overlay preview is never used when transforming an externally provided image
0912     m_currentlyUsingOverlayPreviewStyle = m_preferOverlayPreviewStyle && !externalSource;
0913 
0914     KisStrokeStrategy *strategy = 0;
0915 
0916     if (m_currentlyUsingOverlayPreviewStyle) {
0917         TransformStrokeStrategy *transformStrategy = new TransformStrokeStrategy(mode, m_currentArgs.filterId(), forceReset, rootNodes, selection, image().data(), image().data());
0918         connect(transformStrategy, SIGNAL(sigPreviewDeviceReady(KisPaintDeviceSP)), SLOT(slotPreviewDeviceGenerated(KisPaintDeviceSP)));
0919         connect(transformStrategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)));
0920         strategy = transformStrategy;
0921 
0922         // save unique identifier of the stroke so we could
0923         // recognize it when sigTransactionGenerated() is
0924         // received (theoretically, the user can start two
0925         // strokes at the same time, if he is quick enough)
0926         m_strokeStrategyCookie = transformStrategy;
0927 
0928     } else {
0929         InplaceTransformStrokeStrategy *transformStrategy = new InplaceTransformStrokeStrategy(mode, m_currentArgs.filterId(), forceReset, rootNodes, selection, externalSource, image().data(), image().data(), image()->root(), m_forceLodMode);
0930         connect(transformStrategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)));
0931         strategy = transformStrategy;
0932 
0933         // save unique identifier of the stroke so we could
0934         // recognize it when sigTransactionGenerated() is
0935         // received (theoretically, the user can start two
0936         // strokes at the same time, if he is quick enough)
0937         m_strokeStrategyCookie = transformStrategy;
0938     }
0939 
0940     m_strokeId = image()->startStroke(strategy);
0941 
0942     if (!m_currentlyUsingOverlayPreviewStyle) {
0943         m_asyncUpdateHelper.initUpdateStreamLowLevel(image().data(), m_strokeId);
0944     }
0945 
0946     KIS_SAFE_ASSERT_RECOVER_NOOP(m_changesTracker.isEmpty());
0947 
0948     slotPreviewDeviceGenerated(0);
0949 }
0950 
0951 void KisToolTransform::endStroke()
0952 {
0953     if (!m_strokeId) return;
0954 
0955     if (m_currentlyUsingOverlayPreviewStyle &&
0956         !m_transaction.rootNodes().isEmpty() &&
0957         !m_currentArgs.isUnchanging()) {
0958 
0959         image()->addJob(m_strokeId,
0960                         new TransformStrokeStrategy::TransformAllData(m_currentArgs));
0961     }
0962 
0963     if (m_asyncUpdateHelper.isActive()) {
0964         m_asyncUpdateHelper.endUpdateStream();
0965     }
0966 
0967     image()->endStroke(m_strokeId);
0968 
0969     m_strokeStrategyCookie = 0;
0970     m_strokeId.clear();
0971     m_changesTracker.reset();
0972     m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeList(), {});
0973     outlineChanged();
0974 }
0975 
0976 void KisToolTransform::slotTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args, void *strokeStrategyCookie)
0977 {
0978     if (!m_strokeId || strokeStrategyCookie != m_strokeStrategyCookie) return;
0979 
0980     if (transaction.transformedNodes().isEmpty() ||
0981         transaction.originalRect().isEmpty()) {
0982 
0983         KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0984         KIS_ASSERT(kisCanvas);
0985         kisCanvas->viewManager()->
0986             showFloatingMessage(
0987                 i18nc("floating message in transformation tool",
0988                       "Cannot transform empty layer "),
0989                 QIcon(), 1000, KisFloatingMessage::Medium);
0990 
0991         cancelStroke();
0992         return;
0993     }
0994 
0995     m_transaction = transaction;
0996     m_currentArgs = args;
0997     m_transaction.setCurrentConfigLocation(&m_currentArgs);
0998 
0999     if (!m_currentlyUsingOverlayPreviewStyle) {
1000         m_asyncUpdateHelper.startUpdateStreamLowLevel();
1001     }
1002 
1003     KIS_SAFE_ASSERT_RECOVER_NOOP(m_changesTracker.isEmpty());
1004     commitChanges();
1005 
1006     initGuiAfterTransformMode();
1007 
1008     if (m_transaction.hasInvisibleNodes()) {
1009         KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1010         KIS_ASSERT(kisCanvas);
1011         kisCanvas->viewManager()->
1012             showFloatingMessage(
1013                 i18nc("floating message in transformation tool",
1014                       "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "),
1015                 QIcon(), 4000, KisFloatingMessage::Low);
1016     }
1017 }
1018 
1019 void KisToolTransform::slotPreviewDeviceGenerated(KisPaintDeviceSP device)
1020 {
1021     if (device && device->exactBounds().isEmpty()) {
1022         KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1023         KIS_SAFE_ASSERT_RECOVER(kisCanvas) { cancelStroke(); return; }
1024         kisCanvas->viewManager()->
1025             showFloatingMessage(
1026                 i18nc("floating message in transformation tool",
1027                       "Cannot transform empty layer "),
1028                 QIcon(), 1000, KisFloatingMessage::Medium);
1029 
1030         cancelStroke();
1031     } else {
1032         initThumbnailImage(device);
1033         initGuiAfterTransformMode();
1034     }
1035 }
1036 
1037 void KisToolTransform::cancelStroke()
1038 {
1039     if (!m_strokeId) return;
1040 
1041     if (m_asyncUpdateHelper.isActive()) {
1042         m_asyncUpdateHelper.cancelUpdateStream();
1043     }
1044 
1045     image()->cancelStroke(m_strokeId);
1046     m_strokeStrategyCookie = 0;
1047     m_strokeId.clear();
1048     m_changesTracker.reset();
1049     m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeList(), {});
1050     outlineChanged();
1051 }
1052 
1053 void KisToolTransform::commitChanges()
1054 {
1055     if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1056 
1057     m_changesTracker.commitConfig(toQShared(m_currentArgs.clone()));
1058 }
1059 
1060 void KisToolTransform::slotTrackerChangedConfig(KisToolChangesTrackerDataSP status)
1061 {
1062     const ToolTransformArgs *newArgs = dynamic_cast<const ToolTransformArgs*>(status.data());
1063     KIS_SAFE_ASSERT_RECOVER_RETURN(newArgs);
1064 
1065     *m_transaction.currentConfig() = *newArgs;
1066 
1067     slotUiChangedConfig(true);
1068     updateOptionWidget();
1069 }
1070 
1071 QList<KisNodeSP> KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool isExternalSourcePresent)
1072 {
1073     QList<KisNodeSP> result;
1074 
1075     auto fetchFunc =
1076         [&result, mode, root] (KisNodeSP node) {
1077         if (node->isEditable(node == root) &&
1078                 (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) &&
1079                 !node->inherits("KisFileLayer") &&
1080                 (!node->inherits("KisTransformMask") || node == root)) {
1081 
1082                 result << node;
1083             }
1084     };
1085 
1086     if (isExternalSourcePresent) {
1087         fetchFunc(root);
1088     } else {
1089         KisLayerUtils::recursiveApplyNodes(root, fetchFunc);
1090     }
1091 
1092     return result;
1093 }
1094 
1095 QWidget* KisToolTransform::createOptionWidget()
1096 {
1097     if (!m_canvas) return 0;
1098      
1099     m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, 0);
1100     Q_CHECK_PTR(m_optionsWidget);
1101     m_optionsWidget->setObjectName(toolId() + " option widget");
1102 
1103     // See https://bugs.kde.org/show_bug.cgi?id=316896
1104     QWidget *specialSpacer = new QWidget(m_optionsWidget);
1105     specialSpacer->setObjectName("SpecialSpacer");
1106     specialSpacer->setFixedSize(0, 0);
1107     m_optionsWidget->layout()->addWidget(specialSpacer);
1108 
1109 
1110     connect(m_optionsWidget, SIGNAL(sigConfigChanged(bool)),
1111             this, SLOT(slotUiChangedConfig(bool)));
1112 
1113     connect(m_optionsWidget, SIGNAL(sigApplyTransform()),
1114             this, SLOT(slotApplyTransform()));
1115 
1116     connect(m_optionsWidget, SIGNAL(sigResetTransform(ToolTransformArgs::TransformMode)),
1117             this, SLOT(slotResetTransform(ToolTransformArgs::TransformMode)));
1118 
1119     connect(m_optionsWidget, SIGNAL(sigCancelTransform()),
1120             this, SLOT(slotCancelTransform()));
1121 
1122     connect(m_optionsWidget, SIGNAL(sigRestartTransform()),
1123             this, SLOT(slotRestartTransform()));
1124 
1125     connect(m_optionsWidget, SIGNAL(sigUpdateGlobalConfig()),
1126             this, SLOT(slotGlobalConfigChanged()));
1127 
1128     connect(m_optionsWidget, SIGNAL(sigRestartAndContinueTransform()),
1129             this, SLOT(slotRestartAndContinueTransform()));
1130 
1131     connect(m_optionsWidget, SIGNAL(sigEditingFinished()),
1132             this, SLOT(slotEditingFinished()));
1133 
1134 
1135     connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX()));
1136     connect(mirrorVerticalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY()));
1137     connect(rotateNinetyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW()));
1138     connect(rotateNinetyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW()));
1139 
1140 
1141     connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType()));
1142     connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType()));
1143     connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType()));
1144     connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType()));
1145     connect(meshAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToMeshType()));
1146     connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType()));
1147 
1148     connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform()));
1149     connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotCancelTransform()));
1150 
1151 
1152     updateOptionWidget();
1153 
1154     return m_optionsWidget;
1155 }
1156 
1157 void KisToolTransform::updateOptionWidget()
1158 {    
1159     if (!m_optionsWidget) return;
1160 
1161     if (!currentNode()) {
1162         m_optionsWidget->setEnabled(false);
1163         return;
1164     }
1165     else {
1166         m_optionsWidget->setEnabled(true);
1167         m_optionsWidget->updateConfig(m_currentArgs);
1168     }
1169 }
1170 
1171 void KisToolTransform::updateApplyResetAvailability()
1172 {
1173     if (m_optionsWidget) {
1174         m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity());
1175     }
1176 }
1177 
1178 void KisToolTransform::slotUiChangedConfig(bool needsPreviewRecalculation)
1179 {
1180     if (mode() == KisTool::PAINT_MODE) return;
1181 
1182     if (needsPreviewRecalculation) {
1183         currentStrategy()->externalConfigChanged();
1184     }
1185 
1186     if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) {
1187         m_currentArgs.saveLiquifyTransformMode();
1188     }
1189 
1190     outlineChanged();
1191     updateApplyResetAvailability();
1192 }
1193 
1194 void KisToolTransform::slotApplyTransform()
1195 {
1196     KisCursorOverrideLock cursorLock(KisCursor::waitCursor());
1197     endStroke();
1198 }
1199 
1200 void KisToolTransform::slotResetTransform(ToolTransformArgs::TransformMode mode)
1201 {
1202     ToolTransformArgs *config = m_transaction.currentConfig();
1203     const ToolTransformArgs::TransformMode previousMode = config->mode();
1204     config->setMode(mode);
1205 
1206     if (mode == ToolTransformArgs::WARP) {
1207         config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID);
1208     }
1209 
1210     if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1211 
1212     if (m_currentArgs.continuedTransform()) {
1213         ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode();
1214 
1215         /**
1216          * Our reset transform button can be used for two purposes:
1217          *
1218          * 1) Reset current transform to the initial one, which was
1219          *    loaded from the previous user action.
1220          *
1221          * 2) Reset transform frame to infinity when the frame is unchanged
1222          */
1223 
1224         const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs);
1225 
1226         if (transformDiffers &&
1227             m_currentArgs.continuedTransform()->mode() == savedMode) {
1228 
1229             m_currentArgs.restoreContinuedState();
1230             initGuiAfterTransformMode();
1231             slotEditingFinished();
1232 
1233         } else {
1234             cancelStroke();
1235             startStroke(savedMode, true);
1236 
1237             KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform());
1238         }
1239     } else {
1240         if (!KisTransformUtils::shouldRestartStrokeOnModeChange(previousMode,
1241                                                                 m_currentArgs.mode(),
1242                                                                 m_transaction.transformedNodes())) {
1243             initTransformMode(m_currentArgs.mode());
1244             slotEditingFinished();
1245 
1246         } else {
1247             cancelStroke();
1248             startStroke(m_currentArgs.mode(), true);
1249 
1250         }
1251     }
1252 }
1253 
1254 void KisToolTransform::slotCancelTransform()
1255 {
1256     slotResetTransform(m_transaction.currentConfig()->mode());
1257 }
1258 
1259 void KisToolTransform::slotRestartTransform()
1260 {
1261     if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1262 
1263     KisNodeSP root = m_transaction.rootNodes()[0];
1264     KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
1265 
1266     ToolTransformArgs savedArgs(m_currentArgs);
1267     cancelStroke();
1268     startStroke(savedArgs.mode(), true);
1269 }
1270 
1271 void KisToolTransform::slotRestartAndContinueTransform()
1272 {
1273     if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1274 
1275     KisNodeSP root = m_transaction.rootNodes()[0];
1276     KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
1277 
1278     ToolTransformArgs savedArgs(m_currentArgs);
1279     endStroke();
1280     startStroke(savedArgs.mode(), false);
1281 }
1282 
1283 void KisToolTransform::slotEditingFinished()
1284 {
1285     commitChanges();
1286 }
1287 
1288 void KisToolTransform::slotMoveDiscreteUp()
1289 {
1290     setTranslateY(translateY()-1.0);
1291 }
1292 
1293 void KisToolTransform::slotMoveDiscreteUpMore()
1294 {
1295     setTranslateY(translateY()-10.0);
1296 }
1297 
1298 void KisToolTransform::slotMoveDiscreteDown()
1299 {
1300     setTranslateY(translateY()+1.0);
1301 }
1302 
1303 void KisToolTransform::slotMoveDiscreteDownMore()
1304 {
1305     setTranslateY(translateY()+10.0);
1306 }
1307 
1308 void KisToolTransform::slotMoveDiscreteLeft()
1309 {
1310     setTranslateX(translateX()-1.0);
1311 }
1312 
1313 void KisToolTransform::slotMoveDiscreteLeftMore()
1314 {
1315     setTranslateX(translateX()-10.0);
1316 }
1317 
1318 void KisToolTransform::slotMoveDiscreteRight()
1319 {
1320     setTranslateX(translateX()+1.0);
1321 }
1322 
1323 void KisToolTransform::slotMoveDiscreteRightMore()
1324 {
1325     setTranslateX(translateX()+10.0);
1326 }
1327 
1328 void KisToolTransform::slotUpdateToWarpType()
1329 {
1330     setTransformMode(KisToolTransform::TransformToolMode::WarpTransformMode);
1331 }
1332 
1333 void KisToolTransform::slotUpdateToPerspectiveType()
1334 {
1335     setTransformMode(KisToolTransform::TransformToolMode::PerspectiveTransformMode);
1336 }
1337 
1338 void KisToolTransform::slotUpdateToFreeTransformType()
1339 {
1340     setTransformMode(KisToolTransform::TransformToolMode::FreeTransformMode);
1341 }
1342 
1343 void KisToolTransform::slotUpdateToLiquifyType()
1344 {
1345     setTransformMode(KisToolTransform::TransformToolMode::LiquifyTransformMode);
1346 }
1347 
1348 void KisToolTransform::slotUpdateToMeshType()
1349 {
1350     setTransformMode(KisToolTransform::TransformToolMode::MeshTransformMode);
1351 }
1352 
1353 void KisToolTransform::slotUpdateToCageType()
1354 {
1355     setTransformMode(KisToolTransform::TransformToolMode::CageTransformMode);
1356 }
1357 
1358 void KisToolTransform::setShearY(double shear)
1359 {
1360     m_optionsWidget->slotSetShearY(shear);
1361 }
1362 
1363 void KisToolTransform::setShearX(double shear)
1364 {
1365     m_optionsWidget->slotSetShearX(shear);
1366 }
1367 
1368 void KisToolTransform::setScaleY(double scale)
1369 {
1370     m_optionsWidget->slotSetScaleY(scale);
1371 }
1372 
1373 void KisToolTransform::setScaleX(double scale)
1374 {
1375     m_optionsWidget->slotSetScaleX(scale);
1376 }
1377 
1378 void KisToolTransform::setTranslateY(double translation)
1379 {
1380     TransformToolMode mode = transformMode();
1381 
1382     if (m_strokeId && (mode == FreeTransformMode || mode == PerspectiveTransformMode)) {
1383         m_currentArgs.setTransformedCenter(QPointF(translateX(), translation));
1384         currentStrategy()->externalConfigChanged();
1385         updateOptionWidget();
1386         outlineChanged();
1387     }
1388 }
1389 
1390 void KisToolTransform::setTranslateX(double translation)
1391 {
1392     TransformToolMode mode = transformMode();
1393 
1394     if (m_strokeId && (mode == FreeTransformMode || mode == PerspectiveTransformMode)) {
1395         m_currentArgs.setTransformedCenter(QPointF(translation, translateY()));
1396         currentStrategy()->externalConfigChanged();
1397         updateOptionWidget();
1398         outlineChanged();
1399     }
1400 }
1401 
1402 QList<QAction *> KisToolTransformFactory::createActionsImpl()
1403 {
1404     KisActionRegistry *actionRegistry = KisActionRegistry::instance();
1405     QList<QAction *> actions = KisToolPaintFactoryBase::createActionsImpl();
1406 
1407     actions << actionRegistry->makeQAction("movetool-move-up", this);
1408     actions << actionRegistry->makeQAction("movetool-move-down", this);
1409     actions << actionRegistry->makeQAction("movetool-move-left", this);
1410     actions << actionRegistry->makeQAction("movetool-move-right", this);
1411     actions << actionRegistry->makeQAction("movetool-move-up-more", this);
1412     actions << actionRegistry->makeQAction("movetool-move-down-more", this);
1413     actions << actionRegistry->makeQAction("movetool-move-left-more", this);
1414     actions << actionRegistry->makeQAction("movetool-move-right-more", this);
1415 
1416     return actions;
1417 }