[krita/kazakov/svg-loading] /: Implement copy-pasting of shapes!

Dmitry Kazakov null at kde.org
Tue Mar 14 09:00:20 UTC 2017


Git commit 1189dccb135a5f35923b8fe9f7adc271eb2887b7 by Dmitry Kazakov.
Committed on 14/03/2017 at 08:59.
Pushed by dkazakov into branch 'kazakov/svg-loading'.

Implement copy-pasting of shapes!

This patch implements the following:

1) The shapes can be copy/pasted inside Krita
2) The shapes can be copy/pasted Krita->Inkscape
   (reverse does not yet work)
3) There are two shortcuts (reverse to Inkscape :( )
   Ctrl+V paste at original position
   Ctrl+Alt+V paste at cursor position

CC:kimageshop at kde.org

M  +0    -12   krita/krita.action
M  +2    -1    krita/krita.xmlgui
M  +12   -0    krita/kritamenu.action
M  +1    -0    libs/flake/CMakeLists.txt
M  +7    -0    libs/flake/KoCanvasController.h
M  +7    -0    libs/flake/KoCanvasControllerWidget.cpp
M  +2    -0    libs/flake/KoCanvasControllerWidget.h
M  +25   -57   libs/flake/KoDrag.cpp
M  +6    -1    libs/flake/KoDrag.h
M  +9    -0    libs/flake/KoShape.cpp
M  +6    -0    libs/flake/KoShape.h
M  +18   -11   libs/flake/KoShapeBasedDocumentBase.cpp
M  +17   -0    libs/flake/KoShapeBasedDocumentBase.h
M  +15   -0    libs/flake/KoShapeController.cpp
M  +16   -0    libs/flake/KoShapeController.h
A  +72   -0    libs/flake/KoSvgPaste.cpp     [License: GPL (v2+)]
A  +38   -0    libs/flake/KoSvgPaste.h     [License: GPL (v2+)]
M  +9    -3    libs/flake/KoToolManager.cpp
M  +117  -11   libs/flake/KoToolProxy.cpp
M  +1    -1    libs/flake/KoToolProxy.h
M  +15   -2    libs/flake/commands/KoShapeMoveCommand.cpp
M  +3    -0    libs/flake/commands/KoShapeMoveCommand.h
M  +0    -1    libs/flake/svg/SvgParser.cpp
M  +6    -0    libs/flake/tests/CMakeLists.txt
M  +8    -0    libs/flake/tests/MockShapes.h
A  +87   -0    libs/flake/tests/TestKoDrag.cpp     [License: GPL (v2+)]
A  +32   -0    libs/flake/tests/TestKoDrag.h     [License: GPL (v2+)]
A  +76   -0    libs/flake/tests/data/test_svg_file.svg
M  +16   -0    libs/global/kis_algebra_2d.h
M  +15   -4    libs/ui/actions/kis_selection_action_factories.cpp
M  +8    -3    libs/ui/actions/kis_selection_action_factories.h
M  +9    -0    libs/ui/canvas/kis_canvas_controller.cpp
M  +2    -0    libs/ui/canvas/kis_canvas_controller.h
M  +10   -0    libs/ui/flake/kis_shape_controller.cpp
M  +5    -2    libs/ui/flake/kis_shape_controller.h
M  +3    -2    libs/ui/kis_selection_manager.cc
M  +4    -4    plugins/tools/defaulttool/defaulttool/DefaultTool.cpp

https://commits.kde.org/krita/1189dccb135a5f35923b8fe9f7adc271eb2887b7

diff --git a/krita/krita.action b/krita/krita.action
index fe025f82241..e062893251d 100644
--- a/krita/krita.action
+++ b/krita/krita.action
@@ -388,18 +388,6 @@
       <isCheckable>true</isCheckable>
       <statusTip></statusTip>
     </Action>
-    <Action name="paste_at">
-      <icon></icon>
-      <text>Paste at cursor</text>
-      <whatsThis></whatsThis>
-      <toolTip>Paste at cursor</toolTip>
-      <iconText>Paste at cursor</iconText>
-      <activationFlags>0</activationFlags>
-      <activationConditions>0</activationConditions>
-      <shortcut></shortcut>
-      <isCheckable>false</isCheckable>
-      <statusTip></statusTip>
-    </Action>
     <Action name="invert">
       <icon></icon>
       <text>&Invert Selection</text>
diff --git a/krita/krita.xmlgui b/krita/krita.xmlgui
index 13afe46a443..18f97d110c7 100644
--- a/krita/krita.xmlgui
+++ b/krita/krita.xmlgui
@@ -2,7 +2,7 @@
 <kpartgui xmlns="http://www.kde.org/standards/kxmlgui/1.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 name="Krita"
-version="103"
+version="104"
 xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0  http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd">
   <MenuBar>
     <Menu name="file">
@@ -49,6 +49,7 @@ xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0  http://www.kde.org
       <Action name="copy_sharp"/>
       <Action name="copy_merged"/>
       <Action name="edit_paste"/>
+      <Action name="paste_at"/>
       <Action name="paste_new"/>
       <Action name="clear"/>
       <Action name="fill_selection_foreground_color"/>
diff --git a/krita/kritamenu.action b/krita/kritamenu.action
index 3ca097eb1f6..85266fe675a 100644
--- a/krita/kritamenu.action
+++ b/krita/kritamenu.action
@@ -368,6 +368,18 @@
       <isCheckable>false</isCheckable>
       <statusTip></statusTip>
     </Action>
+    <Action name="paste_at">
+      <icon></icon>
+      <text>Paste at Cursor</text>
+      <whatsThis></whatsThis>
+      <toolTip>Paste at cursor</toolTip>
+      <iconText>Paste at cursor</iconText>
+      <activationFlags>0</activationFlags>
+      <activationConditions>0</activationConditions>
+      <shortcut>Ctrl+Alt+V</shortcut>
+      <isCheckable>false</isCheckable>
+      <statusTip></statusTip>
+    </Action>
     <Action name="paste_new">
       <icon></icon>
       <text>Paste into &New Image</text>
diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt
index 8cf05f904ff..5b771412353 100644
--- a/libs/flake/CMakeLists.txt
+++ b/libs/flake/CMakeLists.txt
@@ -80,6 +80,7 @@ set(kritaflake_SRCS
     KoVectorPatternBackground.cpp
     KoShapeConfigWidgetBase.cpp
     KoDrag.cpp
+    KoSvgPaste.cpp
     KoDragOdfSaveHelper.cpp
     KoShapeOdfSaveHelper.cpp
     KoShapePaste.cpp
diff --git a/libs/flake/KoCanvasController.h b/libs/flake/KoCanvasController.h
index e1ab05fb1ac..297650c3001 100644
--- a/libs/flake/KoCanvasController.h
+++ b/libs/flake/KoCanvasController.h
@@ -299,6 +299,12 @@ public:
 
     QPoint documentOffset() const;
 
+    /**
+     * @return the current position of the cursor fetched from QCursor::pos() and
+     *         converted into document coordinates
+     */
+    virtual QPointF currentCursorPosition() const = 0;
+
 protected:
     void setDocumentSize(const QSize &sz);
     QSize documentSize() const;
@@ -465,6 +471,7 @@ public:
     virtual void updateDocumentSize(const QSize &/*sz*/, bool /*recalculateCenter*/) {}
     virtual void setZoomWithWheel(bool /*zoom*/) {}
     virtual void setVastScrolling(qreal /*factor*/) {}
+    QPointF currentCursorPosition() const override { return QPointF(); }
 
 };
 
diff --git a/libs/flake/KoCanvasControllerWidget.cpp b/libs/flake/KoCanvasControllerWidget.cpp
index 91695c757c3..820559bd722 100644
--- a/libs/flake/KoCanvasControllerWidget.cpp
+++ b/libs/flake/KoCanvasControllerWidget.cpp
@@ -480,6 +480,13 @@ void KoCanvasControllerWidget::setVastScrolling(qreal factor)
     d->vastScrollingFactor = factor;
 }
 
+QPointF KoCanvasControllerWidget::currentCursorPosition() const
+{
+    QWidget *canvasWidget = d->canvas->canvasWidget();
+    const KoViewConverter *converter = d->canvas->viewConverter();
+    return converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + d->canvas->canvasController()->documentOffset() - canvasWidget->pos());
+}
+
 void KoCanvasControllerWidget::pan(const QPoint &distance)
 {
     QPoint sourcePoint = scrollBarValue();
diff --git a/libs/flake/KoCanvasControllerWidget.h b/libs/flake/KoCanvasControllerWidget.h
index f8f3bf1da7c..68d9d80f2fa 100644
--- a/libs/flake/KoCanvasControllerWidget.h
+++ b/libs/flake/KoCanvasControllerWidget.h
@@ -139,6 +139,8 @@ public:
 
     virtual void setVastScrolling(qreal factor);
 
+    QPointF currentCursorPosition() const override;
+
     /**
      * \internal
      */
diff --git a/libs/flake/KoDrag.cpp b/libs/flake/KoDrag.cpp
index 2f82798c272..51b9f1bd075 100644
--- a/libs/flake/KoDrag.cpp
+++ b/libs/flake/KoDrag.cpp
@@ -38,6 +38,11 @@
 #include <KoEmbeddedDocumentSaver.h>
 #include "KoShapeSavingContext.h"
 
+#include <KoShape.h>
+#include <QRect>
+#include <SvgWriter.h>
+
+
 class KoDragPrivate {
 public:
     KoDragPrivate() : mimeData(0) { }
@@ -55,73 +60,36 @@ KoDrag::~KoDrag()
     delete d;
 }
 
-bool KoDrag::setOdf(const char *mimeType, KoDragOdfSaveHelper &helper)
+bool KoDrag::setOdf(const char *, KoDragOdfSaveHelper &)
 {
-    struct Finally {
-        Finally(KoStore *s) : store(s) { }
-        ~Finally() {
-            delete store;
-        }
-        KoStore *store;
-    };
-
-    QBuffer buffer;
-    KoStore *store = KoStore::createStore(&buffer, KoStore::Write, mimeType);
-    Finally finally(store); // delete store when we exit this scope
-    Q_ASSERT(store);
-    Q_ASSERT(!store->bad());
-
-    KoOdfWriteStore odfStore(store);
-    KoEmbeddedDocumentSaver embeddedSaver;
-
-    KoXmlWriter *manifestWriter = odfStore.manifestWriter(mimeType);
-    KoXmlWriter *contentWriter = odfStore.contentWriter();
-
-    if (!contentWriter) {
-        return false;
-    }
+    return false;
+}
 
-    KoGenStyles mainStyles;
-    KoXmlWriter *bodyWriter = odfStore.bodyWriter();
-    KoShapeSavingContext *context = helper.context(bodyWriter, mainStyles, embeddedSaver);
+bool KoDrag::setSvg(const QList<KoShape *> originalShapes)
+{
+    QRectF boundingRect;
+    QList<KoShape*> shapes;
 
-    if (!helper.writeBody()) {
-        return false;
+    Q_FOREACH (KoShape *shape, originalShapes) {
+        boundingRect |= shape->boundingRect();
+        shapes.append(shape->cloneShape());
     }
 
-    mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter);
+    qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
 
-    odfStore.closeContentWriter();
-
-    //add manifest line for content.xml
-    manifestWriter->addManifestEntry("content.xml", "text/xml");
-
-
-    if (!mainStyles.saveOdfStylesDotXml(store, manifestWriter)) {
-        return false;
-    }
-
-    if (!context->saveDataCenter(store, manifestWriter)) {
-        debugFlake << "save data centers failed";
-        return false;
-    }
+    QBuffer buffer;
+    QLatin1String mimeType("image/svg+xml");
 
-    // Save embedded objects
-    KoDocumentBase::SavingContext documentContext(odfStore, embeddedSaver);
-    if (!embeddedSaver.saveEmbeddedDocuments(documentContext)) {
-        debugFlake << "save embedded documents failed";
-        return false;
-    }
+    buffer.open(QIODevice::WriteOnly);
 
-    // Write out manifest file
-    if (!odfStore.closeManifestWriter()) {
-        return false;
-    }
+    const QSizeF pageSize(boundingRect.right(), boundingRect.bottom());
+    SvgWriter writer(shapes, pageSize);
+    writer.save(buffer);
 
-    delete store; // make sure the buffer if fully flushed.
-    finally.store = 0;
-    setData(mimeType, buffer.buffer());
+    buffer.close();
+    qDeleteAll(shapes);
 
+    setData(mimeType, buffer.data());
     return true;
 }
 
diff --git a/libs/flake/KoDrag.h b/libs/flake/KoDrag.h
index 8ef1c26b0fc..db1938fe0f3 100644
--- a/libs/flake/KoDrag.h
+++ b/libs/flake/KoDrag.h
@@ -22,11 +22,14 @@
 
 #include "kritaflake_export.h"
 
+#include <QList>
+
 class QMimeData;
 class QString;
 class QByteArray;
 class KoDragOdfSaveHelper;
 class KoDragPrivate;
+class KoShape;
 
 /**
  * Class for simplifying adding a odf to the clip board
@@ -50,7 +53,9 @@ public:
      * @param mimeType used for creating the odf document
      * @param helper helper for saving the body of the odf document
      */
-    bool setOdf(const char *mimeType, KoDragOdfSaveHelper &helper);
+    bool setOdf(const char *, KoDragOdfSaveHelper &);
+
+    bool setSvg(const QList<KoShape*> shapes);
 
     /**
      * Add additional mimeTypes
diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp
index d1bb8c42267..bdaacdeec67 100644
--- a/libs/flake/KoShape.cpp
+++ b/libs/flake/KoShape.cpp
@@ -454,6 +454,15 @@ QRectF KoShape::boundingRect() const
     return bb;
 }
 
+QRectF KoShape::boundingRect(const QList<KoShape *> &shapes)
+{
+    QRectF boundingRect;
+    Q_FOREACH (KoShape *shape, shapes) {
+        boundingRect |= shape->boundingRect();
+    }
+    return boundingRect;
+}
+
 QTransform KoShape::absoluteTransformation(const KoViewConverter *converter) const
 {
     Q_D(const KoShape);
diff --git a/libs/flake/KoShape.h b/libs/flake/KoShape.h
index 4e2699fbcbd..bee698062cb 100644
--- a/libs/flake/KoShape.h
+++ b/libs/flake/KoShape.h
@@ -332,6 +332,12 @@ public:
     virtual QRectF boundingRect() const;
 
     /**
+     * Get the united bounding box of a group of shapes. This is a utility
+     * function used in many places in Krita.
+     */
+    static QRectF boundingRect(const QList<KoShape*> &shapes);
+
+    /**
      * @brief Add a connector point to the shape
      *
      * A connector is a place on the shape that allows a graphical connection to be made
diff --git a/libs/flake/KoShapeBasedDocumentBase.cpp b/libs/flake/KoShapeBasedDocumentBase.cpp
index fc13c9e9c02..b8dfc0725e4 100644
--- a/libs/flake/KoShapeBasedDocumentBase.cpp
+++ b/libs/flake/KoShapeBasedDocumentBase.cpp
@@ -19,6 +19,7 @@
  * Boston, MA 02110-1301, USA.
 */
 
+#include <QTransform>
 #include "KoShapeBasedDocumentBase.h"
 #include "KoDocumentResourceManager.h"
 #include "KoShapeRegistry.h"
@@ -41,17 +42,15 @@ public:
         }
         // read persistent application wide resources
         KSharedConfigPtr config =  KSharedConfig::openConfig();
-        if (config->hasGroup("Misc")) {
-            KConfigGroup miscGroup = config->group("Misc");
-            const qreal pasteOffset = miscGroup.readEntry("CopyOffset", 10.0);
-            resourceManager->setPasteOffset(pasteOffset);
-            const bool pasteAtCursor = miscGroup.readEntry("PasteAtCursor", true);
-            resourceManager->enablePasteAtCursor(pasteAtCursor);
-            const uint grabSensitivity = miscGroup.readEntry("GrabSensitivity", 3);
-            resourceManager->setGrabSensitivity(grabSensitivity);
-            const uint handleRadius = miscGroup.readEntry("HandleRadius", 3);
-            resourceManager->setHandleRadius(handleRadius);
-        }
+        KConfigGroup miscGroup = config->group("Misc");
+        const qreal pasteOffset = miscGroup.readEntry("CopyOffset", 10.0);
+        resourceManager->setPasteOffset(pasteOffset);
+        const bool pasteAtCursor = miscGroup.readEntry("PasteAtCursor", true);
+        resourceManager->enablePasteAtCursor(pasteAtCursor);
+        const uint grabSensitivity = miscGroup.readEntry("GrabSensitivity", 3);
+        resourceManager->setGrabSensitivity(grabSensitivity);
+        const uint handleRadius = miscGroup.readEntry("HandleRadius", 3);
+        resourceManager->setHandleRadius(handleRadius);
     }
 
     ~KoShapeBasedDocumentBasePrivate()
@@ -80,3 +79,11 @@ KoDocumentResourceManager *KoShapeBasedDocumentBase::resourceManager() const
 {
     return d->resourceManager;
 }
+
+QRectF KoShapeBasedDocumentBase::documentRect() const
+{
+    const qreal pxToPt = 72.0 / pixelsPerInch();
+
+    QTransform t = QTransform::fromScale(pxToPt, pxToPt);
+    return t.mapRect(documentRectInPixels());
+}
diff --git a/libs/flake/KoShapeBasedDocumentBase.h b/libs/flake/KoShapeBasedDocumentBase.h
index b2f3565b034..95e6327d6d5 100644
--- a/libs/flake/KoShapeBasedDocumentBase.h
+++ b/libs/flake/KoShapeBasedDocumentBase.h
@@ -27,6 +27,7 @@
 
 #include <QList>
 
+class QRectF;
 class KoShape;
 class KoShapeBasedDocumentBasePrivate;
 class KoDocumentResourceManager;
@@ -78,6 +79,22 @@ public:
      */
     virtual KoDocumentResourceManager *resourceManager() const;
 
+    /**
+     * The size of the document measured in rasterized pixels. This information is needed for loading
+     * SVG documents that use 'px' as the default unit.
+     */
+    virtual QRectF documentRectInPixels() const = 0;
+
+    /**
+     * The size of the document measured in 'pt'
+     */
+    QRectF documentRect() const;
+
+    /**
+     * Resolution of the rasterized representaiton of the document. Used to load SVG documents correctly.
+     */
+    virtual qreal pixelsPerInch() const = 0;
+
 private:
     KoShapeBasedDocumentBasePrivate * const d;
 };
diff --git a/libs/flake/KoShapeController.cpp b/libs/flake/KoShapeController.cpp
index 7d4559247ad..d2f692fd348 100644
--- a/libs/flake/KoShapeController.cpp
+++ b/libs/flake/KoShapeController.cpp
@@ -172,6 +172,21 @@ void KoShapeController::setShapeControllerBase(KoShapeBasedDocumentBase *shapeBa
     d->shapeBasedDocument = shapeBasedDocument;
 }
 
+QRectF KoShapeController::documentRectInPixels() const
+{
+    return d->shapeBasedDocument ? d->shapeBasedDocument->documentRectInPixels() : QRectF(0,0,1920,1080);
+}
+
+qreal KoShapeController::pixelsPerInch() const
+{
+    return d->shapeBasedDocument ? d->shapeBasedDocument->pixelsPerInch() : 72.0;
+}
+
+QRectF KoShapeController::documentRect() const
+{
+    return d->shapeBasedDocument ? d->shapeBasedDocument->documentRect() : documentRectInPixels();
+}
+
 KoDocumentResourceManager *KoShapeController::resourceManager() const
 {
     if (!d->shapeBasedDocument)
diff --git a/libs/flake/KoShapeController.h b/libs/flake/KoShapeController.h
index 42f7d450689..377cae0dbec 100644
--- a/libs/flake/KoShapeController.h
+++ b/libs/flake/KoShapeController.h
@@ -111,6 +111,22 @@ public:
     void setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument);
 
     /**
+     * The size of the document measured in rasterized pixels. This information is needed for loading
+     * SVG documents that use 'px' as the default unit.
+     */
+    QRectF documentRectInPixels() const;
+
+    /**
+     * Resolution of the rasterized representaiton of the document. Used to load SVG documents correctly.
+     */
+    qreal pixelsPerInch() const;
+
+    /**
+     * Document rect measured in 'pt'
+     */
+    QRectF documentRect() const;
+
+    /**
      * Return a pointer to the resource manager associated with the
      * shape-set (typically a document). The resource manager contains
      * document wide resources * such as variable managers, the image
diff --git a/libs/flake/KoSvgPaste.cpp b/libs/flake/KoSvgPaste.cpp
new file mode 100644
index 00000000000..205cca02851
--- /dev/null
+++ b/libs/flake/KoSvgPaste.cpp
@@ -0,0 +1,72 @@
+/*
+ *  Copyright (c) 2017 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KoSvgPaste.h"
+
+#include <QApplication>
+#include <QClipboard>
+#include <QMimeData>
+
+#include <SvgParser.h>
+#include <KoDocumentResourceManager.h>
+#include <KoXmlReader.h>
+#include <FlakeDebug.h>
+#include <QRectF>
+
+KoSvgPaste::KoSvgPaste()
+{
+}
+
+bool KoSvgPaste::hasShapes() const
+{
+    const QMimeData *mimeData = QApplication::clipboard()->mimeData();
+    return mimeData && mimeData->hasFormat("image/svg+xml");
+}
+
+QList<KoShape*> KoSvgPaste::fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize) const
+{
+    QList<KoShape*> shapes;
+
+    const QMimeData *mimeData = QApplication::clipboard()->mimeData();
+    if (!mimeData) return shapes;
+
+    QByteArray data = mimeData->data("image/svg+xml");
+    if (data.isEmpty()) return shapes;
+
+    KoXmlDocument doc;
+
+    QString errorMsg;
+    int errorLine = 0;
+    int errorColumn = 0;
+
+    const bool documentValid = doc.setContent(data, false, &errorMsg, &errorLine, &errorColumn);
+
+    if (!documentValid) {
+        errorFlake << "Failed to process an SVG file at"
+                   << errorLine << ":" << errorColumn << "->" << errorMsg;
+        return shapes;
+    }
+
+    KoDocumentResourceManager resourceManager;
+    SvgParser parser(&resourceManager);
+    parser.setResolution(viewportInPx, resolutionPPI);
+
+    shapes = parser.parseSvg(doc.documentElement(), fragmentSize);
+
+    return shapes;
+}
diff --git a/libs/flake/KoSvgPaste.h b/libs/flake/KoSvgPaste.h
new file mode 100644
index 00000000000..98400cf6e14
--- /dev/null
+++ b/libs/flake/KoSvgPaste.h
@@ -0,0 +1,38 @@
+/*
+ *  Copyright (c) 2017 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KOSVGPASTE_H
+#define KOSVGPASTE_H
+
+#include "kritaflake_export.h"
+#include <QList>
+
+class KoShape;
+class QRectF;
+class QSizeF;
+
+class KRITAFLAKE_EXPORT KoSvgPaste
+{
+public:
+    KoSvgPaste();
+
+    bool hasShapes() const;
+    QList<KoShape*> fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0) const;
+};
+
+#endif // KOSVGPASTE_H
diff --git a/libs/flake/KoToolManager.cpp b/libs/flake/KoToolManager.cpp
index 7fc1ca04a23..b2571512a84 100644
--- a/libs/flake/KoToolManager.cpp
+++ b/libs/flake/KoToolManager.cpp
@@ -44,6 +44,8 @@
 #include "kis_action_registry.h"
 #include "KoToolFactoryBase.h"
 
+#include <krita_container_utils.h>
+
 // Qt + kde
 #include <QWidget>
 #include <QEvent>
@@ -374,13 +376,17 @@ KoCanvasController *KoToolManager::activeCanvasController() const
 QString KoToolManager::preferredToolForSelection(const QList<KoShape*> &shapes)
 {
     QList<QString> types;
-    Q_FOREACH (KoShape *shape, shapes)
-        if (! types.contains(shape->shapeId()))
-            types.append(shape->shapeId());
+    Q_FOREACH (KoShape *shape, shapes) {
+        types << shape->shapeId();
+    }
+
+    KritaUtils::makeContainerUnique(types);
 
     QString toolType = KoInteractionTool_ID;
     int prio = INT_MAX;
     Q_FOREACH (ToolHelper *helper, d->tools) {
+        if (helper->id() == KoCreateShapesTool_ID) continue;
+
         if (helper->priority() >= prio)
             continue;
 
diff --git a/libs/flake/KoToolProxy.cpp b/libs/flake/KoToolProxy.cpp
index 0b71e3580a2..e69760ff322 100644
--- a/libs/flake/KoToolProxy.cpp
+++ b/libs/flake/KoToolProxy.cpp
@@ -50,6 +50,12 @@
 #include "KoViewConverter.h"
 #include "KoShapeFactoryBase.h"
 
+#include <KoSvgPaste.h>
+#include <KoSelectedShapesProxy.h>
+#include "kis_algebra_2d.h"
+#include <KoShapeMoveCommand.h>
+#include <KoViewConverter.h>
+
 
 KoToolProxyPrivate::KoToolProxyPrivate(KoToolProxy *p)
     : activeTool(0),
@@ -456,27 +462,127 @@ void KoToolProxy::copy() const
         d->activeTool->copy();
 }
 
-bool KoToolProxy::paste()
+
+namespace {
+QPointF getFittingOffset(QList<KoShape*> shapes,
+                         const QPointF &shapesOffset,
+                         const QRectF &documentRect,
+                         const qreal fitRatio)
+{
+    QPointF accumulatedFitOffset;
+
+    Q_FOREACH (KoShape *shape, shapes) {
+        const QRectF bounds = shape->boundingRect();
+
+        const QPointF center = bounds.center() + shapesOffset;
+
+        const qreal wMargin = (0.5 - fitRatio) * bounds.width();
+        const qreal hMargin = (0.5 - fitRatio) * bounds.height();
+        const QRectF allowedRect = documentRect.adjusted(-wMargin, -hMargin, wMargin, hMargin);
+
+        const QPointF fittedCenter = KisAlgebra2D::clampPoint(center, allowedRect);
+
+        accumulatedFitOffset += fittedCenter - center;
+    }
+
+    return accumulatedFitOffset;
+}
+}
+
+bool KoToolProxy::paste(bool pasteAtCursorPosition)
 {
     bool success = false;
     KoCanvasBase *canvas = d->controller->canvas();
 
-    if (d->activeTool && d->isActiveLayerEditable())
+    if (d->activeTool && d->isActiveLayerEditable()) {
         success = d->activeTool->paste();
+    }
 
-    if (!success) {
-        const QMimeData *data = QApplication::clipboard()->mimeData();
+    KoSvgPaste paste;
+    if (!success && paste.hasShapes()) {
+        QSizeF fragmentSize;
+
+        QList<KoShape*> shapes =
+            paste.fetchShapes(canvas->shapeController()->documentRectInPixels(),
+                              canvas->shapeController()->pixelsPerInch(), &fragmentSize);
 
-        if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))) {
+        if (!shapes.isEmpty()) {
             KoShapeManager *shapeManager = canvas->shapeManager();
-            KoShapePaste paste(canvas, shapeManager->selection()->activeLayer());
-            success = paste.paste(KoOdf::Text, data);
-            if (success) {
-                shapeManager->selection()->deselectAll();
-                Q_FOREACH (KoShape *shape, paste.pastedShapes()) {
-                    shapeManager->selection()->select(shape);
+            shapeManager->selection()->deselectAll();
+
+            KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Paste shapes"));
+
+            Q_FOREACH (KoShape *shape, shapes) {
+                canvas->shapeController()->addShapeDirect(shape, parentCommand);
+            }
+
+            QPointF finalShapesOffset;
+
+
+            if (pasteAtCursorPosition) {
+                QRectF boundingRect = KoShape::boundingRect(shapes);
+                const QPointF cursorPos = canvas->canvasController()->currentCursorPosition();
+                finalShapesOffset = cursorPos - boundingRect.center();
+
+            } else {
+                bool foundOverlapping = false;
+
+                QRectF boundingRect = KoShape::boundingRect(shapes);
+                const QPointF offsetStep = 0.1 * QPointF(boundingRect.width(), boundingRect.height());
+
+                QPointF offset;
+
+                Q_FOREACH (KoShape *shape, shapes) {
+                    QRectF br1 = shape->boundingRect();
+
+                    bool hasOverlappingShape = false;
+
+                    do {
+                        hasOverlappingShape = false;
+
+                        // we cannot use shapesAt() here, because the groups are not
+                        // handled in the shape manager's tree
+                        QList<KoShape*> conflicts = shapeManager->shapes();
+
+                        Q_FOREACH (KoShape *intersectedShape, conflicts) {
+                            if (intersectedShape == shape) continue;
+
+                            QRectF br2 = intersectedShape->boundingRect();
+
+                            const qreal tolerance = 2.0; /* pt */
+                            if (KisAlgebra2D::fuzzyCompareRects(br1, br2, tolerance)) {
+                                br1.translate(offsetStep.x(), offsetStep.y());
+                                offset += offsetStep;
+
+                                hasOverlappingShape = true;
+                                foundOverlapping = true;
+                                break;
+                            }
+                        }
+                    } while (hasOverlappingShape);
+
+                    if (foundOverlapping) break;
+                }
+
+                if (foundOverlapping) {
+                    finalShapesOffset = offset;
                 }
             }
+
+            const QRectF documentRect = canvas->shapeController()->documentRect();
+            finalShapesOffset += getFittingOffset(shapes, finalShapesOffset, documentRect, 0.1);
+
+            if (!finalShapesOffset.isNull()) {
+                new KoShapeMoveCommand(shapes, finalShapesOffset, parentCommand);
+            }
+
+            canvas->addCommand(parentCommand);
+
+            Q_FOREACH (KoShape *shape, shapes) {
+                canvas->selectedShapesProxy()->selection()->select(shape);
+            }
+
+            success = true;
         }
     }
 
diff --git a/libs/flake/KoToolProxy.h b/libs/flake/KoToolProxy.h
index 234fbdfe168..8adbad8e442 100644
--- a/libs/flake/KoToolProxy.h
+++ b/libs/flake/KoToolProxy.h
@@ -142,7 +142,7 @@ public:
     void copy() const;
 
     /// Forwarded to the current KoToolBase
-    bool paste();
+    bool paste(bool pasteAtCursorPosition = false);
 
     /// Forwarded to the current KoToolBase
     QStringList supportedPasteMimeTypes() const;
diff --git a/libs/flake/commands/KoShapeMoveCommand.cpp b/libs/flake/commands/KoShapeMoveCommand.cpp
index 86c2867ac88..b9a88e67f73 100644
--- a/libs/flake/commands/KoShapeMoveCommand.cpp
+++ b/libs/flake/commands/KoShapeMoveCommand.cpp
@@ -33,7 +33,7 @@ public:
 };
 
 KoShapeMoveCommand::KoShapeMoveCommand(const QList<KoShape*> &shapes, QList<QPointF> &previousPositions, QList<QPointF> &newPositions, KoFlake::AnchorPosition anchor, KUndo2Command *parent)
-        : KUndo2Command(parent),
+        : KUndo2Command(kundo2_i18n("Move shapes"), parent),
         d(new Private())
 {
     d->shapes = shapes;
@@ -42,8 +42,21 @@ KoShapeMoveCommand::KoShapeMoveCommand(const QList<KoShape*> &shapes, QList<QPoi
     d->anchor = anchor;
     Q_ASSERT(d->shapes.count() == d->previousPositions.count());
     Q_ASSERT(d->shapes.count() == d->newPositions.count());
+}
+
+KoShapeMoveCommand::KoShapeMoveCommand(const QList<KoShape *> &shapes, const QPointF &offset, KUndo2Command *parent)
+    : KUndo2Command(kundo2_i18n("Move shapes"), parent),
+      d(new Private())
+{
+    d->shapes = shapes;
+    d->anchor = KoFlake::Center;
+
+    Q_FOREACH (KoShape *shape, d->shapes) {
+        const QPointF pos = shape->absolutePosition();
 
-    setText(kundo2_i18n("Move shapes"));
+        d->previousPositions << pos;
+        d->newPositions << pos + offset;
+    }
 }
 
 KoShapeMoveCommand::~KoShapeMoveCommand()
diff --git a/libs/flake/commands/KoShapeMoveCommand.h b/libs/flake/commands/KoShapeMoveCommand.h
index c619edc7dcc..711222440ad 100644
--- a/libs/flake/commands/KoShapeMoveCommand.h
+++ b/libs/flake/commands/KoShapeMoveCommand.h
@@ -45,6 +45,9 @@ public:
      */
     KoShapeMoveCommand(const QList<KoShape*> &shapes, QList<QPointF> &previousPositions, QList<QPointF> &newPositions,
                        KoFlake::AnchorPosition anchor = KoFlake::Center, KUndo2Command *parent = 0);
+
+    KoShapeMoveCommand(const QList<KoShape*> &shapes, const QPointF &offset, KUndo2Command *parent = 0);
+
     ~KoShapeMoveCommand();
     /// redo the command
     void redo() override;
diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp
index e96d967720b..970730f12f1 100644
--- a/libs/flake/svg/SvgParser.cpp
+++ b/libs/flake/svg/SvgParser.cpp
@@ -1300,7 +1300,6 @@ QList<KoShape*> SvgParser::parseSingleElement(const KoXmlElement &b)
              */
             KoShape *defsShape = parseGroup(b);
             defsShape->setVisible(false);
-            shapes += defsShape;
         }
     } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") {
     } else if (b.tagName() == "pattern") {
diff --git a/libs/flake/tests/CMakeLists.txt b/libs/flake/tests/CMakeLists.txt
index 0a4abe45131..edb3516a4f7 100644
--- a/libs/flake/tests/CMakeLists.txt
+++ b/libs/flake/tests/CMakeLists.txt
@@ -60,6 +60,12 @@ krita_add_broken_unit_test(TestPointMergeCommand.cpp
     LINK_LIBRARIES kritaflake Qt5::Test)
 
 ecm_add_test(
+    TestKoDrag.cpp
+    TEST_NAME libs-kritaflake-TestKoDrag
+    LINK_LIBRARIES kritaflake Qt5::Test
+)
+
+ecm_add_test(
     TestKoMarkerCollection.cpp
     TEST_NAME libs-kritaflake-TestKoMarkerCollection
     LINK_LIBRARIES kritaflake Qt5::Test
diff --git a/libs/flake/tests/MockShapes.h b/libs/flake/tests/MockShapes.h
index 829791931a9..07d9cec6370 100644
--- a/libs/flake/tests/MockShapes.h
+++ b/libs/flake/tests/MockShapes.h
@@ -99,6 +99,14 @@ public:
         m_shapeManager = shapeManager;
     }
 
+    QRectF documentRectInPixels() const {
+        return QRectF(0,0,100,100);
+    }
+
+    qreal pixelsPerInch() const {
+        return 72.0;
+    }
+
 private:
     QSet<KoShape * > m_shapes;
     KoShapeManager *m_shapeManager = 0;
diff --git a/libs/flake/tests/TestKoDrag.cpp b/libs/flake/tests/TestKoDrag.cpp
new file mode 100644
index 00000000000..543f40bf833
--- /dev/null
+++ b/libs/flake/tests/TestKoDrag.cpp
@@ -0,0 +1,87 @@
+/*
+ *  Copyright (c) 2017 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "TestKoDrag.h"
+
+#include <KoDrag.h>
+#include <KoSvgPaste.h>
+
+#include <kis_debug.h>
+#include <kis_global.h>
+#include <svg/SvgParser.h>
+#include <KoDocumentResourceManager.h>
+#include <KoShapeGroup.h>
+
+#include "../../sdk/tests/qimage_test_util.h"
+
+void TestKoDrag::test()
+{
+    const QString fileName = TestUtil::fetchDataFileLazy("test_svg_file.svg");
+    QVERIFY(!fileName.isEmpty());
+
+    QFile testShapes(fileName);
+    testShapes.open(QIODevice::ReadOnly);
+
+    KoXmlDocument doc;
+    doc.setContent(testShapes.readAll());
+
+    KoDocumentResourceManager resourceManager;
+    SvgParser parser(&resourceManager);
+    parser.setResolution(QRectF(0, 0, 30, 30) /* px */, 72 /* ppi */);
+
+    QSizeF fragmentSize;
+    QList<KoShape*> shapes = parser.parseSvg(doc.documentElement(), &fragmentSize);
+    QCOMPARE(fragmentSize, QSizeF(30,30));
+
+    {
+        QCOMPARE(shapes.size(), 1);
+
+        KoShapeGroup *layer = dynamic_cast<KoShapeGroup*>(shapes.first());
+        QVERIFY(layer);
+        QCOMPARE(layer->shapeCount(), 2);
+
+        QCOMPARE(KoShape::boundingRect(shapes).toAlignedRect(), QRect(4,3,24,24));
+    }
+
+    KoDrag drag;
+    drag.setSvg(shapes);
+    drag.addToClipboard();
+
+    KoSvgPaste paste;
+    QVERIFY(paste.hasShapes());
+
+    QList<KoShape*> newShapes = paste.fetchShapes(QRectF(0,0,15,15) /* px */, 144 /* ppi */, &fragmentSize);
+
+    {
+        QCOMPARE(newShapes.size(), 1);
+
+        KoShapeGroup *layer = dynamic_cast<KoShapeGroup*>(newShapes.first());
+        QVERIFY(layer);
+        QCOMPARE(layer->shapeCount(), 2);
+
+        QCOMPARE(fragmentSize.toSize(), QSize(54, 53));
+        QCOMPARE(KoShape::boundingRect(newShapes).toAlignedRect(), QRect(4,3,24,24));
+    }
+
+
+    qDeleteAll(shapes);
+    qDeleteAll(newShapes);
+}
+
+
+QTEST_MAIN(TestKoDrag)
diff --git a/libs/flake/tests/TestKoDrag.h b/libs/flake/tests/TestKoDrag.h
new file mode 100644
index 00000000000..edb6a58083d
--- /dev/null
+++ b/libs/flake/tests/TestKoDrag.h
@@ -0,0 +1,32 @@
+/*
+ *  Copyright (c) 2017 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef TESTKODRAG_H
+#define TESTKODRAG_H
+
+#include <QObject>
+#include <QTest>
+
+class TestKoDrag : public QObject
+{
+    Q_OBJECT
+private Q_SLOTS:
+    void test();
+};
+
+#endif // TESTKODRAG_H
diff --git a/libs/flake/tests/data/test_svg_file.svg b/libs/flake/tests/data/test_svg_file.svg
new file mode 100644
index 00000000000..025850d432b
--- /dev/null
+++ b/libs/flake/tests/data/test_svg_file.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="30pt"
+   height="30pt"
+   viewBox="0 0 37.500001 37.5"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="test_svg_file.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.2"
+     inkscape:cx="21.813814"
+     inkscape:cy="20.149396"
+     inkscape:document-units="pt"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="pt"
+     inkscape:window-width="1861"
+     inkscape:window-height="1056"
+     inkscape:window-x="59"
+     inkscape:window-y="24"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1014.8622)">
+    <rect
+       style="color:#000000;solid-opacity:1;fill:#fa8072;fill-opacity:0.46551729;fill-rule:evenodd;stroke:#d541a6;stroke-width:2.06454587;stroke-dashoffset:71.24002075;stroke-opacity:0.53448277"
+       id="rect3593"
+       width="16.14974"
+       height="15.703311"
+       x="8.085845"
+       y="1022.6802" />
+    <path
+       style="color:#000000;solid-opacity:1;fill:#1a8072;fill-opacity:0.85714285;fill-rule:evenodd;stroke:#1241a6;stroke-width:2.04706669;stroke-dashoffset:71.24002075;stroke-opacity:0.53448277"
+       id="path4397"
+       sodipodi:type="arc"
+       sodipodi:cx="22.411266"
+       sodipodi:cy="1036.6479"
+       sodipodi:rx="8.440196"
+       sodipodi:ry="8.0838118"
+       sodipodi:start="2.0413895e-11"
+       sodipodi:end="4.9529128"
+       d="m 30.851462,1036.6479 a 8.440196,8.0838118 0 0 1 -5.684712,7.6409 8.440196,8.0838118 0 0 1 -9.396506,-2.6518 8.440196,8.0838118 0 0 1 -0.45067,-9.3724 8.440196,8.0838118 0 0 1 9.102243,-3.4678 l -2.010551,7.8511 z" />
+  </g>
+</svg>
diff --git a/libs/global/kis_algebra_2d.h b/libs/global/kis_algebra_2d.h
index a64f7874c4f..d0015860268 100644
--- a/libs/global/kis_algebra_2d.h
+++ b/libs/global/kis_algebra_2d.h
@@ -479,6 +479,22 @@ inline QPointF absoluteToRelative(const QPointF &pt, const QRectF &rc) {
  */
 bool KRITAGLOBAL_EXPORT fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta);
 
+/**
+ * Compare two rectangles with tolerance \p tolerance. The tolerance means that the
+ * coordinates of top left and bottom right corners should not differ more than \p tolerance
+ * pixels.
+ */
+template<class Rect, typename Difference = decltype(Rect::width())>
+bool fuzzyCompareRects(const Rect &r1, const Rect &r2, Difference tolerance) {
+    typedef decltype(r1.topLeft()) Point;
+
+    const Point d1 = abs(r1.topLeft() - r2.topLeft());
+    const Point d2 = abs(r1.bottomRight() - r2.bottomRight());
+
+    const Difference maxError = std::max({d1.x(), d1.y(), d2.x(), d2.y()});
+    return maxError < tolerance;
+}
+
 struct KRITAGLOBAL_EXPORT DecomposedMatix {
     DecomposedMatix();
 
diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp
index 6cbf6e881a1..7c0d377b417 100644
--- a/libs/ui/actions/kis_selection_action_factories.cpp
+++ b/libs/ui/actions/kis_selection_action_factories.cpp
@@ -381,14 +381,25 @@ void KisCopyMergedActionFactory::run(KisViewManager *view)
     endAction(ap, KisOperationConfiguration(id()).toXML());
 }
 
-void KisPasteActionFactory::run(KisViewManager *view)
+void KisPasteActionFactory::run(bool pasteAtCursorPosition, KisViewManager *view)
 {
-    KisImageWSP image = view->image();
+    KisImageSP image = view->image();
     if (!image) return;
 
-    KisPaintDeviceSP clip = KisClipboard::instance()->clip(image->bounds(), true);
+    const QRect fittingBounds = pasteAtCursorPosition ? QRect() : image->bounds();
+    KisPaintDeviceSP clip = KisClipboard::instance()->clip(fittingBounds, true);
 
     if (clip) {
+        if (pasteAtCursorPosition) {
+            const QPointF docPos = view->canvasBase()->canvasController()->currentCursorPosition();
+            const QPointF imagePos = view->canvasBase()->coordinatesConverter()->documentToImage(docPos);
+
+            const QPointF offset = (imagePos - QRectF(clip->exactBounds()).center()).toPoint();
+
+            clip->setX(clip->x() + offset.x());
+            clip->setY(clip->y() + offset.y());
+        }
+
         KisImportCatcher::adaptClipToImageColorSpace(clip, image);
         KisPaintLayer *newLayer = new KisPaintLayer(image.data(), image->nextLayerName() + i18n("(pasted)"), OPACITY_OPAQUE_U8, clip);
         KisNodeSP aboveNode = view->activeLayer();
@@ -400,7 +411,7 @@ void KisPasteActionFactory::run(KisViewManager *view)
         endAction(ap, KisOperationConfiguration(id()).toXML());
     } else {
         // XXX: "Add saving of XML data for Paste of shapes"
-        view->canvasBase()->toolProxy()->paste();
+        view->canvasBase()->toolProxy()->paste(pasteAtCursorPosition);
     }
 }
 
diff --git a/libs/ui/actions/kis_selection_action_factories.h b/libs/ui/actions/kis_selection_action_factories.h
index d5a285805d2..a9885c35d72 100644
--- a/libs/ui/actions/kis_selection_action_factories.h
+++ b/libs/ui/actions/kis_selection_action_factories.h
@@ -89,9 +89,14 @@ struct KRITAUI_EXPORT KisCopyMergedActionFactory : public KisNoParameterActionFa
     void run(KisViewManager *view);
 };
 
-struct KRITAUI_EXPORT KisPasteActionFactory : public KisNoParameterActionFactory {
-    KisPasteActionFactory() : KisNoParameterActionFactory("paste-ui-action") {}
-    void run(KisViewManager *view);
+struct KRITAUI_EXPORT KisPasteActionFactory : public KisOperation {
+    KisPasteActionFactory() : KisOperation("paste-ui-action") {}
+
+    void runFromXML(KisViewManager *view, const KisOperationConfiguration &config) {
+        run(config.getBool("paste-at-cursor-position", false), view);
+    }
+
+    void run(bool pasteAtCursorPosition, KisViewManager *view);
 };
 
 struct KRITAUI_EXPORT KisPasteNewActionFactory : public KisNoParameterActionFactory {
diff --git a/libs/ui/canvas/kis_canvas_controller.cpp b/libs/ui/canvas/kis_canvas_controller.cpp
index 2ff3fc13407..a10b8c0208d 100644
--- a/libs/ui/canvas/kis_canvas_controller.cpp
+++ b/libs/ui/canvas/kis_canvas_controller.cpp
@@ -122,6 +122,15 @@ void KisCanvasController::activate()
     KoCanvasControllerWidget::activate();
 }
 
+QPointF KisCanvasController::currentCursorPosition() const
+{
+    KoCanvasBase *canvas = m_d->view->canvasBase();
+    QWidget *canvasWidget = canvas->canvasWidget();
+    const QPointF cursorPosWidget = canvasWidget->mapFromGlobal(QCursor::pos());
+
+    return m_d->coordinatesConverter->widgetToDocument(cursorPosWidget);
+}
+
 void KisCanvasController::keyPressEvent(QKeyEvent *event)
 {
     /**
diff --git a/libs/ui/canvas/kis_canvas_controller.h b/libs/ui/canvas/kis_canvas_controller.h
index c371e2616f4..f0fa9b8c732 100644
--- a/libs/ui/canvas/kis_canvas_controller.h
+++ b/libs/ui/canvas/kis_canvas_controller.h
@@ -42,6 +42,8 @@ public:
     virtual void updateDocumentSize(const QSize &sz, bool recalculateCenter);
     virtual void activate();
 
+    QPointF currentCursorPosition() const override;
+
 public:
     using KoCanvasController::documentSize;
     bool wrapAroundMode() const;
diff --git a/libs/ui/flake/kis_shape_controller.cpp b/libs/ui/flake/kis_shape_controller.cpp
index d108bf59371..cfd3d883d01 100644
--- a/libs/ui/flake/kis_shape_controller.cpp
+++ b/libs/ui/flake/kis_shape_controller.cpp
@@ -198,6 +198,16 @@ void KisShapeController::removeShape(KoShape* shape)
     m_d->doc->setModified(true);
 }
 
+QRectF KisShapeController::documentRectInPixels() const
+{
+    return m_d->doc->image()->bounds();
+}
+
+qreal KisShapeController::pixelsPerInch() const
+{
+    return m_d->doc->image()->xRes() * 72.0;
+}
+
 void KisShapeController::setInitialShapeForCanvas(KisCanvas2 *canvas)
 {
     if (!image()) return;
diff --git a/libs/ui/flake/kis_shape_controller.h b/libs/ui/flake/kis_shape_controller.h
index b25e38d1cbe..01e6dbb0864 100644
--- a/libs/ui/flake/kis_shape_controller.h
+++ b/libs/ui/flake/kis_shape_controller.h
@@ -73,8 +73,11 @@ Q_SIGNALS:
     void currentLayerChanged(const KoShapeLayer*);
 
 public:
-    void addShape(KoShape* shape);
-    void removeShape(KoShape* shape);
+    void addShape(KoShape* shape) override;
+    void removeShape(KoShape* shape) override;
+
+    QRectF documentRectInPixels() const override;
+    qreal pixelsPerInch() const override;
 
 private:
     struct Private;
diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc
index 8912eb5001f..4883f647776 100644
--- a/libs/ui/kis_selection_manager.cc
+++ b/libs/ui/kis_selection_manager.cc
@@ -383,12 +383,13 @@ void KisSelectionManager::copyMerged()
 void KisSelectionManager::paste()
 {
     KisPasteActionFactory factory;
-    factory.run(m_view);
+    factory.run(false, m_view);
 }
 
 void KisSelectionManager::pasteAt()
 {
-    //XXX
+    KisPasteActionFactory factory;
+    factory.run(true, m_view);
 }
 
 void KisSelectionManager::pasteNew()
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
index faa81d799f0..fe7101368ac 100644
--- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
+++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
@@ -59,7 +59,6 @@
 
 #include <QAction>
 #include <QKeyEvent>
-#include <QClipboard>
 #include <KoResourcePaths.h>
 
 #include <KoCanvasController.h>
@@ -815,11 +814,12 @@ void DefaultTool::repaintDecorations()
 
 void DefaultTool::copy() const
 {
+    // all the selected shapes, not only editable!
     QList<KoShape *> shapes = canvas()->selectedShapesProxy()->selection()->selectedShapes();
-    if (!shapes.empty()) {
-        KoShapeOdfSaveHelper saveHelper(shapes);
+
+    if (!shapes.isEmpty()) {
         KoDrag drag;
-        drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper);
+        drag.setSvg(shapes);
         drag.addToClipboard();
     }
 }


More information about the kimageshop mailing list