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 }