Krita Source Code Documentation
Loading...
Searching...
No Matches
KisPasteActionFactories.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include "kis_config.h"
10#include "kis_image.h"
11#include "KisViewManager.h"
12#include "kis_tool_proxy.h"
13#include "kis_canvas2.h"
15#include "kis_group_layer.h"
16#include "kis_paint_device.h"
17#include "kis_paint_layer.h"
18#include "kis_shape_layer.h"
19#include "kis_import_catcher.h"
20#include "kis_clipboard.h"
21#include "kis_selection.h"
26#include "kis_node_manager.h"
27
28#include <KoDocumentInfo.h>
29#include <KoSvgPaste.h>
30#include <KoShapeController.h>
32#include <KoShapeManager.h>
33#include <KoSelection.h>
35#include "kis_algebra_2d.h"
36#include <KoShapeMoveCommand.h>
38#include "kis_time_span.h"
41#include "kis_painter.h"
42#include <KisPart.h>
43#include <KisDocument.h>
47#include <KoShapeBackground.h>
48#include <KoShapeStroke.h>
49#include <KisMainWindow.h>
50#include <QApplication>
51#include <QClipboard>
52#include <kis_tool_utils.h>
53
54namespace {
55QPointF getFittingOffset(QList<KoShape*> shapes,
56 const QPointF &shapesOffset,
57 const QRectF &documentRect,
58 const qreal fitRatio)
59{
60 QPointF accumulatedFitOffset;
61
62 Q_FOREACH (KoShape *shape, shapes) {
63 const QRectF bounds = shape->boundingRect();
64
65 const QPointF center = bounds.center() + shapesOffset;
66
67 const qreal wMargin = (0.5 - fitRatio) * bounds.width();
68 const qreal hMargin = (0.5 - fitRatio) * bounds.height();
69 const QRectF allowedRect = documentRect.adjusted(-wMargin, -hMargin, wMargin, hMargin);
70
71 const QPointF fittedCenter = KisAlgebra2D::clampPoint(center, allowedRect);
72
73 accumulatedFitOffset += fittedCenter - center;
74 }
75
76 return accumulatedFitOffset;
77}
78
79bool tryPasteShapes(KisPasteActionFactory::Flags flags, KisViewManager *view)
80{
81 bool result = false;
82
84
85 if (paste.hasShapes() && view->activeNode()->isEditable()) {
86 KoCanvasBase *canvas = view->canvasBase();
87
88 QSizeF fragmentSize;
89 QList<KoShape*> shapes =
90 paste.fetchShapes(canvas->shapeController()->documentRectInPixels(),
91 canvas->shapeController()->pixelsPerInch(), &fragmentSize);
92
93 if (!shapes.isEmpty()) {
94 KoShapeManager *shapeManager = canvas->shapeManager();
95 shapeManager->selection()->deselectAll();
96
97 // adjust z-index of the shapes so that they would be
98 // pasted on the top of the stack
99 QList<KoShape*> topLevelShapes = shapeManager->topLevelShapes();
100 auto it = std::max_element(topLevelShapes.constBegin(), topLevelShapes.constEnd(), KoShape::compareShapeZIndex);
101 if (it != topLevelShapes.constEnd()) {
102 const int zIndexOffset = (*it)->zIndex();
103
104 std::stable_sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
105
107 std::transform(shapes.constBegin(), shapes.constEnd(),
108 std::back_inserter(indexedShapes),
109 [zIndexOffset] (KoShape *shape) {
110 KoShapeReorderCommand::IndexedShape indexedShape(shape);
111 indexedShape.zIndex += zIndexOffset;
112 return indexedShape;
113 });
114
115 indexedShapes = KoShapeReorderCommand::homogenizeZIndexesLazy(indexedShapes);
116
117 KoShapeReorderCommand cmd(indexedShapes);
118 cmd.redo();
119 }
120
121 KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Paste shapes"));
122
123 const bool forceCreateNewLayer = flags.testFlag(KisPasteActionFactory::ForceNewLayer);
124 KoShapeContainer *parentLayer = nullptr;
125
126 if (forceCreateNewLayer) {
127 parentLayer = canvas->shapeController()->documentBase()->createParentForShapes(shapes, true, parentCommand);
128 }
129
130 canvas->shapeController()->addShapesDirect(shapes, parentLayer, parentCommand);
131
132 QPointF finalShapesOffset;
133
134
135 if (flags.testFlag(KisPasteActionFactory::PasteAtCursor)) {
136 QRectF boundingRect = KoShape::boundingRect(shapes);
137 const QPointF cursorPos = canvas->canvasController()->currentCursorPosition();
138 finalShapesOffset = cursorPos - boundingRect.center();
139
140 } else if (!forceCreateNewLayer) {
141 bool foundOverlapping = false;
142
143 QRectF boundingRect = KoShape::boundingRect(shapes);
144 const QPointF offsetStep = 0.1 * QPointF(boundingRect.width(), boundingRect.height());
145
146 QPointF offset;
147
148 Q_FOREACH (KoShape *shape, shapes) {
149 QRectF br1 = shape->boundingRect();
150
151 bool hasOverlappingShape = false;
152
153 do {
154 hasOverlappingShape = false;
155
156 // we cannot use shapesAt() here, because the groups are not
157 // handled in the shape manager's tree
158 QList<KoShape*> conflicts = shapeManager->shapes();
159
160 Q_FOREACH (KoShape *intersectedShape, conflicts) {
161 if (intersectedShape == shape) continue;
162
163 QRectF br2 = intersectedShape->boundingRect();
164
165 const qreal tolerance = 2.0; /* pt */
166 if (KisAlgebra2D::fuzzyCompareRects(br1, br2, tolerance)) {
167 br1.translate(offsetStep.x(), offsetStep.y());
168 offset += offsetStep;
169
170 hasOverlappingShape = true;
171 foundOverlapping = true;
172 break;
173 }
174 }
175 } while (hasOverlappingShape);
176
177 if (foundOverlapping) break;
178 }
179
180 if (foundOverlapping) {
181 finalShapesOffset = offset;
182 }
183 }
184
185 const QRectF documentRect = canvas->shapeController()->documentRect();
186
187 if (!forceCreateNewLayer) {
188 finalShapesOffset += getFittingOffset(shapes, finalShapesOffset, documentRect, 0.1);
189 }
190
191 if (!finalShapesOffset.isNull()) {
192 new KoShapeMoveCommand(shapes, finalShapesOffset, parentCommand);
193 }
194
195 canvas->addCommand(parentCommand);
196
197 Q_FOREACH (KoShape *shape, shapes) {
198 canvas->selectedShapesProxy()->selection()->select(shape);
199 }
200
201 result = true;
202 }
203 }
204
205 return result;
206}
207
208}
209
211{
212 const bool pasteAtCursorPosition = flags.testFlag(PasteAtCursor);
213
214 KisImageSP image = view->image();
215 if (!image) return;
216
217 const QPointF docPos = view->canvasBase()->canvasController()->currentCursorPosition();
218
219 // Activate the current tool's paste functionality
220 if (view->canvasBase()->toolProxy()->paste()) {
221 // XXX: "Add saving of XML data for Paste of shapes"
222 return;
223 }
224
225 // Paste shapes
226 if (tryPasteShapes(flags, view)) {
227 return;
228 }
229
230 // If no shapes, check for layers
231 if (KisClipboard::instance()->hasLayers()) {
232 const std::optional<QPoint> preferredClipCenter = pasteAtCursorPosition
233 ? std::make_optional(view->canvasBase()->coordinatesConverter()->documentToImage(docPos).toPoint())
234 : std::nullopt;
235
236 if (view->selection()) {
239 KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Add Layer"));
240 KUndo2Command *deselectCmd = new KisDeselectActiveSelectionCommand(view->selection(), image);
241 ap->applyCommand(deselectCmd);
242 view->nodeManager()->pasteLayersFromClipboard(preferredClipCenter,
243 ap);
244 endAction(ap, KisOperationConfiguration(id()).toXML());
245 } else {
246 view->nodeManager()->pasteLayersFromClipboard(preferredClipCenter);
247 }
248
249 return;
250 }
251
253 if (clipImage) {
254 KisPaintDeviceSP clip = new KisPaintDevice(*clipImage->projection());
255
256 if (pasteAtCursorPosition) {
257 const QPoint imagePos = view->canvasBase()->coordinatesConverter()->documentToImage(docPos).toPoint();
258 KisToolUtils::recenterClip(clip, imagePos);
259 }
260
262 bool renamePastedLayers = KisConfig(true).renamePastedLayers();
263 QString pastedLayerName = renamePastedLayers ? image->nextLayerName() + " " + i18n("(pasted)") :
264 image->nextLayerName();
265 KisPaintLayerSP newLayer = new KisPaintLayer(image.data(),
266 pastedLayerName,
268 KisNodeSP aboveNode = view->activeLayer();
269 KisNodeSP parentNode = aboveNode ? aboveNode->parent() : image->root();
270
271 const QRect rc = clip->extent();
272 KisPainter::copyAreaOptimized(rc.topLeft(), clip, newLayer->paintDevice(), rc);
273
274 KUndo2Command *cmd = new KisImageLayerAddCommand(image, newLayer, parentNode, aboveNode);
275 KisProcessingApplicator *ap = beginAction(view, cmd->text());
277
278 if (view->selection()) {
279 KUndo2Command *deselectCmd = new KisDeselectActiveSelectionCommand(view->selection(), image);
281 }
282
283 if (KisConfig(true).activateTransformToolAfterPaste()) {
284 KUndo2Command *transformToolCmd = new KisTransformToolActivationCommand(view);
286 }
287
288 endAction(ap, KisOperationConfiguration(id()).toXML());
289 }
290}
291
293{
294 if (!viewManager->activeDevice()) return;
295
296 KisImageSP image = viewManager->image();
297 if (!image) return;
298
299 KisPaintDeviceSP clip = KisClipboard::instance()->clip(new KisDefaultBounds(image), true, -1);
300
301 if (!clip) return;
302
305
306 if (viewManager->selection()) {
307 KUndo2Command *deselectCmd = new KisDeselectActiveSelectionCommand(viewManager->selection(), image);
308 KisProcessingApplicator::runSingleCommandStroke(viewManager->image(), deselectCmd);
309 }
310
311 KisTool* tool = dynamic_cast<KisTool*>(KoToolManager::instance()->toolById(viewManager->canvasBase(), "KisToolTransform"));
312 KIS_ASSERT(tool);
314}
315
317{
318 Q_UNUSED(viewManager);
319
320 KisImageSP clipImage = KisClipboard::instance()->clipImage(true);
321 if (!clipImage) return;
322
324 doc->documentInfo()->setAboutInfo("title", i18n("Untitled"));
325 KisImageSP image = new KisImage(doc->createUndoStore(),
326 clipImage->width(),
327 clipImage->height(),
328 clipImage->colorSpace(),
329 i18n("Pasted"));
330 bool renamePastedLayers = KisConfig(true).renamePastedLayers();
331 QString pastedLayerName = renamePastedLayers ? image->nextLayerName() + " " + i18n("(pasted)") :
332 image->nextLayerName();
333 KisPaintLayerSP layer =
334 new KisPaintLayer(image.data(), pastedLayerName,
335 OPACITY_OPAQUE_U8, clipImage->colorSpace());
336
337 KisPainter::copyAreaOptimized(QPoint(), clipImage->projection(), layer->paintDevice(), clipImage->bounds());
338
339 image->addNode(layer.data(), image->rootLayer());
340 doc->setCurrentImage(image);
342
343 KisMainWindow *win = viewManager->mainWindow();
344 win->addViewAndNotifyLoadingCompleted(doc);
345}
346
348{
349 KisCanvas2 *canvasBase = viewManager->canvasBase();
350 if (!canvasBase) return;
351
353 if (!reference) return;
354
355 KisDocument *doc = viewManager->document();
356 canvasBase->addCommand(KisReferenceImagesLayer::addReferenceImages(doc, {reference}));
357
358 KoToolManager::instance()->switchToolRequested("ToolReferenceImages");
359}
360
362{
363 KoSvgPaste paste;
364
365 KisCanvas2 *canvas = view->canvasBase();
366
367 KoShapeManager *shapeManager = canvas->shapeManager();
368 QList<KoShape*> selectedShapes = shapeManager->selection()->selectedEditableShapes();
369
370 if (selectedShapes.isEmpty()) return;
371
372 if (paste.hasShapes()) {
373 KoCanvasBase *canvas = view->canvasBase();
374
375 QSizeF fragmentSize;
376 QList<KoShape*> shapes =
377 paste.fetchShapes(canvas->shapeController()->documentRectInPixels(),
378 canvas->shapeController()->pixelsPerInch(), &fragmentSize);
379
380 if (!shapes.isEmpty()) {
381 KoShape *referenceShape = shapes.first();
382
383
384 KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Paste Style"));
385
386 new KoShapeBackgroundCommand(selectedShapes, referenceShape->background(), parentCommand);
387 new KoShapeStrokeCommand(selectedShapes, referenceShape->stroke(), parentCommand);
388
389
390 canvas->addCommand(parentCommand);
391 }
392
393 qDeleteAll(shapes);
394 }
395}
const quint8 OPACITY_OPAQUE_U8
KUndo2MagicString text() const
KisCoordinatesConverter * coordinatesConverter
void addCommand(KUndo2Command *command) override
KoShapeManager shapeManager
KisToolProxy toolProxy
static KisClipboard * instance()
KisImageSP clipImageFromBoardContents(bool showPopup, int overridePasteBehaviour, QPair< bool, PasteFormatBehaviour > source={false, PasteFormatBehaviour::PASTE_FORMAT_ASK}) const
KisImageSP clipImage(bool showPopup, int overridePasteBehaviour=-1) const
bool renamePastedLayers(bool defaultValue=false) const
_Private::Traits< T >::Result documentToImage(const T &obj) const
KisUndoStore * createUndoStore()
KoDocumentInfo * documentInfo() const
void setCurrentImage(KisImageSP image, bool forceInitialUpdate=true, KisNodeSP preActivatedNode=nullptr)
The command for adding a layer.
KisGroupLayerSP rootLayer() const
const KoColorSpace * colorSpace() const
QString nextLayerName(const QString &baseName="") const
Definition kis_image.cc:741
KisPaintDeviceSP projection() const
qint32 width() const
qint32 height() const
QRect bounds() const override
static void adaptClipToImageColorSpace(KisPaintDeviceSP dev, KisImageSP image)
Main window for Krita.
void pasteLayersFromClipboard(std::optional< QPoint > preferredClipCenter=std::nullopt, KisProcessingApplicator *applicator=nullptr)
void endAction(KisProcessingApplicator *applicator, const QString &xmlData)
KisProcessingApplicator * beginAction(KisViewManager *view, const KUndo2MagicString &actionName, Flags flags=Normal)
QRect extent() const
static void copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect)
static KisPart * instance()
Definition KisPart.cpp:130
void addDocument(KisDocument *document, bool notify=true)
Definition KisPart.cpp:209
KisDocument * createDocument() const
Definition KisPart.cpp:228
static void runSingleCommandStroke(KisImageSP image, KUndo2Command *cmd, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
runSingleCommandStroke creates a stroke and runs cmd in it. The text() field of cmd is used as a titl...
void applyCommand(KUndo2Command *command, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
The KisReferenceImage class represents a single reference image.
static KisReferenceImage * fromClipboard(const KisCoordinatesConverter &converter)
static KUndo2Command * addReferenceImages(KisDocument *document, QList< KoShape * > referenceImages)
KisMainWindow * mainWindow() const
KisDocument * document() const
KisCanvas2 * canvasBase() const
Return the canvas base class.
KisNodeSP activeNode()
KisSelectionSP selection()
KisLayerSP activeLayer()
Convenience method to get at the active layer.
KisPaintDeviceSP activeDevice()
Convenience method to get at the active paint device.
KisNodeManager * nodeManager() const
The node manager handles everything about nodes.
KisImageWSP image() const
Return the image this view is displaying.
QPointer< KoShapeController > shapeController
virtual KoShapeManager * shapeManager() const =0
virtual void addCommand(KUndo2Command *command)=0
KoCanvasController * canvasController() const
virtual KoSelectedShapesProxy * selectedShapesProxy() const =0
selectedShapesProxy() is a special interface for keeping a persistent connections to selectionChanged...
virtual QPointF currentCursorPosition() const =0
void setAboutInfo(const QString &info, const QString &data)
virtual KoSelection * selection()=0
void deselectAll()
clear the selections list
void select(KoShape *shape)
const QList< KoShape * > selectedEditableShapes() const
The undo / redo command for setting the shape background.
QList< KoShape * > topLevelShapes() const
QList< KoShape * > shapes
KoSelection * selection
The undo / redo command for shape moving.
This command allows you to change the zIndex of a number of shapes.
static QList< KoShapeReorderCommand::IndexedShape > homogenizeZIndexesLazy(QList< IndexedShape > shapes)
The undo / redo command for setting the shape stroke.
virtual KoShapeStrokeModelSP stroke() const
Definition KoShape.cpp:885
static bool compareShapeZIndex(KoShape *s1, KoShape *s2)
Definition KoShape.cpp:388
virtual QRectF boundingRect() const
Get the bounding box of the shape.
Definition KoShape.cpp:300
virtual QSharedPointer< KoShapeBackground > background() const
Definition KoShape.cpp:754
KoToolBase * toolById(KoCanvasBase *canvas, const QString &id) const
void switchToolRequested(const QString &id)
static KoToolManager * instance()
Return the toolmanager singleton.
bool paste()
Forwarded to the current KoToolBase.
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define bounds(x, a, b)
KUndo2MagicString kundo2_i18n(const char *text)
QAction * paste(const QObject *recvr, const char *slot, QObject *parent)
bool fuzzyCompareRects(const Rect &r1, const Rect &r2, Difference tolerance)
Point clampPoint(Point pt, const Rect &bounds)
void ensureClipVisible(const QRect &imageBounds, KisPaintDeviceSP clip)
void recenterClip(KisPaintDeviceSP clip, const QPoint &newCenter)
bool isEditable(bool checkVisibility=true) const
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
KisNodeWSP parent
Definition kis_node.cpp:86
KisPaintDeviceSP paintDevice
void run(Flags flags, KisViewManager *view)
void run(KisViewManager *view) override
void run(KisViewManager *view) override
void run(KisViewManager *view) override
void run(KisViewManager *view) override
virtual void newActivationWithExternalSource(KisPaintDeviceSP externalSource)
newActivationWithExternalSource Makes sure that the tool is active and starts a new stroke,...
Definition kis_tool.cc:573