diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt index a0523a67a9..1793a0d3ef 100644 --- a/libs/flake/CMakeLists.txt +++ b/libs/flake/CMakeLists.txt @@ -1,235 +1,236 @@ project(kritaflake) include_directories( ${CMAKE_SOURCE_DIR}/libs/flake/commands ${CMAKE_SOURCE_DIR}/libs/flake/tools ${CMAKE_SOURCE_DIR}/libs/flake/svg ${CMAKE_SOURCE_DIR}/libs/flake/text ${CMAKE_BINARY_DIR}/libs/flake ) add_subdirectory(styles) add_subdirectory(tests) add_subdirectory(resources/tests) set(kritaflake_SRCS KoGradientHelper.cpp KoFlake.cpp KoCanvasBase.cpp KoResourceManager_p.cpp KoDerivedResourceConverter.cpp KoResourceUpdateMediator.cpp KoCanvasResourceProvider.cpp KoDocumentResourceManager.cpp KoCanvasObserverBase.cpp KoCanvasSupervisor.cpp KoDockFactoryBase.cpp KoDockRegistry.cpp KoDataCenterBase.cpp KoInsets.cpp KoPathShape.cpp KoPathPoint.cpp KoPathSegment.cpp KoSelection.cpp KoSelectedShapesProxy.cpp KoSelectedShapesProxySimple.cpp KoShape.cpp KoShapeAnchor.cpp KoShapeControllerBase.cpp KoShapeApplicationData.cpp KoShapeContainer.cpp KoShapeContainerModel.cpp KoShapeGroup.cpp KoShapeManager.cpp KoShapePaintingContext.cpp KoFrameShape.cpp KoMarker.cpp KoMarkerCollection.cpp KoToolBase.cpp KoCanvasController.cpp KoCanvasControllerWidget.cpp KoCanvasControllerWidgetViewport_p.cpp KoShapeRegistry.cpp KoDeferredShapeFactoryBase.cpp KoToolFactoryBase.cpp KoPathShapeFactory.cpp KoShapeFactoryBase.cpp KoShapeUserData.cpp KoParameterShape.cpp KoPointerEvent.cpp KoShapeController.cpp KoToolSelection.cpp KoShapeLayer.cpp KoPostscriptPaintDevice.cpp KoInputDevice.cpp KoToolManager_p.cpp KoToolManager.cpp KoToolRegistry.cpp KoToolProxy.cpp KoShapeSavingContext.cpp KoShapeLoadingContext.cpp KoLoadingShapeUpdater.cpp KoPathShapeLoader.cpp KoShapeStrokeModel.cpp KoShapeStroke.cpp KoShapeBackground.cpp KoColorBackground.cpp KoGradientBackground.cpp KoHatchBackground.cpp KoPatternBackground.cpp KoVectorPatternBackground.cpp KoShapeFillWrapper.cpp KoShapeFillResourceConnector.cpp KoShapeConfigWidgetBase.cpp KoDrag.cpp KoSvgPaste.cpp KoSnapGuide.cpp KoSnapProxy.cpp KoSnapStrategy.cpp KoSnapData.cpp KoShapeShadow.cpp KoSharedLoadingData.cpp KoSharedSavingData.cpp KoViewConverter.cpp KoInputDeviceHandler.cpp KoInputDeviceHandlerEvent.cpp KoInputDeviceHandlerRegistry.cpp KoImageData.cpp KoImageData_p.cpp KoImageCollection.cpp KoFilterEffect.cpp KoFilterEffectStack.cpp KoFilterEffectFactoryBase.cpp KoFilterEffectRegistry.cpp KoFilterEffectConfigWidgetBase.cpp KoFilterEffectRenderContext.cpp KoFilterEffectLoadingContext.cpp KoTextShapeDataBase.cpp KoTosContainer.cpp KoTosContainerModel.cpp KoClipPath.cpp KoClipMask.cpp KoClipMaskPainter.cpp KoCurveFit.cpp commands/KoShapeGroupCommand.cpp commands/KoShapeAlignCommand.cpp commands/KoShapeBackgroundCommand.cpp commands/KoShapeCreateCommand.cpp commands/KoShapeDeleteCommand.cpp commands/KoShapeDistributeCommand.cpp commands/KoShapeLockCommand.cpp commands/KoShapeMoveCommand.cpp commands/KoShapeResizeCommand.cpp commands/KoShapeShearCommand.cpp commands/KoShapeSizeCommand.cpp commands/KoShapeStrokeCommand.cpp commands/KoShapeUngroupCommand.cpp commands/KoShapeReorderCommand.cpp commands/KoShapeKeepAspectRatioCommand.cpp commands/KoPathBaseCommand.cpp commands/KoPathPointMoveCommand.cpp commands/KoPathControlPointMoveCommand.cpp commands/KoPathPointTypeCommand.cpp commands/KoPathPointRemoveCommand.cpp commands/KoPathPointInsertCommand.cpp commands/KoPathSegmentBreakCommand.cpp commands/KoPathBreakAtPointCommand.cpp commands/KoPathSegmentTypeCommand.cpp commands/KoPathCombineCommand.cpp commands/KoSubpathRemoveCommand.cpp commands/KoSubpathJoinCommand.cpp commands/KoParameterHandleMoveCommand.cpp commands/KoParameterToPathCommand.cpp commands/KoShapeTransformCommand.cpp commands/KoPathFillRuleCommand.cpp commands/KoShapeShadowCommand.cpp commands/KoPathReverseCommand.cpp commands/KoShapeRenameCommand.cpp commands/KoShapeRunAroundCommand.cpp commands/KoPathPointMergeCommand.cpp commands/KoShapeTransparencyCommand.cpp commands/KoShapeClipCommand.cpp commands/KoShapeUnclipCommand.cpp commands/KoPathShapeMarkerCommand.cpp commands/KoMultiPathPointMergeCommand.cpp commands/KoMultiPathPointJoinCommand.cpp commands/KoKeepShapesSelectedCommand.cpp commands/KoPathMergeUtils.cpp + commands/KoAddRemoveShapeCommands.cpp html/HtmlSavingContext.cpp html/HtmlWriter.cpp tools/KoPathToolFactory.cpp tools/KoPathTool.cpp tools/KoPathToolSelection.cpp tools/KoPathToolHandle.cpp tools/PathToolOptionWidget.cpp tools/KoPathPointRubberSelectStrategy.cpp tools/KoPathPointMoveStrategy.cpp tools/KoPathControlPointMoveStrategy.cpp tools/KoParameterChangeStrategy.cpp tools/KoZoomTool.cpp tools/KoZoomToolFactory.cpp tools/KoZoomToolWidget.cpp tools/KoZoomStrategy.cpp tools/KoInteractionTool.cpp tools/KoInteractionStrategy.cpp tools/KoInteractionStrategyFactory.cpp tools/KoShapeRubberSelectStrategy.cpp tools/KoPathSegmentChangeStrategy.cpp svg/KoShapePainter.cpp svg/SvgUtil.cpp svg/SvgGraphicContext.cpp svg/SvgSavingContext.cpp svg/SvgWriter.cpp svg/SvgStyleWriter.cpp svg/SvgShape.cpp svg/SvgParser.cpp svg/SvgStyleParser.cpp svg/SvgGradientHelper.cpp svg/SvgFilterHelper.cpp svg/SvgCssHelper.cpp svg/SvgClipPathHelper.cpp svg/SvgLoadingContext.cpp svg/SvgShapeFactory.cpp svg/parsers/SvgTransformParser.cpp text/KoSvgText.cpp text/KoSvgTextProperties.cpp text/KoSvgTextChunkShape.cpp text/KoSvgTextShape.cpp text/KoSvgTextShapeMarkupConverter.cpp resources/KoSvgSymbolCollectionResource.cpp resources/KoGamutMask.cpp FlakeDebug.cpp tests/MockShapes.cpp ) ki18n_wrap_ui(kritaflake_SRCS tools/PathToolOptionWidgetBase.ui tools/KoZoomToolWidget.ui ) add_library(kritaflake SHARED ${kritaflake_SRCS}) generate_export_header(kritaflake BASE_NAME kritaflake) target_include_directories(kritaflake PUBLIC $ $ $ $ ) target_link_libraries(kritaflake kritapigment kritawidgetutils kritacommand KF5::WidgetsAddons Qt5::Svg KF5::CoreAddons KF5::ConfigCore KF5::I18n Qt5::Gui Qt5::Xml) set_target_properties(kritaflake PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaflake ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/flake/KoShapeController.cpp b/libs/flake/KoShapeController.cpp index 8cc064e320..23fb99c4e4 100644 --- a/libs/flake/KoShapeController.cpp +++ b/libs/flake/KoShapeController.cpp @@ -1,190 +1,196 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2006-2008 Thorsten Zachmann * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeController.h" #include "KoShapeControllerBase.h" #include "KoShapeRegistry.h" #include "KoDocumentResourceManager.h" #include "KoShapeManager.h" #include "KoShapeLayer.h" #include "KoSelection.h" #include "commands/KoShapeCreateCommand.h" #include "commands/KoShapeDeleteCommand.h" #include "KoCanvasBase.h" #include "KoShapeConfigWidgetBase.h" #include "KoShapeFactoryBase.h" #include "KoShape.h" #include #include #include #include class KoShapeController::Private { public: Private() : canvas(0), shapeController(0) { } KoCanvasBase *canvas; KoShapeControllerBase *shapeController; KUndo2Command* addShape(KoShape *shape, bool showDialog, KoShapeContainer *parentShape, KUndo2Command *parent) { if (canvas) { if (showDialog && !shape->shapeId().isEmpty()) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shape->shapeId()); Q_ASSERT(factory); qint16 z = 0; Q_FOREACH (KoShape *sh, canvas->shapeManager()->shapes()) { z = qMax(z, sh->zIndex()); } shape->setZIndex(z + 1); // show config dialog. KPageDialog *dialog = new KPageDialog(canvas->canvasWidget()); dialog->setWindowTitle(i18n("%1 Options", factory->name())); int pageCount = 0; QList widgets; Q_FOREACH (KoShapeConfigWidgetBase* panel, factory->createShapeOptionPanels()) { if (! panel->showOnShapeCreate()) continue; panel->open(shape); panel->connect(panel, SIGNAL(accept()), dialog, SLOT(accept())); widgets.append(panel); panel->setResourceManager(canvas->resourceManager()); panel->setUnit(canvas->unit()); QString title = panel->windowTitle().isEmpty() ? panel->objectName() : panel->windowTitle(); dialog->addPage(panel, title); pageCount ++; } if (pageCount > 0) { if (pageCount > 1) dialog->setFaceType(KPageDialog::Tabbed); if (dialog->exec() != KPageDialog::Accepted) { delete dialog; return 0; } Q_FOREACH (KoShapeConfigWidgetBase *widget, widgets) widget->save(); } delete dialog; } } return addShapesDirect({shape}, parentShape, parent); } KUndo2Command* addShapesDirect(const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) { - return new KoShapeCreateCommand(shapeController, shapes, parentShape, parent); + KUndo2Command *resultCommand = 0; + + if (!parentShape) { + resultCommand = new KUndo2Command(parent); + parentShape = shapeController->createParentForShapes(shapes, resultCommand); + KUndo2Command *addShapeCommand = new KoShapeCreateCommand(shapeController, shapes, parentShape, resultCommand); + resultCommand->setText(addShapeCommand->text()); + } else { + resultCommand = new KoShapeCreateCommand(shapeController, shapes, parentShape, parent); + } + + return resultCommand; } }; KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeControllerBase *shapeController) : d(new Private()) { d->canvas = canvas; d->shapeController = shapeController; } KoShapeController::~KoShapeController() { delete d; } void KoShapeController::reset() { d->canvas = 0; d->shapeController = 0; } KUndo2Command* KoShapeController::addShape(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) { return d->addShape(shape, true, parentShape, parent); } KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) { return d->addShapesDirect({shape}, parentShape, parent); } KUndo2Command *KoShapeController::addShapesDirect(const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) { return d->addShapesDirect(shapes, parentShape, parent); } KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent) { - KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeController, shape, parent); - QList shapes; - shapes.append(shape); - d->shapeController->shapesRemoved(shapes, cmd); - return cmd; + return removeShapes({shape}, parent); } KUndo2Command* KoShapeController::removeShapes(const QList &shapes, KUndo2Command *parent) { KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeController, shapes, parent); - d->shapeController->shapesRemoved(shapes, cmd); return cmd; } void KoShapeController::setShapeControllerBase(KoShapeControllerBase *shapeController) { d->shapeController = shapeController; } QRectF KoShapeController::documentRectInPixels() const { return d->shapeController ? d->shapeController->documentRectInPixels() : QRectF(0,0,1920,1080); } qreal KoShapeController::pixelsPerInch() const { return d->shapeController ? d->shapeController->pixelsPerInch() : 72.0; } QRectF KoShapeController::documentRect() const { return d->shapeController ? d->shapeController->documentRect() : documentRectInPixels(); } KoDocumentResourceManager *KoShapeController::resourceManager() const { if (!d->shapeController) { qWarning() << "THIS IS NOT GOOD!"; return 0; } return d->shapeController->resourceManager(); } KoShapeControllerBase *KoShapeController::documentBase() const { return d->shapeController; } diff --git a/libs/flake/KoShapeControllerBase.cpp b/libs/flake/KoShapeControllerBase.cpp index ce0e445d25..824bf8cd3c 100644 --- a/libs/flake/KoShapeControllerBase.cpp +++ b/libs/flake/KoShapeControllerBase.cpp @@ -1,92 +1,92 @@ /* This file is part of the KDE project Copyright (C) 2006, 2010 Thomas Zander Copyright (C) 2011 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include "KoShapeControllerBase.h" #include "KoDocumentResourceManager.h" #include "KoShapeRegistry.h" #include "KoShapeFactoryBase.h" #include #include #include +#include class KoshapeControllerBasePrivate { public: KoshapeControllerBasePrivate() : resourceManager(new KoDocumentResourceManager()) { KoShapeRegistry *registry = KoShapeRegistry::instance(); foreach (const QString &id, registry->keys()) { KoShapeFactoryBase *shapeFactory = registry->value(id); shapeFactory->newDocumentResourceManager(resourceManager); } // read persistent application wide resources KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup miscGroup = config->group("Misc"); const uint grabSensitivity = miscGroup.readEntry("GrabSensitivity", 10); resourceManager->setGrabSensitivity(grabSensitivity); const uint handleRadius = miscGroup.readEntry("HandleRadius", 5); resourceManager->setHandleRadius(handleRadius); } ~KoshapeControllerBasePrivate() { delete resourceManager; } QPointer resourceManager; }; KoShapeControllerBase::KoShapeControllerBase() : d(new KoshapeControllerBasePrivate()) { } KoShapeControllerBase::~KoShapeControllerBase() { delete d; } -void KoShapeControllerBase::addShape(KoShape *shape) +KoShapeContainer* KoShapeControllerBase::createParentForShapes(const QList shapes, KUndo2Command *parentCommand) { - addShapes({shape}); -} + Q_UNUSED(parentCommand); + Q_UNUSED(shapes); -void KoShapeControllerBase::shapesRemoved(const QList & /*shapes*/, KUndo2Command * /*command*/) -{ + return 0; } KoDocumentResourceManager *KoShapeControllerBase::resourceManager() const { return d->resourceManager; } QRectF KoShapeControllerBase::documentRect() const { const qreal pxToPt = 72.0 / pixelsPerInch(); QTransform t = QTransform::fromScale(pxToPt, pxToPt); return t.mapRect(documentRectInPixels()); } diff --git a/libs/flake/KoShapeControllerBase.h b/libs/flake/KoShapeControllerBase.h index f14eee34a5..6491dfa700 100644 --- a/libs/flake/KoShapeControllerBase.h +++ b/libs/flake/KoShapeControllerBase.h @@ -1,110 +1,88 @@ /* This file is part of the KDE project Copyright (C) 2006 Jan Hambrecht Copyright (C) 2006, 2010 Thomas Zander Copyright (C) 2008 C. Boemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOshapeControllerBASE_H #define KOshapeControllerBASE_H #include "kritaflake_export.h" #include class QRectF; class KoShape; +class KoShapeContainer; class KoshapeControllerBasePrivate; class KoDocumentResourceManager; class KUndo2Command; /** * The KoshapeControllerBase is an abstract interface that the application's class * that owns the shapes should implement. This tends to be the document. * @see KoShapeDeleteCommand, KoShapeCreateCommand */ class KRITAFLAKE_EXPORT KoShapeControllerBase { public: KoShapeControllerBase(); virtual ~KoShapeControllerBase(); /** - * Add a shape to the shape controller, allowing it to be seen and saved. - * The controller should add the shape to the ShapeManager instance(s) manually - * if the shape is one that should be currently shown on screen. - * @param shape the new shape - */ - void addShape(KoShape *shape); - - /** - * Add shapes to the shape controller, allowing it to be seen and saved. - * The controller should add the shape to the ShapeManager instance(s) manually - * if the shape is one that should be currently shown on screen. - * @param shapes the shapes to add - */ - virtual void addShapes(const QList shapes) = 0; - - /** - * Remove a shape from the shape controllers control, allowing it to be deleted shortly after - * The controller should remove the shape from all the ShapeManager instance(s) manually - * @param shape the shape to remove - */ - virtual void removeShape(KoShape *shape) = 0; - - /** - * This method gets called after the KoShapeDeleteCommand is executed + * When shapes are dropped to the canvas, the document should decide, where to + * which parent to put them. In some cases the document should even create a + * special layer for the new \p shapes. * - * This passes the KoShapeDeleteCommand as the command parameter. This makes it possible - * for applications that need to do something after the KoShapeDeleteCommand is done, e.g. - * adding one commands that need to be executed when a shape was deleted. - * The default implementation is empty. - * @param shapes The list of shapes that got removed. - * @param command The command that was used to remove the shapes from the document. + * \return the proposed parent for \p shapes + * \param parentCommand the command, which should be executed before the + * proposed parent will be added to the document (if + * new layer should be created) */ - virtual void shapesRemoved(const QList &shapes, KUndo2Command *command); + virtual KoShapeContainer* createParentForShapes(const QList shapes, KUndo2Command *parentCommand); /** * 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 * collection and others. */ 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 representation of the document. Used to load SVG documents correctly. */ virtual qreal pixelsPerInch() const = 0; private: KoshapeControllerBasePrivate * const d; }; #endif diff --git a/libs/flake/SimpleShapeContainerModel.h b/libs/flake/SimpleShapeContainerModel.h index 8a777b36c5..ff2c935adc 100644 --- a/libs/flake/SimpleShapeContainerModel.h +++ b/libs/flake/SimpleShapeContainerModel.h @@ -1,126 +1,167 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SIMPLESHAPECONTAINERMODEL_H #define SIMPLESHAPECONTAINERMODEL_H #include "KoShapeContainerModel.h" #include +#include /// \internal class SimpleShapeContainerModel: public KoShapeContainerModel { public: SimpleShapeContainerModel() {} ~SimpleShapeContainerModel() override {} SimpleShapeContainerModel(const SimpleShapeContainerModel &rhs) : KoShapeContainerModel(rhs), m_inheritsTransform(rhs.m_inheritsTransform), m_clipped(rhs.m_clipped) { Q_FOREACH (KoShape *shape, rhs.m_members) { KoShape *clone = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER_NOOP(clone && "Copying this shape is not implemented!"); if (clone) { m_members << clone; } } KIS_ASSERT_RECOVER(m_members.size() == m_inheritsTransform.size() && m_members.size() == m_clipped.size()) { qDeleteAll(m_members); m_members.clear(); m_inheritsTransform.clear(); m_clipped.clear(); } } void add(KoShape *child) override { if (m_members.contains(child)) return; m_members.append(child); m_clipped.append(false); m_inheritsTransform.append(true); } void setClipped(const KoShape *shape, bool value) override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0); m_clipped[index] = value; } bool isClipped(const KoShape *shape) const override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER(index >= 0) { return false;} return m_clipped[index]; } void remove(KoShape *shape) override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0); m_members.removeAt(index); m_clipped.removeAt(index); m_inheritsTransform.removeAt(index); } int count() const override { return m_members.count(); } QList shapes() const override { return QList(m_members); } void containerChanged(KoShapeContainer *, KoShape::ChangeType) override { } void setInheritsTransform(const KoShape *shape, bool value) override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0); m_inheritsTransform[index] = value; } bool inheritsTransform(const KoShape *shape) const override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER(index >= 0) { return true;} return m_inheritsTransform[index]; } void proposeMove(KoShape *shape, QPointF &move) override { KoShapeContainer *parent = shape->parent(); bool allowedToMove = true; while (allowedToMove && parent) { allowedToMove = parent->isShapeEditable(); parent = parent->parent(); } if (! allowedToMove) { move.setX(0); move.setY(0); } } + void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { + if (m_associatedRootShapeManager) { + m_associatedRootShapeManager->addShape(shape); + } + KoShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); + } + + void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { + if (m_associatedRootShapeManager) { + m_associatedRootShapeManager->remove(shape); + } + KoShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); + } + + KoShapeManager *associatedRootShapeManager() const { + return m_associatedRootShapeManager; + } + + /** + * If the container is the root of shapes hierarchy, it should also track the content + * of the shape manager. Add all added/removed shapes should be also + * added to \p shapeManager. + */ + void setAssociatedRootShapeManager(KoShapeManager *manager) { + if (m_associatedRootShapeManager) { + Q_FOREACH(KoShape *shape, this->shapes()) { + m_associatedRootShapeManager->remove(shape); + } + } + + m_associatedRootShapeManager = manager; + + if (m_associatedRootShapeManager) { + Q_FOREACH(KoShape *shape, this->shapes()) { + m_associatedRootShapeManager->addShape(shape); + } + } + } + private: int indexOf(const KoShape *shape) const { // workaround indexOf constness! return m_members.indexOf(const_cast(shape)); } private: // members QList m_members; QList m_inheritsTransform; QList m_clipped; + KoShapeManager *m_associatedRootShapeManager = 0; }; #endif diff --git a/libs/flake/commands/KoAddRemoveShapeCommands.cpp b/libs/flake/commands/KoAddRemoveShapeCommands.cpp new file mode 100644 index 0000000000..1b4181431e --- /dev/null +++ b/libs/flake/commands/KoAddRemoveShapeCommands.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020 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-1301, USA. + */ + +#include "KoAddRemoveShapeCommands.h" + +#include +#include + + +KoAddRemoveShapeCommandImpl::KoAddRemoveShapeCommandImpl(KoShape *shape, KoShapeContainer *parent, KisCommandUtils::FlipFlopCommand::State state, KUndo2Command *parentCommand) + : KisCommandUtils::FlipFlopCommand(state, parentCommand), + m_shape(shape), + m_parent(parent) +{ +} + +KoAddRemoveShapeCommandImpl::~KoAddRemoveShapeCommandImpl() { + if (m_ownShape) { + delete m_shape; + } +} + +void KoAddRemoveShapeCommandImpl::partB() +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(m_shape); + KIS_SAFE_ASSERT_RECOVER_RETURN(m_parent); + + m_parent->removeShape(m_shape); + m_ownShape = true; +} + +void KoAddRemoveShapeCommandImpl::partA() +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(m_shape); + KIS_SAFE_ASSERT_RECOVER_RETURN(m_parent); + + m_parent->addShape(m_shape); + m_ownShape = false; +} + +KoAddShapeCommand::KoAddShapeCommand(KoShape *shape, KoShapeContainer *parent, KUndo2Command *parentCommand) + : KoAddRemoveShapeCommandImpl(shape, parent, INITIALIZING, parentCommand) +{ +} + +KoRemoveShapeCommand::KoRemoveShapeCommand(KoShape *shape, KoShapeContainer *parent, KUndo2Command *parentCommand) + : KoAddRemoveShapeCommandImpl(shape, parent, FINALIZING, parentCommand) +{ +} diff --git a/libs/flake/commands/KoAddRemoveShapeCommands.h b/libs/flake/commands/KoAddRemoveShapeCommands.h new file mode 100644 index 0000000000..410093de36 --- /dev/null +++ b/libs/flake/commands/KoAddRemoveShapeCommands.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 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-1301, USA. + */ + +#ifndef KoAddRemoveShapeCommands_H +#define KoAddRemoveShapeCommands_H + +#include "kritaflake_export.h" + +#include "kis_command_utils.h" + +class KoShape; +class KoShapeContainer; + + +struct KRITAFLAKE_EXPORT KoAddRemoveShapeCommandImpl : public KisCommandUtils::FlipFlopCommand +{ + KoAddRemoveShapeCommandImpl(KoShape *shape, KoShapeContainer *parent, State state, KUndo2Command *parentCommand); + ~KoAddRemoveShapeCommandImpl(); + + void partA() override; + void partB() override; + +private: + bool m_ownShape = true; + KoShape *m_shape = 0; + KoShapeContainer *m_parent = 0; +}; + +struct KRITAFLAKE_EXPORT KoAddShapeCommand : public KoAddRemoveShapeCommandImpl +{ + KoAddShapeCommand(KoShape *shape, KoShapeContainer *parent, KUndo2Command *parentCommand = 0); +}; + +struct KRITAFLAKE_EXPORT KoRemoveShapeCommand : public KoAddRemoveShapeCommandImpl +{ + KoRemoveShapeCommand(KoShape *shape, KoShapeContainer *parent, KUndo2Command *parentCommand = 0); +}; + +#endif // KoAddRemoveShapeCommands_H diff --git a/libs/flake/commands/KoPathCombineCommand.cpp b/libs/flake/commands/KoPathCombineCommand.cpp index ae2334e88c..9b9d472292 100644 --- a/libs/flake/commands/KoPathCombineCommand.cpp +++ b/libs/flake/commands/KoPathCombineCommand.cpp @@ -1,144 +1,140 @@ /* This file is part of the KDE project * Copyright (C) 2006,2008 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathCombineCommand.h" #include "KoShapeControllerBase.h" #include "KoShapeContainer.h" #include "KoPathShape.h" #include #include "kis_assert.h" #include #include class Q_DECL_HIDDEN KoPathCombineCommand::Private { public: Private(KoShapeControllerBase *c, const QList &p) : controller(c), paths(p) , combinedPath(0) , combinedPathParent(0) , isCombined(false) { foreach (KoPathShape * path, paths) { oldParents.append(path->parent()); } } ~Private() { if (isCombined && controller) { Q_FOREACH (KoPathShape* path, paths) { delete path; } } else { delete combinedPath; } } KoShapeControllerBase *controller; QList paths; QList oldParents; KoPathShape *combinedPath; KoShapeContainer *combinedPathParent; QHash shapeStartSegmentIndex; bool isCombined; }; KoPathCombineCommand::KoPathCombineCommand(KoShapeControllerBase *controller, const QList &paths, KUndo2Command *parent) : KUndo2Command(kundo2_i18n("Combine paths"), parent) , d(new Private(controller, paths)) { KIS_SAFE_ASSERT_RECOVER_RETURN(!paths.isEmpty()); Q_FOREACH (KoPathShape* path, d->paths) { if (!d->combinedPath) { KoPathShape *clone = dynamic_cast(path->cloneShape()); KIS_ASSERT_RECOVER_BREAK(clone); d->combinedPath = clone; d->combinedPathParent = path->parent(); d->shapeStartSegmentIndex[path] = 0; } else { const int startSegmentIndex = d->combinedPath->combine(path); d->shapeStartSegmentIndex[path] = startSegmentIndex; } } } KoPathCombineCommand::~KoPathCombineCommand() { delete d; } void KoPathCombineCommand::redo() { KUndo2Command::redo(); if (d->paths.isEmpty()) return; d->isCombined = true; if (d->controller) { Q_FOREACH (KoPathShape* p, d->paths) { - d->controller->removeShape(p); p->setParent(0); } d->combinedPath->setParent(d->combinedPathParent); - d->controller->addShape(d->combinedPath); } } void KoPathCombineCommand::undo() { if (! d->paths.size()) return; d->isCombined = false; if (d->controller) { - d->controller->removeShape(d->combinedPath); d->combinedPath->setParent(0); auto parentIt = d->oldParents.begin(); Q_FOREACH (KoPathShape* p, d->paths) { p->setParent(*parentIt); - d->controller->addShape(p); ++parentIt; } } KUndo2Command::undo(); } KoPathShape *KoPathCombineCommand::combinedPath() const { return d->combinedPath; } KoPathPointData KoPathCombineCommand::originalToCombined(KoPathPointData pd) const { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(d->shapeStartSegmentIndex.contains(pd.pathShape), pd); const int segmentOffet = d->shapeStartSegmentIndex[pd.pathShape]; KoPathPointIndex newIndex(segmentOffet + pd.pointIndex.first, pd.pointIndex.second); return KoPathPointData(d->combinedPath, newIndex); } diff --git a/libs/flake/commands/KoShapeClipCommand.cpp b/libs/flake/commands/KoShapeClipCommand.cpp index 7acfdddc51..f753a71c33 100644 --- a/libs/flake/commands/KoShapeClipCommand.cpp +++ b/libs/flake/commands/KoShapeClipCommand.cpp @@ -1,132 +1,131 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeClipCommand.h" #include "KoClipPath.h" #include "KoShape.h" #include "KoShapeContainer.h" #include "KoPathShape.h" #include "KoShapeControllerBase.h" #include #include "kis_pointer_utils.h" class Q_DECL_HIDDEN KoShapeClipCommand::Private { public: Private(KoShapeControllerBase *c) : controller(c), executed(false) { } ~Private() { if (executed) { qDeleteAll(oldClipPaths); } else { qDeleteAll(newClipPaths); } } QList shapesToClip; QList oldClipPaths; QList clipPathShapes; QList newClipPaths; QList oldParents; KoShapeControllerBase *controller; bool executed; }; KoShapeClipCommand::KoShapeClipCommand(KoShapeControllerBase *controller, const QList &shapes, const QList &clipPathShapes, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapesToClip = shapes; d->clipPathShapes = clipPathShapes; Q_FOREACH (KoShape *shape, d->shapesToClip) { d->oldClipPaths.append(shape->clipPath()); d->newClipPaths.append(new KoClipPath(implicitCastList(clipPathShapes), KoFlake::UserSpaceOnUse)); } Q_FOREACH (KoPathShape *path, clipPathShapes) { d->oldParents.append(path->parent()); } setText(kundo2_i18n("Clip Shape")); } KoShapeClipCommand::KoShapeClipCommand(KoShapeControllerBase *controller, KoShape *shape, const QList &clipPathShapes, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapesToClip.append(shape); d->clipPathShapes = clipPathShapes; d->oldClipPaths.append(shape->clipPath()); d->newClipPaths.append(new KoClipPath(implicitCastList(clipPathShapes), KoFlake::UserSpaceOnUse)); Q_FOREACH (KoPathShape *path, clipPathShapes) { d->oldParents.append(path->parent()); } setText(kundo2_i18n("Clip Shape")); } KoShapeClipCommand::~KoShapeClipCommand() { delete d; } void KoShapeClipCommand::redo() { const uint shapeCount = d->shapesToClip.count(); for (uint i = 0; i < shapeCount; ++i) { d->shapesToClip[i]->setClipPath(d->newClipPaths[i]); d->shapesToClip[i]->update(); } const uint clipPathCount = d->clipPathShapes.count(); for (uint i = 0; i < clipPathCount; ++i) { - d->controller->removeShape(d->clipPathShapes[i]); - if (d->oldParents.at(i)) + if (d->oldParents.at(i)) { d->oldParents.at(i)->removeShape(d->clipPathShapes[i]); + } } d->executed = true; KUndo2Command::redo(); } void KoShapeClipCommand::undo() { KUndo2Command::undo(); const uint shapeCount = d->shapesToClip.count(); for (uint i = 0; i < shapeCount; ++i) { d->shapesToClip[i]->setClipPath(d->oldClipPaths[i]); d->shapesToClip[i]->update(); } const uint clipPathCount = d->clipPathShapes.count(); for (uint i = 0; i < clipPathCount; ++i) { - if (d->oldParents.at(i)) + if (d->oldParents.at(i)) { d->oldParents.at(i)->addShape(d->clipPathShapes[i]); - // the parent has to be there when it is added to the KoShapeControllerBase - d->controller->addShape(d->clipPathShapes[i]); + } } d->executed = false; } diff --git a/libs/flake/commands/KoShapeCreateCommand.cpp b/libs/flake/commands/KoShapeCreateCommand.cpp index bada674723..72b566e4d6 100644 --- a/libs/flake/commands/KoShapeCreateCommand.cpp +++ b/libs/flake/commands/KoShapeCreateCommand.cpp @@ -1,129 +1,106 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeCreateCommand.h" #include "KoShape.h" #include "KoShapeContainer.h" #include "KoShapeControllerBase.h" #include #include "kis_assert.h" #include #include #include #include +#include +#include + class Q_DECL_HIDDEN KoShapeCreateCommand::Private { public: Private(KoShapeControllerBase *_document, const QList &_shapes, KoShapeContainer *_parentShape) : shapesDocument(_document), shapes(_shapes), - explicitParentShape(_parentShape), - deleteShapes(true) + explicitParentShape(_parentShape) { } - ~Private() { - if (deleteShapes) { - qDeleteAll(shapes); - } - } - KoShapeControllerBase *shapesDocument; QList shapes; KoShapeContainer *explicitParentShape; - bool deleteShapes; - std::vector> reorderingCommands; + KisSurrogateUndoStore undoStore; + bool firstRedo = true; + }; KoShapeCreateCommand::KoShapeCreateCommand(KoShapeControllerBase *controller, KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) : KoShapeCreateCommand(controller, QList() << shape, parentShape, parent) { } KoShapeCreateCommand::KoShapeCreateCommand(KoShapeControllerBase *controller, const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) : KoShapeCreateCommand(controller, shapes, parentShape, parent, kundo2_i18np("Create shape", "Create %1 shapes", shapes.size())) { } KoShapeCreateCommand::KoShapeCreateCommand(KoShapeControllerBase *controller, const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent, const KUndo2MagicString &undoString) : KUndo2Command(undoString, parent) , d(new Private(controller, shapes, parentShape)) { } KoShapeCreateCommand::~KoShapeCreateCommand() { delete d; } void KoShapeCreateCommand::redo() { KUndo2Command::redo(); - KIS_ASSERT(d->shapesDocument); - - d->deleteShapes = false; - d->reorderingCommands.clear(); - - Q_FOREACH(KoShape *shape, d->shapes) { - if (d->explicitParentShape) { - shape->setParent(d->explicitParentShape); - } - - d->shapesDocument->addShape(shape); + KIS_SAFE_ASSERT_RECOVER_RETURN(d->explicitParentShape); - KoShapeContainer *shapeParent = shape->parent(); + if (d->firstRedo) { + Q_FOREACH(KoShape *shape, d->shapes) { - KIS_SAFE_ASSERT_RECOVER_NOOP(shape->parent() || - dynamic_cast(shape)); + d->undoStore.addCommand(new KoAddShapeCommand(shape, d->explicitParentShape)); - if (shapeParent) { - KUndo2Command *cmd = KoShapeReorderCommand::mergeInShape(shapeParent->shapes(), shape); + KoShapeContainer *shapeParent = shape->parent(); + KIS_SAFE_ASSERT_RECOVER_NOOP(shape->parent() || + dynamic_cast(shape)); - if (cmd) { - cmd->redo(); - d->reorderingCommands.push_back( - std::unique_ptr(cmd)); + if (shapeParent) { + d->undoStore.addCommand(KoShapeReorderCommand::mergeInShape(shapeParent->shapes(), shape)); } } + d->firstRedo = false; + } else { + d->undoStore.redoAll(); } } void KoShapeCreateCommand::undo() { + d->undoStore.undoAll(); KUndo2Command::undo(); - KIS_ASSERT(d->shapesDocument); - - while (!d->reorderingCommands.empty()) { - std::unique_ptr cmd = std::move(d->reorderingCommands.back()); - cmd->undo(); - d->reorderingCommands.pop_back(); - } - - Q_FOREACH(KoShape *shape, d->shapes) { - d->shapesDocument->removeShape(shape); - } - - d->deleteShapes = true; } diff --git a/libs/flake/commands/KoShapeDeleteCommand.cpp b/libs/flake/commands/KoShapeDeleteCommand.cpp index 3d369bdfdd..9bc335e9d2 100644 --- a/libs/flake/commands/KoShapeDeleteCommand.cpp +++ b/libs/flake/commands/KoShapeDeleteCommand.cpp @@ -1,105 +1,103 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeDeleteCommand.h" #include "KoShapeContainer.h" #include "KoShapeControllerBase.h" #include class Q_DECL_HIDDEN KoShapeDeleteCommand::Private { public: Private(KoShapeControllerBase *c) : controller(c), deleteShapes(false) { } ~Private() { if (! deleteShapes) return; Q_FOREACH (KoShape *shape, shapes) delete shape; } KoShapeControllerBase *controller; ///< the shape controller to use for removing/readding QList shapes; ///< the list of shapes to delete QList oldParents; ///< the old parents of the shapes bool deleteShapes; ///< shows if shapes should be deleted when deleting the command }; KoShapeDeleteCommand::KoShapeDeleteCommand(KoShapeControllerBase *controller, KoShape *shape, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapes.append(shape); d->oldParents.append(shape->parent()); setText(kundo2_i18n("Delete shape")); } KoShapeDeleteCommand::KoShapeDeleteCommand(KoShapeControllerBase *controller, const QList &shapes, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapes = shapes; Q_FOREACH (KoShape *shape, d->shapes) { d->oldParents.append(shape->parent()); } setText(kundo2_i18np("Delete shape", "Delete shapes", shapes.count())); } KoShapeDeleteCommand::~KoShapeDeleteCommand() { delete d; } void KoShapeDeleteCommand::redo() { KUndo2Command::redo(); if (! d->controller) return; for (int i = 0; i < d->shapes.count(); i++) { - // the parent has to be there when it is removed from the KoShapeControllerBase - d->controller->removeShape(d->shapes[i]); - if (d->oldParents.at(i)) + if (d->oldParents.at(i)) { d->oldParents.at(i)->removeShape(d->shapes[i]); + } } d->deleteShapes = true; } void KoShapeDeleteCommand::undo() { KUndo2Command::undo(); if (! d->controller) return; for (int i = 0; i < d->shapes.count(); i++) { - if (d->oldParents.at(i)) + if (d->oldParents.at(i)) { d->oldParents.at(i)->addShape(d->shapes[i]); - // the parent has to be there when it is added to the KoShapeControllerBase - d->controller->addShape(d->shapes[i]); + } } d->deleteShapes = false; } diff --git a/libs/flake/commands/KoShapeUnclipCommand.cpp b/libs/flake/commands/KoShapeUnclipCommand.cpp index 7d365df77f..61c250946c 100644 --- a/libs/flake/commands/KoShapeUnclipCommand.cpp +++ b/libs/flake/commands/KoShapeUnclipCommand.cpp @@ -1,154 +1,153 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeUnclipCommand.h" #include "KoClipPath.h" #include "KoShape.h" #include "KoShapeContainer.h" #include "KoPathShape.h" #include "KoShapeControllerBase.h" #include #include class KoShapeUnclipCommand::Private { public: Private(KoShapeControllerBase *c) : controller(c), executed(false) { } ~Private() { if (executed) { qDeleteAll(oldClipPaths); } else { qDeleteAll(clipPathShapes); } } void createClipPathShapes() { // check if we have already created the clip path shapes if (!clipPathShapes.isEmpty()) return; Q_FOREACH (KoShape *shape, shapesToUnclip) { KoClipPath *clipPath = shape->clipPath(); if (!clipPath) continue; Q_FOREACH (KoShape *clipShape, clipPath->clipPathShapes()) { KoShape *clone = clipShape->cloneShape(); KoPathShape *pathShape = dynamic_cast(clone); KIS_SAFE_ASSERT_RECOVER(pathShape) { delete clone; continue; } clipPathShapes.append(pathShape); } // apply transformations Q_FOREACH (KoPathShape *pathShape, clipPathShapes) { // apply transformation so that it matches the current clipped shapes clip path pathShape->applyAbsoluteTransformation(clipPath->clipDataTransformation(shape)); pathShape->setZIndex(shape->zIndex() + 1); clipPathParents.append(shape->parent()); } } } QList shapesToUnclip; QList oldClipPaths; QList clipPathShapes; QList clipPathParents; // TODO: damn! this is not a controller, this is a document! Needs refactoring! KoShapeControllerBase *controller; bool executed; }; KoShapeUnclipCommand::KoShapeUnclipCommand(KoShapeControllerBase *controller, const QList &shapes, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapesToUnclip = shapes; Q_FOREACH (KoShape *shape, d->shapesToUnclip) { d->oldClipPaths.append(shape->clipPath()); } setText(kundo2_i18n("Unclip Shape")); } KoShapeUnclipCommand::KoShapeUnclipCommand(KoShapeControllerBase *controller, KoShape *shape, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapesToUnclip.append(shape); d->oldClipPaths.append(shape->clipPath()); setText(kundo2_i18n("Unclip Shapes")); } KoShapeUnclipCommand::~KoShapeUnclipCommand() { delete d; } void KoShapeUnclipCommand::redo() { d->createClipPathShapes(); const uint shapeCount = d->shapesToUnclip.count(); for (uint i = 0; i < shapeCount; ++i) { d->shapesToUnclip[i]->setClipPath(0); d->shapesToUnclip[i]->update(); } const uint clipPathCount = d->clipPathShapes.count(); for (uint i = 0; i < clipPathCount; ++i) { - // the parent has to be there when it is added to the KoShapeControllerBase - if (d->clipPathParents.at(i)) + if (d->clipPathParents.at(i)) { d->clipPathParents.at(i)->addShape(d->clipPathShapes[i]); - d->controller->addShape(d->clipPathShapes[i]); + } } d->executed = true; KUndo2Command::redo(); } void KoShapeUnclipCommand::undo() { KUndo2Command::undo(); const uint shapeCount = d->shapesToUnclip.count(); for (uint i = 0; i < shapeCount; ++i) { d->shapesToUnclip[i]->setClipPath(d->oldClipPaths[i]); d->shapesToUnclip[i]->update(); } const uint clipPathCount = d->clipPathShapes.count(); for (uint i = 0; i < clipPathCount; ++i) { - d->controller->removeShape(d->clipPathShapes[i]); - if (d->clipPathParents.at(i)) + if (d->clipPathParents.at(i)) { d->clipPathParents.at(i)->removeShape(d->clipPathShapes[i]); + } } d->executed = false; } diff --git a/libs/flake/tests/MockShapes.h b/libs/flake/tests/MockShapes.h index 376fa792b5..05514e7213 100644 --- a/libs/flake/tests/MockShapes.h +++ b/libs/flake/tests/MockShapes.h @@ -1,240 +1,240 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * 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 MOCKSHAPES_H #define MOCKSHAPES_H #include #include #include #include #include #include #include "KoShapeManager.h" #include "FlakeDebug.h" #include "KoSnapData.h" #include "KoUnit.h" #include "kritaflake_export.h" +#include "kis_assert.h" class KRITAFLAKE_EXPORT MockShape : public KoShape { public: MockShape() : paintedCount(0) {} void paint(QPainter &painter, KoShapePaintingContext &) const override { Q_UNUSED(painter); //qDebug() << "Shape" << kBacktrace( 10 ); paintedCount++; } mutable int paintedCount; }; +#include + class KRITAFLAKE_EXPORT MockContainer : public KoShapeContainer { public: - MockContainer(KoShapeContainerModel *model = 0) : KoShapeContainer(model), paintedCount(0) {} + MockContainer(KoShapeContainerModel *model = new SimpleShapeContainerModel()) : KoShapeContainer(model), paintedCount(0) {} void paintComponent(QPainter &painter, KoShapePaintingContext &) const override { Q_UNUSED(painter); //qDebug() << "Container:" << kBacktrace( 10 ); paintedCount++; } mutable int paintedCount; + + bool contains(KoShape *shape) const { + return shapes().contains(shape); + } + + void setAssociatedRootShapeManager(KoShapeManager *shapeManager) + { + SimpleShapeContainerModel *model = dynamic_cast(this->model()); + KIS_SAFE_ASSERT_RECOVER_RETURN(model); + model->setAssociatedRootShapeManager(shapeManager); + } + + KoShapeManager* associatedRootShapeManager() const + { + SimpleShapeContainerModel *model = dynamic_cast(this->model()); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(model, 0); + return model->associatedRootShapeManager(); + } + }; class KRITAFLAKE_EXPORT MockGroup : public KoShapeGroup { void paintComponent(QPainter &painter, KoShapePaintingContext &) const override { Q_UNUSED(painter); + paintedCount++; } +public: + bool contains(KoShape *shape) const { + return shapes().contains(shape); + } + + mutable int paintedCount; }; class KoToolProxy; class KRITAFLAKE_EXPORT MockShapeController : public KoShapeControllerBase { public: - void addShapes(const QList shapes) override { - Q_FOREACH (KoShape *shape, shapes) { - m_shapes.insert(shape); - if (m_shapeManager) { - m_shapeManager->addShape(shape); - } - } - } - void removeShape(KoShape* shape) override { - m_shapes.remove(shape); - if (m_shapeManager) { - m_shapeManager->remove(shape); - } - } - bool contains(KoShape* shape) { - return m_shapes.contains(shape); - } - - void setShapeManager(KoShapeManager *shapeManager) { - m_shapeManager = shapeManager; - } - QRectF documentRectInPixels() const override { return QRectF(0,0,100,100); } qreal pixelsPerInch() const override { return 72.0; } - -private: - QSet m_shapes; - KoShapeManager *m_shapeManager = 0; }; class KRITAFLAKE_EXPORT MockCanvas : public KoCanvasBase { Q_OBJECT public: MockCanvas(KoShapeControllerBase *aKoShapeControllerBase =0)//made for TestSnapStrategy.cpp : KoCanvasBase(aKoShapeControllerBase), m_shapeManager(new KoShapeManager(this)), m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data())) { - if (MockShapeController *controller = dynamic_cast(aKoShapeControllerBase)) { - controller->setShapeManager(m_shapeManager.data()); - } } ~MockCanvas() override {} void setHorz(qreal pHorz){ m_horz = pHorz; } void setVert(qreal pVert){ m_vert = pVert; } void gridSize(QPointF *offset, QSizeF *spacing) const override { Q_UNUSED(offset); spacing->setWidth(m_horz); spacing->setHeight(m_vert); } bool snapToGrid() const override { return true; } void addCommand(KUndo2Command*) override { } KoShapeManager *shapeManager() const override { return m_shapeManager.data(); } KoSelectedShapesProxy *selectedShapesProxy() const override { return m_selectedShapesProxy.data(); } void updateCanvas(const QRectF&) override {} KoToolProxy * toolProxy() const override { return 0; } KoViewConverter *viewConverter() const override { return 0; } QWidget* canvasWidget() override { return 0; } const QWidget* canvasWidget() const override { return 0; } KoUnit unit() const override { return KoUnit(KoUnit::Millimeter); } void updateInputMethodInfo() override {} void setCursor(const QCursor &) override {} private: QScopedPointer m_shapeManager; QScopedPointer m_selectedShapesProxy; qreal m_horz; qreal m_vert; }; class KRITAFLAKE_EXPORT MockContainerModel : public KoShapeContainerModel { public: MockContainerModel() { resetCounts(); } /// reimplemented void add(KoShape *child) override { m_children.append(child); // note that we explicitly do not check for duplicates here! } /// reimplemented void remove(KoShape *child) override { m_children.removeAll(child); } /// reimplemented void setClipped(const KoShape *, bool) override { } // ignored /// reimplemented bool isClipped(const KoShape *) const override { return false; }// ignored /// reimplemented int count() const override { return m_children.count(); } /// reimplemented QList shapes() const override { return m_children; } /// reimplemented void containerChanged(KoShapeContainer *, KoShape::ChangeType) override { m_containerChangedCalled++; } /// reimplemented void proposeMove(KoShape *, QPointF &) override { m_proposeMoveCalled++; } /// reimplemented void childChanged(KoShape *, KoShape::ChangeType) override { m_childChangedCalled++; } void setInheritsTransform(const KoShape *, bool) override { } bool inheritsTransform(const KoShape *) const override { return false; } int containerChangedCalled() const { return m_containerChangedCalled; } int childChangedCalled() const { return m_childChangedCalled; } int proposeMoveCalled() const { return m_proposeMoveCalled; } void resetCounts() { m_containerChangedCalled = 0; m_childChangedCalled = 0; m_proposeMoveCalled = 0; } private: QList m_children; int m_containerChangedCalled, m_childChangedCalled, m_proposeMoveCalled; }; #endif diff --git a/libs/flake/tests/TestPointMergeCommand.cpp b/libs/flake/tests/TestPointMergeCommand.cpp index 5c6cf20cbe..54d60c9c01 100644 --- a/libs/flake/tests/TestPointMergeCommand.cpp +++ b/libs/flake/tests/TestPointMergeCommand.cpp @@ -1,578 +1,571 @@ /* This file is part of the KDE project * Copyright (C) 2009 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestPointMergeCommand.h" #include "KoPathPointMergeCommand.h" #include "KoPathShape.h" #include "KoPathPoint.h" #include "KoPathPointData.h" #include #include #include #include void TestPointMergeCommand::closeSingleLinePath() { KoPathShape path1; path1.moveTo(QPointF(40, 0)); path1.lineTo(QPointF(60, 0)); path1.lineTo(QPointF(60, 30)); path1.lineTo(QPointF(0, 30)); path1.lineTo(QPointF(0, 0)); path1.lineTo(QPointF(20, 0)); KoPathPointIndex index1(0,0); KoPathPointIndex index2(0,5); KoPathPointData pd1(&path1, index1); KoPathPointData pd2(&path1, index2); KoPathPoint * p1 = path1.pointByIndex(index1); KoPathPoint * p2 = path1.pointByIndex(index2); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 6); QCOMPARE(p1->point(), QPointF(40,0)); QCOMPARE(p2->point(), QPointF(20,0)); KoPathPointMergeCommand cmd1(pd1,pd2); cmd1.redo(); QVERIFY(path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 5); QCOMPARE(p2->point(), QPointF(20,0)); cmd1.undo(); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 6); QCOMPARE(p1->point(), QPointF(40,0)); QCOMPARE(p2->point(), QPointF(20,0)); KoPathPointMergeCommand cmd2(pd2,pd1); cmd2.redo(); QVERIFY(path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 5); QCOMPARE(p2->point(), QPointF(20,0)); cmd2.undo(); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 6); QCOMPARE(p1->point(), QPointF(40,0)); QCOMPARE(p2->point(), QPointF(20,0)); } void TestPointMergeCommand::closeSingleCurvePath() { KoPathShape path1; path1.moveTo(QPointF(40, 0)); path1.curveTo(QPointF(60, 0), QPointF(60,0), QPointF(60,60)); path1.lineTo(QPointF(0, 60)); path1.curveTo(QPointF(0, 0), QPointF(0,0), QPointF(20,0)); KoPathPointIndex index1(0,0); KoPathPointIndex index2(0,3); KoPathPointData pd1(&path1, index1); KoPathPointData pd2(&path1, index2); KoPathPoint * p1 = path1.pointByIndex(index1); KoPathPoint * p2 = path1.pointByIndex(index2); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 4); QCOMPARE(p1->point(), QPointF(40,0)); QVERIFY(!p1->activeControlPoint1()); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(!p2->activeControlPoint2()); KoPathPointMergeCommand cmd1(pd1,pd2); cmd1.redo(); QVERIFY(path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 3); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(p2->activeControlPoint1()); QVERIFY(!p2->activeControlPoint2()); cmd1.undo(); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 4); QCOMPARE(p1->point(), QPointF(40,0)); QVERIFY(!p1->activeControlPoint1()); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(!p2->activeControlPoint2()); KoPathPointMergeCommand cmd2(pd2,pd1); cmd2.redo(); QVERIFY(path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 3); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(p2->activeControlPoint1()); QVERIFY(!p2->activeControlPoint2()); cmd2.undo(); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 4); QCOMPARE(p1->point(), QPointF(40,0)); QVERIFY(!p1->activeControlPoint1()); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(!p2->activeControlPoint2()); } void TestPointMergeCommand::connectLineSubpaths() { KoPathShape path1; path1.moveTo(QPointF(0,0)); path1.lineTo(QPointF(10,0)); path1.moveTo(QPointF(20,0)); path1.lineTo(QPointF(30,0)); KoPathPointIndex index1(0,1); KoPathPointIndex index2(1,0); KoPathPointData pd1(&path1, index1); KoPathPointData pd2(&path1, index2); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(10,0)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(20,0)); KoPathPointMergeCommand cmd1(pd1, pd2); cmd1.redo(); QCOMPARE(path1.subpathCount(), 1); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(15,0)); cmd1.undo(); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(10,0)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(20,0)); KoPathPointMergeCommand cmd2(pd2, pd1); cmd2.redo(); QCOMPARE(path1.subpathCount(), 1); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(15,0)); cmd2.undo(); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(10,0)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(20,0)); } void TestPointMergeCommand::connectCurveSubpaths() { KoPathShape path1; path1.moveTo(QPointF(0,0)); path1.curveTo(QPointF(20,0),QPointF(0,20),QPointF(20,20)); path1.moveTo(QPointF(50,0)); path1.curveTo(QPointF(30,0), QPointF(50,20), QPointF(30,20)); KoPathPointIndex index1(0,1); KoPathPointIndex index2(1,1); KoPathPointData pd1(&path1, index1); KoPathPointData pd2(&path1, index2); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(20,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(0,20)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(30,20)); QCOMPARE(path1.pointByIndex(index2)->controlPoint1(), QPointF(50,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(!path1.pointByIndex(index1)->activeControlPoint2()); KoPathPointMergeCommand cmd1(pd1, pd2); cmd1.redo(); QCOMPARE(path1.subpathCount(), 1); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(25,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(5,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint2(), QPointF(45,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(path1.pointByIndex(index1)->activeControlPoint2()); cmd1.undo(); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(20,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(0,20)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(30,20)); QCOMPARE(path1.pointByIndex(index2)->controlPoint1(), QPointF(50,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(!path1.pointByIndex(index1)->activeControlPoint2()); KoPathPointMergeCommand cmd2(pd2, pd1); cmd2.redo(); QCOMPARE(path1.subpathCount(), 1); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(25,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(5,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint2(), QPointF(45,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(path1.pointByIndex(index1)->activeControlPoint2()); cmd2.undo(); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(20,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(0,20)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(30,20)); QCOMPARE(path1.pointByIndex(index2)->controlPoint1(), QPointF(50,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(!path1.pointByIndex(index1)->activeControlPoint2()); } #include #include #include "kis_debug.h" void TestPointMergeCommand::testCombineShapes() { MockShapeController mockController; MockCanvas canvas(&mockController); + QScopedPointer rootContainer(new MockContainer()); + rootContainer->setAssociatedRootShapeManager(canvas.shapeManager()); QList shapesToCombine; for (int i = 0; i < 3; i++) { const QPointF step(15,15); const QRectF rect = QRectF(5,5,10,10).translated(step * i); QPainterPath p; p.addRect(rect); KoPathShape *shape = KoPathShape::createShapeFromPainterPath(p); QCOMPARE(shape->absoluteOutlineRect(), rect); shapesToCombine << shape; - mockController.addShape(shape); + rootContainer->addShape(shape); } KoPathCombineCommand cmd(&mockController, shapesToCombine); cmd.redo(); - QCOMPARE(canvas.shapeManager()->shapes().size(), 1); + QCOMPARE(rootContainer->shapes().size(), 1); - KoPathShape *combinedShape = dynamic_cast(canvas.shapeManager()->shapes().first()); + KoPathShape *combinedShape = dynamic_cast(rootContainer->shapes().first()); QCOMPARE(combinedShape, cmd.combinedPath()); QCOMPARE(combinedShape->subpathCount(), 3); QCOMPARE(combinedShape->absoluteOutlineRect(), QRectF(5,5,40,40)); QList tstPoints; QList expPoints; tstPoints << KoPathPointData(shapesToCombine[0], KoPathPointIndex(0,1)); expPoints << KoPathPointData(combinedShape, KoPathPointIndex(0,1)); tstPoints << KoPathPointData(shapesToCombine[1], KoPathPointIndex(0,2)); expPoints << KoPathPointData(combinedShape, KoPathPointIndex(1,2)); tstPoints << KoPathPointData(shapesToCombine[2], KoPathPointIndex(0,3)); expPoints << KoPathPointData(combinedShape, KoPathPointIndex(2,3)); for (int i = 0; i < tstPoints.size(); i++) { KoPathPointData convertedPoint = cmd.originalToCombined(tstPoints[i]); QCOMPARE(convertedPoint, expPoints[i]); } - Q_FOREACH (KoShape *shape, canvas.shapeManager()->shapes()) { - mockController.removeShape(shape); - shape->setParent(0); - delete shape; - } - + rootContainer.reset(); // 'shapesToCombine' will be deleted by KoPathCombineCommand } #include #include #include #include "kis_algebra_2d.h" inline QPointF fetchPoint(KoPathShape *shape, int subpath, int pointIndex) { return shape->absoluteTransformation().map( shape->pointByIndex(KoPathPointIndex(subpath, pointIndex))->point()); } void dumpShape(KoPathShape *shape, const QString &fileName) { QImage tmp(50,50, QImage::Format_ARGB32); tmp.fill(0); QPainter p(&tmp); p.drawPath(shape->absoluteTransformation().map(shape->outline())); tmp.save(fileName); } template void testMultipathMergeShapesImpl(const int srcPointIndex1, const int srcPointIndex2, const QList &expectedResultPoints, bool singleShape = false) { MockShapeController mockController; MockCanvas canvas(&mockController); + QScopedPointer rootContainer(new MockContainer()); + rootContainer->setAssociatedRootShapeManager(canvas.shapeManager()); QList shapes; for (int i = 0; i < 3; i++) { const QPointF step(15,15); const QRectF rect = QRectF(5,5,10,10).translated(step * i); QPainterPath p; p.moveTo(rect.topLeft()); p.lineTo(rect.bottomRight()); p.lineTo(rect.topRight()); KoPathShape *shape = KoPathShape::createShapeFromPainterPath(p); QCOMPARE(shape->absoluteOutlineRect(), rect); shapes << shape; - mockController.addShape(shape); + rootContainer->addShape(shape); } - { KoPathPointData pd1(shapes[0], KoPathPointIndex(0,srcPointIndex1)); KoPathPointData pd2(shapes[singleShape ? 0 : 1], KoPathPointIndex(0,srcPointIndex2)); MergeCommand cmd(pd1, pd2, &mockController, canvas.shapeManager()->selection()); cmd.redo(); const int expectedShapesCount = singleShape ? 3 : 2; - QCOMPARE(canvas.shapeManager()->shapes().size(), expectedShapesCount); + QCOMPARE(rootContainer->shapes().size(), expectedShapesCount); KoPathShape *combinedShape = 0; if (!singleShape) { - combinedShape = dynamic_cast(canvas.shapeManager()->shapes()[1]); + combinedShape = dynamic_cast(rootContainer->shapes()[1]); QCOMPARE(combinedShape, cmd.testingCombinedPath()); } else { - combinedShape = dynamic_cast(canvas.shapeManager()->shapes()[0]); + combinedShape = dynamic_cast(rootContainer->shapes()[0]); QCOMPARE(combinedShape, shapes[0]); } QCOMPARE(combinedShape->subpathCount(), 1); QRectF expectedOutlineRect; KisAlgebra2D::accumulateBounds(expectedResultPoints, &expectedOutlineRect); QVERIFY(KisAlgebra2D::fuzzyCompareRects(combinedShape->absoluteOutlineRect(), expectedOutlineRect, 0.01)); if (singleShape) { QCOMPARE(combinedShape->isClosedSubpath(0), true); } QCOMPARE(combinedShape->subpathPointCount(0), expectedResultPoints.size()); for (int i = 0; i < expectedResultPoints.size(); i++) { if (fetchPoint(combinedShape, 0, i) != expectedResultPoints[i]) { qDebug() << ppVar(i); qDebug() << ppVar(fetchPoint(combinedShape, 0, i)); qDebug() << ppVar(expectedResultPoints[i]); QFAIL("Resulting shape points are different!"); } } QList shapes = canvas.shapeManager()->selection()->selectedEditableShapes(); QCOMPARE(shapes.size(), 1); QCOMPARE(shapes.first(), combinedShape); //dumpShape(combinedShape, "tmp_0_seq.png"); cmd.undo(); - QCOMPARE(canvas.shapeManager()->shapes().size(), 3); - } - - Q_FOREACH (KoShape *shape, canvas.shapeManager()->shapes()) { - mockController.removeShape(shape); - shape->setParent(0); - delete shape; + QCOMPARE(rootContainer->shapes().size(), 3); } + rootContainer.reset(); // combined shapes will be deleted by the corresponding commands } void TestPointMergeCommand::testMultipathMergeShapesBothSequential() { // both sequential testMultipathMergeShapesImpl(2, 0, { QPointF(5,5), QPointF(15,15), QPointF(17.5,12.5), // merged by melding the points! QPointF(30,30), QPointF(30,20) }); } void TestPointMergeCommand::testMultipathMergeShapesFirstReversed() { // first reversed testMultipathMergeShapesImpl(0, 0, { QPointF(15,5), QPointF(15,15), QPointF(12.5,12.5), // merged by melding the points! QPointF(30,30), QPointF(30,20) }); } void TestPointMergeCommand::testMultipathMergeShapesSecondReversed() { // second reversed testMultipathMergeShapesImpl(2, 2, { QPointF(5,5), QPointF(15,15), QPointF(22.5,12.5), // merged by melding the points! QPointF(30,30), QPointF(20,20) }); } void TestPointMergeCommand::testMultipathMergeShapesBothReversed() { // both reversed testMultipathMergeShapesImpl(0, 2, { QPointF(15,5), QPointF(15,15), QPointF(17.5,12.5), // merged by melding the points! QPointF(30,30), QPointF(20,20) }); } void TestPointMergeCommand::testMultipathMergeShapesSingleShapeEndToStart() { // close end->start testMultipathMergeShapesImpl(2, 0, { QPointF(10,5), QPointF(15,15) }, true); } void TestPointMergeCommand::testMultipathMergeShapesSingleShapeStartToEnd() { // close start->end testMultipathMergeShapesImpl(0, 2, { QPointF(10,5), QPointF(15,15) }, true); } void TestPointMergeCommand::testMultipathJoinShapesBothSequential() { // both sequential testMultipathMergeShapesImpl (2, 0, { QPointF(5,5), QPointF(15,15), QPointF(15,5), QPointF(20,20), QPointF(30,30), QPointF(30,20) }); } void TestPointMergeCommand::testMultipathJoinShapesFirstReversed() { // first reversed testMultipathMergeShapesImpl (0, 0, { QPointF(15,5), QPointF(15,15), QPointF(5,5), QPointF(20,20), QPointF(30,30), QPointF(30,20) }); } void TestPointMergeCommand::testMultipathJoinShapesSecondReversed() { // second reversed testMultipathMergeShapesImpl (2, 2, { QPointF(5,5), QPointF(15,15), QPointF(15,5), QPointF(30,20), QPointF(30,30), QPointF(20,20) }); } void TestPointMergeCommand::testMultipathJoinShapesBothReversed() { // both reversed testMultipathMergeShapesImpl (0, 2, { QPointF(15,5), QPointF(15,15), QPointF(5,5), QPointF(30,20), QPointF(30,30), QPointF(20,20) }); } void TestPointMergeCommand::testMultipathJoinShapesSingleShapeEndToStart() { // close end->start testMultipathMergeShapesImpl (2, 0, { QPointF(5,5), QPointF(15,15), QPointF(15,5) }, true); } void TestPointMergeCommand::testMultipathJoinShapesSingleShapeStartToEnd() { // close start->end testMultipathMergeShapesImpl (0, 2, { QPointF(5,5), QPointF(15,15), QPointF(15,5) }, true); } KISTEST_MAIN(TestPointMergeCommand) diff --git a/libs/flake/tests/TestPointRemoveCommand.cpp b/libs/flake/tests/TestPointRemoveCommand.cpp index 0c3adcf0cf..207dfd3f0e 100644 --- a/libs/flake/tests/TestPointRemoveCommand.cpp +++ b/libs/flake/tests/TestPointRemoveCommand.cpp @@ -1,303 +1,304 @@ /* This file is part of the KDE project Copyright (C) 2007 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestPointRemoveCommand.h" #include #include "KoPathShape.h" #include "KoPathPointRemoveCommand.h" #include "KoShapeController.h" #include #include #include void TestPointRemoveCommand::redoUndoPointRemove() { KoPathShape path1; path1.moveTo(QPointF(0, 0)); path1.lineTo(QPointF(0, 100)); KoPathPoint *point1 = path1.curveTo(QPointF(0, 50), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *point2 = path1.lineTo(QPointF(200, 100)); path1.curveTo(QPointF(200, 50), QPointF(300, 50), QPointF(300, 100)); QPainterPath orig1(QPointF(0, 0)); orig1.lineTo(0, 100); orig1.cubicTo(0, 50, 100, 50, 100, 100); orig1.lineTo(200, 100); orig1.cubicTo(200, 50, 300, 50, 300, 100); QVERIFY(orig1 == path1.outline()); KoPathShape path2; path2.moveTo(QPointF(0, 0)); KoPathPoint *point3 = path2.curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); path2.curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); path2.closeMerge(); QList pd; pd.append(KoPathPointData(&path1, path1.pathPointIndex(point1))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point2))); pd.append(KoPathPointData(&path2, path2.pathPointIndex(point3))); QPainterPath ppath1Org = path1.outline(); QPainterPath ppath2Org = path2.outline(); MockShapeController mockController; KoShapeController shapeController(0, &mockController); KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(pd, &shapeController); cmd->redo(); QPainterPath ppath1(QPointF(0, 0)); ppath1.lineTo(0, 100); ppath1.cubicTo(0, 50, 300, 50, 300, 100); QPainterPath ppath2(QPointF(0, 0)); ppath2.cubicTo(50, 0, 0, 50, 0, 0); ppath2.closeSubpath(); QVERIFY(ppath1 == path1.outline()); QVERIFY(ppath2 == path2.outline()); cmd->undo(); QVERIFY(ppath1Org == path1.outline()); QVERIFY(ppath2Org == path2.outline()); delete cmd; } void TestPointRemoveCommand::redoUndoSubpathRemove() { KoPathShape path1; KoPathPoint *point11 = path1.moveTo(QPointF(0, 0)); KoPathPoint *point12 = path1.lineTo(QPointF(0, 100)); KoPathPoint *point13 = path1.curveTo(QPointF(0, 50), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *point14 = path1.lineTo(QPointF(200, 100)); KoPathPoint *point15 = path1.curveTo(QPointF(200, 50), QPointF(300, 50), QPointF(300, 100)); KoPathPoint *point21 = path1.moveTo(QPointF(0, 0)); KoPathPoint *point22 = path1.curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); path1.curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); path1.closeMerge(); path1.moveTo(QPointF(100, 0)); path1.lineTo(QPointF(100, 100)); QList pd; pd.append(KoPathPointData(&path1, path1.pathPointIndex(point11))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point12))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point13))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point14))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point15))); QPainterPath ppath1Org = path1.outline(); MockShapeController mockController; KoShapeController shapeController(0, &mockController); KUndo2Command *cmd1 = KoPathPointRemoveCommand::createCommand(pd, &shapeController); cmd1->redo(); QPainterPath ppath1(QPointF(0, 0)); ppath1.cubicTo(50, 0, 100, 50, 100, 100); ppath1.cubicTo(50, 100, 0, 50, 0, 0); ppath1.closeSubpath(); ppath1.moveTo(100, 0); ppath1.lineTo(100, 100); QPainterPath ppath1mod = path1.outline(); QVERIFY(ppath1 == ppath1mod); QList pd2; pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point21))); pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point22))); KUndo2Command *cmd2 = KoPathPointRemoveCommand::createCommand(pd2, &shapeController); cmd2->redo(); QPainterPath ppath2(QPointF(0, 0)); ppath2.lineTo(0, 100); QVERIFY(ppath2 == path1.outline()); cmd2->undo(); QVERIFY(ppath1mod == path1.outline()); cmd1->undo(); QVERIFY(ppath1Org == path1.outline()); delete cmd2; delete cmd1; } void TestPointRemoveCommand::redoUndoShapeRemove() { KoPathShape *path1 = new KoPathShape(); KoPathPoint *point11 = path1->moveTo(QPointF(0, 0)); KoPathPoint *point12 = path1->lineTo(QPointF(0, 100)); KoPathPoint *point13 = path1->curveTo(QPointF(0, 50), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *point14 = path1->lineTo(QPointF(200, 100)); KoPathPoint *point15 = path1->curveTo(QPointF(200, 50), QPointF(300, 50), QPointF(300, 100)); KoPathShape *path2 = new KoPathShape(); KoPathPoint *point21 = path2->moveTo(QPointF(0, 0)); KoPathPoint *point22 = path2->curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); path2->curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); path2->closeMerge(); QList pd; pd.append(KoPathPointData(path1, path1->pathPointIndex(point12))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point11))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point13))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point15))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point14))); pd.append(KoPathPointData(path2, path2->pathPointIndex(point22))); pd.append(KoPathPointData(path2, path2->pathPointIndex(point21))); QPainterPath ppath1Org = path1->outline(); QPainterPath ppath2Org = path2->outline(); MockShapeController mockController; - mockController.addShape(path1); - mockController.addShape(path2); KoShapeController shapeController(0, &mockController); + QScopedPointer rootContainer(new MockContainer()); + rootContainer->addShape(path1); + rootContainer->addShape(path2); KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(pd, &shapeController); cmd->redo(); - QVERIFY(!mockController.contains(path1)); - QVERIFY(!mockController.contains(path2)); + QVERIFY(!rootContainer->contains(path1)); + QVERIFY(!rootContainer->contains(path2)); cmd->undo(); - QVERIFY(mockController.contains(path1)); - QVERIFY(mockController.contains(path2)); + QVERIFY(rootContainer->contains(path1)); + QVERIFY(rootContainer->contains(path2)); QVERIFY(ppath1Org == path1->outline()); QVERIFY(ppath2Org == path2->outline()); delete cmd; - delete path1; - delete path2; + rootContainer.reset(); } void TestPointRemoveCommand::redoUndo() { KoPathShape *path1 = new KoPathShape(); KoPathPoint *point11 = path1->moveTo(QPointF(0, 0)); KoPathPoint *point12 = path1->lineTo(QPointF(0, 100)); KoPathPoint *point13 = path1->curveTo(QPointF(0, 50), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *point14 = path1->lineTo(QPointF(200, 100)); KoPathPoint *point15 = path1->curveTo(QPointF(200, 50), QPointF(300, 50), QPointF(300, 100)); KoPathPoint *point16 = path1->moveTo(QPointF(100, 0)); KoPathPoint *point17 = path1->curveTo(QPointF(150, 0), QPointF(200, 50), QPointF(200, 100)); path1->curveTo(QPointF(150, 100), QPointF(100, 50), QPointF(100, 0)); path1->closeMerge(); KoPathPoint *point18 = path1->moveTo(QPointF(200, 0)); KoPathPoint *point19 = path1->lineTo(QPointF(200, 100)); KoPathShape *path2 = new KoPathShape(); KoPathPoint *point21 = path2->moveTo(QPointF(0, 0)); KoPathPoint *point22 = path2->curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); path2->curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); path2->closeMerge(); KoPathShape *path3 = new KoPathShape(); KoPathPoint *point31 = path3->moveTo(QPointF(0, 0)); KoPathPoint *point32 = path3->lineTo(QPointF(100, 100)); KoPathPoint *point33 = path3->lineTo(QPointF(200, 150)); MockShapeController mockController; - mockController.addShape(path1); - mockController.addShape(path2); - mockController.addShape(path3); + QScopedPointer rootContainer(new MockContainer()); + rootContainer->addShape(path1); + rootContainer->addShape(path2); + rootContainer->addShape(path3); QList pd; pd.append(KoPathPointData(path2, path2->pathPointIndex(point21))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point13))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point11))); pd.append(KoPathPointData(path3, path3->pathPointIndex(point31))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point12))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point15))); pd.append(KoPathPointData(path2, path2->pathPointIndex(point22))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point14))); KoShapeController shapeController(0, &mockController); QPainterPath ppath1Org = path1->outline(); QPainterPath ppath2Org = path2->outline(); QPainterPath ppath3Org = path3->outline(); KUndo2Command *cmd1 = KoPathPointRemoveCommand::createCommand(pd, &shapeController); cmd1->redo(); - QVERIFY(mockController.contains(path1)); - QVERIFY(!mockController.contains(path2)); - QVERIFY(mockController.contains(path3)); + QVERIFY(rootContainer->contains(path1)); + QVERIFY(!rootContainer->contains(path2)); + QVERIFY(rootContainer->contains(path3)); QPainterPath ppath1(QPointF(0, 0)); ppath1.cubicTo(50, 0, 100, 50, 100, 100); ppath1.cubicTo(50, 100, 0, 50, 0, 0); ppath1.closeSubpath(); ppath1.moveTo(100, 0); ppath1.lineTo(100, 100); QPainterPath ppath1mod = path1->outline(); QVERIFY(ppath1 == ppath1mod); QPainterPath ppath3(QPointF(0, 0)); ppath3.lineTo(100, 50); QPainterPath ppath3mod = path3->outline(); QVERIFY(ppath3 == ppath3mod); QList pd2; pd2.append(KoPathPointData(path1, path1->pathPointIndex(point16))); pd2.append(KoPathPointData(path1, path1->pathPointIndex(point17))); pd2.append(KoPathPointData(path1, path1->pathPointIndex(point18))); pd2.append(KoPathPointData(path1, path1->pathPointIndex(point19))); pd2.append(KoPathPointData(path3, path3->pathPointIndex(point32))); pd2.append(KoPathPointData(path3, path3->pathPointIndex(point33))); KUndo2Command *cmd2 = KoPathPointRemoveCommand::createCommand(pd2, &shapeController); cmd2->redo(); - QVERIFY(!mockController.contains(path1)); - QVERIFY(!mockController.contains(path2)); - QVERIFY(!mockController.contains(path3)); + QVERIFY(!rootContainer->contains(path1)); + QVERIFY(!rootContainer->contains(path2)); + QVERIFY(!rootContainer->contains(path3)); cmd2->undo(); - QVERIFY(mockController.contains(path1)); - QVERIFY(!mockController.contains(path2)); - QVERIFY(mockController.contains(path3)); + QVERIFY(rootContainer->contains(path1)); + QVERIFY(!rootContainer->contains(path2)); + QVERIFY(rootContainer->contains(path3)); QVERIFY(ppath1 == ppath1mod); QVERIFY(ppath3 == ppath3mod); cmd1->undo(); QVERIFY(ppath1Org == path1->outline()); QVERIFY(ppath2Org == path2->outline()); QVERIFY(ppath3Org == path3->outline()); cmd1->redo(); cmd2->redo(); delete cmd2; delete cmd1; } KISTEST_MAIN(TestPointRemoveCommand) diff --git a/libs/flake/tests/TestShapePainting.cpp b/libs/flake/tests/TestShapePainting.cpp index e5bfb25157..284cc2a1d4 100644 --- a/libs/flake/tests/TestShapePainting.cpp +++ b/libs/flake/tests/TestShapePainting.cpp @@ -1,317 +1,315 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * 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 "TestShapePainting.h" #include #include "KoShapeContainer.h" #include "KoShapeManager.h" #include "KoShapePaintingContext.h" #include #include #include void TestShapePainting::testPaintShape() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); QScopedPointer container(new MockContainer()); container->addShape(shape1); container->addShape(shape2); QCOMPARE(shape1->parent(), container.data()); QCOMPARE(shape2->parent(), container.data()); container->setClipped(shape1, false); container->setClipped(shape2, false); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(container.data()); QCOMPARE(manager.shapes().count(), 3); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); manager.paint(painter, false); // with the shape not being clipped, the shapeManager will paint it for us. QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); // the container should thus not paint the shape shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; KoShapePaintingContext paintContext; container->paint(painter, paintContext); QCOMPARE(shape1->paintedCount, 0); QCOMPARE(shape2->paintedCount, 0); QCOMPARE(container->paintedCount, 1); container->setClipped(shape1, false); container->setClipped(shape2, true); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), true); shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; manager.paint(painter, false); // with this shape not being clipped, the shapeManager will paint the container and this shape QCOMPARE(shape1->paintedCount, 1); // with this shape being clipped, the container will paint it for us. QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); } void TestShapePainting::testPaintHiddenShape() { QScopedPointer top(new MockContainer()); MockShape *shape = new MockShape(); MockContainer *fourth = new MockContainer(); MockContainer *thirth = new MockContainer(); MockContainer *second = new MockContainer(); top->addShape(second); second->addShape(thirth); thirth->addShape(fourth); fourth->addShape(shape); second->setVisible(false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top.data()); QCOMPARE(manager.shapes().count(), 5); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); manager.paint(painter, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(second->paintedCount, 0); QCOMPARE(thirth->paintedCount, 0); QCOMPARE(fourth->paintedCount, 0); QCOMPARE(shape->paintedCount, 0); } void TestShapePainting::testPaintOrder() { // the stacking order determines the painting order so things on top // get their paint called last. // Each shape has a zIndex and within the children a container has // it determines the stacking order. Its important to realize that // the zIndex is thus local to a container, if you have layer1 and layer2 // with both various child shapes the stacking order of the layer shapes // is most important, then within this the child shape index is used. class OrderedMockShape : public MockShape { public: OrderedMockShape(QList *list) : order(list) {} void paint(QPainter &painter, KoShapePaintingContext &paintcontext) const override { order->append(this); MockShape::paint(painter, paintcontext); } mutable QList *order; }; QList order; { QScopedPointer top(new MockContainer()); top->setZIndex(2); OrderedMockShape *shape1 = new OrderedMockShape(&order); shape1->setZIndex(5); OrderedMockShape *shape2 = new OrderedMockShape(&order); shape2->setZIndex(0); top->addShape(shape1); top->addShape(shape2); QScopedPointer bottom(new MockContainer()); bottom->setZIndex(1); OrderedMockShape *shape3 = new OrderedMockShape(&order); shape3->setZIndex(-1); OrderedMockShape *shape4 = new OrderedMockShape(&order); shape4->setZIndex(9); bottom->addShape(shape3); bottom->addShape(shape4); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top.data()); manager.addShape(bottom.data()); QCOMPARE(manager.shapes().count(), 6); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); manager.paint(painter, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(bottom->paintedCount, 1); QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(shape3->paintedCount, 1); QCOMPARE(shape4->paintedCount, 1); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); // again, with clipping. order.clear(); painter.setClipRect(0, 0, 100, 100); manager.paint(painter, false); QCOMPARE(top->paintedCount, 2); QCOMPARE(bottom->paintedCount, 2); QCOMPARE(shape1->paintedCount, 2); QCOMPARE(shape2->paintedCount, 2); QCOMPARE(shape3->paintedCount, 2); QCOMPARE(shape4->paintedCount, 2); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); } order.clear(); { QScopedPointer root(new MockContainer()); root->setZIndex(0); MockContainer *branch1 = new MockContainer(); branch1->setZIndex(1); OrderedMockShape *child1_1 = new OrderedMockShape(&order); child1_1->setZIndex(1); OrderedMockShape *child1_2 = new OrderedMockShape(&order); child1_2->setZIndex(2); branch1->addShape(child1_1); branch1->addShape(child1_2); MockContainer *branch2 = new MockContainer(); branch2->setZIndex(2); OrderedMockShape *child2_1 = new OrderedMockShape(&order); child2_1->setZIndex(1); OrderedMockShape *child2_2 = new OrderedMockShape(&order); child2_2->setZIndex(2); branch2->addShape(child2_1); branch2->addShape(child2_2); root->addShape(branch1); root->addShape(branch2); QList sortedShapes; sortedShapes.append(root.data()); sortedShapes.append(branch1); sortedShapes.append(branch2); sortedShapes.append(branch1->shapes()); sortedShapes.append(branch2->shapes()); std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); QCOMPARE(sortedShapes.count(), 7); QVERIFY(sortedShapes[0] == root.data()); QVERIFY(sortedShapes[1] == branch1); QVERIFY(sortedShapes[2] == child1_1); QVERIFY(sortedShapes[3] == child1_2); QVERIFY(sortedShapes[4] == branch2); QVERIFY(sortedShapes[5] == child2_1); QVERIFY(sortedShapes[6] == child2_2); } } #include #include #include #include #include "kis_debug.h" void TestShapePainting::testGroupUngroup() { - QScopedPointer shapesFakeLayer(new MockContainer); + MockShapeController controller; + MockCanvas canvas(&controller); + + KoShapeManager *manager = canvas.shapeManager(); + + QScopedPointer shapesFakeLayer(new MockContainer()); + shapesFakeLayer->setAssociatedRootShapeManager(manager); + MockShape *shape1(new MockShape()); MockShape *shape2(new MockShape()); shape1->setName("shape1"); shape2->setName("shape2"); shape1->setParent(shapesFakeLayer.data()); shape2->setParent(shapesFakeLayer.data()); QList groupedShapes = {shape1, shape2}; - - MockShapeController controller; - MockCanvas canvas(&controller); - KoShapeManager *manager = canvas.shapeManager(); - - controller.addShape(shape1); - controller.addShape(shape2); - QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); painter.setClipRect(image.rect()); for (int i = 0; i < 3; i++) { KoShapeGroup *group = new KoShapeGroup(); - group->setParent(shapesFakeLayer.data()); { group->setName("group"); KUndo2Command groupingCommand; - canvas.shapeController()->addShapeDirect(group, 0, &groupingCommand); + canvas.shapeController()->addShapeDirect(group, shapesFakeLayer.data(), &groupingCommand); new KoShapeGroupCommand(group, groupedShapes, true, &groupingCommand); groupingCommand.redo(); manager->paint(painter, false); QCOMPARE(shape1->paintedCount, 2 * i + 1); QCOMPARE(shape2->paintedCount, 2 * i + 1); QCOMPARE(manager->shapes().size(), 3); } { KUndo2Command ungroupingCommand; new KoShapeUngroupCommand(group, group->shapes(), QList(), &ungroupingCommand); canvas.shapeController()->removeShape(group, &ungroupingCommand); // NOTE: group will be deleted in ungroupingCommand's d-tor ungroupingCommand.redo(); manager->paint(painter, false); QCOMPARE(shape1->paintedCount, 2 * i + 2); QCOMPARE(shape2->paintedCount, 2 * i + 2); QCOMPARE(manager->shapes().size(), 2); } } } KISTEST_MAIN(TestShapePainting) diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp index 7d84a53dea..32ebdd0ee1 100644 --- a/libs/ui/actions/kis_selection_action_factories.cpp +++ b/libs/ui/actions/kis_selection_action_factories.cpp @@ -1,622 +1,624 @@ /* * Copyright (c) 2012 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-1301, USA. */ #include "kis_selection_action_factories.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_pixel_selection.h" #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_fill_painter.h" #include "kis_transaction.h" #include "kis_iterator_ng.h" #include "kis_processing_applicator.h" #include "kis_group_layer.h" #include "commands/kis_selection_commands.h" #include "commands/kis_image_layer_add_command.h" #include "kis_tool_proxy.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_selection_manager.h" #include "commands_new/kis_transaction_based_command.h" #include "kis_selection_filters.h" #include "kis_shape_selection.h" #include "kis_shape_layer.h" #include #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_keyframe_channel.h" #include #include #include "kis_figure_painting_tool_helper.h" #include "kis_update_outline_job.h" namespace ActionHelper { void copyFromDevice(KisViewManager *view, KisPaintDeviceSP device, bool makeSharpClip = false, const KisTimeRange &range = KisTimeRange()) { KisImageWSP image = view->image(); if (!image) return; KisSelectionSP selection = view->selection(); QRect rc = (selection) ? selection->selectedExactRect() : image->bounds(); KisPaintDeviceSP clip = new KisPaintDevice(device->colorSpace()); Q_CHECK_PTR(clip); const KoColorSpace *cs = clip->colorSpace(); // TODO if the source is linked... copy from all linked layers?!? // Copy image data KisPainter::copyAreaOptimized(QPoint(), device, clip, rc); if (selection) { // Apply selection mask. KisPaintDeviceSP selectionProjection = selection->projection(); KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width()); KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); const KoColorSpace *selCs = selection->projection()->colorSpace(); for (qint32 y = 0; y < rc.height(); y++) { for (qint32 x = 0; x < rc.width(); x++) { /** * Sharp method is an exact reverse of COMPOSITE_OVER * so if you cover the cut/copied piece over its source * you get an exactly the same image without any seams */ if (makeSharpClip) { qreal dstAlpha = cs->opacityF(layerIt->rawData()); qreal sel = selCs->opacityF(selectionIt->oldRawData()); qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha); float mask = newAlpha / dstAlpha; cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1); } else { cs->applyAlphaU8Mask(layerIt->rawData(), selectionIt->oldRawData(), 1); } layerIt->nextPixel(); selectionIt->nextPixel(); } layerIt->nextRow(); selectionIt->nextRow(); } } KisClipboard::instance()->setClip(clip, rc.topLeft(), range); } } void KisSelectAllActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Select All")); if (!image->globalSelection()) { ap->applyCommand(new KisSetEmptyGlobalSelectionCommand(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } struct SelectAll : public KisTransactionBasedCommand { SelectAll(KisImageSP image) : m_image(image) {} KisImageSP m_image; KUndo2Command* paint() override { KisSelectionSP selection = m_image->globalSelection(); KisSelectionTransaction transaction(selection->pixelSelection()); selection->pixelSelection()->clear(); selection->pixelSelection()->select(m_image->bounds()); return transaction.endAndTake(); } }; ap->applyCommand(new SelectAll(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisDeselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisDeselectActiveSelectionCommand(view->selection(), image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisReselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisReselectActiveSelectionCommand(view->activeNode(), image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisFillActionFactory::run(const QString &fillSource, KisViewManager *view) { KisNodeSP node = view->activeNode(); if (!node || !node->hasEditablePaintDevice()) return; KisSelectionSP selection = view->selection(); QRect selectedRect = selection ? selection->selectedRect() : view->image()->bounds(); Q_UNUSED(selectedRect); KisPaintDeviceSP filled = node->paintDevice()->createCompositionSourceDevice(); Q_UNUSED(filled); bool usePattern = false; bool useBgColor = false; if (fillSource.contains("pattern")) { usePattern = true; } else if (fillSource.contains("bg")) { useBgColor = true; } KisProcessingApplicator applicator(view->image(), node, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Flood Fill Layer")); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(view->image(), node, view->canvasResourceProvider()->resourceManager()); if (!fillSource.contains("opacity")) { resources->setOpacity(1.0); } KisProcessingVisitorSP visitor = new FillProcessingVisitor(resources->image()->projection(), QPoint(0, 0), // start position selection, resources, false, // fast mode usePattern, true, // fill only selection, 0, // feathering radius 0, // sizemod 80, // threshold, false, // use unmerged useBgColor); applicator.applyVisitor(visitor, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); view->canvasResourceProvider()->slotPainting(); } void KisClearActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Clear action" view->canvasBase()->toolProxy()->deleteSelection(); } void KisImageResizeToSelectionActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Image Resize To Selection action" KisSelectionSP selection = view->selection(); if (!selection) return; view->image()->cropImage(selection->selectedExactRect()); } void KisCutCopyActionFactory::run(bool willCut, bool makeSharpClip, KisViewManager *view) { KisImageSP image = view->image(); if (!image) return; bool haveShapesSelected = view->selectionManager()->haveShapesSelected(); if (haveShapesSelected) { // XXX: "Add saving of XML data for Cut/Copy of shapes" KisImageBarrierLocker locker(image); if (willCut) { view->canvasBase()->toolProxy()->cut(); } else { view->canvasBase()->toolProxy()->copy(); } } else { KisNodeSP node = view->activeNode(); if (!node) return; KisSelectionSP selection = view->selection(); if (selection.isNull()) return; { KisImageBarrierLocker locker(image); KisPaintDeviceSP dev = node->paintDevice(); if (!dev) { dev = node->projection(); } if (!dev) { view->showFloatingMessage( i18nc("floating message when cannot copy from a node", "Cannot copy pixels from this type of layer "), QIcon(), 3000, KisFloatingMessage::Medium); return; } if (dev->exactBounds().isEmpty()) { view->showFloatingMessage( i18nc("floating message when copying empty selection", "Selection is empty: no pixels were copied "), QIcon(), 3000, KisFloatingMessage::Medium); return; } KisTimeRange range; KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (channel) { const int currentTime = image->animationInterface()->currentTime(); range = channel->affectedFrames(currentTime); } ActionHelper::copyFromDevice(view, dev, makeSharpClip, range); } KUndo2Command *command = 0; if (willCut && node->hasEditablePaintDevice()) { struct ClearSelection : public KisTransactionBasedCommand { ClearSelection(KisNodeSP node, KisSelectionSP sel) : m_node(node), m_sel(sel) {} KisNodeSP m_node; KisSelectionSP m_sel; KUndo2Command* paint() override { KisSelectionSP cutSelection = m_sel; // Shrinking the cutting area was previously used // for getting seamless cut-paste. Now we use makeSharpClip // instead. // QRect originalRect = cutSelection->selectedExactRect(); // static const int preciseSelectionThreshold = 16; // // if (originalRect.width() > preciseSelectionThreshold || // originalRect.height() > preciseSelectionThreshold) { // cutSelection = new KisSelection(*m_sel); // delete cutSelection->flatten(); // // KisSelectionFilter* filter = new KisShrinkSelectionFilter(1, 1, false); // // QRect processingRect = filter->changeRect(originalRect); // filter->process(cutSelection->pixelSelection(), processingRect); // } KisTransaction transaction(m_node->paintDevice()); m_node->paintDevice()->clearSelection(cutSelection); m_node->setDirty(cutSelection->selectedRect()); return transaction.endAndTake(); } }; command = new ClearSelection(node, selection); } KUndo2MagicString actionName = willCut ? kundo2_i18n("Cut") : kundo2_i18n("Copy"); KisProcessingApplicator *ap = beginAction(view, actionName); if (command) { ap->applyCommand(command, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } KisOperationConfiguration config(id()); config.setProperty("will-cut", willCut); endAction(ap, config.toXML()); } } void KisCopyMergedActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; if (!view->blockUntilOperationsFinished(image)) return; image->barrierLock(); KisPaintDeviceSP dev = image->root()->projection(); ActionHelper::copyFromDevice(view, dev); image->unlock(); KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Copy Merged")); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisInvertSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { KisSelectionFilter* filter = new KisInvertSelectionFilter(); runFilter(filter, view, config); } void KisSelectionToVectorActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (selection->hasShapeSelection()) { view->showFloatingMessage(i18nc("floating message", "Selection is already in a vector format "), QIcon(), 2000, KisFloatingMessage::Low); return; } if (!selection->outlineCacheValid()) { view->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, false, Qt::transparent)); if (!view->blockUntilOperationsFinished(view->image())) { return; } } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); /** * Mark a shape that it belongs to a shape selection */ if(!shape->userData()) { shape->setUserData(new KisShapeSelectionMarker); } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisSelectionToRasterActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->hasShapeSelection()) { view->showFloatingMessage(i18nc("floating message", "Selection is already in a raster format "), QIcon(), 2000, KisFloatingMessage::Low); return; } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); struct RasterizeSelection : public KisTransactionBasedCommand { RasterizeSelection(KisSelectionSP sel) : m_sel(sel) {} KisSelectionSP m_sel; KUndo2Command* paint() override { // just create an empty transaction: it will rasterize the // selection and emit the necessary signals KisTransaction transaction(m_sel->pixelSelection()); return transaction.endAndTake(); } }; ap->applyCommand(new RasterizeSelection(selection), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisShapesToVectorSelectionActionFactory::run(KisViewManager* view) { const QList originalShapes = view->canvasBase()->shapeManager()->selection()->selectedShapes(); bool hasSelectionShapes = false; QList clonedShapes; Q_FOREACH (KoShape *shape, originalShapes) { if (dynamic_cast(shape->userData())) { hasSelectionShapes = true; continue; } clonedShapes << shape->cloneShape(); } if (clonedShapes.isEmpty()) { if (hasSelectionShapes) { view->showFloatingMessage(i18nc("floating message", "The shape already belongs to a selection"), QIcon(), 2000, KisFloatingMessage::Low); } return; } KisSelectionToolHelper helper(view->canvasBase(), kundo2_i18n("Convert shapes to vector selection")); helper.addSelectionShapes(clonedShapes); } void KisSelectionToShapeActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->outlineCacheValid()) { return; } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); KoColor fgColor = view->canvasBase()->resourceManager()->resource(KoCanvasResourceProvider::ForegroundColor).value(); KoShapeStrokeSP border(new KoShapeStroke(1.0, fgColor.toQColor())); shape->setStroke(border); - view->document()->shapeController()->addShape(shape); + KUndo2Command *cmd = view->canvasBase()->shapeController()->addShapeDirect(shape, 0); + KisProcessingApplicator::runSingleCommandStroke(view->image(), cmd); } void KisStrokeSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } int size = params.lineSize; KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } QPainterPath outline = pixelSelection->outlineCache(); QColor color = params.color.toQColor(); KisNodeSP currentNode = view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->paintDevice()) { KoCanvasResourceProvider * rManager = view->canvasResourceProvider()->resourceManager(); KisToolShapeUtils::StrokeStyle strokeStyle = KisToolShapeUtils::StrokeStyleForeground; KisToolShapeUtils::FillStyle fillStyle = params.fillStyle(); KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager , strokeStyle, fillStyle); helper.setFGColorOverride(params.color); helper.setSelectionOverride(0); QPen pen(Qt::red, size); pen.setJoinStyle(Qt::RoundJoin); if (fillStyle != KisToolShapeUtils::FillStyleNone) { helper.paintPainterPathQPenFill(outline, pen, params.fillColor); } else { helper.paintPainterPathQPen(outline, pen, params.fillColor); } } else if (currentNode->inherits("KisShapeLayer")) { QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(outline)); shape->setShapeId(KoPathShapeId); KoShapeStrokeSP border(new KoShapeStroke(size, color)); shape->setStroke(border); - view->document()->shapeController()->addShape(shape); + KUndo2Command *cmd = view->canvasBase()->shapeController()->addShapeDirect(shape, 0); + KisProcessingApplicator::runSingleCommandStroke(view->image(), cmd); } image->setModified(); - } void KisStrokeBrushSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } KisNodeSP currentNode = view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->paintDevice()) { KoCanvasResourceProvider * rManager = view->canvasResourceProvider()->resourceManager(); QPainterPath outline = pixelSelection->outlineCache(); KisToolShapeUtils::StrokeStyle strokeStyle = KisToolShapeUtils::StrokeStyleForeground; KisToolShapeUtils::FillStyle fillStyle = KisToolShapeUtils::FillStyleNone; KoColor color = params.color; KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager, strokeStyle, fillStyle); helper.setFGColorOverride(color); helper.setSelectionOverride(0); helper.paintPainterPath(outline); image->setModified(); } } diff --git a/libs/ui/flake/kis_shape_controller.cpp b/libs/ui/flake/kis_shape_controller.cpp index 71a5e7e894..3ac7afe702 100644 --- a/libs/ui/flake/kis_shape_controller.cpp +++ b/libs/ui/flake/kis_shape_controller.cpp @@ -1,275 +1,271 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * 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 "kis_shape_controller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" #include "kis_shape_selection.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "kis_adjustment_layer.h" #include "kis_clone_layer.h" #include "canvas/kis_canvas2.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_group_layer.h" #include "kis_node_shape.h" #include "kis_node_shapes_graph.h" #include "kis_name_server.h" #include "kis_mask.h" #include "kis_shape_layer.h" #include "KisViewManager.h" #include "kis_node.h" #include #include #include #include #include "KoSelectedShapesProxy.h" #include "kis_signal_auto_connection.h" +#include "KoAddRemoveShapeCommands.h" + struct KisShapeController::Private { public: KisDocument *doc; KisNameServer *nameServer; KisSignalAutoConnectionsStore imageConnections; KisNodeShapesGraph shapesGraph; }; KisShapeController::KisShapeController(KisDocument *doc, KisNameServer *nameServer) : KisDummiesFacadeBase(doc) , m_d(new Private()) { m_d->doc = doc; m_d->nameServer = nameServer; resourceManager()->setUndoStack(doc->undoStack()); } KisShapeController::~KisShapeController() { KisNodeDummy *node = m_d->shapesGraph.rootDummy(); if (node) { m_d->shapesGraph.removeNode(node->node()); } delete m_d; } void KisShapeController::slotUpdateDocumentResolution() { const qreal pixelsPerInch = m_d->doc->image()->xRes() * 72.0; resourceManager()->setResource(KoDocumentResourceManager::DocumentResolution, pixelsPerInch); } void KisShapeController::slotUpdateDocumentSize() { resourceManager()->setResource(KoDocumentResourceManager::DocumentRectInPixels, m_d->doc->image()->bounds()); } void KisShapeController::addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis) { KisNodeShape *newShape = m_d->shapesGraph.addNode(node, parent, aboveThis); // XXX: what are we going to do with this shape? Q_UNUSED(newShape); KisShapeLayer *shapeLayer = dynamic_cast(node.data()); if (shapeLayer) { /** * Forward signals for global shape manager * \see comment in the constructor of KisCanvas2 */ connect(shapeLayer, SIGNAL(selectionChanged()), SIGNAL(selectionChanged())); connect(shapeLayer->shapeManager(), SIGNAL(selectionContentChanged()), SIGNAL(selectionContentChanged())); connect(shapeLayer, SIGNAL(currentLayerChanged(const KoShapeLayer*)), SIGNAL(currentLayerChanged(const KoShapeLayer*))); } } void KisShapeController::removeNodeImpl(KisNodeSP node) { KisShapeLayer *shapeLayer = dynamic_cast(node.data()); if (shapeLayer) { shapeLayer->disconnect(this); } m_d->shapesGraph.removeNode(node); } bool KisShapeController::hasDummyForNode(KisNodeSP node) const { return m_d->shapesGraph.containsNode(node); } KisNodeDummy* KisShapeController::dummyForNode(KisNodeSP node) const { return m_d->shapesGraph.nodeToDummy(node); } KisNodeDummy* KisShapeController::rootDummy() const { return m_d->shapesGraph.rootDummy(); } int KisShapeController::dummiesCount() const { return m_d->shapesGraph.shapesCount(); } - static inline bool belongsToShapeSelection(KoShape* shape) { return dynamic_cast(shape->userData()); } -void KisShapeController::addShapes(const QList shapes) +KoShapeContainer *KisShapeController::createParentForShapes(const QList shapes, KUndo2Command *parentCommand) { - KIS_SAFE_ASSERT_RECOVER_RETURN(!shapes.isEmpty()); + KoShapeContainer *resultParent = 0; + KisCommandUtils::CompositeCommand *resultCommand = + new KisCommandUtils::CompositeCommand(parentCommand); + + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!shapes.isEmpty(), resultParent); + Q_FOREACH (KoShape *shape, shapes) { + KIS_SAFE_ASSERT_RECOVER_BREAK(!shape->parent()); + } KisCanvas2 *canvas = dynamic_cast(KoToolManager::instance()->activeCanvasController()->canvas()); - KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas, resultParent); - const KoShape *baseShapeParent = shapes.first()->parent(); const bool baseBelongsToSelection = belongsToShapeSelection(shapes.first()); - bool allSameParent = true; bool allSameBelongsToShapeSelection = true; - bool hasNullParent = false; Q_FOREACH (KoShape *shape, shapes) { - hasNullParent |= !shape->parent(); - allSameParent &= shape->parent() == baseShapeParent; allSameBelongsToShapeSelection &= belongsToShapeSelection(shape) == baseBelongsToSelection; } - KIS_SAFE_ASSERT_RECOVER_RETURN(!baseBelongsToSelection || allSameBelongsToShapeSelection); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!baseBelongsToSelection || allSameBelongsToShapeSelection, resultParent); + + if (baseBelongsToSelection && allSameBelongsToShapeSelection) { + KisSelectionSP selection = canvas->viewManager()->selection(); + if (selection) { + KisSelectionComponent* shapeSelectionComponent = selection->shapeSelection(); - if (!allSameParent || hasNullParent) { - if (baseBelongsToSelection && allSameBelongsToShapeSelection) { - KisSelectionSP selection = canvas->viewManager()->selection(); - if (selection) { - if (!selection->shapeSelection()) { - selection->setShapeSelection(new KisShapeSelection(this, image(), selection)); - } - KisShapeSelection * shapeSelection = static_cast(selection->shapeSelection()); + if (!shapeSelectionComponent) { + // TODO: this change will land in the next patch only! + // shapeSelectionComponent = new KisShapeSelection(this, image(), selection); + // resultCommand->addCommand(selection->convertToVectorSelection(shapeSelectionComponent)); - Q_FOREACH(KoShape *shape, shapes) { - shapeSelection->addShape(shape); - } + // TODO: now, the old implementation for the patch "compilability" rule + selection->setShapeSelection(new KisShapeSelection(this, image(), selection)); } - } else { - KisShapeLayer *shapeLayer = + + KisShapeSelection * shapeSelection = static_cast(shapeSelectionComponent); + resultParent = shapeSelection; + } + } else { + KisShapeLayer *shapeLayer = dynamic_cast( canvas->selectedShapesProxy()->selection()->activeLayer()); - if (!shapeLayer) { - shapeLayer = new KisShapeLayer(this, image(), - i18n("Vector Layer %1", m_d->nameServer->number()), - OPACITY_OPAQUE_U8); + if (!shapeLayer) { + shapeLayer = new KisShapeLayer(this, image(), + i18n("Vector Layer %1", m_d->nameServer->number()), + OPACITY_OPAQUE_U8); - image()->undoAdapter()->addCommand(new KisImageLayerAddCommand(image(), shapeLayer, image()->rootLayer(), image()->rootLayer()->childCount())); - } - - Q_FOREACH(KoShape *shape, shapes) { - shapeLayer->addShape(shape); - } + resultCommand->addCommand( + new KisImageLayerAddCommand(image(), + shapeLayer, + image()->rootLayer(), + image()->rootLayer()->childCount())); } - } - m_d->doc->setModified(true); -} + resultParent = shapeLayer; + } -void KisShapeController::removeShape(KoShape* shape) -{ - /** - * Krita layers have their own destruction path. - * It goes through slotRemoveNode() - */ - Q_ASSERT(shape->shapeId() != KIS_NODE_SHAPE_ID && - shape->shapeId() != KIS_SHAPE_LAYER_ID); - - shape->setParent(0); + return resultParent; } 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; KisNodeSP rootNode = image()->root(); if (m_d->shapesGraph.containsNode(rootNode)) { Q_ASSERT(canvas); Q_ASSERT(canvas->shapeManager()); KoSelection *selection = canvas->shapeManager()->selection(); if (selection && m_d->shapesGraph.nodeToShape(rootNode)) { selection->select(m_d->shapesGraph.nodeToShape(rootNode)); KoToolManager::instance()->switchToolRequested(KoToolManager::instance()->preferredToolForSelection(selection->selectedShapes())); } } } void KisShapeController::setImage(KisImageWSP image) { m_d->imageConnections.clear(); if (image) { m_d->imageConnections.addConnection(image, SIGNAL(sigResolutionChanged(double, double)), this, SLOT(slotUpdateDocumentResolution())); m_d->imageConnections.addConnection(image, SIGNAL(sigSizeChanged(QPointF, QPointF)), this, SLOT(slotUpdateDocumentSize())); } slotUpdateDocumentResolution(); slotUpdateDocumentSize(); KisDummiesFacadeBase::setImage(image); } KoShapeLayer* KisShapeController::shapeForNode(KisNodeSP node) const { if (node) { return m_d->shapesGraph.nodeToShape(node); } return 0; } diff --git a/libs/ui/flake/kis_shape_controller.h b/libs/ui/flake/kis_shape_controller.h index 296f714fa2..6c3c4ca388 100644 --- a/libs/ui/flake/kis_shape_controller.h +++ b/libs/ui/flake/kis_shape_controller.h @@ -1,93 +1,92 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * 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 KIS_SHAPE_CONTROLLER #define KIS_SHAPE_CONTROLLER #include #include "kis_dummies_facade_base.h" #include class KisNodeDummy; class KoShapeLayer; class KisCanvas2; class KisDocument; class KisNameServer; /** * KisShapeController keeps track of new layers, shapes, masks and * selections -- everything that needs to be wrapped as a shape for * the tools to work on. */ class KRITAUI_EXPORT KisShapeController : public KisDummiesFacadeBase, public KoShapeControllerBase { Q_OBJECT public: KisShapeController(KisDocument *doc, KisNameServer *nameServer); ~KisShapeController() override; bool hasDummyForNode(KisNodeSP node) const override; KisNodeDummy* dummyForNode(KisNodeSP layer) const override; KisNodeDummy* rootDummy() const override; int dummiesCount() const override; KoShapeLayer* shapeForNode(KisNodeSP layer) const; void setInitialShapeForCanvas(KisCanvas2 *canvas); void setImage(KisImageWSP image) override; private: void addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis) override; void removeNodeImpl(KisNodeSP node) override; private Q_SLOTS: void slotUpdateDocumentResolution(); void slotUpdateDocumentSize(); Q_SIGNALS: /** * These three signals are forwarded from the local shape manager of * KisShapeLayer. This is done because we switch KoShapeManager and * therefore KoSelection in KisCanvas2, so we need to connect local * managers to the UI as well. * * \see comment in the constructor of KisCanvas2 */ void selectionChanged(); void selectionContentChanged(); void currentLayerChanged(const KoShapeLayer*); public: - void addShapes(const QList shapes) override; - void removeShape(KoShape* shape) override; + KoShapeContainer* createParentForShapes(const QList shapes, KUndo2Command *parentCommand) override; QRectF documentRectInPixels() const override; qreal pixelsPerInch() const override; private: struct Private; Private * const m_d; }; #endif diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index 35ebf2f84a..7c74e3977d 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,687 +1,682 @@ /* * Copyright (c) 2006-2008 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2011 Jan Hambrecht * * 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 "kis_shape_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SvgWriter.h" #include "SvgParser.h" #include #include #include "kis_default_bounds.h" #include #include "kis_shape_layer_canvas.h" #include "kis_image_view_converter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_effect_mask.h" #include "commands/KoShapeReorderCommand.h" #include "kis_do_something_command.h" #include #include #include #include class ShapeLayerContainerModel : public SimpleShapeContainerModel { public: ShapeLayerContainerModel(KisShapeLayer *parent) : q(parent) -{} + { + } void add(KoShape *child) override { SimpleShapeContainerModel::add(child); /** * The shape is always added with the absolute transformation set appropriately. * Here we should just squeeze it into the layer's transformation. */ KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(); child->applyAbsoluteTransformation(parentTransform.inverted()); } } void remove(KoShape *child) override { KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(); child->applyAbsoluteTransformation(parentTransform); } SimpleShapeContainerModel::remove(child); } - void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { - q->shapeManager()->addShape(shape); - SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); - } - - void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { - q->shapeManager()->remove(shape); - SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); - } - private: KisShapeLayer *q; }; struct KisShapeLayer::Private { public: Private() : canvas(0) , controller(0) , x(0) , y(0) {} KisPaintDeviceSP paintDevice; KisShapeLayerCanvasBase * canvas; KoShapeControllerBase* controller; int x; int y; }; KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller, KisImageWSP image, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity), KoShapeLayer(new ShapeLayerContainerModel(this)), m_d(new Private()) { initShapeLayer(controller); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs) : KisShapeLayer(rhs, rhs.m_d->controller) { } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeControllerBase* controller, KisShapeLayerCanvasBase *canvas) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // copy the projection to avoid extra round of updates! initShapeLayer(controller, _rhs.m_d->paintDevice, canvas); /** * The transformaitons of the added shapes are automatically merged into the transformation * of the layer, so we should apply this extra transform separately */ const QTransform thisInvertedTransform = this->absoluteTransformation().inverted(); m_d->canvas->shapeManager()->setUpdatesBlocked(true); Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform); addShape(clonedShape); } m_d->canvas->shapeManager()->setUpdatesBlocked(false); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(_rhs.m_d->controller); /** * With current implementation this matrix will always be an identity, because * we do not copy the transformation from any of the source layers. But we should * handle this anyway, to not be caught by this in the future. */ const QTransform thisInvertedTransform = this->absoluteTransformation().inverted(); QList shapesAbove; QList shapesBelow; // copy in _rhs's shapes Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform); shapesBelow.append(clonedShape); } // copy in _addShapes's shapes Q_FOREACH (KoShape *shape, _addShapes.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform); shapesAbove.append(clonedShape); } QList shapes = KoShapeReorderCommand::mergeDownShapes(shapesBelow, shapesAbove); KoShapeReorderCommand cmd(shapes); cmd.redo(); Q_FOREACH (KoShape *shape, shapesBelow + shapesAbove) { addShape(shape); } } KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller, KisImageWSP image, const QString &name, quint8 opacity, KisShapeLayerCanvasBase *canvas) : KisExternalLayer(image, name, opacity) , KoShapeLayer(new ShapeLayerContainerModel(this)) , m_d(new Private()) { initShapeLayer(controller, nullptr, canvas); } KisShapeLayer::~KisShapeLayer() { /** * Small hack alert: we should avoid updates on shape deletion */ m_d->canvas->prepareForDestroying(); Q_FOREACH (KoShape *shape, shapes()) { shape->setParent(0); delete shape; } delete m_d->canvas; delete m_d; } void KisShapeLayer::initShapeLayer(KoShapeControllerBase* controller, KisPaintDeviceSP copyFromProjection, KisShapeLayerCanvasBase *canvas) { setSupportsLodMoves(false); setShapeId(KIS_SHAPE_LAYER_ID); KIS_ASSERT_RECOVER_NOOP(this->image()); if (!copyFromProjection) { m_d->paintDevice = new KisPaintDevice(image()->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image())); m_d->paintDevice->setParentNode(this); } else { m_d->paintDevice = new KisPaintDevice(*copyFromProjection); } if (!canvas) { auto *slCanvas = new KisShapeLayerCanvas(this, image()); slCanvas->setProjection(m_d->paintDevice); canvas = slCanvas; } m_d->canvas = canvas; m_d->canvas->moveToThread(this->thread()); m_d->controller = controller; m_d->canvas->shapeManager()->selection()->disconnect(this); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), this, SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF))); + + ShapeLayerContainerModel *model = dynamic_cast(this->model()); + KIS_SAFE_ASSERT_RECOVER_RETURN(model); + model->setAssociatedRootShapeManager(m_d->canvas->shapeManager()); } bool KisShapeLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } void KisShapeLayer::setImage(KisImageWSP _image) { KisLayer::setImage(_image); m_d->canvas->setImage(_image); m_d->paintDevice->convertTo(_image->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image)); } KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { KisShapeLayer *prevShape = dynamic_cast(prevLayer.data()); if (prevShape) return new KisShapeLayer(*prevShape, *this); else return KisExternalLayer::createMergedLayerTemplate(prevLayer); } void KisShapeLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { if (!dynamic_cast(dstLayer.data())) { KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer); } } void KisShapeLayer::setParent(KoShapeContainer *parent) { Q_UNUSED(parent) KIS_ASSERT_RECOVER_RETURN(0); } QIcon KisShapeLayer::icon() const { return KisIconUtils::loadIcon("vectorLayer"); } KisPaintDeviceSP KisShapeLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisShapeLayer::paintDevice() const { return 0; } qint32 KisShapeLayer::x() const { return m_d->x; } qint32 KisShapeLayer::y() const { return m_d->y; } void KisShapeLayer::setX(qint32 x) { qint32 delta = x - this->x(); QPointF diff = QPointF(m_d->canvas->viewConverter()->viewToDocumentX(delta), 0); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->x = x; } void KisShapeLayer::setY(qint32 y) { qint32 delta = y - this->y(); QPointF diff = QPointF(0, m_d->canvas->viewConverter()->viewToDocumentY(delta)); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->y = y; } namespace { void filterTransformableShapes(QList &shapes) { auto it = shapes.begin(); while (it != shapes.end()) { if (shapes.size() == 1) break; if ((*it)->inheritsTransformFromAny(shapes)) { it = shapes.erase(it); } else { ++it; } } } } QList KisShapeLayer::shapesToBeTransformed() { QList shapes = shapeManager()->shapes(); // We expect that **all** the shapes inherit the transform from its parent // SANITY_CHECK: we expect all the shapes inside the // shape layer to inherit transform! Q_FOREACH (KoShape *shape, shapes) { if (shape->parent()) { KIS_SAFE_ASSERT_RECOVER(shape->parent()->inheritsTransform(shape)) { break; } } } shapes << this; filterTransformableShapes(shapes); return shapes; } void KisShapeLayer::slotMoveShapes(const QPointF &diff) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return; KoShapeMoveCommand cmd(shapes, diff); cmd.redo(); } void KisShapeLayer::slotTransformShapes(const QTransform &newTransform) { KoShapeTransformCommand cmd({this}, {transformation()}, {newTransform}); cmd.redo(); } bool KisShapeLayer::accept(KisNodeVisitor& visitor) { return visitor.visit(this); } void KisShapeLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KoShapeManager* KisShapeLayer::shapeManager() const { return m_d->canvas->shapeManager(); } KoViewConverter* KisShapeLayer::converter() const { return m_d->canvas->viewConverter(); } bool KisShapeLayer::visible(bool recursive) const { return KisExternalLayer::visible(recursive); } void KisShapeLayer::setVisible(bool visible, bool isLoading) { const bool oldVisible = this->visible(false); KoShapeLayer::setVisible(visible); KisExternalLayer::setVisible(visible, isLoading); if (visible && !oldVisible && m_d->canvas->hasChangedWhileBeingInvisible()) { m_d->canvas->rerenderAfterBeingInvisible(); } } void KisShapeLayer::setUserLocked(bool value) { KoShapeLayer::setGeometryProtected(value); KisExternalLayer::setUserLocked(value); } bool KisShapeLayer::isShapeEditable(bool recursive) const { return KoShapeLayer::isShapeEditable(recursive) && isEditable(true); } // we do not override KoShape::setGeometryProtected() as we consider // the user not being able to access the layer shape from Krita UI! void KisShapeLayer::forceUpdateTimedNode() { m_d->canvas->forceRepaint(); } bool KisShapeLayer::hasPendingTimedUpdates() const { return m_d->canvas->hasPendingUpdates(); } void KisShapeLayer::forceUpdateHiddenAreaOnOriginal() { m_d->canvas->forceRepaintWithHiddenAreas(); } bool KisShapeLayer::saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt) { if (!store->open("content.svg")) { return false; } KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); writer.save(storeDev, sizeInPt); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc = SvgParser::createDocumentFromSvg(device, &errorMsg, &errorLine, &errorColumn); if (doc.isNull()) { errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } SvgParser parser(resourceManager); parser.setXmlBaseDir(baseXmlDir); parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */); return parser.parseSvg(doc.documentElement(), fragmentSize); } bool KisShapeLayer::saveLayer(KoStore * store) const { // FIXME: we handle xRes() only! const QSizeF sizeInPx = image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes()); return saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir) { QSizeF fragmentSize; // unused! KisImageSP image = this->image(); // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes())); const qreal resolutionPPI = 72.0 * image->xRes(); QList shapes = createShapesFromSvg(device, baseXmlDir, image->bounds(), resolutionPPI, m_d->controller->resourceManager(), &fragmentSize); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } bool KisShapeLayer::loadLayer(KoStore* store) { if (!store) { warnKrita << i18n("No store backend"); return false; } if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); loadSvg(&storeDev, ""); store->close(); return true; } return false; } void KisShapeLayer::resetCache() { m_d->canvas->resetCache(); } KUndo2Command* KisShapeLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } class TransformShapeLayerDeferred : public KUndo2Command { public: TransformShapeLayerDeferred(KisShapeLayer *shapeLayer, const QTransform &globalDocTransform) : m_shapeLayer(shapeLayer), m_globalDocTransform(globalDocTransform), m_blockingConnection(std::bind(&KisShapeLayer::slotTransformShapes, shapeLayer, std::placeholders::_1)) { } void undo() { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != qApp->thread()); m_blockingConnection.start(m_savedTransform); } void redo() { m_savedTransform = m_shapeLayer->transformation(); const QTransform globalTransform = m_shapeLayer->absoluteTransformation(); const QTransform localTransform = globalTransform * m_globalDocTransform * globalTransform.inverted(); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != qApp->thread()); m_blockingConnection.start(localTransform * m_savedTransform); } private: KisShapeLayer *m_shapeLayer; QTransform m_globalDocTransform; QTransform m_savedTransform; KisSafeBlockingQueueConnectionProxy m_blockingConnection; }; KUndo2Command* KisShapeLayer::transform(const QTransform &transform) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return 0; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shapes.size() == 1 && shapes.first() == this, 0); /** * We cannot transform shapes in the worker thread. Therefor we emit blocking-queued * signal to transform them in the GUI thread and then return. */ KisImageViewConverter *converter = dynamic_cast(this->converter()); QTransform docSpaceTransform = converter->documentToView() * transform * converter->viewToDocument(); return new TransformShapeLayerDeferred(this, docSpaceTransform); } KUndo2Command *KisShapeLayer::setProfile(const KoColorProfile *profile) { using namespace KisDoSomethingCommandOps; KUndo2Command *cmd = new KUndo2Command(); new KisDoSomethingCommand(this, false, cmd); m_d->paintDevice->setProfile(profile, cmd); new KisDoSomethingCommand(this, true, cmd); return cmd; } KUndo2Command *KisShapeLayer::convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { using namespace KisDoSomethingCommandOps; KUndo2Command *cmd = new KUndo2Command(); new KisDoSomethingCommand(this, false, cmd); m_d->paintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags, cmd); new KisDoSomethingCommand(this, true, cmd); return cmd; } KoShapeControllerBase *KisShapeLayer::shapeController() const { return m_d->controller; }