Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_tool_transform.cc
Go to the documentation of this file.
1/*
2 * kis_tool_transform.cc -- part of Krita
3 *
4 * SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
5 * SPDX-FileCopyrightText: 2005 C. Boemann <cbo@boemann.dk>
6 * SPDX-FileCopyrightText: 2010 Marc Pegon <pe.marc@free.fr>
7 * SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73@gmail.com>
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12#include "kis_tool_transform.h"
13
14
15#include <math.h>
16#include <limits>
17
18#include <QPainter>
19#include <QPen>
20#include <QObject>
21#include <QApplication>
22#include <QMatrix4x4>
23#include <QMenu>
24
25#include <kis_debug.h>
26#include <klocalizedstring.h>
27
28#include <KoPointerEvent.h>
29#include <KoID.h>
30#include <KoCanvasBase.h>
31#include <KoViewConverter.h>
32#include <KoSelection.h>
33#include <KoCompositeOp.h>
35
36#include <kis_global.h>
37#include <canvas/kis_canvas2.h>
38#include <KisViewManager.h>
39#include <kis_painter.h>
40#include <kis_cursor.h>
41#include <kis_image.h>
42#include <kis_undo_adapter.h>
43#include <kis_transaction.h>
44#include <kis_selection.h>
45#include <kis_filter_strategy.h>
47#include <kis_statusbar.h>
51#include <kis_pixel_selection.h>
52#include <kis_shape_selection.h>
54#include <krita_utils.h>
57
59#include <KoCanvasController.h>
61
62#include "kis_action_registry.h"
63
65
66#include "kis_transform_utils.h"
73
74#include "kis_transform_mask.h"
76
78#include "kis_layer_utils.h"
80#include "kis_config_notifier.h"
81
84
86 : KisTool(canvas, KisCursor::rotateCursor())
87 , m_warpStrategy(
89 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
90 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
91 m_currentArgs, m_transaction))
92 , m_cageStrategy(
94 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
95 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
96 m_currentArgs, m_transaction))
97 , m_liquifyStrategy(
99 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
100 m_currentArgs, m_transaction, canvas->resourceManager()))
101 , m_meshStrategy(
103 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
104 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
105 m_currentArgs, m_transaction))
106 , m_freeStrategy(
108 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
109 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
110 m_currentArgs, m_transaction))
111 , m_perspectiveStrategy(
113 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
114 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
115 m_currentArgs, m_transaction))
116{
117 m_canvas = dynamic_cast<KisCanvas2*>(canvas);
118 Q_ASSERT(m_canvas);
119
120 setObjectName("tool_transform");
121 m_optionsWidget = 0;
122
123 warpAction = new KisAction(i18nc("Warp Transform Tab Label", "Warp"));
124 liquifyAction = new KisAction(i18nc("Liquify Transform Tab Label", "Liquify"));
125 meshAction = new KisAction(i18nc("Mesh Transform Tab Label", "Mesh"));
126 cageAction = new KisAction(i18nc("Cage Transform Tab Label", "Cage"));
127 freeTransformAction = new KisAction(i18nc("Free Transform Tab Label", "Free"));
128 perspectiveAction = new KisAction(i18nc("Perspective Transform Tab Label", "Perspective"));
129
130 // extra actions for free transform that are in the tool options
131 mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal"));
132 mirrorVerticalAction = new KisAction(i18n("Mirror Vertical"));
133 rotateNinetyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise"));
134 rotateNinetyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise"));
135
136 keepAspectRatioAction = new KisAction(i18n("Keep Aspect Ratio"));
137 keepAspectRatioAction->setCheckable(true);
138 keepAspectRatioAction->setChecked(false);
139
140 applyTransformation = new KisAction(i18n("Apply"));
141 resetTransformation = new KisAction(i18n("Reset"));
142
143 m_contextMenu.reset(new QMenu());
144
145 connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
146 connect(m_warpStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
147 connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
148 connect(m_cageStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
149 connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
150 connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(QPointF)), SLOT(cursorOutlineUpdateRequested(QPointF)));
151 connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget()));
153 connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
154 connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested()));
155 connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
156 connect(m_freeStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
157 connect(m_freeStrategy.data(), SIGNAL(requestConvexHullCalculation()), SLOT(convexHullCalculationRequested()));
158 connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
159 connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
161 connect(m_meshStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
162 connect(m_meshStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
163
164 connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)),
166
167 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotGlobalConfigChanged()));
168}
169
171{
172 cancelStroke();
173
174 delete warpAction;
175 delete meshAction;
176 delete liquifyAction;
177 delete cageAction;
178 delete freeTransformAction;
179 delete perspectiveAction;
180 delete applyTransformation;
181 delete resetTransformation;
187}
188
190{
191 Q_EMIT freeTransformChanged();
192 m_canvas->updateCanvas();
193}
194
196{
197 m_canvas->updateCanvas();
198}
199
204
206{
207 KConfigGroup group = KSharedConfig::openConfig()->group(toolId());
208 m_preferOverlayPreviewStyle = group.readEntry("useOverlayPreviewStyle", false);
209 m_forceLodMode = group.readEntry("forceLodMode", true);
210}
211
217
223
246void KisToolTransform::slotConvexHullCalculated(QPolygon hull, void *strokeStrategyCookie)
247{
248 if (!m_strokeId || strokeStrategyCookie != m_strokeStrategyCookie) return;
249 QPolygonF hullF = hull;
256 if (hullF.boundingRect() == m_transaction.originalRect()) {
260 } else {
261 warnTools << "WARNING: KisToolTransform: calculated convex hull's bounds "
262 "differ from the bounding rect of the source clip. It shouldn't "
263 "have happened";
264 }
265}
266
268{
270 return m_freeStrategy.data();
272 return m_warpStrategy.data();
274 return m_cageStrategy.data();
276 return m_liquifyStrategy.data();
278 return m_meshStrategy.data();
279 } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ {
280 return m_perspectiveStrategy.data();
281 }
282}
283
284void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter)
285{
286 Q_UNUSED(converter);
287
288 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
289
290 QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0));
291 if (m_refRect != newRefRect) {
292 m_refRect = newRefRect;
294 }
296 currentStrategy()->paint(gc, m_canvas->displayRendererInterface());
297
298
299 if (!m_cursorOutline.isEmpty()) {
300 QPainterPath mappedOutline =
302 m_canvas->coordinatesConverter()).map(m_cursorOutline);
303 paintToolOutline(&gc, mappedOutline);
304 }
305}
306
308{
310 return;
311 }
312
313 if (!m_strokeId) {
315 } else if (m_strokeId && m_transaction.rootNodes().isEmpty()) {
316 // we are in the middle of stroke initialization
318 } else {
319 useCursor(currentStrategy()->getCurrentCursor());
320 }
321}
322
324{
325 QRect canvasUpdateRect;
326
327 if (!m_cursorOutline.isEmpty()) {
328 canvasUpdateRect = m_canvas->coordinatesConverter()->
329 imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
330 }
331
333 getCursorOutline().translated(imagePos);
334
335 if (!m_cursorOutline.isEmpty()) {
336 canvasUpdateRect |=
337 m_canvas->coordinatesConverter()->
338 imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
339 }
340
341 if (!canvasUpdateRect.isEmpty()) {
342 // grow rect a bit to follow interpolation fuzziness
343 canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2);
344 m_canvas->updateCanvas(canvasUpdateRect);
345 }
346}
347
349{
350 if (!nodeEditable()) {
351 event->ignore();
352 return;
353 }
354
355 if (!m_strokeId) {
357 } else if (!m_transaction.rootNodes().isEmpty()) {
358 bool result = false;
359
360 if (usePrimaryAction) {
361 result = currentStrategy()->beginPrimaryAction(event);
362 } else {
363 result = currentStrategy()->beginAlternateAction(event, action);
364 }
365
366 if (result) {
368 }
369 }
370
372
374}
375
377{
378 if (mode() != KisTool::PAINT_MODE) return;
379 if (m_transaction.rootNodes().isEmpty()) return;
380
382
383 if (usePrimaryAction) {
385 } else {
387 }
388
391}
392
394{
395 if (mode() != KisTool::PAINT_MODE) return;
396
398
400 currentStrategy()->acceptsClicks()) {
401
402 bool result = false;
403
404 if (usePrimaryAction) {
405 result = currentStrategy()->endPrimaryAction(event);
406 } else {
407 result = currentStrategy()->endAlternateAction(event, action);
408 }
409
410 if (result) {
412 }
413
415 }
416
419}
420
422{
423 if (m_contextMenu) {
424 m_contextMenu->clear();
425
426 m_contextMenu->addSection(i18n("Transform Tool Actions"));
427 // add a quick switch to different transform types
430 m_contextMenu->addAction(warpAction);
431 m_contextMenu->addAction(cageAction);
432 m_contextMenu->addAction(liquifyAction);
433 m_contextMenu->addAction(meshAction);
434
435 // extra options if free transform is selected
437 m_contextMenu->addSeparator();
442
443 m_contextMenu->addSeparator();
446 }
447
448 m_contextMenu->addSeparator();
451 }
452
453 return m_contextMenu.data();
454}
455
460
465
470
476
481
487
492
497
502
507
509{
510 // When using touch drawing, we only ever receive move events after the
511 // finger has pressed down. This confuses the strategies greatly, since they
512 // expect to receive a hover to tell which anchor the user wants to
513 // manipulate or similar. So in this case, we send an artificial hover.
514 if (event->isTouchEvent() && this->mode() != KisTool::PAINT_MODE) {
517 }
519}
520
522{
523 QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point);
524
526
527 if (this->mode() != KisTool::PAINT_MODE) {
531 return;
532 }
533}
534
539
544
549
551{
553
554 switch (m_currentArgs.mode())
555 {
558 break;
561 break;
564 break;
567 break;
570 break;
573 break;
574 default:
575 KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
576 }
577
578 return mode;
579}
580
582{
583 return m_currentArgs.transformedCenter().x();
584}
585
587{
588 return m_currentArgs.transformedCenter().y();
589}
590
592{
593 return m_currentArgs.aX();
594}
595
597{
598 return m_currentArgs.aY();
599}
600
602{
603 return m_currentArgs.aZ();
604}
605
607{
608 return m_currentArgs.scaleX();
609}
610
612{
613 return m_currentArgs.scaleY();
614}
615
617{
618 return m_currentArgs.shearX();
619}
620
622{
623 return m_currentArgs.shearY();
624}
625
639
641{
642 return m_currentArgs.alpha();
643}
644
649
679
681{
683
684 if( mode != m_currentArgs.mode() ) {
685 if( newMode == FreeTransformMode ) {
687 } else if( newMode == WarpTransformMode ) {
689 } else if( newMode == CageTransformMode ) {
691 } else if( newMode == LiquifyTransformMode ) {
693 } else if( newMode == PerspectiveTransformMode ) {
695 } else if( newMode == MeshTransformMode ) {
697 }
698
699 Q_EMIT transformModeChanged();
700 }
701}
702
703void KisToolTransform::setRotateX( double rotation )
704{
705 m_currentArgs.setAX( rotation );
706}
707
708void KisToolTransform::setRotateY( double rotation )
709{
710 m_currentArgs.setAY( rotation );
711}
712
713void KisToolTransform::setRotateZ( double rotation )
714{
715 m_currentArgs.setAZ( rotation );
716}
717
734
735void KisToolTransform::setWarpFlexibility( double flexibility )
736{
737 m_currentArgs.setAlpha( flexibility );
738}
739
744
750
759
761{
762 QImage origImg;
763 m_selectedPortionCache = previewDevice;
764
765 QTransform thumbToImageTransform;
766
767 const int maxSize = 2000;
768
769 QRect srcRect(m_transaction.originalRect().toAlignedRect());
770 int x, y, w, h;
771 srcRect.getRect(&x, &y, &w, &h);
772
774 if (w > maxSize || h > maxSize) {
775 qreal scale = qreal(maxSize) / (w > h ? w : h);
776 QTransform scaleTransform = QTransform::fromScale(scale, scale);
777
778 QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect();
779
780 QSize size = thumbRect.size();
781 if (size.width() < 1) size.setWidth(1);
782 if (size.height() < 1) size.setHeight(1);
783 KisPaintDeviceSP dev = m_selectedPortionCache->createThumbnailDeviceOversampled(size.width(), size.height(), 1, srcRect);
784 origImg = m_canvas->displayColorConverter()->convertImageToDisplayColorSpace(dev, QRect(QPoint(0, 0), size), true);
785
786 thumbToImageTransform = scaleTransform.inverted();
787
788 } else {
789 origImg = m_canvas->displayColorConverter()->convertImageToDisplayColorSpace(m_selectedPortionCache, QRect(x, y, w, h), true);
790 thumbToImageTransform = QTransform();
791 }
792 }
793
794 // init both strokes since the thumbnail is initialized only once
795 // during the stroke
796 m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform);
797 m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform);
798 m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform);
799 m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform);
800 m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform);
801 m_meshStrategy->setThumbnailImage(origImg, thumbToImageTransform);
802}
803
805{
806 m_externalSourceForNextActivation = externalSource;
807 if (isActive()) {
808 QSet<KoShape*> dummy;
809 deactivate();
810 activate(dummy);
811 } else {
812 KoToolManager::instance()->switchToolRequested("KisToolTransform");
813 }
814}
815
816void KisToolTransform::activate(const QSet<KoShape*> &shapes)
817{
818 KisTool::activate(shapes);
819
823
824 m_actionConnections.addConnection(action("movetool-move-up"), SIGNAL(triggered(bool)),
825 this, SLOT(slotMoveDiscreteUp()));
826 m_actionConnections.addConnection(action("movetool-move-up-more"), SIGNAL(triggered(bool)),
827 this, SLOT(slotMoveDiscreteUpMore()));
828 m_actionConnections.addConnection(action("movetool-move-down"), SIGNAL(triggered(bool)),
829 this, SLOT(slotMoveDiscreteDown()));
830 m_actionConnections.addConnection(action("movetool-move-down-more"), SIGNAL(triggered(bool)),
831 this, SLOT(slotMoveDiscreteDownMore()));
832 m_actionConnections.addConnection(action("movetool-move-left"), SIGNAL(triggered(bool)),
833 this, SLOT(slotMoveDiscreteLeft()));
834 m_actionConnections.addConnection(action("movetool-move-left-more"), SIGNAL(triggered(bool)),
835 this, SLOT(slotMoveDiscreteLeftMore()));
836 m_actionConnections.addConnection(action("movetool-move-right"), SIGNAL(triggered(bool)),
837 this, SLOT(slotMoveDiscreteRight()));
838 m_actionConnections.addConnection(action("movetool-move-right-more"), SIGNAL(triggered(bool)),
839 this, SLOT(slotMoveDiscreteRightMore()));
840 m_actionConnections.addConnection(action("increase_brush_size"),
841 SIGNAL(triggered()),
842 this,
843 SLOT(slotIncreaseBrushSize()));
844 m_actionConnections.addConnection(action("decrease_brush_size"),
845 SIGNAL(triggered()),
846 this,
847 SLOT(slotDecreaseBrushSize()));
848
849 if (currentNode()) {
851 }
852
855}
856
858{
859 endStroke();
860 m_canvas->updateCanvas();
863}
864
866{
867 if (!m_strokeId || m_transaction.rootNodes().isEmpty() || mode() != HOVER_MODE) return;
868
869 if (!m_changesTracker.canUndo()) {
870 cancelStroke();
871 } else {
873 }
874}
875
877{
878 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
879
882 }
883}
884
889
891{
892 if (m_transaction.rootNodes().isEmpty() || m_currentArgs.isIdentity()) {
893 cancelStroke();
894 } else {
896 }
897}
898
909
911{
912 Q_ASSERT(!m_strokeId);
913
916
917 // set up and null checks before we do anything
918 KisResourcesSnapshotSP resources =
919 new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager(), 0, selectedNodes(), 0);
920 KisNodeList rootNodes = resources->selectedNodes();
921 //Filter out any nodes that might be children of other selected nodes so they aren't used twice
924
927
928 Q_FOREACH (KisNodeSP currentNode, resources->selectedNodes()) {
929 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
930 KIS_ASSERT(kisCanvas);
931
932 if (!currentNode || !currentNode->isEditable()) {
934 kisCanvas->viewManager()->
935 showFloatingMessage(
936 i18nc("floating message in transformation tool",
937 "Cannot transform locked layers"),
938 koIcon("object-locked"), 4000, KisFloatingMessage::High);
939 } else if (currentNode && !currentNode->visible()) {
940 kisCanvas->viewManager()->
941 showFloatingMessage(
942 i18nc("floating message in transformation tool",
943 "Cannot transform hidden layers"),
944 koIcon("object-locked"), 4000, KisFloatingMessage::High);
945 } else {
946 kisCanvas->viewManager()->
947 showFloatingMessage(
948 i18nc("floating message in transformation tool",
949 "Cannot use transform tool on this set of layers"),
950 koIcon("object-locked"), 4000, KisFloatingMessage::High);
951 }
952
953 return;
954 }
955
956 // some layer types cannot be transformed. Give a message and return if a user tries it
957 if (currentNode->inherits("KisColorizeMask") ||
958 currentNode->inherits("KisFileLayer") ||
959 currentNode->inherits("KisCloneLayer")) {
960
961 if(currentNode->inherits("KisColorizeMask")){
962 kisCanvas->viewManager()->
963 showFloatingMessage(
964 i18nc("floating message in transformation tool",
965 "Layer type cannot use the transform tool"),
966 koIcon("object-locked"), 4000, KisFloatingMessage::High);
967 }
968 else{
969 kisCanvas->viewManager()->
970 showFloatingMessage(
971 i18nc("floating message in transformation tool",
972 "Layer type cannot use the transform tool. Use transform mask instead."),
973 koIcon("object-locked"), 4000, KisFloatingMessage::High);
974 }
975 return;
976 }
977
978 KisNodeSP impossibleMask =
980 [currentNode] (KisNodeSP node) {
981 // we can process transform masks of the first level
982 if (node == currentNode || node->parent() == currentNode) return false;
983
984 return node->inherits("KisTransformMask") && node->visible(true);
985 });
986
987 if (impossibleMask) {
988 kisCanvas->viewManager()->
989 showFloatingMessage(
990 i18nc("floating message in transformation tool",
991 "Layer has children with transform masks. Please disable them before doing transformation."),
992 koIcon("object-locked"), 8000, KisFloatingMessage::High);
993 return;
994 }
995
1000 if (selection && dynamic_cast<KisTransformMask*>(currentNode.data())) {
1001 kisCanvas->viewManager()->
1002 showFloatingMessage(
1003 i18nc("floating message in transformation tool",
1004 "Selections are not used when editing transform masks "),
1005 QIcon(), 4000, KisFloatingMessage::Low);
1006
1007 selection = 0;
1008 }
1009 }
1010 // Overlay preview is never used when transforming an externally provided image
1012
1013 KisStrokeStrategy *strategy = 0;
1014
1016 TransformStrokeStrategy *transformStrategy = new TransformStrokeStrategy(mode, m_currentArgs.filterId(), forceReset, rootNodes, selection, image().data(), image().data());
1017 connect(transformStrategy, SIGNAL(sigPreviewDeviceReady(KisPaintDeviceSP)), SLOT(slotPreviewDeviceGenerated(KisPaintDeviceSP)));
1018 connect(transformStrategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)));
1019 connect(transformStrategy, SIGNAL(sigConvexHullCalculated(QPolygon, void*)), SLOT(slotConvexHullCalculated(QPolygon, void*)));
1020 strategy = transformStrategy;
1021
1022 // save unique identifier of the stroke so we could
1023 // recognize it when sigTransactionGenerated() is
1024 // received (theoretically, the user can start two
1025 // strokes at the same time, if he is quick enough)
1026 m_strokeStrategyCookie = transformStrategy;
1027
1028 } else {
1029 InplaceTransformStrokeStrategy *transformStrategy = new InplaceTransformStrokeStrategy(mode, m_currentArgs.filterId(), forceReset, rootNodes, selection, externalSource, image().data(), image().data(), image()->root(), m_forceLodMode);
1030 connect(transformStrategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)));
1031 connect(transformStrategy, SIGNAL(sigConvexHullCalculated(QPolygon, void*)), SLOT(slotConvexHullCalculated(QPolygon, void*)));
1032 strategy = transformStrategy;
1033
1034 // save unique identifier of the stroke so we could
1035 // recognize it when sigTransactionGenerated() is
1036 // received (theoretically, the user can start two
1037 // strokes at the same time, if he is quick enough)
1038 m_strokeStrategyCookie = transformStrategy;
1039 }
1040
1041 m_strokeId = image()->startStroke(strategy);
1042
1045 }
1046
1048
1050}
1051
1076
1078{
1079 if (!m_strokeId || strokeStrategyCookie != m_strokeStrategyCookie) return;
1080
1081 if (transaction.transformedNodes().isEmpty() ||
1082 transaction.originalRect().isEmpty()) {
1083
1084 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1085 KIS_ASSERT(kisCanvas);
1086 kisCanvas->viewManager()->
1087 showFloatingMessage(
1088 i18nc("floating message in transformation tool",
1089 "Cannot transform empty layer "),
1090 QIcon(), 1000, KisFloatingMessage::Medium);
1091
1092 cancelStroke();
1093 return;
1094 }
1095
1096 m_transaction = transaction;
1097 m_currentArgs = args;
1099
1102 }
1103
1105 commitChanges();
1106
1108
1110 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1111 KIS_ASSERT(kisCanvas);
1112 kisCanvas->viewManager()->
1113 showFloatingMessage(
1114 i18nc("floating message in transformation tool",
1115 "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "),
1116 QIcon(), 4000, KisFloatingMessage::Low);
1117 }
1118}
1119
1121{
1122 if (device && device->exactBounds().isEmpty()) {
1123 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1124 KIS_SAFE_ASSERT_RECOVER(kisCanvas) { cancelStroke(); return; }
1125 kisCanvas->viewManager()->
1126 showFloatingMessage(
1127 i18nc("floating message in transformation tool",
1128 "Cannot transform empty layer "),
1129 QIcon(), 1000, KisFloatingMessage::Medium);
1130
1131 cancelStroke();
1132 } else {
1133 initThumbnailImage(device);
1135 }
1136}
1137
1153
1160
1162{
1163 const ToolTransformArgs *newArgs = dynamic_cast<const ToolTransformArgs*>(status.data());
1165
1166 *m_transaction.currentConfig() = *newArgs;
1167
1168 slotUiChangedConfig(true);
1170}
1171
1173{
1174 if (!m_canvas) return 0;
1175
1177 Q_CHECK_PTR(m_optionsWidget);
1178 m_optionsWidget->setObjectName(toolId() + " option widget");
1179
1180 // See https://bugs.kde.org/show_bug.cgi?id=316896
1181 QWidget *specialSpacer = new QWidget(m_optionsWidget);
1182 specialSpacer->setObjectName("SpecialSpacer");
1183 specialSpacer->setFixedSize(0, 0);
1184 m_optionsWidget->layout()->addWidget(specialSpacer);
1185
1186
1187 connect(m_optionsWidget, SIGNAL(sigConfigChanged(bool)),
1188 this, SLOT(slotUiChangedConfig(bool)));
1189
1190 connect(m_optionsWidget, SIGNAL(sigApplyTransform()),
1191 this, SLOT(slotApplyTransform()));
1192
1193 connect(m_optionsWidget, SIGNAL(sigResetTransform(ToolTransformArgs::TransformMode)),
1195
1196 connect(m_optionsWidget, SIGNAL(sigCancelTransform()),
1197 this, SLOT(slotCancelTransform()));
1198
1199 connect(m_optionsWidget, SIGNAL(sigRestartTransform()),
1200 this, SLOT(slotRestartTransform()));
1201
1202 connect(m_optionsWidget, SIGNAL(sigUpdateGlobalConfig()),
1203 this, SLOT(slotGlobalConfigChanged()));
1204
1205 connect(m_optionsWidget, SIGNAL(sigRestartAndContinueTransform()),
1206 this, SLOT(slotRestartAndContinueTransform()));
1207
1208 connect(m_optionsWidget, SIGNAL(sigEditingFinished()),
1209 this, SLOT(slotEditingFinished()));
1210
1211
1212 connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX()));
1213 connect(mirrorVerticalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY()));
1214 connect(rotateNinetyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW()));
1215 connect(rotateNinetyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW()));
1216
1217 connect(keepAspectRatioAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotSetKeepAspectRatio(bool)));
1218
1219
1220 connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType()));
1221 connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType()));
1222 connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType()));
1223 connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType()));
1224 connect(meshAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToMeshType()));
1225 connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType()));
1226
1227 connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform()));
1228 connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotCancelTransform()));
1229
1230
1232
1233 return m_optionsWidget;
1234}
1235
1237{
1238 if (!m_optionsWidget) return;
1239
1240 if (!currentNode()) {
1241 m_optionsWidget->setEnabled(false);
1242 return;
1243 }
1244 else {
1245 m_optionsWidget->setEnabled(true);
1247 }
1248}
1249
1256
1257void KisToolTransform::slotUiChangedConfig(bool needsPreviewRecalculation)
1258{
1259 if (mode() == KisTool::PAINT_MODE) return;
1260
1261 if (needsPreviewRecalculation) {
1263 }
1264
1267 }
1268
1271}
1272
1274{
1275 KisCursorOverrideLock cursorLock(KisCursor::waitCursor());
1276 endStroke();
1277}
1278
1280{
1282 const ToolTransformArgs::TransformMode previousMode = config->mode();
1283 config->setMode(mode);
1284
1286 config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID);
1287 }
1288
1289 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1290
1293
1303 const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs);
1304
1305 if (transformDiffers &&
1306 m_currentArgs.continuedTransform()->mode() == savedMode) {
1307
1311
1312 } else {
1313 cancelStroke();
1314 startStroke(savedMode, true);
1315
1317 }
1318 } else {
1324
1325 } else {
1326 cancelStroke();
1328
1329 }
1330 }
1331}
1332
1337
1339{
1340 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1341
1342 KisNodeSP root = m_transaction.rootNodes()[0];
1343 KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
1344
1346 cancelStroke();
1347 startStroke(savedArgs.mode(), true);
1348}
1349
1351{
1352 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1353
1354 KisNodeSP root = m_transaction.rootNodes()[0];
1355 KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
1356
1358 endStroke();
1359 startStroke(savedArgs.mode(), false);
1360}
1361
1366
1371
1376
1381
1386
1391
1396
1401
1406
1411
1416
1421
1426
1431
1436
1441
1446
1448{
1450}
1451
1453{
1455}
1456
1458{
1460}
1461
1463{
1465}
1466
1478
1490
1492{
1495
1496 actions << actionRegistry->makeQAction("movetool-move-up", this);
1497 actions << actionRegistry->makeQAction("movetool-move-down", this);
1498 actions << actionRegistry->makeQAction("movetool-move-left", this);
1499 actions << actionRegistry->makeQAction("movetool-move-right", this);
1500 actions << actionRegistry->makeQAction("movetool-move-up-more", this);
1501 actions << actionRegistry->makeQAction("movetool-move-down-more", this);
1502 actions << actionRegistry->makeQAction("movetool-move-left-more", this);
1503 actions << actionRegistry->makeQAction("movetool-move-right-more", this);
1504
1505 auto makeSubtoolAction = [&actionRegistry, &actions, this](QString actionName, const char *slot) {
1506 QAction *action = actionRegistry->makeQAction(actionName, this);
1507 action->setProperty("always_enabled", true); // To allow this action to be triggered when the transform tool isn't already active
1508 connect(action, SIGNAL(triggered()), slot);
1509 actions << action;
1510 };
1511 makeSubtoolAction("KisToolTransformFree", SLOT(activateSubtoolFree()));
1512 makeSubtoolAction("KisToolTransformPerspective", SLOT(activateSubtoolPerspective()));
1513 makeSubtoolAction("KisToolTransformWarp", SLOT(activateSubtoolWarp()));
1514 makeSubtoolAction("KisToolTransformCage", SLOT(activateSubtoolCage()));
1515 makeSubtoolAction("KisToolTransformLiquify", SLOT(activateSubtoolLiquify()));
1516 makeSubtoolAction("KisToolTransformMesh", SLOT(activateSubtoolMesh()));
1517
1518 return actions;
1519}
1520
1522{
1523 KoToolManager *toolManager = KoToolManager::instance();
1524
1525 KoCanvasController *canvasController = toolManager->activeCanvasController();
1526 if (!canvasController) return;
1527 KoCanvasBase *canvas = canvasController->canvas();
1528 if (!canvas) return;
1529
1530 KoToolBase *tool = toolManager->toolById(canvas, id());
1532 KisToolTransform *transformTool = dynamic_cast<KisToolTransform*>(tool);
1533 KIS_SAFE_ASSERT_RECOVER_RETURN(transformTool);
1534
1535 if (toolManager->activeToolId() == id()) {
1536 // Transform tool is already active, switch the current mode
1537 transformTool->setTransformMode(mode);
1538 } else {
1539 // Works like KoToolFactoryBase::activateTool, but tells the tool beforehand which initial transform mode to use
1540 transformTool->setNextActivationTransformMode(mode);
1541 toolManager->switchToolRequested(id());
1542 }
1543}
float value(const T *src, size_t ch)
QAction * makeQAction(const QString &name, QObject *parent=0)
static KisActionRegistry * instance()
void initUpdateStreamLowLevel(KisStrokesFacade *strokesFacade, KisStrokeId strokeId)
KisViewManager * viewManager() const
static KisConfigNotifier * instance()
static QCursor waitCursor()
Definition kis_cursor.cc:54
static QCursor pointingHandCursor()
bool cancelStroke(KisStrokeId id) override
void addJob(KisStrokeId id, KisStrokeJobData *data) override
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override
void endStroke(KisStrokeId id) override
QRect exactBounds() const
KisPaintDeviceSP createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect=QRect(), QRect outputRect=QRect()) const
The KisResourcesSnapshot class takes a snapshot of the various resources like colors and settings use...
KisNodeList selectedNodes() const
KisSelectionSP activeSelection() const
void addConnection(Sender sender, Signal signal, Receiver receiver, Method method, Qt::ConnectionType type=Qt::AutoConnection)
void commitConfig(KisToolChangesTrackerDataSP state)
QList< QAction * > createActionsImpl() override
createActionsImpl should be reimplemented if the tool needs any actions. The actions should have a va...
void updateConfig(const ToolTransformArgs &config)
void activateSubtool(KisToolTransform::TransformToolMode mode)
QList< QAction * > createActionsImpl() override
createActionsImpl should be reimplemented if the tool needs any actions. The actions should have a va...
void setRotateZ(double rotation)
void requestStrokeEnd() override
void deactivateAlternateAction(AlternateAction action) override
void setWarpType(WarpType type)
KisAction * perspectiveAction
void paint(QPainter &gc, const KoViewConverter &converter) override
KisAction * resetTransformation
void setRotateY(double rotation)
void setScaleX(double scaleX)
void setTranslateY(double translateY)
static ToolTransformArgs::TransformMode toArgsMode(KisToolTransform::TransformToolMode toolMode)
void mouseReleaseEvent(KoPointerEvent *e) override
KisAction * applyTransformation
KisToolTransformConfigWidget * m_optionsWidget
void mouseMoveEvent(KoPointerEvent *e) override
void slotUiChangedConfig(bool needsPreviewRecalculation)
void beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
void endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
KisToolChangesTracker m_changesTracker
KisAction * keepAspectRatioAction
void initTransformMode(ToolTransformArgs::TransformMode mode)
void setShearY(double shearY)
TransformToolMode transformMode
void deactivate() override
void setWarpFlexibility(double flexibility)
QScopedPointer< KisMeshTransformStrategy > m_meshStrategy
void requestUndoDuringStroke() override
void slotTrackerChangedConfig(KisToolChangesTrackerDataSP status)
QScopedPointer< KisFreeTransformStrategy > m_freeStrategy
void freeTransformChanged()
void initThumbnailImage(KisPaintDeviceSP previewDevice)
void setWarpPointDensity(int density)
void activateAlternateAction(AlternateAction action) override
void cursorOutlineUpdateRequested(const QPointF &imagePos)
void imageTooBigRequested(bool value)
void setRotateX(double rotation)
void slotConvexHullCalculated(QPolygon hull, void *strokeStrategyCookie)
void beginPrimaryAction(KoPointerEvent *event) override
void slotPreviewDeviceGenerated(KisPaintDeviceSP device)
QMenu * popupActionsMenu() override
void newActivationWithExternalSource(KisPaintDeviceSP externalSource) override
newActivationWithExternalSource Makes sure that the tool is active and starts a new stroke,...
void requestStrokeCancellation() override
ToolTransformArgs m_currentArgs
QPointer< KisCanvas2 > m_canvas
void activatePrimaryAction() override
QScopedPointer< KisCageTransformStrategy > m_cageStrategy
QScopedPointer< QMenu > m_contextMenu
void setScaleY(double scaleY)
void mousePressEvent(KoPointerEvent *e) override
TransformToolMode nextActivationTransformMode
KisAction * mirrorVerticalAction
void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override
void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override
void continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
void startStroke(ToolTransformArgs::TransformMode mode, bool forceReset)
QScopedPointer< KisPerspectiveTransformStrategy > m_perspectiveStrategy
QPainterPath m_cursorOutline
KisAction * mirrorHorizontalAction
KisPaintDeviceSP m_externalSourceForNextActivation
QWidget * createOptionWidget() override
void setTranslateX(double translateX)
void setNextActivationTransformMode(TransformToolMode mode)
KisSignalAutoConnectionsStore m_actionConnections
KisToolTransform(KoCanvasBase *canvas)
void activate(const QSet< KoShape * > &shapes) override
void endAlternateAction(KoPointerEvent *event, AlternateAction action) override
void setTransformMode(KisToolTransform::TransformToolMode newMode)
KisAction * rotateNinetyCWAction
TransformTransactionProperties m_transaction
KisAction * warpAction
actions for the context click menu
void endPrimaryAction(KoPointerEvent *event) override
void resetRotationCenterButtonsRequested()
void transformModeChanged()
void continuePrimaryAction(KoPointerEvent *event) override
void setShearX(double shearX)
KisAction * freeTransformAction
KisAction * rotateNinetyCCWAction
KisPaintDeviceSP m_selectedPortionCache
void slotResetTransform(ToolTransformArgs::TransformMode mode)
void deactivatePrimaryAction() override
void slotTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args, void *strokeStrategyCookie)
void resetCursorStyle() override
QScopedPointer< KisWarpTransformStrategy > m_warpStrategy
KisAsynchronousStrokeUpdateHelper m_asyncUpdateHelper
QScopedPointer< KisLiquifyTransformStrategy > m_liquifyStrategy
KisTransformStrategyBase * currentStrategy() const
void requestRedoDuringStroke() override
virtual void activateAlternateAction(KisTool::AlternateAction action)
virtual bool beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
virtual void continuePrimaryAction(KoPointerEvent *event)=0
virtual bool endPrimaryAction(KoPointerEvent *event)=0
virtual void externalConfigChanged()=0
virtual void hoverActionCommon(KoPointerEvent *event)=0
virtual void decreaseBrushSize(KoCanvasBase *canvas)
virtual void paint(QPainter &gc, const KoColorDisplayRendererInterface *displayRendererInterface)=0
virtual void continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
virtual bool endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
virtual void setDecorationThickness(int thickness)
virtual void increaseBrushSize(KoCanvasBase *canvas)
virtual bool beginPrimaryAction(KoPointerEvent *event)=0
virtual void deactivateAlternateAction(KisTool::AlternateAction action)
static ToolTransformArgs resetArgsForMode(ToolTransformArgs::TransformMode mode, const QString &filterId, const TransformTransactionProperties &transaction, KisPaintDeviceSP externalSource)
static T imageToFlake(const KisCoordinatesConverter *converter, T object)
static QTransform imageToFlakeTransform(const KisCoordinatesConverter *converter)
static bool shouldRestartStrokeOnModeChange(ToolTransformArgs::TransformMode oldMode, ToolTransformArgs::TransformMode newMode, KisNodeList processedNodes)
virtual KoCanvasBase * canvas() const
bool isTouchEvent() const
QPointF point
The point in document coordinates.
Q_INVOKABLE QString toolId() const
virtual KoToolSelection * selection()
void useCursor(const QCursor &cursor)
QAction * action(const QString &name) const
int decorationThickness() const
decorationThickness The minimum thickness for tool decoration lines, this is derived from the screen ...
KoToolBase * toolById(KoCanvasBase *canvas, const QString &id) const
void switchToolRequested(const QString &id)
KoCanvasController * activeCanvasController() const
QString activeToolId() const
Returns the toolId of the currently active tool.
static KoToolManager * instance()
Return the toolmanager singleton.
QPointF transformedCenter() const
void setWarpCalculation(KisWarpTransformWorker::WarpCalculation warpCalc)
KisToolChangesTrackerData * clone() const override
void setAlpha(double alpha)
KisWarpTransformWorker::WarpType warpType() const
void saveLiquifyTransformMode() const
void setTransformedCenter(QPointF transformedCenter)
void setMode(TransformMode mode)
bool isSameMode(const ToolTransformArgs &other) const
QString filterId() const
void setWarpType(KisWarpTransformWorker::WarpType warpType)
TransformMode mode() const
KisPaintDeviceSP externalSource() const
const ToolTransformArgs * continuedTransform() const
void setCurrentConfigLocation(ToolTransformArgs *config)
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define warnTools
Definition kis_debug.h:90
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
#define koIcon(name)
Use these macros for icons without any issues.
Definition kis_icon.h:25
QSharedPointer< T > toQShared(T *ptr)
QList< KisNodeSP > KisNodeList
Definition kis_types.h:264
KisNodeSP recursiveFindNode(KisNodeSP node, std::function< bool(KisNodeSP)> func)
void filterMergeableNodes(KisNodeList &nodes, bool allowMasks)
bool isEditable(bool checkVisibility=true) const
bool userLocked() const
virtual bool visible(bool recursive=false) const
KisNodeWSP parent
Definition kis_node.cpp:86
KisNodeList selectedNodes() const
Definition kis_tool.cc:376
virtual ToolMode mode() const
Definition kis_tool.cc:407
void mouseReleaseEvent(KoPointerEvent *event) override
Definition kis_tool.cc:515
bool nodeEditable()
Checks checks if the current node is editable.
Definition kis_tool.cc:651
bool isActive
Definition kis_tool.h:44
KisNodeSP currentNode() const
Definition kis_tool.cc:370
void mousePressEvent(KoPointerEvent *event) override
Definition kis_tool.cc:510
void mouseMoveEvent(KoPointerEvent *event) override
Definition kis_tool.cc:520
bool overrideCursorIfNotEditable()
Override the cursor appropriately if current node is not editable.
Definition kis_tool.cc:618
void activate(const QSet< KoShape * > &shapes) override
Definition kis_tool.cc:93
void paintToolOutline(QPainter *painter, const KisOptimizedBrushOutline &path)
Definition kis_tool.cc:589
void deactivate() override
Definition kis_tool.cc:131
KisImageWSP image() const
Definition kis_tool.cc:332
@ PAINT_MODE
Definition kis_tool.h:300
@ HOVER_MODE
Definition kis_tool.h:299
AlternateAction
Definition kis_tool.h:134
@ ChangeSize
Definition kis_tool.h:135
virtual void setMode(ToolMode mode)
Definition kis_tool.cc:403
KisCanvas2 * canvas