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 }