File indexing completed on 2024-12-22 04:12:14

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisPasteActionFactories.h"
0008 
0009 #include "kis_config.h"
0010 #include "kis_image.h"
0011 #include "KisViewManager.h"
0012 #include "kis_tool_proxy.h"
0013 #include "kis_canvas2.h"
0014 #include "kis_canvas_controller.h"
0015 #include "kis_group_layer.h"
0016 #include "kis_paint_device.h"
0017 #include "kis_paint_layer.h"
0018 #include "kis_shape_layer.h"
0019 #include "kis_import_catcher.h"
0020 #include "kis_clipboard.h"
0021 #include "kis_selection.h"
0022 #include "commands/kis_selection_commands.h"
0023 #include "commands/kis_image_layer_add_command.h"
0024 #include "KisTransformToolActivationCommand.h"
0025 #include "kis_processing_applicator.h"
0026 #include "kis_node_manager.h"
0027 
0028 #include <KoDocumentInfo.h>
0029 #include <KoSvgPaste.h>
0030 #include <KoShapeController.h>
0031 #include <KoShapeManager.h>
0032 #include <KoSelection.h>
0033 #include <KoSelectedShapesProxy.h>
0034 #include "kis_algebra_2d.h"
0035 #include <KoShapeMoveCommand.h>
0036 #include <KoShapeReorderCommand.h>
0037 #include "kis_time_span.h"
0038 #include "kis_keyframe_channel.h"
0039 #include "kis_raster_keyframe_channel.h"
0040 #include "kis_painter.h"
0041 #include <KisPart.h>
0042 #include <KisDocument.h>
0043 #include <KisReferenceImagesLayer.h>
0044 #include <KoShapeBackgroundCommand.h>
0045 #include <KoShapeStrokeCommand.h>
0046 #include <KoShapeBackground.h>
0047 #include <KoShapeStroke.h>
0048 #include <KisMainWindow.h>
0049 #include <QApplication>
0050 #include <QClipboard>
0051 
0052 
0053 namespace {
0054 QPointF getFittingOffset(QList<KoShape*> shapes,
0055                          const QPointF &shapesOffset,
0056                          const QRectF &documentRect,
0057                          const qreal fitRatio)
0058 {
0059     QPointF accumulatedFitOffset;
0060 
0061     Q_FOREACH (KoShape *shape, shapes) {
0062         const QRectF bounds = shape->boundingRect();
0063 
0064         const QPointF center = bounds.center() + shapesOffset;
0065 
0066         const qreal wMargin = (0.5 - fitRatio) * bounds.width();
0067         const qreal hMargin = (0.5 - fitRatio) * bounds.height();
0068         const QRectF allowedRect = documentRect.adjusted(-wMargin, -hMargin, wMargin, hMargin);
0069 
0070         const QPointF fittedCenter = KisAlgebra2D::clampPoint(center, allowedRect);
0071 
0072         accumulatedFitOffset += fittedCenter - center;
0073     }
0074 
0075     return accumulatedFitOffset;
0076 }
0077 
0078 bool tryPasteShapes(bool pasteAtCursorPosition, KisViewManager *view)
0079 {
0080     bool result = false;
0081 
0082     KoSvgPaste paste;
0083 
0084     if (paste.hasShapes() && view->activeNode()->isEditable()) {
0085         KoCanvasBase *canvas = view->canvasBase();
0086 
0087         QSizeF fragmentSize;
0088         QList<KoShape*> shapes =
0089             paste.fetchShapes(canvas->shapeController()->documentRectInPixels(),
0090                               canvas->shapeController()->pixelsPerInch(), &fragmentSize);
0091 
0092         if (!shapes.isEmpty()) {
0093             KoShapeManager *shapeManager = canvas->shapeManager();
0094             shapeManager->selection()->deselectAll();
0095 
0096             // adjust z-index of the shapes so that they would be
0097             // pasted on the top of the stack
0098             QList<KoShape*> topLevelShapes = shapeManager->topLevelShapes();
0099             auto it = std::max_element(topLevelShapes.constBegin(), topLevelShapes.constEnd(), KoShape::compareShapeZIndex);
0100             if (it != topLevelShapes.constEnd()) {
0101                 const int zIndexOffset = (*it)->zIndex();
0102 
0103                 std::stable_sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
0104 
0105                 QList<KoShapeReorderCommand::IndexedShape> indexedShapes;
0106                 std::transform(shapes.constBegin(), shapes.constEnd(),
0107                                std::back_inserter(indexedShapes),
0108                     [zIndexOffset] (KoShape *shape) {
0109                         KoShapeReorderCommand::IndexedShape indexedShape(shape);
0110                         indexedShape.zIndex += zIndexOffset;
0111                         return indexedShape;
0112                     });
0113 
0114                 indexedShapes = KoShapeReorderCommand::homogenizeZIndexesLazy(indexedShapes);
0115 
0116                 KoShapeReorderCommand cmd(indexedShapes);
0117                 cmd.redo();
0118             }
0119 
0120             KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Paste shapes"));
0121             canvas->shapeController()->addShapesDirect(shapes, 0, parentCommand);
0122 
0123             QPointF finalShapesOffset;
0124 
0125 
0126             if (pasteAtCursorPosition) {
0127                 QRectF boundingRect = KoShape::boundingRect(shapes);
0128                 const QPointF cursorPos = canvas->canvasController()->currentCursorPosition();
0129                 finalShapesOffset = cursorPos - boundingRect.center();
0130 
0131             } else {
0132                 bool foundOverlapping = false;
0133 
0134                 QRectF boundingRect = KoShape::boundingRect(shapes);
0135                 const QPointF offsetStep = 0.1 * QPointF(boundingRect.width(), boundingRect.height());
0136 
0137                 QPointF offset;
0138 
0139                 Q_FOREACH (KoShape *shape, shapes) {
0140                     QRectF br1 = shape->boundingRect();
0141 
0142                     bool hasOverlappingShape = false;
0143 
0144                     do {
0145                         hasOverlappingShape = false;
0146 
0147                         // we cannot use shapesAt() here, because the groups are not
0148                         // handled in the shape manager's tree
0149                         QList<KoShape*> conflicts = shapeManager->shapes();
0150 
0151                         Q_FOREACH (KoShape *intersectedShape, conflicts) {
0152                             if (intersectedShape == shape) continue;
0153 
0154                             QRectF br2 = intersectedShape->boundingRect();
0155 
0156                             const qreal tolerance = 2.0; /* pt */
0157                             if (KisAlgebra2D::fuzzyCompareRects(br1, br2, tolerance)) {
0158                                 br1.translate(offsetStep.x(), offsetStep.y());
0159                                 offset += offsetStep;
0160 
0161                                 hasOverlappingShape = true;
0162                                 foundOverlapping = true;
0163                                 break;
0164                             }
0165                         }
0166                     } while (hasOverlappingShape);
0167 
0168                     if (foundOverlapping) break;
0169                 }
0170 
0171                 if (foundOverlapping) {
0172                     finalShapesOffset = offset;
0173                 }
0174             }
0175 
0176             const QRectF documentRect = canvas->shapeController()->documentRect();
0177             finalShapesOffset += getFittingOffset(shapes, finalShapesOffset, documentRect, 0.1);
0178 
0179             if (!finalShapesOffset.isNull()) {
0180                 new KoShapeMoveCommand(shapes, finalShapesOffset, parentCommand);
0181             }
0182 
0183             canvas->addCommand(parentCommand);
0184 
0185             Q_FOREACH (KoShape *shape, shapes) {
0186                 canvas->selectedShapesProxy()->selection()->select(shape);
0187             }
0188 
0189             result = true;
0190         }
0191     }
0192 
0193     return result;
0194 }
0195 
0196 }
0197 
0198 void KisPasteActionFactory::run(bool pasteAtCursorPosition, KisViewManager *view)
0199 {
0200     KisImageSP image = view->image();
0201     if (!image) return;
0202 
0203     const QPointF docPos = view->canvasBase()->canvasController()->currentCursorPosition();
0204 
0205     // Activate the current tool's paste functionality
0206     if (view->canvasBase()->toolProxy()->paste()) {
0207         // XXX: "Add saving of XML data for Paste of shapes"
0208         return;
0209     }
0210 
0211     // Paste shapes
0212     if (tryPasteShapes(pasteAtCursorPosition, view)) {
0213         return;
0214     }
0215 
0216     const QRect fittingBounds =
0217         pasteAtCursorPosition ? QRect() : image->bounds();
0218 
0219     // If no shapes, check for layers
0220     if (KisClipboard::instance()->hasLayers()) {
0221         const QPointF offsetTopLeft = [&]() -> QPointF {
0222             KisPaintDeviceSP clip =
0223                 KisClipboard::instance()->clipFromKritaLayers(
0224                     fittingBounds,
0225                     image->colorSpace());
0226 
0227             if (!clip) {
0228                 pasteAtCursorPosition = false;
0229                 return {};
0230             }
0231 
0232 
0233             QPointF imagePos;
0234             if (pasteAtCursorPosition) {
0235                 imagePos =
0236                     view->canvasBase()->coordinatesConverter()->documentToImage(
0237                         docPos);
0238 
0239             } else if (!clip->exactBounds().intersects(image->bounds())) {
0240                  // BUG:459111
0241                 pasteAtCursorPosition = true;
0242                 imagePos = QPointF(image->bounds().center());
0243             } else {
0244                 return {};
0245             }
0246             const QPointF offset =
0247                 (imagePos - QRectF(clip->exactBounds()).center()).toPoint();
0248             return offset;
0249         }();
0250 
0251         view->nodeManager()->pasteLayersFromClipboard(pasteAtCursorPosition,
0252                                                       offsetTopLeft);
0253         return;
0254     }
0255 
0256     KisTimeSpan range;
0257     KisPaintDeviceSP clip = KisClipboard::instance()->clip(fittingBounds, true, -1, &range);
0258 
0259     if (clip) {
0260         if (pasteAtCursorPosition) {
0261             const QPointF imagePos = view->canvasBase()->coordinatesConverter()->documentToImage(docPos);
0262 
0263             const QPoint offset =
0264                 (imagePos - QRectF(clip->exactBounds()).center()).toPoint();
0265 
0266             clip->setX(clip->x() + offset.x());
0267             clip->setY(clip->y() + offset.y());
0268         }
0269 
0270         KisImportCatcher::adaptClipToImageColorSpace(clip, image);
0271         bool renamePastedLayers = KisConfig(true).renamePastedLayers();
0272         QString pastedLayerName = renamePastedLayers ? image->nextLayerName() + " " + i18n("(pasted)") :
0273                                                        image->nextLayerName();
0274         KisPaintLayerSP newLayer = new KisPaintLayer(image.data(),
0275                                                      pastedLayerName,
0276                                                      OPACITY_OPAQUE_U8);
0277         KisNodeSP aboveNode = view->activeLayer();
0278         KisNodeSP parentNode = aboveNode ? aboveNode->parent() : image->root();
0279 
0280         if (range.isValid()) {
0281             newLayer->enableAnimation();
0282             KisKeyframeChannel *channel = newLayer->getKeyframeChannel(KisKeyframeChannel::Raster.id(), true);
0283             KisRasterKeyframeChannel *rasterChannel = dynamic_cast<KisRasterKeyframeChannel*>(channel);
0284             KIS_SAFE_ASSERT_RECOVER_RETURN(rasterChannel);
0285             rasterChannel->importFrame(range.start(), clip, nullptr);
0286 
0287             if (!range.isInfinite()) {
0288                 rasterChannel->addKeyframe(range.end() + 1, 0);
0289             }
0290         } else {
0291             const QRect rc = clip->extent();
0292             KisPainter::copyAreaOptimized(rc.topLeft(), clip, newLayer->paintDevice(), rc);
0293         }
0294 
0295         KUndo2Command *cmd = new KisImageLayerAddCommand(image, newLayer, parentNode, aboveNode);
0296         KisProcessingApplicator *ap = beginAction(view, cmd->text());
0297         ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL);
0298         
0299         if (KisConfig(true).activateTransformToolAfterPaste()) {
0300             KUndo2Command *deselectCmd = new KisDeselectActiveSelectionCommand(view->selection(), image);
0301             ap->applyCommand(deselectCmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL);
0302             
0303             KUndo2Command *transformToolCmd = new KisTransformToolActivationCommand(view);
0304             ap->applyCommand(transformToolCmd, KisStrokeJobData::BARRIER, KisStrokeJobData::NORMAL);
0305         }
0306         
0307         endAction(ap, KisOperationConfiguration(id()).toXML());
0308     }
0309 }
0310 
0311 void KisPasteIntoActionFactory::run(KisViewManager *viewManager)
0312 {
0313     if (!viewManager->activeDevice()) return;
0314 
0315     KisImageSP image = viewManager->image();
0316     if (!image) return;
0317 
0318     QRect imageBounds = image->bounds();
0319 
0320     KisPaintDeviceSP clipdev = KisClipboard::instance()->clipFromKritaLayers(imageBounds, image->colorSpace());
0321     KisPaintDeviceSP clip = clipdev ? new KisPaintDevice(*clipdev) : nullptr;
0322 
0323     if (clip)
0324     {
0325         QRect clipBounds = clip->exactBounds();
0326 
0327         if (!clipBounds.intersects(imageBounds))
0328         {
0329             QPoint diff = imageBounds.center() - clipBounds.center();
0330             clip->setX(diff.x());
0331             clip->setY(diff.y());
0332         }
0333     }
0334     else
0335     {
0336         clip = KisClipboard::instance()->clip(imageBounds, true, -1, nullptr);
0337     }
0338 
0339     if (!clip) return;
0340 
0341     KisImportCatcher::adaptClipToImageColorSpace(clip, image);
0342 
0343     KisTool* tool = dynamic_cast<KisTool*>(KoToolManager::instance()->toolById(viewManager->canvasBase(), "KisToolTransform"));
0344     KIS_ASSERT(tool);
0345     tool->newActivationWithExternalSource(clip);
0346 }
0347 
0348 void KisPasteNewActionFactory::run(KisViewManager *viewManager)
0349 {
0350     Q_UNUSED(viewManager);
0351 
0352     KisPaintDeviceSP clip = KisClipboard::instance()->clip(QRect(), true);
0353     if (!clip) return;
0354 
0355     QRect rect = clip->exactBounds();
0356     if (rect.isEmpty()) return;
0357 
0358     KisDocument *doc = KisPart::instance()->createDocument();
0359     doc->documentInfo()->setAboutInfo("title", i18n("Untitled"));
0360     KisImageSP image = new KisImage(doc->createUndoStore(),
0361                                     rect.width(),
0362                                     rect.height(),
0363                                     clip->colorSpace(),
0364                                     i18n("Pasted"));
0365     bool renamePastedLayers = KisConfig(true).renamePastedLayers();
0366     QString pastedLayerName = renamePastedLayers ? image->nextLayerName() + " " + i18n("(pasted)") :
0367                                                    image->nextLayerName();
0368     KisPaintLayerSP layer =
0369             new KisPaintLayer(image.data(), pastedLayerName,
0370                               OPACITY_OPAQUE_U8, clip->colorSpace());
0371 
0372     KisPainter::copyAreaOptimized(QPoint(), clip, layer->paintDevice(), rect);
0373 
0374     image->addNode(layer.data(), image->rootLayer());
0375     doc->setCurrentImage(image);
0376     KisPart::instance()->addDocument(doc);
0377 
0378     KisMainWindow *win = viewManager->mainWindow();
0379     win->addViewAndNotifyLoadingCompleted(doc);
0380 }
0381 
0382 void KisPasteReferenceActionFactory::run(KisViewManager *viewManager)
0383 {
0384     KisCanvas2 *canvasBase = viewManager->canvasBase();
0385     if (!canvasBase) return;
0386 
0387     KisReferenceImage* reference = KisReferenceImage::fromClipboard(*canvasBase->coordinatesConverter());
0388     if (!reference) return;
0389 
0390     KisDocument *doc = viewManager->document();
0391     canvasBase->addCommand(KisReferenceImagesLayer::addReferenceImages(doc, {reference}));
0392 
0393     KoToolManager::instance()->switchToolRequested("ToolReferenceImages");
0394 }
0395 
0396 void KisPasteShapeStyleActionFactory::run(KisViewManager *view)
0397 {
0398     KoSvgPaste paste;
0399 
0400     KisCanvas2 *canvas = view->canvasBase();
0401 
0402     KoShapeManager *shapeManager = canvas->shapeManager();
0403     QList<KoShape*> selectedShapes = shapeManager->selection()->selectedEditableShapes();
0404 
0405     if (selectedShapes.isEmpty()) return;
0406 
0407     if (paste.hasShapes()) {
0408         KoCanvasBase *canvas = view->canvasBase();
0409 
0410         QSizeF fragmentSize;
0411         QList<KoShape*> shapes =
0412             paste.fetchShapes(canvas->shapeController()->documentRectInPixels(),
0413                               canvas->shapeController()->pixelsPerInch(), &fragmentSize);
0414 
0415         if (!shapes.isEmpty()) {
0416             KoShape *referenceShape = shapes.first();
0417 
0418 
0419             KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Paste Style"));
0420 
0421             new KoShapeBackgroundCommand(selectedShapes, referenceShape->background(), parentCommand);
0422             new KoShapeStrokeCommand(selectedShapes, referenceShape->stroke(), parentCommand);
0423 
0424 
0425             canvas->addCommand(parentCommand);
0426         }
0427 
0428         qDeleteAll(shapes);
0429     }
0430 }