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@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 @@ true - - - Paste at cursor - - Paste at cursor - Paste at cursor - 0 - 0 - - false - - &Invert Selection 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 @@ @@ -49,6 +49,7 @@ xsi:schemaLocation=3D"http://www.kde.org/standards/kxmlgu= i/1.0 http://www.kde.org + 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 @@ false + + + Paste at Cursor + + Paste at cursor + Paste at cursor + 0 + 0 + Ctrl+Alt+V + false + + Paste into &New Image 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/KoCanvasControlle= r.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::po= s() and + * converted into document coordinates + */ + virtual QPointF currentCursorPosition() const =3D 0; + protected: void setDocumentSize(const QSize &sz); QSize documentSize() const; @@ -465,6 +471,7 @@ public: virtual void updateDocumentSize(const QSize &/*sz*/, bool /*recalculat= eCenter*/) {} 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/KoCanvasC= ontrollerWidget.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 =3D factor; } = +QPointF KoCanvasControllerWidget::currentCursorPosition() const +{ + QWidget *canvasWidget =3D d->canvas->canvasWidget(); + const KoViewConverter *converter =3D d->canvas->viewConverter(); + return converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::= pos()) + d->canvas->canvasController()->documentOffset() - canvasWidget->po= s()); +} + void KoCanvasControllerWidget::pan(const QPoint &distance) { QPoint sourcePoint =3D scrollBarValue(); diff --git a/libs/flake/KoCanvasControllerWidget.h b/libs/flake/KoCanvasCon= trollerWidget.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 #include "KoShapeSavingContext.h" = +#include +#include +#include + + 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 =3D KoStore::createStore(&buffer, KoStore::Write, mimeT= ype); - Finally finally(store); // delete store when we exit this scope - Q_ASSERT(store); - Q_ASSERT(!store->bad()); - - KoOdfWriteStore odfStore(store); - KoEmbeddedDocumentSaver embeddedSaver; - - KoXmlWriter *manifestWriter =3D odfStore.manifestWriter(mimeType); - KoXmlWriter *contentWriter =3D odfStore.contentWriter(); - - if (!contentWriter) { - return false; - } + return false; +} = - KoGenStyles mainStyles; - KoXmlWriter *bodyWriter =3D odfStore.bodyWriter(); - KoShapeSavingContext *context =3D helper.context(bodyWriter, mainStyle= s, embeddedSaver); +bool KoDrag::setSvg(const QList originalShapes) +{ + QRectF boundingRect; + QList shapes; = - if (!helper.writeBody()) { - return false; + Q_FOREACH (KoShape *shape, originalShapes) { + boundingRect |=3D shape->boundingRect(); + shapes.append(shape->cloneShape()); } = - mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, content= Writer); + 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 =3D 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 + 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 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 &shapes) +{ + QRectF boundingRect; + Q_FOREACH (KoShape *shape, shapes) { + boundingRect |=3D shape->boundingRect(); + } + return boundingRect; +} + QTransform KoShape::absoluteTransformation(const KoViewConverter *converte= r) 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 &shapes); + + /** * @brief Add a connector point to the shape * * A connector is a place on the shape that allows a graphical connect= ion to be made diff --git a/libs/flake/KoShapeBasedDocumentBase.cpp b/libs/flake/KoShapeBa= sedDocumentBase.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 #include "KoShapeBasedDocumentBase.h" #include "KoDocumentResourceManager.h" #include "KoShapeRegistry.h" @@ -41,17 +42,15 @@ public: } // read persistent application wide resources KSharedConfigPtr config =3D KSharedConfig::openConfig(); - if (config->hasGroup("Misc")) { - KConfigGroup miscGroup =3D config->group("Misc"); - const qreal pasteOffset =3D miscGroup.readEntry("CopyOffset", = 10.0); - resourceManager->setPasteOffset(pasteOffset); - const bool pasteAtCursor =3D miscGroup.readEntry("PasteAtCurso= r", true); - resourceManager->enablePasteAtCursor(pasteAtCursor); - const uint grabSensitivity =3D miscGroup.readEntry("GrabSensit= ivity", 3); - resourceManager->setGrabSensitivity(grabSensitivity); - const uint handleRadius =3D miscGroup.readEntry("HandleRadius"= , 3); - resourceManager->setHandleRadius(handleRadius); - } + KConfigGroup miscGroup =3D config->group("Misc"); + const qreal pasteOffset =3D miscGroup.readEntry("CopyOffset", 10.0= ); + resourceManager->setPasteOffset(pasteOffset); + const bool pasteAtCursor =3D miscGroup.readEntry("PasteAtCursor", = true); + resourceManager->enablePasteAtCursor(pasteAtCursor); + const uint grabSensitivity =3D miscGroup.readEntry("GrabSensitivit= y", 3); + resourceManager->setGrabSensitivity(grabSensitivity); + const uint handleRadius =3D miscGroup.readEntry("HandleRadius", 3); + resourceManager->setHandleRadius(handleRadius); } = ~KoShapeBasedDocumentBasePrivate() @@ -80,3 +79,11 @@ KoDocumentResourceManager *KoShapeBasedDocumentBase::res= ourceManager() const { return d->resourceManager; } + +QRectF KoShapeBasedDocumentBase::documentRect() const +{ + const qreal pxToPt =3D 72.0 / pixelsPerInch(); + + QTransform t =3D QTransform::fromScale(pxToPt, pxToPt); + return t.mapRect(documentRectInPixels()); +} diff --git a/libs/flake/KoShapeBasedDocumentBase.h b/libs/flake/KoShapeBase= dDocumentBase.h index b2f3565b034..95e6327d6d5 100644 --- a/libs/flake/KoShapeBasedDocumentBase.h +++ b/libs/flake/KoShapeBasedDocumentBase.h @@ -27,6 +27,7 @@ = #include = +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 inform= ation is needed for loading + * SVG documents that use 'px' as the default unit. + */ + virtual QRectF documentRectInPixels() const =3D 0; + + /** + * The size of the document measured in 'pt' + */ + QRectF documentRect() const; + + /** + * Resolution of the rasterized representaiton of the document. Used t= o load SVG documents correctly. + */ + virtual qreal pixelsPerInch() const =3D 0; + private: KoShapeBasedDocumentBasePrivate * const d; }; diff --git a/libs/flake/KoShapeController.cpp b/libs/flake/KoShapeControlle= r.cpp index 7d4559247ad..d2f692fd348 100644 --- a/libs/flake/KoShapeController.cpp +++ b/libs/flake/KoShapeController.cpp @@ -172,6 +172,21 @@ void KoShapeController::setShapeControllerBase(KoShape= BasedDocumentBase *shapeBa d->shapeBasedDocument =3D shapeBasedDocument; } = +QRectF KoShapeController::documentRectInPixels() const +{ + return d->shapeBasedDocument ? d->shapeBasedDocument->documentRectInPi= xels() : 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 *shapeBasedDocume= nt); = /** + * The size of the document measured in rasterized pixels. This inform= ation 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 t= o 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 + * + * 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-13= 01, USA. + */ + +#include "KoSvgPaste.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +KoSvgPaste::KoSvgPaste() +{ +} + +bool KoSvgPaste::hasShapes() const +{ + const QMimeData *mimeData =3D QApplication::clipboard()->mimeData(); + return mimeData && mimeData->hasFormat("image/svg+xml"); +} + +QList KoSvgPaste::fetchShapes(const QRectF viewportInPx, qreal r= esolutionPPI, QSizeF *fragmentSize) const +{ + QList shapes; + + const QMimeData *mimeData =3D QApplication::clipboard()->mimeData(); + if (!mimeData) return shapes; + + QByteArray data =3D mimeData->data("image/svg+xml"); + if (data.isEmpty()) return shapes; + + KoXmlDocument doc; + + QString errorMsg; + int errorLine =3D 0; + int errorColumn =3D 0; + + const bool documentValid =3D doc.setContent(data, false, &errorMsg, &e= rrorLine, &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 =3D 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 + * + * 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-13= 01, USA. + */ + +#ifndef KOSVGPASTE_H +#define KOSVGPASTE_H + +#include "kritaflake_export.h" +#include + +class KoShape; +class QRectF; +class QSizeF; + +class KRITAFLAKE_EXPORT KoSvgPaste +{ +public: + KoSvgPaste(); + + bool hasShapes() const; + QList fetchShapes(const QRectF viewportInPx, qreal resolutio= nPPI, QSizeF *fragmentSize =3D 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 + // Qt + kde #include #include @@ -374,13 +376,17 @@ KoCanvasController *KoToolManager::activeCanvasContro= ller() const QString KoToolManager::preferredToolForSelection(const QList &sh= apes) { QList 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 =3D KoInteractionTool_ID; int prio =3D INT_MAX; Q_FOREACH (ToolHelper *helper, d->tools) { + if (helper->id() =3D=3D KoCreateShapesTool_ID) continue; + if (helper->priority() >=3D 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 +#include +#include "kis_algebra_2d.h" +#include +#include + = KoToolProxyPrivate::KoToolProxyPrivate(KoToolProxy *p) : activeTool(0), @@ -456,27 +462,127 @@ void KoToolProxy::copy() const d->activeTool->copy(); } = -bool KoToolProxy::paste() + +namespace { +QPointF getFittingOffset(QList shapes, + const QPointF &shapesOffset, + const QRectF &documentRect, + const qreal fitRatio) +{ + QPointF accumulatedFitOffset; + + Q_FOREACH (KoShape *shape, shapes) { + const QRectF bounds =3D shape->boundingRect(); + + const QPointF center =3D bounds.center() + shapesOffset; + + const qreal wMargin =3D (0.5 - fitRatio) * bounds.width(); + const qreal hMargin =3D (0.5 - fitRatio) * bounds.height(); + const QRectF allowedRect =3D documentRect.adjusted(-wMargin, -hMar= gin, wMargin, hMargin); + + const QPointF fittedCenter =3D KisAlgebra2D::clampPoint(center, al= lowedRect); + + accumulatedFitOffset +=3D fittedCenter - center; + } + + return accumulatedFitOffset; +} +} + +bool KoToolProxy::paste(bool pasteAtCursorPosition) { bool success =3D false; KoCanvasBase *canvas =3D d->controller->canvas(); = - if (d->activeTool && d->isActiveLayerEditable()) + if (d->activeTool && d->isActiveLayerEditable()) { success =3D d->activeTool->paste(); + } = - if (!success) { - const QMimeData *data =3D QApplication::clipboard()->mimeData(); + KoSvgPaste paste; + if (!success && paste.hasShapes()) { + QSizeF fragmentSize; + + QList shapes =3D + paste.fetchShapes(canvas->shapeController()->documentRectInPix= els(), + canvas->shapeController()->pixelsPerInch(), = &fragmentSize); = - if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))) { + if (!shapes.isEmpty()) { KoShapeManager *shapeManager =3D canvas->shapeManager(); - KoShapePaste paste(canvas, shapeManager->selection()->activeLa= yer()); - success =3D 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 =3D new KUndo2Command(kundo2_i18n= ("Paste shapes")); + + Q_FOREACH (KoShape *shape, shapes) { + canvas->shapeController()->addShapeDirect(shape, parentCom= mand); + } + + QPointF finalShapesOffset; + + + if (pasteAtCursorPosition) { + QRectF boundingRect =3D KoShape::boundingRect(shapes); + const QPointF cursorPos =3D canvas->canvasController()->cu= rrentCursorPosition(); + finalShapesOffset =3D cursorPos - boundingRect.center(); + + } else { + bool foundOverlapping =3D false; + + QRectF boundingRect =3D KoShape::boundingRect(shapes); + const QPointF offsetStep =3D 0.1 * QPointF(boundingRect.wi= dth(), boundingRect.height()); + + QPointF offset; + + Q_FOREACH (KoShape *shape, shapes) { + QRectF br1 =3D shape->boundingRect(); + + bool hasOverlappingShape =3D false; + + do { + hasOverlappingShape =3D false; + + // we cannot use shapesAt() here, because the grou= ps are not + // handled in the shape manager's tree + QList conflicts =3D shapeManager->shapes= (); + + Q_FOREACH (KoShape *intersectedShape, conflicts) { + if (intersectedShape =3D=3D shape) continue; + + QRectF br2 =3D intersectedShape->boundingRect(= ); + + const qreal tolerance =3D 2.0; /* pt */ + if (KisAlgebra2D::fuzzyCompareRects(br1, br2, = tolerance)) { + br1.translate(offsetStep.x(), offsetStep.y= ()); + offset +=3D offsetStep; + + hasOverlappingShape =3D true; + foundOverlapping =3D true; + break; + } + } + } while (hasOverlappingShape); + + if (foundOverlapping) break; + } + + if (foundOverlapping) { + finalShapesOffset =3D offset; } } + + const QRectF documentRect =3D canvas->shapeController()->docum= entRect(); + finalShapesOffset +=3D getFittingOffset(shapes, finalShapesOff= set, documentRect, 0.1); + + if (!finalShapesOffset.isNull()) { + new KoShapeMoveCommand(shapes, finalShapesOffset, parentCo= mmand); + } + + canvas->addCommand(parentCommand); + + Q_FOREACH (KoShape *shape, shapes) { + canvas->selectedShapesProxy()->selection()->select(shape); + } + + success =3D 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 =3D false); = /// Forwarded to the current KoToolBase QStringList supportedPasteMimeTypes() const; diff --git a/libs/flake/commands/KoShapeMoveCommand.cpp b/libs/flake/comman= ds/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 &shapes, QLis= t &previousPositions, QList &newPositions, KoFlake::Ancho= rPosition anchor, KUndo2Command *parent) - : KUndo2Command(parent), + : KUndo2Command(kundo2_i18n("Move shapes"), parent), d(new Private()) { d->shapes =3D shapes; @@ -42,8 +42,21 @@ KoShapeMoveCommand::KoShapeMoveCommand(const QList &shapes, QListanchor =3D anchor; Q_ASSERT(d->shapes.count() =3D=3D d->previousPositions.count()); Q_ASSERT(d->shapes.count() =3D=3D d->newPositions.count()); +} + +KoShapeMoveCommand::KoShapeMoveCommand(const QList &shapes, con= st QPointF &offset, KUndo2Command *parent) + : KUndo2Command(kundo2_i18n("Move shapes"), parent), + d(new Private()) +{ + d->shapes =3D shapes; + d->anchor =3D KoFlake::Center; + + Q_FOREACH (KoShape *shape, d->shapes) { + const QPointF pos =3D 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 &shapes, QList &prev= iousPositions, QList &newPositions, KoFlake::AnchorPosition anchor =3D KoFlake::Center,= KUndo2Command *parent =3D 0); + + KoShapeMoveCommand(const QList &shapes, const QPointF &offse= t, KUndo2Command *parent =3D 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 SvgParser::parseSingleElement(const K= oXmlElement &b) */ KoShape *defsShape =3D parseGroup(b); defsShape->setVisible(false); - shapes +=3D defsShape; } } else if (b.tagName() =3D=3D "linearGradient" || b.tagName() =3D=3D "= radialGradient") { } else if (b.tagName() =3D=3D "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 =3D shapeManager; } = + QRectF documentRectInPixels() const { + return QRectF(0,0,100,100); + } + + qreal pixelsPerInch() const { + return 72.0; + } + private: QSet m_shapes; KoShapeManager *m_shapeManager =3D 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 + * + * 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-13= 01, USA. + */ + +#include "TestKoDrag.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "../../sdk/tests/qimage_test_util.h" + +void TestKoDrag::test() +{ + const QString fileName =3D 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 shapes =3D parser.parseSvg(doc.documentElement(), &fra= gmentSize); + QCOMPARE(fragmentSize, QSizeF(30,30)); + + { + QCOMPARE(shapes.size(), 1); + + KoShapeGroup *layer =3D dynamic_cast(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 newShapes =3D paste.fetchShapes(QRectF(0,0,15,15) /* p= x */, 144 /* ppi */, &fragmentSize); + + { + QCOMPARE(newShapes.size(), 1); + + KoShapeGroup *layer =3D dynamic_cast(newShapes.firs= t()); + 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 + * + * 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-13= 01, USA. + */ + +#ifndef TESTKODRAG_H +#define TESTKODRAG_H + +#include +#include + +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/dat= a/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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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, c= onst QRectF &rc) { */ bool KRITAGLOBAL_EXPORT fuzzyMatrixCompare(const QTransform &t1, const QTr= ansform &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 +bool fuzzyCompareRects(const Rect &r1, const Rect &r2, Difference toleranc= e) { + typedef decltype(r1.topLeft()) Point; + + const Point d1 =3D abs(r1.topLeft() - r2.topLeft()); + const Point d2 =3D abs(r1.bottomRight() - r2.bottomRight()); + + const Difference maxError =3D 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/a= ctions/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 =3D view->image(); + KisImageSP image =3D view->image(); if (!image) return; = - KisPaintDeviceSP clip =3D KisClipboard::instance()->clip(image->bounds= (), true); + const QRect fittingBounds =3D pasteAtCursorPosition ? QRect() : image-= >bounds(); + KisPaintDeviceSP clip =3D KisClipboard::instance()->clip(fittingBounds= , true); = if (clip) { + if (pasteAtCursorPosition) { + const QPointF docPos =3D view->canvasBase()->canvasController(= )->currentCursorPosition(); + const QPointF imagePos =3D view->canvasBase()->coordinatesConv= erter()->documentToImage(docPos); + + const QPointF offset =3D (imagePos - QRectF(clip->exactBounds(= )).center()).toPoint(); + + clip->setX(clip->x() + offset.x()); + clip->setY(clip->y() + offset.y()); + } + KisImportCatcher::adaptClipToImageColorSpace(clip, image); KisPaintLayer *newLayer =3D new KisPaintLayer(image.data(), image-= >nextLayerName() + i18n("(pasted)"), OPACITY_OPAQUE_U8, clip); KisNodeSP aboveNode =3D 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/act= ions/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 : publi= c KisNoParameterActionFa void run(KisViewManager *view); }; = -struct KRITAUI_EXPORT KisPasteActionFactory : public KisNoParameterActionF= actory { - 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 KisNoParameterActi= onFactory { 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 =3D m_d->view->canvasBase(); + QWidget *canvasWidget =3D canvas->canvasWidget(); + const QPointF cursorPosWidget =3D 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_ca= nvas_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 recalculateCente= r); 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_sha= pe_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_manag= er.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/plugin= s/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 #include -#include #include = #include @@ -815,11 +814,12 @@ void DefaultTool::repaintDecorations() = void DefaultTool::copy() const { + // all the selected shapes, not only editable! QList shapes =3D 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(); } }