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
392}
393
395{
396 if (mode() != KisTool::PAINT_MODE) return;
397
399
401 currentStrategy()->acceptsClicks()) {
402
403 bool result = false;
404
405 if (usePrimaryAction) {
406 result = currentStrategy()->endPrimaryAction(event);
407 } else {
408 result = currentStrategy()->endAlternateAction(event, action);
409 }
410
411 if (result) {
413 }
414
416 }
417
420}
421
423{
424 if (m_contextMenu) {
425 m_contextMenu->clear();
426
427 m_contextMenu->addSection(i18n("Transform Tool Actions"));
428 // add a quick switch to different transform types
431 m_contextMenu->addAction(warpAction);
432 m_contextMenu->addAction(cageAction);
433 m_contextMenu->addAction(liquifyAction);
434 m_contextMenu->addAction(meshAction);
435
436 // extra options if free transform is selected
438 m_contextMenu->addSeparator();
443
444 m_contextMenu->addSeparator();
447 }
448
449 m_contextMenu->addSeparator();
452 }
453
454 return m_contextMenu.data();
455}
456
461
466
471
477
482
488
493
498
503
508
510{
511 // When using touch drawing, we only ever receive move events after the
512 // finger has pressed down. This confuses the strategies greatly, since they
513 // expect to receive a hover to tell which anchor the user wants to
514 // manipulate or similar. So in this case, we send an artificial hover.
515 if (event->isTouchEvent() && this->mode() != KisTool::PAINT_MODE) {
518 }
520}
521
523{
524 QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point);
525
527
528 if (this->mode() != KisTool::PAINT_MODE) {
532 return;
533 }
534}
535
540
545
550
552{
554
555 switch (m_currentArgs.mode())
556 {
559 break;
562 break;
565 break;
568 break;
571 break;
574 break;
575 default:
576 KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
577 }
578
579 return mode;
580}
581
583{
584 return m_currentArgs.transformedCenter().x();
585}
586
588{
589 return m_currentArgs.transformedCenter().y();
590}
591
593{
594 return m_currentArgs.aX();
595}
596
598{
599 return m_currentArgs.aY();
600}
601
603{
604 return m_currentArgs.aZ();
605}
606
608{
609 return m_currentArgs.scaleX();
610}
611
613{
614 return m_currentArgs.scaleY();
615}
616
618{
619 return m_currentArgs.shearX();
620}
621
623{
624 return m_currentArgs.shearY();
625}
626
640
642{
643 return m_currentArgs.alpha();
644}
645
650
680
682{
684
685 if( mode != m_currentArgs.mode() ) {
686 if( newMode == FreeTransformMode ) {
688 } else if( newMode == WarpTransformMode ) {
690 } else if( newMode == CageTransformMode ) {
692 } else if( newMode == LiquifyTransformMode ) {
694 } else if( newMode == PerspectiveTransformMode ) {
696 } else if( newMode == MeshTransformMode ) {
698 }
699
700 Q_EMIT transformModeChanged();
701 }
702}
703
704void KisToolTransform::setRotateX( double rotation )
705{
706 m_currentArgs.setAX( rotation );
707}
708
709void KisToolTransform::setRotateY( double rotation )
710{
711 m_currentArgs.setAY( rotation );
712}
713
714void KisToolTransform::setRotateZ( double rotation )
715{
716 m_currentArgs.setAZ( rotation );
717}
718
735
736void KisToolTransform::setWarpFlexibility( double flexibility )
737{
738 m_currentArgs.setAlpha( flexibility );
739}
740
745
751
760
762{
763 QImage origImg;
764 m_selectedPortionCache = previewDevice;
765
766 QTransform thumbToImageTransform;
767
768 const int maxSize = 2000;
769
770 QRect srcRect(m_transaction.originalRect().toAlignedRect());
771 int x, y, w, h;
772 srcRect.getRect(&x, &y, &w, &h);
773
775 if (w > maxSize || h > maxSize) {
776 qreal scale = qreal(maxSize) / (w > h ? w : h);
777 QTransform scaleTransform = QTransform::fromScale(scale, scale);
778
779 QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect();
780
781 QSize size = thumbRect.size();
782 if (size.width() < 1) size.setWidth(1);
783 if (size.height() < 1) size.setHeight(1);
784 KisPaintDeviceSP dev = m_selectedPortionCache->createThumbnailDeviceOversampled(size.width(), size.height(), 1, srcRect);
785 origImg = m_canvas->displayColorConverter()->convertImageToDisplayColorSpace(dev, QRect(QPoint(0, 0), size), true);
786
787 thumbToImageTransform = scaleTransform.inverted();
788
789 } else {
790 origImg = m_canvas->displayColorConverter()->convertImageToDisplayColorSpace(m_selectedPortionCache, QRect(x, y, w, h), true);
791 thumbToImageTransform = QTransform();
792 }
793 }
794
795 // init both strokes since the thumbnail is initialized only once
796 // during the stroke
797 m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform);
798 m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform);
799 m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform);
800 m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform);
801 m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform);
802 m_meshStrategy->setThumbnailImage(origImg, thumbToImageTransform);
803}
804
806{
807 m_externalSourceForNextActivation = externalSource;
808 if (isActive()) {
809 QSet<KoShape*> dummy;
810 deactivate();
811 activate(dummy);
812 } else {
813 KoToolManager::instance()->switchToolRequested("KisToolTransform");
814 }
815}
816
817void KisToolTransform::activate(const QSet<KoShape*> &shapes)
818{
819 KisTool::activate(shapes);
820
824
825 m_actionConnections.addConnection(action("movetool-move-up"), SIGNAL(triggered(bool)),
826 this, SLOT(slotMoveDiscreteUp()));
827 m_actionConnections.addConnection(action("movetool-move-up-more"), SIGNAL(triggered(bool)),
828 this, SLOT(slotMoveDiscreteUpMore()));
829 m_actionConnections.addConnection(action("movetool-move-down"), SIGNAL(triggered(bool)),
830 this, SLOT(slotMoveDiscreteDown()));
831 m_actionConnections.addConnection(action("movetool-move-down-more"), SIGNAL(triggered(bool)),
832 this, SLOT(slotMoveDiscreteDownMore()));
833 m_actionConnections.addConnection(action("movetool-move-left"), SIGNAL(triggered(bool)),
834 this, SLOT(slotMoveDiscreteLeft()));
835 m_actionConnections.addConnection(action("movetool-move-left-more"), SIGNAL(triggered(bool)),
836 this, SLOT(slotMoveDiscreteLeftMore()));
837 m_actionConnections.addConnection(action("movetool-move-right"), SIGNAL(triggered(bool)),
838 this, SLOT(slotMoveDiscreteRight()));
839 m_actionConnections.addConnection(action("movetool-move-right-more"), SIGNAL(triggered(bool)),
840 this, SLOT(slotMoveDiscreteRightMore()));
841 m_actionConnections.addConnection(action("increase_brush_size"),
842 SIGNAL(triggered()),
843 this,
844 SLOT(slotIncreaseBrushSize()));
845 m_actionConnections.addConnection(action("decrease_brush_size"),
846 SIGNAL(triggered()),
847 this,
848 SLOT(slotDecreaseBrushSize()));
849
850 if (currentNode()) {
852 }
853
856}
857
859{
860 endStroke();
861 m_canvas->updateCanvas();
864}
865
867{
868 if (!m_strokeId || m_transaction.rootNodes().isEmpty() || mode() != HOVER_MODE) return;
869
870 if (!m_changesTracker.canUndo()) {
871 cancelStroke();
872 } else {
874 }
875}
876
878{
879 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
880
883 }
884}
885
890
892{
893 if (m_transaction.rootNodes().isEmpty() || m_currentArgs.isIdentity()) {
894 cancelStroke();
895 } else {
897 }
898}
899
910
912{
913 Q_ASSERT(!m_strokeId);
914
917
918 // set up and null checks before we do anything
919 KisResourcesSnapshotSP resources =
920 new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager(), 0, selectedNodes(), 0);
921 KisNodeList rootNodes = resources->selectedNodes();
922 //Filter out any nodes that might be children of other selected nodes so they aren't used twice
925
928
929 Q_FOREACH (KisNodeSP currentNode, resources->selectedNodes()) {
930 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
931 KIS_ASSERT(kisCanvas);
932
933 if (!currentNode || !currentNode->isEditable()) {
935 kisCanvas->viewManager()->
936 showFloatingMessage(
937 i18nc("floating message in transformation tool",
938 "Cannot transform locked layers"),
939 koIcon("object-locked"), 4000, KisFloatingMessage::High);
940 } else if (currentNode && !currentNode->visible()) {
941 kisCanvas->viewManager()->
942 showFloatingMessage(
943 i18nc("floating message in transformation tool",
944 "Cannot transform hidden layers"),
945 koIcon("object-locked"), 4000, KisFloatingMessage::High);
946 } else {
947 kisCanvas->viewManager()->
948 showFloatingMessage(
949 i18nc("floating message in transformation tool",
950 "Cannot use transform tool on this set of layers"),
951 koIcon("object-locked"), 4000, KisFloatingMessage::High);
952 }
953
954 return;
955 }
956
957 // some layer types cannot be transformed. Give a message and return if a user tries it
958 if (currentNode->inherits("KisColorizeMask") ||
959 currentNode->inherits("KisFileLayer") ||
960 currentNode->inherits("KisCloneLayer")) {
961
962 if(currentNode->inherits("KisColorizeMask")){
963 kisCanvas->viewManager()->
964 showFloatingMessage(
965 i18nc("floating message in transformation tool",
966 "Layer type cannot use the transform tool"),
967 koIcon("object-locked"), 4000, KisFloatingMessage::High);
968 }
969 else{
970 kisCanvas->viewManager()->
971 showFloatingMessage(
972 i18nc("floating message in transformation tool",
973 "Layer type cannot use the transform tool. Use transform mask instead."),
974 koIcon("object-locked"), 4000, KisFloatingMessage::High);
975 }
976 return;
977 }
978
979 KisNodeSP impossibleMask =
981 [currentNode] (KisNodeSP node) {
982 // we can process transform masks of the first level
983 if (node == currentNode || node->parent() == currentNode) return false;
984
985 return node->inherits("KisTransformMask") && node->visible(true);
986 });
987
988 if (impossibleMask) {
989 kisCanvas->viewManager()->
990 showFloatingMessage(
991 i18nc("floating message in transformation tool",
992 "Layer has children with transform masks. Please disable them before doing transformation."),
993 koIcon("object-locked"), 8000, KisFloatingMessage::High);
994 return;
995 }
996
1001 if (selection && dynamic_cast<KisTransformMask*>(currentNode.data())) {
1002 kisCanvas->viewManager()->
1003 showFloatingMessage(
1004 i18nc("floating message in transformation tool",
1005 "Selections are not used when editing transform masks "),
1006 QIcon(), 4000, KisFloatingMessage::Low);
1007
1008 selection = 0;
1009 }
1010 }
1011 // Overlay preview is never used when transforming an externally provided image
1013
1014 KisStrokeStrategy *strategy = 0;
1015
1017 TransformStrokeStrategy *transformStrategy = new TransformStrokeStrategy(mode, m_currentArgs.filterId(), forceReset, rootNodes, selection, image().data(), image().data());
1018 connect(transformStrategy, SIGNAL(sigPreviewDeviceReady(KisPaintDeviceSP)), SLOT(slotPreviewDeviceGenerated(KisPaintDeviceSP)));
1019 connect(transformStrategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)));
1020 connect(transformStrategy, SIGNAL(sigConvexHullCalculated(QPolygon, void*)), SLOT(slotConvexHullCalculated(QPolygon, void*)));
1021 strategy = transformStrategy;
1022
1023 // save unique identifier of the stroke so we could
1024 // recognize it when sigTransactionGenerated() is
1025 // received (theoretically, the user can start two
1026 // strokes at the same time, if he is quick enough)
1027 m_strokeStrategyCookie = transformStrategy;
1028
1029 } else {
1030 InplaceTransformStrokeStrategy *transformStrategy = new InplaceTransformStrokeStrategy(mode, m_currentArgs.filterId(), forceReset, rootNodes, selection, externalSource, image().data(), image().data(), image()->root(), m_forceLodMode);
1031 connect(transformStrategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)));
1032 connect(transformStrategy, SIGNAL(sigConvexHullCalculated(QPolygon, void*)), SLOT(slotConvexHullCalculated(QPolygon, void*)));
1033 strategy = transformStrategy;
1034
1035 // save unique identifier of the stroke so we could
1036 // recognize it when sigTransactionGenerated() is
1037 // received (theoretically, the user can start two
1038 // strokes at the same time, if he is quick enough)
1039 m_strokeStrategyCookie = transformStrategy;
1040 }
1041
1042 m_strokeId = image()->startStroke(strategy);
1043
1046 }
1047
1049
1051}
1052
1077
1079{
1080 if (!m_strokeId || strokeStrategyCookie != m_strokeStrategyCookie) return;
1081
1082 if (transaction.transformedNodes().isEmpty() ||
1083 transaction.originalRect().isEmpty()) {
1084
1085 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1086 KIS_ASSERT(kisCanvas);
1087 kisCanvas->viewManager()->
1088 showFloatingMessage(
1089 i18nc("floating message in transformation tool",
1090 "Cannot transform empty layer "),
1091 QIcon(), 1000, KisFloatingMessage::Medium);
1092
1093 cancelStroke();
1094 return;
1095 }
1096
1097 m_transaction = transaction;
1098 m_currentArgs = args;
1100
1103 }
1104
1106 commitChanges();
1107
1109
1111 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1112 KIS_ASSERT(kisCanvas);
1113 kisCanvas->viewManager()->
1114 showFloatingMessage(
1115 i18nc("floating message in transformation tool",
1116 "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "),
1117 QIcon(), 4000, KisFloatingMessage::Low);
1118 }
1119}
1120
1122{
1123 if (device && device->exactBounds().isEmpty()) {
1124 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1125 KIS_SAFE_ASSERT_RECOVER(kisCanvas) { cancelStroke(); return; }
1126 kisCanvas->viewManager()->
1127 showFloatingMessage(
1128 i18nc("floating message in transformation tool",
1129 "Cannot transform empty layer "),
1130 QIcon(), 1000, KisFloatingMessage::Medium);
1131
1132 cancelStroke();
1133 } else {
1134 initThumbnailImage(device);
1136 }
1137}
1138
1154
1161
1163{
1164 const ToolTransformArgs *newArgs = dynamic_cast<const ToolTransformArgs*>(status.data());
1166
1167 *m_transaction.currentConfig() = *newArgs;
1168
1169 slotUiChangedConfig(true);
1171}
1172
1174{
1175 if (!m_canvas) return 0;
1176
1178 Q_CHECK_PTR(m_optionsWidget);
1179 m_optionsWidget->setObjectName(toolId() + " option widget");
1180
1181 // See https://bugs.kde.org/show_bug.cgi?id=316896
1182 QWidget *specialSpacer = new QWidget(m_optionsWidget);
1183 specialSpacer->setObjectName("SpecialSpacer");
1184 specialSpacer->setFixedSize(0, 0);
1185 m_optionsWidget->layout()->addWidget(specialSpacer);
1186
1187
1188 connect(m_optionsWidget, SIGNAL(sigConfigChanged(bool)),
1189 this, SLOT(slotUiChangedConfig(bool)));
1190
1191 connect(m_optionsWidget, SIGNAL(sigApplyTransform()),
1192 this, SLOT(slotApplyTransform()));
1193
1194 connect(m_optionsWidget, SIGNAL(sigResetTransform(ToolTransformArgs::TransformMode)),
1196
1197 connect(m_optionsWidget, SIGNAL(sigCancelTransform()),
1198 this, SLOT(slotCancelTransform()));
1199
1200 connect(m_optionsWidget, SIGNAL(sigRestartTransform()),
1201 this, SLOT(slotRestartTransform()));
1202
1203 connect(m_optionsWidget, SIGNAL(sigUpdateGlobalConfig()),
1204 this, SLOT(slotGlobalConfigChanged()));
1205
1206 connect(m_optionsWidget, SIGNAL(sigRestartAndContinueTransform()),
1207 this, SLOT(slotRestartAndContinueTransform()));
1208
1209 connect(m_optionsWidget, SIGNAL(sigEditingFinished()),
1210 this, SLOT(slotEditingFinished()));
1211
1212
1213 connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX()));
1214 connect(mirrorVerticalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY()));
1215 connect(rotateNinetyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW()));
1216 connect(rotateNinetyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW()));
1217
1218 connect(keepAspectRatioAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotSetKeepAspectRatio(bool)));
1219
1220
1221 connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType()));
1222 connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType()));
1223 connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType()));
1224 connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType()));
1225 connect(meshAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToMeshType()));
1226 connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType()));
1227
1228 connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform()));
1229 connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotCancelTransform()));
1230
1231
1233
1234 return m_optionsWidget;
1235}
1236
1238{
1239 if (!m_optionsWidget) return;
1240
1241 if (!currentNode()) {
1242 m_optionsWidget->setEnabled(false);
1243 return;
1244 }
1245 else {
1246 m_optionsWidget->setEnabled(true);
1248 }
1249}
1250
1257
1258void KisToolTransform::slotUiChangedConfig(bool needsPreviewRecalculation)
1259{
1260 if (mode() == KisTool::PAINT_MODE) return;
1261
1262 if (needsPreviewRecalculation) {
1264 }
1265
1268 }
1269
1272}
1273
1275{
1276 KisCursorOverrideLock cursorLock(KisCursor::waitCursor());
1277 endStroke();
1278}
1279
1281{
1283 const ToolTransformArgs::TransformMode previousMode = config->mode();
1284 config->setMode(mode);
1285
1287 config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID);
1288 }
1289
1290 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1291
1294
1304 const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs);
1305
1306 if (transformDiffers &&
1307 m_currentArgs.continuedTransform()->mode() == savedMode) {
1308
1312
1313 } else {
1314 cancelStroke();
1315 startStroke(savedMode, true);
1316
1318 }
1319 } else {
1325
1326 } else {
1327 cancelStroke();
1329
1330 }
1331 }
1332}
1333
1338
1340{
1341 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1342
1343 KisNodeSP root = m_transaction.rootNodes()[0];
1344 KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
1345
1347 cancelStroke();
1348 startStroke(savedArgs.mode(), true);
1349}
1350
1352{
1353 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1354
1355 KisNodeSP root = m_transaction.rootNodes()[0];
1356 KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
1357
1359 endStroke();
1360 startStroke(savedArgs.mode(), false);
1361}
1362
1367
1372
1377
1382
1387
1392
1397
1402
1407
1412
1417
1422
1427
1432
1437
1442
1447
1449{
1451}
1452
1454{
1456}
1457
1459{
1461}
1462
1464{
1466}
1467
1479
1491
1493{
1496
1497 actions << actionRegistry->makeQAction("movetool-move-up", this);
1498 actions << actionRegistry->makeQAction("movetool-move-down", this);
1499 actions << actionRegistry->makeQAction("movetool-move-left", this);
1500 actions << actionRegistry->makeQAction("movetool-move-right", this);
1501 actions << actionRegistry->makeQAction("movetool-move-up-more", this);
1502 actions << actionRegistry->makeQAction("movetool-move-down-more", this);
1503 actions << actionRegistry->makeQAction("movetool-move-left-more", this);
1504 actions << actionRegistry->makeQAction("movetool-move-right-more", this);
1505
1506 auto makeSubtoolAction = [&actionRegistry, &actions, this](QString actionName, const char *slot) {
1507 QAction *action = actionRegistry->makeQAction(actionName, this);
1508 action->setProperty("always_enabled", true); // To allow this action to be triggered when the transform tool isn't already active
1509 connect(action, SIGNAL(triggered()), slot);
1510 actions << action;
1511 };
1512 makeSubtoolAction("KisToolTransformFree", SLOT(activateSubtoolFree()));
1513 makeSubtoolAction("KisToolTransformPerspective", SLOT(activateSubtoolPerspective()));
1514 makeSubtoolAction("KisToolTransformWarp", SLOT(activateSubtoolWarp()));
1515 makeSubtoolAction("KisToolTransformCage", SLOT(activateSubtoolCage()));
1516 makeSubtoolAction("KisToolTransformLiquify", SLOT(activateSubtoolLiquify()));
1517 makeSubtoolAction("KisToolTransformMesh", SLOT(activateSubtoolMesh()));
1518
1519 return actions;
1520}
1521
1523{
1524 KoToolManager *toolManager = KoToolManager::instance();
1525
1526 KoCanvasController *canvasController = toolManager->activeCanvasController();
1527 if (!canvasController) return;
1528 KoCanvasBase *canvas = canvasController->canvas();
1529 if (!canvas) return;
1530
1531 KoToolBase *tool = toolManager->toolById(canvas, id());
1533 KisToolTransform *transformTool = dynamic_cast<KisToolTransform*>(tool);
1534 KIS_SAFE_ASSERT_RECOVER_RETURN(transformTool);
1535
1536 if (toolManager->activeToolId() == id()) {
1537 // Transform tool is already active, switch the current mode
1538 transformTool->setTransformMode(mode);
1539 } else {
1540 // Works like KoToolFactoryBase::activateTool, but tells the tool beforehand which initial transform mode to use
1541 transformTool->setNextActivationTransformMode(mode);
1542 toolManager->switchToolRequested(id());
1543 }
1544}
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