[krita/rempt/T1004-recreate-the-text-tool] /: Implement boolean operations on shapes

Dmitry Kazakov null at kde.org
Thu Jun 22 12:11:18 UTC 2017


Git commit eddf51d3b6bdf711e13575262887bc8d86860060 by Dmitry Kazakov.
Committed on 22/06/2017 at 12:07.
Pushed by dkazakov into branch 'rempt/T1004-recreate-the-text-tool'.

Implement boolean operations on shapes

Now one can Unite, Intersect and Subtract shapes

Ref T1005
CC:kimageshop at kde.org

M  +30   -0    krita/data/actions/InteractionTool.action
M  +1    -1    libs/basicflakes/tools/KoCreatePathTool.cpp
M  +1    -1    libs/basicflakes/tools/KoPencilTool.cpp
M  +1    -0    libs/flake/CMakeLists.txt
M  +1    -1    libs/flake/KoCanvasControllerWidgetViewport_p.cpp
M  +10   -10   libs/flake/KoShapeController.cpp
M  +4    -3    libs/flake/KoShapeController.h
M  +1    -1    libs/flake/KoToolProxy.cpp
A  +49   -0    libs/flake/commands/KoKeepShapesSelectedCommand.cpp     [License: GPL (v2+)]
A  +46   -0    libs/flake/commands/KoKeepShapesSelectedCommand.h     [License: GPL (v2+)]
M  +14   -15   libs/flake/commands/KoShapeCreateCommand.cpp
M  +3    -0    libs/flake/commands/KoShapeCreateCommand.h
M  +1    -1    libs/flake/tests/TestShapePainting.cpp
M  +1    -1    libs/flake/tools/KoCreateShapeStrategy.cpp
M  +1    -1    libs/ui/actions/KisPasteActionFactory.cpp
M  +1    -1    libs/ui/actions/kis_selection_action_factories.cpp
M  +1    -1    libs/ui/tool/kis_selection_tool_helper.cpp
M  +1    -1    libs/ui/tool/kis_tool_shape.cc
M  +1    -2    plugins/flake/artistictextshape/ArtisticTextTool.cpp
M  +1    -1    plugins/tools/basictools/kis_tool_line.cc
M  +1    -1    plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp
M  +120  -3    plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
M  +1    -0    plugins/tools/defaulttool/defaulttool/DefaultTool.h
M  +1    -1    plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp

https://commits.kde.org/krita/eddf51d3b6bdf711e13575262887bc8d86860060

diff --git a/krita/data/actions/InteractionTool.action b/krita/data/actions/InteractionTool.action
index 575906569bf..3852938cfd8 100644
--- a/krita/data/actions/InteractionTool.action
+++ b/krita/data/actions/InteractionTool.action
@@ -257,6 +257,36 @@
       <isCheckable>false</isCheckable>
     </Action>
 
+    <Action name="object_unite">
+      <text>Unite</text>
+      <toolTip>Create boolean onion of multiple objects</toolTip>
+      <shortcut></shortcut>
+      <icon></icon>
+      <whatsThis></whatsThis>
+      <statusTip></statusTip>
+      <isCheckable>false</isCheckable>
+    </Action>
+
+    <Action name="object_intersect">
+      <text>Intersect</text>
+      <toolTip>Create boolean intersection of multiple objects</toolTip>
+      <shortcut></shortcut>
+      <icon></icon>
+      <whatsThis></whatsThis>
+      <statusTip></statusTip>
+      <isCheckable>false</isCheckable>
+    </Action>
+
+    <Action name="object_subtract">
+      <text>Subtract</text>
+      <toolTip>Subtract multiple objects from the first selected one</toolTip>
+      <shortcut></shortcut>
+      <icon></icon>
+      <whatsThis></whatsThis>
+      <statusTip></statusTip>
+      <isCheckable>false</isCheckable>
+    </Action>
+
 
   </Actions>
 </ActionCollection>
diff --git a/libs/basicflakes/tools/KoCreatePathTool.cpp b/libs/basicflakes/tools/KoCreatePathTool.cpp
index fa8027221f9..652e3bf8ebf 100644
--- a/libs/basicflakes/tools/KoCreatePathTool.cpp
+++ b/libs/basicflakes/tools/KoCreatePathTool.cpp
@@ -445,7 +445,7 @@ void KoCreatePathTool::addPathShape(KoPathShape *pathShape)
         }
     }
 
-    KUndo2Command *cmd = canvas()->shapeController()->addShape(pathShape);
+    KUndo2Command *cmd = canvas()->shapeController()->addShape(pathShape, 0);
     if (cmd) {
         KoSelection *selection = canvas()->shapeManager()->selection();
         selection->deselectAll();
diff --git a/libs/basicflakes/tools/KoPencilTool.cpp b/libs/basicflakes/tools/KoPencilTool.cpp
index e837a38457c..ac331d38391 100644
--- a/libs/basicflakes/tools/KoPencilTool.cpp
+++ b/libs/basicflakes/tools/KoPencilTool.cpp
@@ -400,7 +400,7 @@ void KoPencilTool::addPathShape(KoPathShape* path, bool closePath)
         }
     }
 
-    KUndo2Command * cmd = canvas()->shapeController()->addShape(path);
+    KUndo2Command * cmd = canvas()->shapeController()->addShape(path, 0);
     if (cmd) {
         KoSelection *selection = canvas()->shapeManager()->selection();
         selection->deselectAll();
diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt
index c0847bdaadf..5bfb182d849 100644
--- a/libs/flake/CMakeLists.txt
+++ b/libs/flake/CMakeLists.txt
@@ -162,6 +162,7 @@ set(kritaflake_SRCS
     commands/KoShapeConnectionChangeCommand.cpp
     commands/KoMultiPathPointMergeCommand.cpp
     commands/KoMultiPathPointJoinCommand.cpp
+    commands/KoKeepShapesSelectedCommand.cpp
     tools/KoCreateShapeStrategy.cpp
     tools/KoPathToolFactory.cpp
     tools/KoPathTool.cpp
diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
index 5ab19095b75..aeb624a9875 100644
--- a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
+++ b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
@@ -213,7 +213,7 @@ void Viewport::handleDropEvent(QDropEvent *event)
     m_draggedShape->setAbsolutePosition(newPos);
 
 
-    KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape);
+    KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape, 0);
 
     if (cmd) {
         m_parent->canvas()->addCommand(cmd);
diff --git a/libs/flake/KoShapeController.cpp b/libs/flake/KoShapeController.cpp
index 0c67c96b159..8fd031c123a 100644
--- a/libs/flake/KoShapeController.cpp
+++ b/libs/flake/KoShapeController.cpp
@@ -54,7 +54,7 @@ public:
     KoCanvasBase *canvas;
     KoShapeBasedDocumentBase *shapeBasedDocument;
 
-    KUndo2Command* addShape(KoShape *shape, bool showDialog, KUndo2Command *parent) {
+    KUndo2Command* addShape(KoShape *shape, bool showDialog, KoShapeContainer *parentShape, KUndo2Command *parent) {
 
         if (canvas) {
             if (showDialog && !shape->shapeId().isEmpty()) {
@@ -98,12 +98,12 @@ public:
             }
         }
 
-        return addShapesDirect({shape}, parent);
+        return addShapesDirect({shape}, parentShape, parent);
     }
 
-    KUndo2Command* addShapesDirect(const QList<KoShape*> shapes, KUndo2Command *parent)
+    KUndo2Command* addShapesDirect(const QList<KoShape*> shapes, KoShapeContainer *parentShape, KUndo2Command *parent)
     {
-        return new KoShapeCreateCommand(shapeBasedDocument, shapes, parent);
+        return new KoShapeCreateCommand(shapeBasedDocument, shapes, parentShape, parent);
     }
 
     void handleAttachedConnections(KoShape *shape, KUndo2Command *parentCmd) {
@@ -143,19 +143,19 @@ void KoShapeController::reset()
     d->shapeBasedDocument = 0;
 }
 
-KUndo2Command* KoShapeController::addShape(KoShape *shape, KUndo2Command *parent)
+KUndo2Command* KoShapeController::addShape(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent)
 {
-    return d->addShape(shape, true, parent);
+    return d->addShape(shape, true, parentShape, parent);
 }
 
-KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KUndo2Command *parent)
+KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent)
 {
-    return d->addShapesDirect({shape}, parent);
+    return d->addShapesDirect({shape}, parentShape, parent);
 }
 
-KUndo2Command *KoShapeController::addShapesDirect(const QList<KoShape *> shapes, KUndo2Command *parent)
+KUndo2Command *KoShapeController::addShapesDirect(const QList<KoShape *> shapes, KoShapeContainer *parentShape, KUndo2Command *parent)
 {
-    return d->addShapesDirect(shapes, parent);
+    return d->addShapesDirect(shapes, parentShape, parent);
 }
 
 KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent)
diff --git a/libs/flake/KoShapeController.h b/libs/flake/KoShapeController.h
index 26171e50af8..b1d2912db2f 100644
--- a/libs/flake/KoShapeController.h
+++ b/libs/flake/KoShapeController.h
@@ -30,6 +30,7 @@
 
 class KoCanvasBase;
 class KoShape;
+class KoShapeContainer;
 class KoShapeBasedDocumentBase;
 class KUndo2Command;
 class KoDocumentResourceManager;
@@ -73,7 +74,7 @@ public:
      * @return command which will insert the shape into the document or 0 if the
      *         insertion was cancelled. The command is not yet executed.
      */
-    KUndo2Command* addShape(KoShape *shape, KUndo2Command *parent = 0);
+    KUndo2Command* addShape(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent = 0);
 
     /**
      * @brief Add a shape to the document, skipping any dialogs or other user interaction.
@@ -83,7 +84,7 @@ public:
      *
      * @return command which will insert the shape into the document. The command is not yet executed.
      */
-    KUndo2Command* addShapeDirect(KoShape *shape, KUndo2Command *parent = 0);
+    KUndo2Command* addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent = 0);
 
     /**
      * @brief Add shapes to the document, skipping any dialogs or other user interaction.
@@ -93,7 +94,7 @@ public:
      *
      * @return command which will insert the shapes into the document. The command is not yet executed.
      */
-    KUndo2Command* addShapesDirect(const QList<KoShape*> shape, KUndo2Command *parent = 0);
+    KUndo2Command* addShapesDirect(const QList<KoShape*> shape, KoShapeContainer *parentShape, KUndo2Command *parent = 0);
 
     /**
      * @brief Remove a shape from the document.
diff --git a/libs/flake/KoToolProxy.cpp b/libs/flake/KoToolProxy.cpp
index fcdb2f3b821..13678d100c2 100644
--- a/libs/flake/KoToolProxy.cpp
+++ b/libs/flake/KoToolProxy.cpp
@@ -516,7 +516,7 @@ bool KoToolProxy::paste()
 
             if (!pastedShapes.isEmpty()) {
                 // add shape to the document
-                canvas->shapeController()->addShapesDirect(pastedShapes, cmd);
+                canvas->shapeController()->addShapesDirect(pastedShapes, 0, cmd);
                 canvas->addCommand(cmd);
             }
         }
diff --git a/libs/flake/commands/KoKeepShapesSelectedCommand.cpp b/libs/flake/commands/KoKeepShapesSelectedCommand.cpp
new file mode 100644
index 00000000000..f1fcc2b82dc
--- /dev/null
+++ b/libs/flake/commands/KoKeepShapesSelectedCommand.cpp
@@ -0,0 +1,49 @@
+/*
+ *  Copyright (c) 2017 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KoKeepShapesSelectedCommand.h"
+
+#include <KoShape.h>
+#include <KoSelection.h>
+
+
+KoKeepShapesSelectedCommand::KoKeepShapesSelectedCommand(const QList<KoShape*> &selectedBefore,
+                                                         const QList<KoShape*> &selectedAfter,
+                                                         KoSelection *selection,
+                                                         bool isFinalizing,
+                                                         KUndo2Command *parent)
+    : KisCommandUtils::FlipFlopCommand(isFinalizing, parent),
+      m_selectedBefore(selectedBefore),
+      m_selectedAfter(selectedAfter),
+      m_selection(selection)
+{
+
+}
+
+void KoKeepShapesSelectedCommand::end()
+{
+    m_selection->deselectAll();
+
+    const QList<KoShape*> newSelectedShapes =
+        isFinalizing() ? m_selectedAfter : m_selectedBefore;
+
+    Q_FOREACH (KoShape *shape, newSelectedShapes) {
+        m_selection->select(shape);
+    }
+}
+
diff --git a/libs/flake/commands/KoKeepShapesSelectedCommand.h b/libs/flake/commands/KoKeepShapesSelectedCommand.h
new file mode 100644
index 00000000000..195ad91a6f8
--- /dev/null
+++ b/libs/flake/commands/KoKeepShapesSelectedCommand.h
@@ -0,0 +1,46 @@
+/*
+ *  Copyright (c) 2017 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KOKEEPSHAPESSELECTEDCOMMAND_H
+#define KOKEEPSHAPESSELECTEDCOMMAND_H
+
+#include "kis_command_utils.h"
+#include <kritaflake_export.h>
+
+class KoSelection;
+class KoShape;
+
+class KRITAFLAKE_EXPORT KoKeepShapesSelectedCommand : public KisCommandUtils::FlipFlopCommand
+{
+public:
+    KoKeepShapesSelectedCommand(const QList<KoShape*> &selectedBefore,
+                                const QList<KoShape*> &selectedAfter,
+                                KoSelection *selection,
+                                bool isFinalizing,
+                                KUndo2Command *parent);
+
+protected:
+    void end();
+
+private:
+    QList<KoShape*> m_selectedBefore;
+    QList<KoShape*> m_selectedAfter;
+    KoSelection *m_selection;
+};
+
+#endif // KOKEEPSHAPESSELECTEDCOMMAND_H
diff --git a/libs/flake/commands/KoShapeCreateCommand.cpp b/libs/flake/commands/KoShapeCreateCommand.cpp
index a2c443c217d..f3312b3eb26 100644
--- a/libs/flake/commands/KoShapeCreateCommand.cpp
+++ b/libs/flake/commands/KoShapeCreateCommand.cpp
@@ -35,14 +35,12 @@
 class Q_DECL_HIDDEN KoShapeCreateCommand::Private
 {
 public:
-    Private(KoShapeBasedDocumentBase *_document, const QList<KoShape*> &_shapes)
+    Private(KoShapeBasedDocumentBase *_document, const QList<KoShape*> &_shapes, KoShapeContainer *_parentShape)
             : shapesDocument(_document),
             shapes(_shapes),
+            explicitParentShape(_parentShape),
             deleteShapes(true)
     {
-        Q_FOREACH(KoShape *shape, shapes) {
-            originalShapeParents << shape->parent();
-        }
     }
 
     ~Private() {
@@ -53,7 +51,7 @@ public:
 
     KoShapeBasedDocumentBase *shapesDocument;
     QList<KoShape*> shapes;
-    QList<KoShapeContainer*> originalShapeParents;
+    KoShapeContainer *explicitParentShape;
     bool deleteShapes;
 
     std::vector<std::unique_ptr<KUndo2Command>> reorderingCommands;
@@ -61,14 +59,14 @@ public:
     QScopedPointer<KUndo2Command> reorderingCommand;
 };
 
-KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, KUndo2Command *parent)
-    : KoShapeCreateCommand(controller, QList<KoShape *>() << shape, parent)
+KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent)
+    : KoShapeCreateCommand(controller, QList<KoShape *>() << shape, parentShape, parent)
 {
 }
 
-KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, const QList<KoShape *> shapes, KUndo2Command *parent)
-        : KUndo2Command(kundo2_i18np("Create shape", "Create shapes", shapes.size()), parent),
-        d(new Private(controller, shapes))
+KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, const QList<KoShape *> shapes, KoShapeContainer *parentShape, KUndo2Command *parent)
+    : KUndo2Command(kundo2_i18np("Create shape", "Create shapes", shapes.size()), parent),
+      d(new Private(controller, shapes, parentShape))
 {
 }
 
@@ -86,6 +84,10 @@ void KoShapeCreateCommand::redo()
     d->reorderingCommands.clear();
 
     Q_FOREACH(KoShape *shape, d->shapes) {
+        if (d->explicitParentShape) {
+            shape->setParent(d->explicitParentShape);
+        }
+
         d->shapesDocument->addShape(shape);
 
         KoShapeContainer *shapeParent = shape->parent();
@@ -116,11 +118,8 @@ void KoShapeCreateCommand::undo()
         d->reorderingCommands.pop_back();
     }
 
-    KIS_SAFE_ASSERT_RECOVER_RETURN(d->shapes.size() == d->originalShapeParents.size());
-
-    for (int i = 0; i < d->shapes.size(); i++) {
-        d->shapesDocument->removeShape(d->shapes[i]);
-        d->shapes[i]->setParent(d->originalShapeParents[i]);
+    Q_FOREACH(KoShape *shape, d->shapes) {
+        d->shapesDocument->removeShape(shape);
     }
 
     d->deleteShapes = true;
diff --git a/libs/flake/commands/KoShapeCreateCommand.h b/libs/flake/commands/KoShapeCreateCommand.h
index 82a7e6f8d81..140f33a4634 100644
--- a/libs/flake/commands/KoShapeCreateCommand.h
+++ b/libs/flake/commands/KoShapeCreateCommand.h
@@ -24,6 +24,7 @@
 #include <kundo2command.h>
 
 class KoShape;
+class KoShapeContainer;
 class KoShapeBasedDocumentBase;
 
 /// The undo / redo command for creating shapes
@@ -37,6 +38,7 @@ public:
      * @param parent the parent command used for macro commands
      */
     KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, KoShape *shape,
+                         KoShapeContainer *parentShape = 0,
                          KUndo2Command *parent = 0);
 
    /**
@@ -46,6 +48,7 @@ public:
     * @param parent the parent command used for macro commands
     */
     KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, const QList<KoShape*> shape,
+                         KoShapeContainer *parentShape = 0,
                          KUndo2Command *parent = 0);
 
     ~KoShapeCreateCommand() override;
diff --git a/libs/flake/tests/TestShapePainting.cpp b/libs/flake/tests/TestShapePainting.cpp
index b74c4e0a67d..3820c2a6ef3 100644
--- a/libs/flake/tests/TestShapePainting.cpp
+++ b/libs/flake/tests/TestShapePainting.cpp
@@ -289,7 +289,7 @@ void TestShapePainting::testGroupUngroup()
             group->setName("group");
 
             KUndo2Command groupingCommand;
-            canvas.shapeController()->addShapeDirect(group, &groupingCommand);
+            canvas.shapeController()->addShapeDirect(group, 0, &groupingCommand);
             new KoShapeGroupCommand(group, groupedShapes, false, true, true, &groupingCommand);
 
             groupingCommand.redo();
diff --git a/libs/flake/tools/KoCreateShapeStrategy.cpp b/libs/flake/tools/KoCreateShapeStrategy.cpp
index 054a56b7c4e..83fbc4424e8 100644
--- a/libs/flake/tools/KoCreateShapeStrategy.cpp
+++ b/libs/flake/tools/KoCreateShapeStrategy.cpp
@@ -80,7 +80,7 @@ KUndo2Command* KoCreateShapeStrategy::createCommand()
     if (newSize.width() > 1.0 && newSize.height() > 1.0)
         shape->setSize(newSize);
 
-    KUndo2Command * cmd = parent->canvas()->shapeController()->addShape(shape);
+    KUndo2Command * cmd = parent->canvas()->shapeController()->addShape(shape, 0);
     if (cmd) {
         KoSelection *selection = parent->canvas()->shapeManager()->selection();
         selection->deselectAll();
diff --git a/libs/ui/actions/KisPasteActionFactory.cpp b/libs/ui/actions/KisPasteActionFactory.cpp
index 5a23f23456c..3f5c66c8dea 100644
--- a/libs/ui/actions/KisPasteActionFactory.cpp
+++ b/libs/ui/actions/KisPasteActionFactory.cpp
@@ -83,7 +83,7 @@ bool tryPasteShapes(bool pasteAtCursorPosition, KisViewManager *view)
             shapeManager->selection()->deselectAll();
 
             KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Paste shapes"));
-            canvas->shapeController()->addShapesDirect(shapes, parentCommand);
+            canvas->shapeController()->addShapesDirect(shapes, 0, parentCommand);
 
             QPointF finalShapesOffset;
 
diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp
index e11d6124cb7..b0660b5debb 100644
--- a/libs/ui/actions/kis_selection_action_factories.cpp
+++ b/libs/ui/actions/kis_selection_action_factories.cpp
@@ -437,7 +437,7 @@ void KisSelectionToVectorActionFactory::run(KisViewManager *view)
 
     KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection"));
 
-    ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape),
+    ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape, 0),
                      KisStrokeJobData::SEQUENTIAL,
                      KisStrokeJobData::EXCLUSIVE);
 
diff --git a/libs/ui/tool/kis_selection_tool_helper.cpp b/libs/ui/tool/kis_selection_tool_helper.cpp
index db14dc4223d..d39f241b451 100644
--- a/libs/ui/tool/kis_selection_tool_helper.cpp
+++ b/libs/ui/tool/kis_selection_tool_helper.cpp
@@ -205,7 +205,7 @@ void KisSelectionToolHelper::addSelectionShapes(QList< KoShape* > shapes)
                 m_shape->setUserData(new KisShapeSelectionMarker);
             }
 
-            return m_view->canvasBase()->shapeController()->addShape(m_shape);
+            return m_view->canvasBase()->shapeController()->addShape(m_shape, 0);
         }
     };
 
diff --git a/libs/ui/tool/kis_tool_shape.cc b/libs/ui/tool/kis_tool_shape.cc
index 306a84a9143..34c62383548 100644
--- a/libs/ui/tool/kis_tool_shape.cc
+++ b/libs/ui/tool/kis_tool_shape.cc
@@ -179,7 +179,7 @@ void KisToolShape::addShape(KoShape* shape)
             shape->setBackground(QSharedPointer<KoShapeBackground>(0));
             break;
     }
-    KUndo2Command * cmd = canvas()->shapeController()->addShape(shape);
+    KUndo2Command * cmd = canvas()->shapeController()->addShape(shape, 0);
     canvas()->addCommand(cmd);
 }
 
diff --git a/plugins/flake/artistictextshape/ArtisticTextTool.cpp b/plugins/flake/artistictextshape/ArtisticTextTool.cpp
index a502f8695b5..4e207d75e72 100644
--- a/plugins/flake/artistictextshape/ArtisticTextTool.cpp
+++ b/plugins/flake/artistictextshape/ArtisticTextTool.cpp
@@ -600,14 +600,13 @@ void ArtisticTextTool::convertText()
     }
 
     KoPathShape *path = KoPathShape::createShapeFromPainterPath(m_currentShape->outline());
-    path->setParent(m_currentShape->parent());
     path->setZIndex(m_currentShape->zIndex());
     path->setStroke(m_currentShape->stroke());
     path->setBackground(m_currentShape->background());
     path->setTransformation(m_currentShape->transformation());
     path->setShapeId(KoPathShapeId);
 
-    KUndo2Command *cmd = canvas()->shapeController()->addShapeDirect(path);
+    KUndo2Command *cmd = canvas()->shapeController()->addShapeDirect(path, 0);
     cmd->setText(kundo2_i18n("Convert to Path"));
     canvas()->shapeController()->removeShape(m_currentShape, cmd);
     canvas()->addCommand(cmd);
diff --git a/plugins/tools/basictools/kis_tool_line.cc b/plugins/tools/basictools/kis_tool_line.cc
index 770498df962..358efd9451c 100644
--- a/plugins/tools/basictools/kis_tool_line.cc
+++ b/plugins/tools/basictools/kis_tool_line.cc
@@ -288,7 +288,7 @@ void KisToolLine::endStroke()
         KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor()));
         path->setStroke(border);
 
-        KUndo2Command * cmd = canvas()->shapeController()->addShape(path);
+        KUndo2Command * cmd = canvas()->shapeController()->addShape(path, 0);
         canvas()->addCommand(cmd);
     }
 
diff --git a/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp b/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp
index fa347dc9a34..8ce68e366a7 100644
--- a/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp
+++ b/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp
@@ -445,7 +445,7 @@ void ConnectionTool::mouseReleaseEvent(KoPointerEvent *event)
                 return;
             } else {
                 // finalize adding the new connection shape with an undo command
-                KUndo2Command *cmd = canvas()->shapeController()->addShape(m_currentShape);
+                KUndo2Command *cmd = canvas()->shapeController()->addShape(m_currentShape, 0);
                 canvas()->addCommand(cmd);
                 setEditMode(EditConnection, m_currentShape, KoConnectionShape::StartHandle);
             }
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
index 7ff84602726..20807c71846 100644
--- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
+++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
@@ -40,6 +40,7 @@
 #include <KoShapeGroup.h>
 #include <KoShapeLayer.h>
 #include <KoShapeOdfSaveHelper.h>
+#include <KoPathShape.h>
 #include <KoDrag.h>
 #include <KoCanvasBase.h>
 #include <KoCanvasResourceManager.h>
@@ -51,6 +52,7 @@
 #include <commands/KoShapeGroupCommand.h>
 #include <commands/KoShapeUngroupCommand.h>
 #include <commands/KoShapeDistributeCommand.h>
+#include <commands/KoKeepShapesSelectedCommand.h>
 #include <KoSnapGuide.h>
 #include <KoStrokeConfigWidget.h>
 #include "kis_action_registry.h"
@@ -95,6 +97,12 @@ enum TransformActionType {
     TransformReset
 };
 
+enum BooleanOp {
+    BooleanUnion,
+    BooleanIntersection,
+    BooleanSubtraction
+};
+
 }
 
 QPolygonF selectionPolygon(KoSelection *selection)
@@ -435,6 +443,13 @@ void DefaultTool::setupActions()
     addMappedAction(transformSignalsMapper, "object_transform_mirror_vertically", TransformMirrorY);
     addMappedAction(transformSignalsMapper, "object_transform_reset", TransformReset);
 
+    QSignalMapper *booleanSignalsMapper = new QSignalMapper(this);
+    connect(booleanSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionBooleanOp(int)));
+
+    addMappedAction(booleanSignalsMapper, "object_unite", BooleanUnion);
+    addMappedAction(booleanSignalsMapper, "object_intersect", BooleanIntersection);
+    addMappedAction(booleanSignalsMapper, "object_subtract", BooleanSubtraction);
+
     m_contextMenu.reset(new QMenu());
 }
 
@@ -999,7 +1014,7 @@ void DefaultTool::selectionGroup()
     KoShapeGroup *group = new KoShapeGroup();
     // TODO what if only one shape is left?
     KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes"));
-    canvas()->shapeController()->addShapeDirect(group, cmd);
+    canvas()->shapeController()->addShapeDirect(group, 0, cmd);
     new KoShapeGroupCommand(group, selectedShapes, false, true, true, cmd);
     canvas()->addCommand(cmd);
 
@@ -1112,6 +1127,89 @@ void DefaultTool::selectionTransform(int transformAction)
     canvas()->addCommand(cmd);
 }
 
+void DefaultTool::selectionBooleanOp(int booleanOp)
+{
+    KoSelection *selection = koSelection();
+    if (!selection) return;
+
+    QList<KoShape *> editableShapes = selection->selectedEditableShapes();
+    if (editableShapes.isEmpty()) {
+        return;
+    }
+
+    QVector<QPainterPath> srcOutlines;
+    QPainterPath dstOutline;
+    KUndo2MagicString actionName = kundo2_noi18n("BUG: boolean action name");
+
+    // TODO: implement a reference shape selection dialog!
+    const int referenceShapeIndex = 0;
+    KoShape *referenceShape = editableShapes[referenceShapeIndex];
+
+    Q_FOREACH (KoShape *shape, editableShapes) {
+        srcOutlines << shape->absoluteTransformation(0).map(shape->outline());
+    }
+
+    if (booleanOp == BooleanUnion) {
+        Q_FOREACH (const QPainterPath &path, srcOutlines) {
+            dstOutline |= path;
+        }
+        actionName = kundo2_i18n("Unite Shapes");
+    } else if (booleanOp == BooleanIntersection) {
+        for (int i = 0; i < srcOutlines.size(); i++) {
+            if (i == 0) {
+                dstOutline = srcOutlines[i];
+            } else {
+                dstOutline &= srcOutlines[i];
+            }
+        }
+
+        // there is a bug in Qt, sometimes it leaves the resulting
+        // outline open, so just close it explicitly.
+        dstOutline.closeSubpath();
+
+        actionName = kundo2_i18n("Intersect Shapes");
+
+    } else if (booleanOp == BooleanSubtraction) {
+        for (int i = 0; i < srcOutlines.size(); i++) {
+            dstOutline = srcOutlines[referenceShapeIndex];
+            if (i != referenceShapeIndex) {
+                dstOutline -= srcOutlines[i];
+            }
+        }
+
+        actionName = kundo2_i18n("Subtract Shapes");
+    }
+
+    KoShape *newShape = 0;
+
+    if (!dstOutline.isEmpty()) {
+        newShape = KoPathShape::createShapeFromPainterPath(dstOutline);
+    }
+
+    KUndo2Command *cmd = new KUndo2Command(actionName);
+
+    new KoKeepShapesSelectedCommand(editableShapes, {}, selection, false, cmd);
+
+    QList<KoShape*> newSelectedShapes;
+
+    if (newShape) {
+        newShape->setBackground(referenceShape->background());
+        newShape->setStroke(referenceShape->stroke());
+        newShape->setZIndex(referenceShape->zIndex());
+
+        KoShapeContainer *parent = referenceShape->parent();
+        canvas()->shapeController()->addShapeDirect(newShape, parent, cmd);
+
+        newSelectedShapes << newShape;
+    }
+
+    canvas()->shapeController()->removeShapes(editableShapes, cmd);
+
+    new KoKeepShapesSelectedCommand({}, newSelectedShapes, selection, true, cmd);
+
+    canvas()->addCommand(cmd);
+}
+
 void DefaultTool::selectionAlign(int _align)
 {
     KoShapeAlignCommand::Align align =
@@ -1376,8 +1474,10 @@ void DefaultTool::updateActions()
     action("object_transform_mirror_vertically")->setEnabled(hasEditableShapes);
     action("object_transform_reset")->setEnabled(hasEditableShapes);
 
+    const bool multipleSelected = editableShapes.size() > 1;
+
     const bool alignmentEnabled =
-       editableShapes.size() > 1 ||
+       multipleSelected ||
        (!editableShapes.isEmpty() &&
         canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize));
 
@@ -1388,7 +1488,12 @@ void DefaultTool::updateActions()
     action("object_align_vertical_center")->setEnabled(alignmentEnabled);
     action("object_align_vertical_bottom")->setEnabled(alignmentEnabled);
 
-    action("object_group")->setEnabled(editableShapes.size() > 1);
+
+    action("object_group")->setEnabled(multipleSelected);
+
+    action("object_unite")->setEnabled(multipleSelected);
+    action("object_intersect")->setEnabled(multipleSelected);
+    action("object_subtract")->setEnabled(multipleSelected);
 
     const bool distributionEnabled = editableShapes.size() > 2;
 
@@ -1444,6 +1549,8 @@ QMenu* DefaultTool::popupActionsMenu()
             m_contextMenu->addAction(action("object_ungroup"));
         }
 
+        m_contextMenu->addSeparator();
+
         QMenu *transform = m_contextMenu->addMenu(i18n("Transform"));
         transform->addAction(action("object_transform_rotate_90_cw"));
         transform->addAction(action("object_transform_rotate_90_ccw"));
@@ -1453,6 +1560,16 @@ QMenu* DefaultTool::popupActionsMenu()
         transform->addAction(action("object_transform_mirror_vertically"));
         transform->addSeparator();
         transform->addAction(action("object_transform_reset"));
+
+        if (action("object_unite")->isEnabled() ||
+            action("object_intersect")->isEnabled() ||
+            action("object_subtract")->isEnabled()) {
+
+            QMenu *transform = m_contextMenu->addMenu(i18n("Logical Operations"));
+            transform->addAction(action("object_unite"));
+            transform->addAction(action("object_intersect"));
+            transform->addAction(action("object_subtract"));
+        }
     }
 
     return m_contextMenu.data();
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.h b/plugins/tools/defaulttool/defaulttool/DefaultTool.h
index 4f41fb9ceae..bd1b33d0368 100644
--- a/plugins/tools/defaulttool/defaulttool/DefaultTool.h
+++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.h
@@ -107,6 +107,7 @@ private Q_SLOTS:
     void selectionUngroup();
 
     void selectionTransform(int transformAction);
+    void selectionBooleanOp(int booleanOp);
 
     void slotActivateEditFillGradient(bool value);
     void slotActivateEditStrokeGradient(bool value);
diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp
index 1f88b06b35c..7e031832fc7 100644
--- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp
+++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp
@@ -148,7 +148,7 @@ void KarbonCalligraphyTool::mouseReleaseEvent(KoPointerEvent *event)
 
     m_shape->simplifyGuidePath();
 
-    KUndo2Command *cmd = canvas()->shapeController()->addShape(m_shape);
+    KUndo2Command *cmd = canvas()->shapeController()->addShape(m_shape, 0);
     if (cmd) {
         m_lastShape = m_shape;
         canvas()->addCommand(cmd);


More information about the kimageshop mailing list