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>
60
61#include "kis_action_registry.h"
62
64
65#include "kis_transform_utils.h"
72
73#include "kis_transform_mask.h"
75
77#include "kis_layer_utils.h"
79#include "kis_config_notifier.h"
80
83
85 : KisTool(canvas, KisCursor::rotateCursor())
86 , m_warpStrategy(
88 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
89 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
90 m_currentArgs, m_transaction))
91 , m_cageStrategy(
93 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
94 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
95 m_currentArgs, m_transaction))
96 , m_liquifyStrategy(
98 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
99 m_currentArgs, m_transaction, canvas->resourceManager()))
100 , m_meshStrategy(
102 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
103 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
104 m_currentArgs, m_transaction))
105 , m_freeStrategy(
107 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
108 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
109 m_currentArgs, m_transaction))
110 , m_perspectiveStrategy(
112 dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
113 dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
114 m_currentArgs, m_transaction))
115{
116 m_canvas = dynamic_cast<KisCanvas2*>(canvas);
117 Q_ASSERT(m_canvas);
118
119 setObjectName("tool_transform");
120 m_optionsWidget = 0;
121
122 warpAction = new KisAction(i18nc("Warp Transform Tab Label", "Warp"));
123 liquifyAction = new KisAction(i18nc("Liquify Transform Tab Label", "Liquify"));
124 meshAction = new KisAction(i18nc("Mesh Transform Tab Label", "Mesh"));
125 cageAction = new KisAction(i18nc("Cage Transform Tab Label", "Cage"));
126 freeTransformAction = new KisAction(i18nc("Free Transform Tab Label", "Free"));
127 perspectiveAction = new KisAction(i18nc("Perspective Transform Tab Label", "Perspective"));
128
129 // extra actions for free transform that are in the tool options
130 mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal"));
131 mirrorVerticalAction = new KisAction(i18n("Mirror Vertical"));
132 rotateNinetyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise"));
133 rotateNinetyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise"));
134
135 keepAspectRatioAction = new KisAction(i18n("Keep Aspect Ratio"));
136 keepAspectRatioAction->setCheckable(true);
137 keepAspectRatioAction->setChecked(false);
138
139 applyTransformation = new KisAction(i18n("Apply"));
140 resetTransformation = new KisAction(i18n("Reset"));
141
142 m_contextMenu.reset(new QMenu());
143
144 connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
145 connect(m_warpStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
146 connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
147 connect(m_cageStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
148 connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
149 connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(QPointF)), SLOT(cursorOutlineUpdateRequested(QPointF)));
150 connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget()));
152 connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
153 connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested()));
154 connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
155 connect(m_freeStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
156 connect(m_freeStrategy.data(), SIGNAL(requestConvexHullCalculation()), SLOT(convexHullCalculationRequested()));
157 connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
158 connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
160 connect(m_meshStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
161 connect(m_meshStrategy.data(), SIGNAL(requestImageRecalculation()), SLOT(requestImageRecalculation()));
162
163 connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)),
165
166 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotGlobalConfigChanged()));
167}
168
170{
171 cancelStroke();
172
173 delete warpAction;
174 delete meshAction;
175 delete liquifyAction;
176 delete cageAction;
177 delete freeTransformAction;
178 delete perspectiveAction;
179 delete applyTransformation;
180 delete resetTransformation;
186}
187
189{
190 Q_EMIT freeTransformChanged();
191 m_canvas->updateCanvas();
192}
193
195{
196 m_canvas->updateCanvas();
197}
198
203
205{
206 KConfigGroup group = KSharedConfig::openConfig()->group(toolId());
207 m_preferOverlayPreviewStyle = group.readEntry("useOverlayPreviewStyle", false);
208 m_forceLodMode = group.readEntry("forceLodMode", true);
209}
210
216
222
245void KisToolTransform::slotConvexHullCalculated(QPolygon hull, void *strokeStrategyCookie)
246{
247 if (!m_strokeId || strokeStrategyCookie != m_strokeStrategyCookie) return;
248 QPolygonF hullF = hull;
255 if (hullF.boundingRect() == m_transaction.originalRect()) {
259 } else {
260 warnTools << "WARNING: KisToolTransform: calculated convex hull's bounds "
261 "differ from the bounding rect of the source clip. It shouldn't "
262 "have happened";
263 }
264}
265
267{
269 return m_freeStrategy.data();
271 return m_warpStrategy.data();
273 return m_cageStrategy.data();
275 return m_liquifyStrategy.data();
277 return m_meshStrategy.data();
278 } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ {
279 return m_perspectiveStrategy.data();
280 }
281}
282
283void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter)
284{
285 Q_UNUSED(converter);
286
287 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
288
289 QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0));
290 if (m_refRect != newRefRect) {
291 m_refRect = newRefRect;
293 }
295 currentStrategy()->paint(gc);
296
297
298 if (!m_cursorOutline.isEmpty()) {
299 QPainterPath mappedOutline =
301 m_canvas->coordinatesConverter()).map(m_cursorOutline);
302 paintToolOutline(&gc, mappedOutline);
303 }
304}
305
307{
309 return;
310 }
311
312 if (!m_strokeId) {
314 } else if (m_strokeId && m_transaction.rootNodes().isEmpty()) {
315 // we are in the middle of stroke initialization
317 } else {
318 useCursor(currentStrategy()->getCurrentCursor());
319 }
320}
321
323{
324 QRect canvasUpdateRect;
325
326 if (!m_cursorOutline.isEmpty()) {
327 canvasUpdateRect = m_canvas->coordinatesConverter()->
328 imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
329 }
330
332 getCursorOutline().translated(imagePos);
333
334 if (!m_cursorOutline.isEmpty()) {
335 canvasUpdateRect |=
336 m_canvas->coordinatesConverter()->
337 imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
338 }
339
340 if (!canvasUpdateRect.isEmpty()) {
341 // grow rect a bit to follow interpolation fuzziness
342 canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2);
343 m_canvas->updateCanvas(canvasUpdateRect);
344 }
345}
346
348{
349 if (!nodeEditable()) {
350 event->ignore();
351 return;
352 }
353
354 if (!m_strokeId) {
356 } else if (!m_transaction.rootNodes().isEmpty()) {
357 bool result = false;
358
359 if (usePrimaryAction) {
360 result = currentStrategy()->beginPrimaryAction(event);
361 } else {
362 result = currentStrategy()->beginAlternateAction(event, action);
363 }
364
365 if (result) {
367 }
368 }
369
371
373}
374
376{
377 if (mode() != KisTool::PAINT_MODE) return;
378 if (m_transaction.rootNodes().isEmpty()) return;
379
381
382 if (usePrimaryAction) {
384 } else {
386 }
387
390}
391
393{
394 if (mode() != KisTool::PAINT_MODE) return;
395
397
399 currentStrategy()->acceptsClicks()) {
400
401 bool result = false;
402
403 if (usePrimaryAction) {
404 result = currentStrategy()->endPrimaryAction(event);
405 } else {
406 result = currentStrategy()->endAlternateAction(event, action);
407 }
408
409 if (result) {
411 }
412
414 }
415
418}
419
421{
422 if (m_contextMenu) {
423 m_contextMenu->clear();
424
425 m_contextMenu->addSection(i18n("Transform Tool Actions"));
426 // add a quick switch to different transform types
429 m_contextMenu->addAction(warpAction);
430 m_contextMenu->addAction(cageAction);
431 m_contextMenu->addAction(liquifyAction);
432 m_contextMenu->addAction(meshAction);
433
434 // extra options if free transform is selected
436 m_contextMenu->addSeparator();
441
442 m_contextMenu->addSeparator();
445 }
446
447 m_contextMenu->addSeparator();
450 }
451
452 return m_contextMenu.data();
453}
454
459
464
469
475
480
486
491
496
501
506
508{
509 // When using touch drawing, we only ever receive move events after the
510 // finger has pressed down. This confuses the strategies greatly, since they
511 // expect to receive a hover to tell which anchor the user wants to
512 // manipulate or similar. So in this case, we send an artificial hover.
513 if (event->isTouchEvent() && this->mode() != KisTool::PAINT_MODE) {
516 }
518}
519
521{
522 QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point);
523
525
526 if (this->mode() != KisTool::PAINT_MODE) {
530 return;
531 }
532}
533
538
543
548
550{
552
553 switch (m_currentArgs.mode())
554 {
557 break;
560 break;
563 break;
566 break;
569 break;
572 break;
573 default:
574 KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
575 }
576
577 return mode;
578}
579
581{
582 return m_currentArgs.transformedCenter().x();
583}
584
586{
587 return m_currentArgs.transformedCenter().y();
588}
589
591{
592 return m_currentArgs.aX();
593}
594
596{
597 return m_currentArgs.aY();
598}
599
601{
602 return m_currentArgs.aZ();
603}
604
606{
607 return m_currentArgs.scaleX();
608}
609
611{
612 return m_currentArgs.scaleY();
613}
614
616{
617 return m_currentArgs.shearX();
618}
619
621{
622 return m_currentArgs.shearY();
623}
624
638
640{
641 return m_currentArgs.alpha();
642}
643
648
678
680{
682
683 if( mode != m_currentArgs.mode() ) {
684 if( newMode == FreeTransformMode ) {
686 } else if( newMode == WarpTransformMode ) {
688 } else if( newMode == CageTransformMode ) {
690 } else if( newMode == LiquifyTransformMode ) {
692 } else if( newMode == PerspectiveTransformMode ) {
694 } else if( newMode == MeshTransformMode ) {
696 }
697
698 Q_EMIT transformModeChanged();
699 }
700}
701
702void KisToolTransform::setRotateX( double rotation )
703{
704 m_currentArgs.setAX( rotation );
705}
706
707void KisToolTransform::setRotateY( double rotation )
708{
709 m_currentArgs.setAY( rotation );
710}
711
712void KisToolTransform::setRotateZ( double rotation )
713{
714 m_currentArgs.setAZ( rotation );
715}
716
733
734void KisToolTransform::setWarpFlexibility( double flexibility )
735{
736 m_currentArgs.setAlpha( flexibility );
737}
738
743
749
758
760{
761 QImage origImg;
762 m_selectedPortionCache = previewDevice;
763
764 QTransform thumbToImageTransform;
765
766 const int maxSize = 2000;
767
768 QRect srcRect(m_transaction.originalRect().toAlignedRect());
769 int x, y, w, h;
770 srcRect.getRect(&x, &y, &w, &h);
771
773 if (w > maxSize || h > maxSize) {
774 qreal scale = qreal(maxSize) / (w > h ? w : h);
775 QTransform scaleTransform = QTransform::fromScale(scale, scale);
776
777 QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect();
778
779 origImg = m_selectedPortionCache->
780 createThumbnail(thumbRect.width(),
781 thumbRect.height(),
782 srcRect, 1,
785 thumbToImageTransform = scaleTransform.inverted();
786
787 } else {
788 origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h,
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
842 if (currentNode()) {
844 }
845
848}
849
851{
852 endStroke();
853 m_canvas->updateCanvas();
856}
857
859{
860 if (!m_strokeId || m_transaction.rootNodes().isEmpty() || mode() != HOVER_MODE) return;
861
862 if (!m_changesTracker.canUndo()) {
863 cancelStroke();
864 } else {
866 }
867}
868
870{
871 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
872
875 }
876}
877
882
884{
885 if (m_transaction.rootNodes().isEmpty() || m_currentArgs.isIdentity()) {
886 cancelStroke();
887 } else {
889 }
890}
891
902
904{
905 Q_ASSERT(!m_strokeId);
906
909
910 // set up and null checks before we do anything
911 KisResourcesSnapshotSP resources =
912 new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager(), 0, selectedNodes(), 0);
913 KisNodeList rootNodes = resources->selectedNodes();
914 //Filter out any nodes that might be children of other selected nodes so they aren't used twice
917
920
921 Q_FOREACH (KisNodeSP currentNode, resources->selectedNodes()) {
922 if (!currentNode || !currentNode->isEditable()) return;
923
924 // some layer types cannot be transformed. Give a message and return if a user tries it
925 if (currentNode->inherits("KisColorizeMask") ||
926 currentNode->inherits("KisFileLayer") ||
927 currentNode->inherits("KisCloneLayer")) {
928
929 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
930 KIS_ASSERT(kisCanvas);
931
932 if(currentNode->inherits("KisColorizeMask")){
933 kisCanvas->viewManager()->
934 showFloatingMessage(
935 i18nc("floating message in transformation tool",
936 "Layer type cannot use the transform tool"),
937 koIcon("object-locked"), 4000, KisFloatingMessage::High);
938 }
939 else{
940 kisCanvas->viewManager()->
941 showFloatingMessage(
942 i18nc("floating message in transformation tool",
943 "Layer type cannot use the transform tool. Use transform mask instead."),
944 koIcon("object-locked"), 4000, KisFloatingMessage::High);
945 }
946 return;
947 }
948
949 KisNodeSP impossibleMask =
951 [currentNode] (KisNodeSP node) {
952 // we can process transform masks of the first level
953 if (node == currentNode || node->parent() == currentNode) return false;
954
955 return node->inherits("KisTransformMask") && node->visible(true);
956 });
957
958 if (impossibleMask) {
959 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
960 kisCanvas->viewManager()->
961 showFloatingMessage(
962 i18nc("floating message in transformation tool",
963 "Layer has children with transform masks. Please disable them before doing transformation."),
964 koIcon("object-locked"), 8000, KisFloatingMessage::High);
965 return;
966 }
967
972 if (selection && dynamic_cast<KisTransformMask*>(currentNode.data())) {
973 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
974 kisCanvas->viewManager()->
975 showFloatingMessage(
976 i18nc("floating message in transformation tool",
977 "Selections are not used when editing transform masks "),
978 QIcon(), 4000, KisFloatingMessage::Low);
979
980 selection = 0;
981 }
982 }
983 // Overlay preview is never used when transforming an externally provided image
985
986 KisStrokeStrategy *strategy = 0;
987
989 TransformStrokeStrategy *transformStrategy = new TransformStrokeStrategy(mode, m_currentArgs.filterId(), forceReset, rootNodes, selection, image().data(), image().data());
990 connect(transformStrategy, SIGNAL(sigPreviewDeviceReady(KisPaintDeviceSP)), SLOT(slotPreviewDeviceGenerated(KisPaintDeviceSP)));
991 connect(transformStrategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)));
992 connect(transformStrategy, SIGNAL(sigConvexHullCalculated(QPolygon, void*)), SLOT(slotConvexHullCalculated(QPolygon, void*)));
993 strategy = transformStrategy;
994
995 // save unique identifier of the stroke so we could
996 // recognize it when sigTransactionGenerated() is
997 // received (theoretically, the user can start two
998 // strokes at the same time, if he is quick enough)
999 m_strokeStrategyCookie = transformStrategy;
1000
1001 } else {
1002 InplaceTransformStrokeStrategy *transformStrategy = new InplaceTransformStrokeStrategy(mode, m_currentArgs.filterId(), forceReset, rootNodes, selection, externalSource, image().data(), image().data(), image()->root(), m_forceLodMode);
1003 connect(transformStrategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)));
1004 connect(transformStrategy, SIGNAL(sigConvexHullCalculated(QPolygon, void*)), SLOT(slotConvexHullCalculated(QPolygon, void*)));
1005 strategy = transformStrategy;
1006
1007 // save unique identifier of the stroke so we could
1008 // recognize it when sigTransactionGenerated() is
1009 // received (theoretically, the user can start two
1010 // strokes at the same time, if he is quick enough)
1011 m_strokeStrategyCookie = transformStrategy;
1012 }
1013
1014 m_strokeId = image()->startStroke(strategy);
1015
1018 }
1019
1021
1023}
1024
1049
1051{
1052 if (!m_strokeId || strokeStrategyCookie != m_strokeStrategyCookie) return;
1053
1054 if (transaction.transformedNodes().isEmpty() ||
1055 transaction.originalRect().isEmpty()) {
1056
1057 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1058 KIS_ASSERT(kisCanvas);
1059 kisCanvas->viewManager()->
1060 showFloatingMessage(
1061 i18nc("floating message in transformation tool",
1062 "Cannot transform empty layer "),
1063 QIcon(), 1000, KisFloatingMessage::Medium);
1064
1065 cancelStroke();
1066 return;
1067 }
1068
1069 m_transaction = transaction;
1070 m_currentArgs = args;
1072
1075 }
1076
1078 commitChanges();
1079
1081
1083 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1084 KIS_ASSERT(kisCanvas);
1085 kisCanvas->viewManager()->
1086 showFloatingMessage(
1087 i18nc("floating message in transformation tool",
1088 "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "),
1089 QIcon(), 4000, KisFloatingMessage::Low);
1090 }
1091}
1092
1094{
1095 if (device && device->exactBounds().isEmpty()) {
1096 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
1097 KIS_SAFE_ASSERT_RECOVER(kisCanvas) { cancelStroke(); return; }
1098 kisCanvas->viewManager()->
1099 showFloatingMessage(
1100 i18nc("floating message in transformation tool",
1101 "Cannot transform empty layer "),
1102 QIcon(), 1000, KisFloatingMessage::Medium);
1103
1104 cancelStroke();
1105 } else {
1106 initThumbnailImage(device);
1108 }
1109}
1110
1126
1133
1135{
1136 const ToolTransformArgs *newArgs = dynamic_cast<const ToolTransformArgs*>(status.data());
1138
1139 *m_transaction.currentConfig() = *newArgs;
1140
1141 slotUiChangedConfig(true);
1143}
1144
1146{
1147 if (!m_canvas) return 0;
1148
1150 Q_CHECK_PTR(m_optionsWidget);
1151 m_optionsWidget->setObjectName(toolId() + " option widget");
1152
1153 // See https://bugs.kde.org/show_bug.cgi?id=316896
1154 QWidget *specialSpacer = new QWidget(m_optionsWidget);
1155 specialSpacer->setObjectName("SpecialSpacer");
1156 specialSpacer->setFixedSize(0, 0);
1157 m_optionsWidget->layout()->addWidget(specialSpacer);
1158
1159
1160 connect(m_optionsWidget, SIGNAL(sigConfigChanged(bool)),
1161 this, SLOT(slotUiChangedConfig(bool)));
1162
1163 connect(m_optionsWidget, SIGNAL(sigApplyTransform()),
1164 this, SLOT(slotApplyTransform()));
1165
1166 connect(m_optionsWidget, SIGNAL(sigResetTransform(ToolTransformArgs::TransformMode)),
1168
1169 connect(m_optionsWidget, SIGNAL(sigCancelTransform()),
1170 this, SLOT(slotCancelTransform()));
1171
1172 connect(m_optionsWidget, SIGNAL(sigRestartTransform()),
1173 this, SLOT(slotRestartTransform()));
1174
1175 connect(m_optionsWidget, SIGNAL(sigUpdateGlobalConfig()),
1176 this, SLOT(slotGlobalConfigChanged()));
1177
1178 connect(m_optionsWidget, SIGNAL(sigRestartAndContinueTransform()),
1179 this, SLOT(slotRestartAndContinueTransform()));
1180
1181 connect(m_optionsWidget, SIGNAL(sigEditingFinished()),
1182 this, SLOT(slotEditingFinished()));
1183
1184
1185 connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX()));
1186 connect(mirrorVerticalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY()));
1187 connect(rotateNinetyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW()));
1188 connect(rotateNinetyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW()));
1189
1190 connect(keepAspectRatioAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotSetKeepAspectRatio(bool)));
1191
1192
1193 connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType()));
1194 connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType()));
1195 connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType()));
1196 connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType()));
1197 connect(meshAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToMeshType()));
1198 connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType()));
1199
1200 connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform()));
1201 connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotCancelTransform()));
1202
1203
1205
1206 return m_optionsWidget;
1207}
1208
1210{
1211 if (!m_optionsWidget) return;
1212
1213 if (!currentNode()) {
1214 m_optionsWidget->setEnabled(false);
1215 return;
1216 }
1217 else {
1218 m_optionsWidget->setEnabled(true);
1220 }
1221}
1222
1229
1230void KisToolTransform::slotUiChangedConfig(bool needsPreviewRecalculation)
1231{
1232 if (mode() == KisTool::PAINT_MODE) return;
1233
1234 if (needsPreviewRecalculation) {
1236 }
1237
1240 }
1241
1244}
1245
1247{
1248 KisCursorOverrideLock cursorLock(KisCursor::waitCursor());
1249 endStroke();
1250}
1251
1253{
1255 const ToolTransformArgs::TransformMode previousMode = config->mode();
1256 config->setMode(mode);
1257
1259 config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID);
1260 }
1261
1262 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1263
1266
1276 const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs);
1277
1278 if (transformDiffers &&
1279 m_currentArgs.continuedTransform()->mode() == savedMode) {
1280
1284
1285 } else {
1286 cancelStroke();
1287 startStroke(savedMode, true);
1288
1290 }
1291 } else {
1297
1298 } else {
1299 cancelStroke();
1301
1302 }
1303 }
1304}
1305
1310
1312{
1313 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1314
1315 KisNodeSP root = m_transaction.rootNodes()[0];
1316 KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
1317
1319 cancelStroke();
1320 startStroke(savedArgs.mode(), true);
1321}
1322
1324{
1325 if (!m_strokeId || m_transaction.rootNodes().isEmpty()) return;
1326
1327 KisNodeSP root = m_transaction.rootNodes()[0];
1328 KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
1329
1331 endStroke();
1332 startStroke(savedArgs.mode(), false);
1333}
1334
1339
1344
1349
1354
1359
1364
1369
1374
1379
1384
1389
1394
1399
1404
1409
1411{
1413}
1414
1416{
1418}
1419
1421{
1423}
1424
1426{
1428}
1429
1441
1453
1455{
1458
1459 actions << actionRegistry->makeQAction("movetool-move-up", this);
1460 actions << actionRegistry->makeQAction("movetool-move-down", this);
1461 actions << actionRegistry->makeQAction("movetool-move-left", this);
1462 actions << actionRegistry->makeQAction("movetool-move-right", this);
1463 actions << actionRegistry->makeQAction("movetool-move-up-more", this);
1464 actions << actionRegistry->makeQAction("movetool-move-down-more", this);
1465 actions << actionRegistry->makeQAction("movetool-move-left-more", this);
1466 actions << actionRegistry->makeQAction("movetool-move-right-more", this);
1467
1468 auto makeSubtoolAction = [&actionRegistry, &actions, this](QString actionName, const char *slot) {
1469 QAction *action = actionRegistry->makeQAction(actionName, this);
1470 action->setProperty("always_enabled", true); // To allow this action to be triggered when the transform tool isn't already active
1471 connect(action, SIGNAL(triggered()), slot);
1472 actions << action;
1473 };
1474 makeSubtoolAction("KisToolTransformFree", SLOT(activateSubtoolFree()));
1475 makeSubtoolAction("KisToolTransformPerspective", SLOT(activateSubtoolPerspective()));
1476 makeSubtoolAction("KisToolTransformWarp", SLOT(activateSubtoolWarp()));
1477 makeSubtoolAction("KisToolTransformCage", SLOT(activateSubtoolCage()));
1478 makeSubtoolAction("KisToolTransformLiquify", SLOT(activateSubtoolLiquify()));
1479 makeSubtoolAction("KisToolTransformMesh", SLOT(activateSubtoolMesh()));
1480
1481 return actions;
1482}
1483
1485{
1486 KoToolManager *toolManager = KoToolManager::instance();
1487
1488 KoCanvasController *canvasController = toolManager->activeCanvasController();
1489 if (!canvasController) return;
1490 KoCanvasBase *canvas = canvasController->canvas();
1491 if (!canvas) return;
1492
1493 KoToolBase *tool = toolManager->toolById(canvas, id());
1495 KisToolTransform *transformTool = dynamic_cast<KisToolTransform*>(tool);
1496 KIS_SAFE_ASSERT_RECOVER_RETURN(transformTool);
1497
1498 if (toolManager->activeToolId() == id()) {
1499 // Transform tool is already active, switch the current mode
1500 transformTool->setTransformMode(mode);
1501 } else {
1502 // Works like KoToolFactoryBase::activateTool, but tells the tool beforehand which initial transform mode to use
1503 transformTool->setNextActivationTransformMode(mode);
1504 toolManager->switchToolRequested(id());
1505 }
1506}
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
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)
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 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)
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
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