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
60#include "kis_action_registry.h"
61
63
64#include "kis_transform_utils.h"
71
72#include "kis_transform_mask.h"
74
76#include "kis_layer_utils.h"
78#include "kis_config_notifier.h"
79
82
84 : KisTool(canvas, KisCursor::rotateCursor())
85 , m_warpStrategy(
87 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
88 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
89 m_currentArgs, m_transaction))
90 , m_cageStrategy(
92 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
93 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
94 m_currentArgs, m_transaction))
95 , m_liquifyStrategy(
97 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
98 m_currentArgs, m_transaction, canvas->resourceManager()))
99 , m_meshStrategy(
101 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
102 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
103 m_currentArgs, m_transaction))
104 , m_freeStrategy(
106 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
107 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
108 m_currentArgs, m_transaction))
109 , m_perspectiveStrategy(
111 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
112 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
113 m_currentArgs, m_transaction))
114{
115 m_canvas = dynamic_cast<KisCanvas2*>(canvas);
116 Q_ASSERT(m_canvas);
117
118 setObjectName("tool_transform");
119 m_optionsWidget = 0;
120
121 warpAction = new KisAction(i18nc("Warp Transform Tab Label", "Warp"));
122 liquifyAction = new KisAction(i18nc("Liquify Transform Tab Label", "Liquify"));
123 meshAction = new KisAction(i18nc("Mesh Transform Tab Label", "Mesh"));
124 cageAction = new KisAction(i18nc("Cage Transform Tab Label", "Cage"));
125 freeTransformAction = new KisAction(i18nc("Free Transform Tab Label", "Free"));
126 perspectiveAction = new KisAction(i18nc("Perspective Transform Tab Label", "Perspective"));
127
128 // extra actions for free transform that are in the tool options
129 mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal"));
130 mirrorVerticalAction = new KisAction(i18n("Mirror Vertical"));
131 rotateNinetyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise"));
132 rotateNinetyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise"));
133
134 keepAspectRatioAction = new KisAction(i18n("Keep Aspect Ratio"));
135 keepAspectRatioAction->setCheckable(true);
136 keepAspectRatioAction->setChecked(false);
137
138 applyTransformation = new KisAction(i18n("Apply"));
139 resetTransformation = new KisAction(i18n("Reset"));
140
141 m_contextMenu.reset(new QMenu());
142
143 connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
145 connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
147 connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
148 connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(QPointF)), SLOT(cursorOutlineUpdateRequested(QPointF)));
149 connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget()));
151 connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
152 connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested()));
153 connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
155 connect(m_freeStrategy.data(), SIGNAL(requestConvexHullCalculation()), SLOT(convexHullCalculationRequested()));
156 connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
157 connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
159 connect(m_meshStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
161
162 connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)),
164
165 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotGlobalConfigChanged()));
166}
167
169{
170 cancelStroke();
171
172 delete warpAction;
173 delete meshAction;
174 delete liquifyAction;
175 delete cageAction;
176 delete freeTransformAction;
177 delete perspectiveAction;
178 delete applyTransformation;
179 delete resetTransformation;
185}
186
188{
189 Q_EMIT freeTransformChanged();
190 m_canvas->updateCanvas();
191}
192
194{
195 m_canvas->updateCanvas();
196}
197
202
204{
205 KConfigGroup group = KSharedConfig::openConfig()->group(toolId());
206 m_preferOverlayPreviewStyle = group.readEntry("useOverlayPreviewStyle", false);
207 m_forceLodMode = group.readEntry("forceLodMode", true);
208}
209
215
221
244void KisToolTransform::slotConvexHullCalculated(QPolygon hull, void *strokeStrategyCookie)
245{
246 if (!m_strokeId || strokeStrategyCookie != m_strokeStrategyCookie) return;
247 QPolygonF hullF = hull;
254 if (hullF.boundingRect() == m_transaction.originalRect()) {
258 } else {
259 warnTools << "WARNING: KisToolTransform: calculated convex hull's bounds "
260 "differ from the bounding rect of the source clip. It shouldn't "
261 "have happened";
262 }
263}
264
266{
268 return m_freeStrategy.data();
270 return m_warpStrategy.data();
272 return m_cageStrategy.data();
274 return m_liquifyStrategy.data();
276 return m_meshStrategy.data();
277 } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ {
278 return m_perspectiveStrategy.data();
279 }
280}
281
282void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter)
283{
284 Q_UNUSED(converter);
285
286 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
287
288 QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0));
289 if (m_refRect != newRefRect) {
290 m_refRect = newRefRect;
292 }
294 currentStrategy()->paint(gc);
295
296
297 if (!m_cursorOutline.isEmpty()) {
298 QPainterPath mappedOutline =
300 m_canvas->coordinatesConverter()).map(m_cursorOutline);
301 paintToolOutline(&gc, mappedOutline);
302 }
303}
304
306{
308 return;
309 }
310
311 if (!m_strokeId) {
313 } else if (m_strokeId && m_transaction.rootNodes().isEmpty()) {
314 // we are in the middle of stroke initialization
316 } else {
317 useCursor(currentStrategy()->getCurrentCursor());
318 }
319}
320
322{
323 QRect canvasUpdateRect;
324
325 if (!m_cursorOutline.isEmpty()) {
326 canvasUpdateRect = m_canvas->coordinatesConverter()->
327 imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
328 }
329
331 getCursorOutline().translated(imagePos);
332
333 if (!m_cursorOutline.isEmpty()) {
334 canvasUpdateRect |=
335 m_canvas->coordinatesConverter()->
336 imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
337 }
338
339 if (!canvasUpdateRect.isEmpty()) {
340 // grow rect a bit to follow interpolation fuzziness
341 canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2);
342 m_canvas->updateCanvas(canvasUpdateRect);
343 }
344}
345
347{
348 if (!nodeEditable()) {
349 event->ignore();
350 return;
351 }
352
353 if (!m_strokeId) {
355 } else if (!m_transaction.rootNodes().isEmpty()) {
356 bool result = false;
357
358 if (usePrimaryAction) {
359 result = currentStrategy()->beginPrimaryAction(event);
360 } else {
361 result = currentStrategy()->beginAlternateAction(event, action);
362 }
363
364 if (result) {
366 }
367 }
368
370
372}
373
375{
376 if (mode() != KisTool::PAINT_MODE) return;
377 if (m_transaction.rootNodes().isEmpty()) return;
378
380
381 if (usePrimaryAction) {
383 } else {
385 }
386
389}
390
392{
393 if (mode() != KisTool::PAINT_MODE) return;
394
396
398 currentStrategy()->acceptsClicks()) {
399
400 bool result = false;
401
402 if (usePrimaryAction) {
403 result = currentStrategy()->endPrimaryAction(event);
404 } else {
405 result = currentStrategy()->endAlternateAction(event, action);
406 }
407
408 if (result) {
410 }
411
413 }
414
417}
418
420{
421 if (m_contextMenu) {
422 m_contextMenu->clear();
423
424 m_contextMenu->addSection(i18n("Transform Tool Actions"));
425 // add a quick switch to different transform types
428 m_contextMenu->addAction(warpAction);
429 m_contextMenu->addAction(cageAction);
430 m_contextMenu->addAction(liquifyAction);
431 m_contextMenu->addAction(meshAction);
432
433 // extra options if free transform is selected
435 m_contextMenu->addSeparator();
440
441 m_contextMenu->addSeparator();
444 }
445
446 m_contextMenu->addSeparator();
449 }
450
451 return m_contextMenu.data();
452}
453
458
463
468
474
479
485
490
495
500
505
507{
508 // When using touch drawing, we only ever receive move events after the
509 // finger has pressed down. This confuses the strategies greatly, since they
510 // expect to receive a hover to tell which anchor the user wants to
511 // manipulate or similar. So in this case, we send an artificial hover.
512 if (event->isTouchEvent() && this->mode() != KisTool::PAINT_MODE) {
515 }
517}
518
520{
521 QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point);
522
524
525 if (this->mode() != KisTool::PAINT_MODE) {
529 return;
530 }
531}
532
537
542
544{
546
547 switch (m_currentArgs.mode())
548 {
551 break;
554 break;
557 break;
560 break;
563 break;
566 break;
567 default:
568 KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
569 }
570
571 return mode;
572}
573
575{
576 return m_currentArgs.transformedCenter().x();
577}
578
580{
581 return m_currentArgs.transformedCenter().y();
582}
583
585{
586 return m_currentArgs.aX();
587}
588
590{
591 return m_currentArgs.aY();
592}
593
595{
596 return m_currentArgs.aZ();
597}
598
600{
601 return m_currentArgs.scaleX();
602}
603
605{
606 return m_currentArgs.scaleY();
607}
608
610{
611 return m_currentArgs.shearX();
612}
613
615{
616 return m_currentArgs.shearY();
617}
618
632
634{
635 return m_currentArgs.alpha();
636}
637
642
644{
646
647 switch (newMode) {
650 break;
653 break;
656 break;
659 break;
662 break;
665 break;
666 default:
667 KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
668 }
669
670 if( mode != m_currentArgs.mode() ) {
671 if( newMode == FreeTransformMode ) {
673 } else if( newMode == WarpTransformMode ) {
675 } else if( newMode == CageTransformMode ) {
677 } else if( newMode == LiquifyTransformMode ) {
679 } else if( newMode == PerspectiveTransformMode ) {
681 } else if( newMode == MeshTransformMode ) {
683 }
684
685 Q_EMIT transformModeChanged();
686 }
687}
688
689void KisToolTransform::setRotateX( double rotation )
690{
691 m_currentArgs.setAX( rotation );
692}
693
694void KisToolTransform::setRotateY( double rotation )
695{
696 m_currentArgs.setAY( rotation );
697}
698
699void KisToolTransform::setRotateZ( double rotation )
700{
701 m_currentArgs.setAZ( rotation );
702}
703
720
721void KisToolTransform::setWarpFlexibility( double flexibility )
722{
723 m_currentArgs.setAlpha( flexibility );
724}
725
730
736
745
747{
748 QImage origImg;
749 m_selectedPortionCache = previewDevice;
750
751 QTransform thumbToImageTransform;
752
753 const int maxSize = 2000;
754
755 QRect srcRect(m_transaction.originalRect().toAlignedRect());
756 int x, y, w, h;
757 srcRect.getRect(&x, &y, &w, &h);
758
760 if (w > maxSize || h > maxSize) {
761 qreal scale = qreal(maxSize) / (w > h ? w : h);
762 QTransform scaleTransform = QTransform::fromScale(scale, scale);
763
764 QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect();
765
766 origImg = m_selectedPortionCache->
767 createThumbnail(thumbRect.width(),
768 thumbRect.height(),
769 srcRect, 1,
772 thumbToImageTransform = scaleTransform.inverted();
773
774 } else {
775 origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h,
778 thumbToImageTransform = QTransform();
779 }
780 }
781
782 // init both strokes since the thumbnail is initialized only once
783 // during the stroke
784 m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform);
785 m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform);
786 m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform);
787 m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform);
788 m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform);
789 m_meshStrategy->setThumbnailImage(origImg, thumbToImageTransform);
790}
791
793{
794 m_externalSourceForNextActivation = externalSource;
795 if (isActive()) {
796 QSet<KoShape*> dummy;
797 deactivate();
798 activate(dummy);
799 } else {
800 KoToolManager::instance()->switchToolRequested("KisToolTransform");
801 }
802}
803
804void KisToolTransform::activate(const QSet<KoShape*> &shapes)
805{
806 KisTool::activate(shapes);
807
811
812 m_actionConnections.addConnection(action("movetool-move-up"), SIGNAL(triggered(bool)),
813 this, SLOT(slotMoveDiscreteUp()));
814 m_actionConnections.addConnection(action("movetool-move-up-more"), SIGNAL(triggered(bool)),
815 this, SLOT(slotMoveDiscreteUpMore()));
816 m_actionConnections.addConnection(action("movetool-move-down"), SIGNAL(triggered(bool)),
817 this, SLOT(slotMoveDiscreteDown()));
818 m_actionConnections.addConnection(action("movetool-move-down-more"), SIGNAL(triggered(bool)),
819 this, SLOT(slotMoveDiscreteDownMore()));
820 m_actionConnections.addConnection(action("movetool-move-left"), SIGNAL(triggered(bool)),
821 this, SLOT(slotMoveDiscreteLeft()));
822 m_actionConnections.addConnection(action("movetool-move-left-more"), SIGNAL(triggered(bool)),
823 this, SLOT(slotMoveDiscreteLeftMore()));
824 m_actionConnections.addConnection(action("movetool-move-right"), SIGNAL(triggered(bool)),
825 this, SLOT(slotMoveDiscreteRight()));
826 m_actionConnections.addConnection(action("movetool-move-right-more"), SIGNAL(triggered(bool)),
827 this, SLOT(slotMoveDiscreteRightMore()));
828
829 if (currentNode()) {
831 }
832
834}
835
837{
838 endStroke();
839 m_canvas->updateCanvas();
842}
843
845{
846 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
847
848 if (!m_changesTracker.canUndo()) {
849 cancelStroke();
850 } else {
852 }
853}
854
856{
857 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
858
861 }
862}
863
868
870{
871 if (m_transaction.rootNodes().isEmpty() || m_currentArgs.isIdentity()) {
872 cancelStroke();
873 } else {
875 }
876}
877
888
890{
891 Q_ASSERT(!m_strokeId);
892
895
896 // set up and null checks before we do anything
897 KisResourcesSnapshotSP resources =
898 new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager(), 0, selectedNodes(), 0);
899 KisNodeList rootNodes = resources->selectedNodes();
900 //Filter out any nodes that might be children of other selected nodes so they aren't used twice
903
906
907 Q_FOREACH (KisNodeSP currentNode, resources->selectedNodes()) {
908 if (!currentNode || !currentNode->isEditable()) return;
909
910 // some layer types cannot be transformed. Give a message and return if a user tries it
911 if (currentNode->inherits("KisColorizeMask") ||
912 currentNode->inherits("KisFileLayer") ||
913 currentNode->inherits("KisCloneLayer")) {
914
915 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
916 KIS_ASSERT(kisCanvas);
917
918 if(currentNode->inherits("KisColorizeMask")){
919 kisCanvas->viewManager()->
920 showFloatingMessage(
921 i18nc("floating message in transformation tool",
922 "Layer type cannot use the transform tool"),
923 koIcon("object-locked"), 4000, KisFloatingMessage::High);
924 }
925 else{
926 kisCanvas->viewManager()->
927 showFloatingMessage(
928 i18nc("floating message in transformation tool",
929 "Layer type cannot use the transform tool. Use transform mask instead."),
930 koIcon("object-locked"), 4000, KisFloatingMessage::High);
931 }
932 return;
933 }
934
935 KisNodeSP impossibleMask =
937 [currentNode] (KisNodeSP node) {
938 // we can process transform masks of the first level
939 if (node == currentNode || node->parent() == currentNode) return false;
940
941 return node->inherits("KisTransformMask") && node->visible(true);
942 });
943
944 if (impossibleMask) {
945 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
946 kisCanvas->viewManager()->
947 showFloatingMessage(
948 i18nc("floating message in transformation tool",
949 "Layer has children with transform masks. Please disable them before doing transformation."),
950 koIcon("object-locked"), 8000, KisFloatingMessage::High);
951 return;
952 }
953
958 if (selection && dynamic_cast<KisTransformMask*>(currentNode.data())) {
959 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
960 kisCanvas->viewManager()->
961 showFloatingMessage(
962 i18nc("floating message in transformation tool",
963 "Selections are not used when editing transform masks "),
964 QIcon(), 4000, KisFloatingMessage::Low);
965
966 selection = 0;
967 }
968 }
969 // Overlay preview is never used when transforming an externally provided image
971
972 KisStrokeStrategy *strategy = 0;
973
975 TransformStrokeStrategy *transformStrategy = new TransformStrokeStrategy(mode, m_currentArgs.filterId(), forceReset, rootNodes, selection, image().data(), image().data());
976 connect(transformStrategy, SIGNAL(sigPreviewDeviceReady(KisPaintDeviceSP)), SLOT(slotPreviewDeviceGenerated(KisPaintDeviceSP)));
977 connect(transformStrategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)));
978 connect(transformStrategy, SIGNAL(sigConvexHullCalculated(QPolygon, void*)), SLOT(slotConvexHullCalculated(QPolygon, void*)));
979 strategy = transformStrategy;
980
981 // save unique identifier of the stroke so we could
982 // recognize it when sigTransactionGenerated() is
983 // received (theoretically, the user can start two
984 // strokes at the same time, if he is quick enough)
985 m_strokeStrategyCookie = transformStrategy;
986
987 } else {
988 InplaceTransformStrokeStrategy *transformStrategy = new InplaceTransformStrokeStrategy(mode, m_currentArgs.filterId(), forceReset, rootNodes, selection, externalSource, image().data(), image().data(), image()->root(), m_forceLodMode);
989 connect(transformStrategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)));
990 connect(transformStrategy, SIGNAL(sigConvexHullCalculated(QPolygon, void*)), SLOT(slotConvexHullCalculated(QPolygon, void*)));
991 strategy = transformStrategy;
992
993 // save unique identifier of the stroke so we could
994 // recognize it when sigTransactionGenerated() is
995 // received (theoretically, the user can start two
996 // strokes at the same time, if he is quick enough)
997 m_strokeStrategyCookie = transformStrategy;
998 }
999
1000 m_strokeId = image()->startStroke(strategy);
1001
1004 }
1005
1007
1009}
1010
1035
1037{
1038 if (!m_strokeId || strokeStrategyCookie != m_strokeStrategyCookie) return;
1039
1040 if (transaction.transformedNodes().isEmpty() ||
1041 transaction.originalRect().isEmpty()) {
1042
1043 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1044 KIS_ASSERT(kisCanvas);
1045 kisCanvas->viewManager()->
1046 showFloatingMessage(
1047 i18nc("floating message in transformation tool",
1048 "Cannot transform empty layer "),
1049 QIcon(), 1000, KisFloatingMessage::Medium);
1050
1051 cancelStroke();
1052 return;
1053 }
1054
1055 m_transaction = transaction;
1056 m_currentArgs = args;
1058
1061 }
1062
1064 commitChanges();
1065
1067
1069 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1070 KIS_ASSERT(kisCanvas);
1071 kisCanvas->viewManager()->
1072 showFloatingMessage(
1073 i18nc("floating message in transformation tool",
1074 "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "),
1075 QIcon(), 4000, KisFloatingMessage::Low);
1076 }
1077}
1078
1080{
1081 if (device && device->exactBounds().isEmpty()) {
1082 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1083 KIS_SAFE_ASSERT_RECOVER(kisCanvas) { cancelStroke(); return; }
1084 kisCanvas->viewManager()->
1085 showFloatingMessage(
1086 i18nc("floating message in transformation tool",
1087 "Cannot transform empty layer "),
1088 QIcon(), 1000, KisFloatingMessage::Medium);
1089
1090 cancelStroke();
1091 } else {
1092 initThumbnailImage(device);
1094 }
1095}
1096
1112
1119
1121{
1122 const ToolTransformArgs *newArgs = dynamic_cast<const ToolTransformArgs*>(status.data());
1124
1125 *m_transaction.currentConfig() = *newArgs;
1126
1127 slotUiChangedConfig(true);
1129}
1130
1132{
1133 if (!m_canvas) return 0;
1134
1136 Q_CHECK_PTR(m_optionsWidget);
1137 m_optionsWidget->setObjectName(toolId() + " option widget");
1138
1139 // See https://bugs.kde.org/show_bug.cgi?id=316896
1140 QWidget *specialSpacer = new QWidget(m_optionsWidget);
1141 specialSpacer->setObjectName("SpecialSpacer");
1142 specialSpacer->setFixedSize(0, 0);
1143 m_optionsWidget->layout()->addWidget(specialSpacer);
1144
1145
1146 connect(m_optionsWidget, SIGNAL(sigConfigChanged(bool)),
1147 this, SLOT(slotUiChangedConfig(bool)));
1148
1149 connect(m_optionsWidget, SIGNAL(sigApplyTransform()),
1150 this, SLOT(slotApplyTransform()));
1151
1152 connect(m_optionsWidget, SIGNAL(sigResetTransform(ToolTransformArgs::TransformMode)),
1154
1155 connect(m_optionsWidget, SIGNAL(sigCancelTransform()),
1156 this, SLOT(slotCancelTransform()));
1157
1158 connect(m_optionsWidget, SIGNAL(sigRestartTransform()),
1159 this, SLOT(slotRestartTransform()));
1160
1161 connect(m_optionsWidget, SIGNAL(sigUpdateGlobalConfig()),
1162 this, SLOT(slotGlobalConfigChanged()));
1163
1164 connect(m_optionsWidget, SIGNAL(sigRestartAndContinueTransform()),
1165 this, SLOT(slotRestartAndContinueTransform()));
1166
1167 connect(m_optionsWidget, SIGNAL(sigEditingFinished()),
1168 this, SLOT(slotEditingFinished()));
1169
1170
1171 connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX()));
1172 connect(mirrorVerticalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY()));
1173 connect(rotateNinetyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW()));
1174 connect(rotateNinetyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW()));
1175
1176 connect(keepAspectRatioAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotSetKeepAspectRatio(bool)));
1177
1178
1179 connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType()));
1180 connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType()));
1181 connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType()));
1182 connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType()));
1183 connect(meshAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToMeshType()));
1184 connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType()));
1185
1186 connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform()));
1187 connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotCancelTransform()));
1188
1189
1191
1192 return m_optionsWidget;
1193}
1194
1196{
1197 if (!m_optionsWidget) return;
1198
1199 if (!currentNode()) {
1200 m_optionsWidget->setEnabled(false);
1201 return;
1202 }
1203 else {
1204 m_optionsWidget->setEnabled(true);
1206 }
1207}
1208
1215
1216void KisToolTransform::slotUiChangedConfig(bool needsPreviewRecalculation)
1217{
1218 if (mode() == KisTool::PAINT_MODE) return;
1219
1220 if (needsPreviewRecalculation) {
1222 }
1223
1226 }
1227
1230}
1231
1233{
1234 KisCursorOverrideLock cursorLock(KisCursor::waitCursor());
1235 endStroke();
1236}
1237
1239{
1241 const ToolTransformArgs::TransformMode previousMode = config->mode();
1242 config->setMode(mode);
1243
1245 config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID);
1246 }
1247
1248 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1249
1252
1262 const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs);
1263
1264 if (transformDiffers &&
1265 m_currentArgs.continuedTransform()->mode() == savedMode) {
1266
1270
1271 } else {
1272 cancelStroke();
1273 startStroke(savedMode, true);
1274
1276 }
1277 } else {
1283
1284 } else {
1285 cancelStroke();
1287
1288 }
1289 }
1290}
1291
1296
1298{
1299 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1300
1301 KisNodeSP root = m_transaction.rootNodes()[0];
1302 KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
1303
1305 cancelStroke();
1306 startStroke(savedArgs.mode(), true);
1307}
1308
1310{
1311 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1312
1313 KisNodeSP root = m_transaction.rootNodes()[0];
1314 KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
1315
1317 endStroke();
1318 startStroke(savedArgs.mode(), false);
1319}
1320
1325
1330
1335
1340
1345
1350
1355
1360
1365
1370
1375
1380
1385
1390
1395
1397{
1399}
1400
1402{
1404}
1405
1407{
1409}
1410
1412{
1414}
1415
1427
1439
1441{
1444
1445 actions << actionRegistry->makeQAction("movetool-move-up", this);
1446 actions << actionRegistry->makeQAction("movetool-move-down", this);
1447 actions << actionRegistry->makeQAction("movetool-move-left", this);
1448 actions << actionRegistry->makeQAction("movetool-move-right", this);
1449 actions << actionRegistry->makeQAction("movetool-move-up-more", this);
1450 actions << actionRegistry->makeQAction("movetool-move-down-more", this);
1451 actions << actionRegistry->makeQAction("movetool-move-left-more", this);
1452 actions << actionRegistry->makeQAction("movetool-move-right-more", this);
1453
1454 return actions;
1455}
float value(const T *src, size_t ch)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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
QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags()) 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)
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)
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
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)
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 continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
virtual void paint(QPainter &gc)=0
virtual bool endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
virtual void setDecorationThickness(int thickness)
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)
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 ...
void switchToolRequested(const QString &id)
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
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