File indexing completed on 2024-06-23 04:28:10

0001 /* This file is part of the KDE project
0002 
0003    SPDX-FileCopyrightText: 2006-2008 Thorsten Zachmann <zachmann@kde.org>
0004    SPDX-FileCopyrightText: 2006-2010 Thomas Zander <zander@kde.org>
0005    SPDX-FileCopyrightText: 2008-2009 Jan Hambrecht <jaham@gmx.net>
0006    SPDX-FileCopyrightText: 2008 C. Boemann <cbo@boemann.dk>
0007 
0008    SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "DefaultTool.h"
0012 #include "DefaultToolGeometryWidget.h"
0013 #include "DefaultToolTabbedWidget.h"
0014 #include "SelectionDecorator.h"
0015 #include "ShapeMoveStrategy.h"
0016 #include "ShapeRotateStrategy.h"
0017 #include "ShapeShearStrategy.h"
0018 #include "ShapeResizeStrategy.h"
0019 
0020 #include <KoPointerEvent.h>
0021 #include <KoToolSelection.h>
0022 #include <KoToolManager.h>
0023 #include <KoSelection.h>
0024 #include <KoShapeController.h>
0025 #include <KoShapeManager.h>
0026 #include <KoSelectedShapesProxy.h>
0027 #include <KoShapeGroup.h>
0028 #include <KoShapeLayer.h>
0029 #include <KoPathShape.h>
0030 #include <KoDrag.h>
0031 #include <KoCanvasBase.h>
0032 #include <KoCanvasResourceProvider.h>
0033 #include <KoShapeRubberSelectStrategy.h>
0034 #include <commands/KoShapeMoveCommand.h>
0035 #include <commands/KoShapeTransformCommand.h>
0036 #include <commands/KoShapeDeleteCommand.h>
0037 #include <commands/KoShapeCreateCommand.h>
0038 #include <commands/KoShapeGroupCommand.h>
0039 #include <commands/KoShapeUngroupCommand.h>
0040 #include <commands/KoShapeDistributeCommand.h>
0041 #include <commands/KoKeepShapesSelectedCommand.h>
0042 #include <KoSnapGuide.h>
0043 #include <KoStrokeConfigWidget.h>
0044 #include "kis_action_registry.h"
0045 #include "kis_node.h"
0046 #include "kis_node_manager.h"
0047 #include "KisViewManager.h"
0048 #include "kis_canvas2.h"
0049 #include "kis_canvas_resource_provider.h"
0050 #include <KoInteractionStrategyFactory.h>
0051 
0052 #include "kis_document_aware_spin_box_unit_manager.h"
0053 
0054 #include <KoIcon.h>
0055 
0056 #include <QPainterPath>
0057 #include <QPointer>
0058 #include <QAction>
0059 #include <QKeyEvent>
0060 #include <QTimer>
0061 #include <KisSignalMapper.h>
0062 #include <KoResourcePaths.h>
0063 
0064 #include <KoCanvasController.h>
0065 #include <kactioncollection.h>
0066 #include <QMenu>
0067 
0068 #include <math.h>
0069 #include "kis_assert.h"
0070 #include "kis_global.h"
0071 #include "kis_debug.h"
0072 #include "krita_utils.h"
0073 
0074 #include <QVector2D>
0075 
0076 #define HANDLE_DISTANCE 10
0077 #define HANDLE_DISTANCE_SQ (HANDLE_DISTANCE * HANDLE_DISTANCE)
0078 
0079 #define INNER_HANDLE_DISTANCE_SQ 16
0080 
0081 namespace {
0082 static const QString EditFillGradientFactoryId = "edit_fill_gradient";
0083 static const QString EditStrokeGradientFactoryId = "edit_stroke_gradient";
0084 static const QString EditFillMeshGradientFactoryId = "edit_fill_meshgradient";
0085 
0086 enum TransformActionType {
0087     TransformRotate90CW,
0088     TransformRotate90CCW,
0089     TransformRotate180,
0090     TransformMirrorX,
0091     TransformMirrorY,
0092     TransformReset
0093 };
0094 
0095 enum BooleanOp {
0096     BooleanUnion,
0097     BooleanIntersection,
0098     BooleanSubtraction
0099 };
0100 
0101 }
0102 
0103 class NopInteractionStrategy : public KoInteractionStrategy
0104 {
0105 public:
0106     explicit NopInteractionStrategy(KoToolBase *parent)
0107         : KoInteractionStrategy(parent)
0108     {
0109     }
0110 
0111     KUndo2Command *createCommand() override
0112     {
0113         return 0;
0114     }
0115 
0116     void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {}
0117     void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {}
0118 
0119     void paint(QPainter &painter, const KoViewConverter &converter) override {
0120         Q_UNUSED(painter);
0121         Q_UNUSED(converter);
0122     }
0123 };
0124 
0125 class SelectionInteractionStrategy : public KoShapeRubberSelectStrategy
0126 {
0127 public:
0128     explicit SelectionInteractionStrategy(KoToolBase *parent, const QPointF &clicked, bool useSnapToGrid)
0129         : KoShapeRubberSelectStrategy(parent, clicked, useSnapToGrid)
0130     {
0131     }
0132 
0133     void paint(QPainter &painter, const KoViewConverter &converter) override {
0134         KoShapeRubberSelectStrategy::paint(painter, converter);
0135     }
0136 
0137     void cancelInteraction() override
0138     {
0139         tool()->canvas()->updateCanvas(selectedRectangle() | tool()->decorationsRect());
0140     }
0141 
0142     void finishInteraction(Qt::KeyboardModifiers modifiers = QFlags<Qt::KeyboardModifier>()) override
0143     {
0144         Q_UNUSED(modifiers);
0145         DefaultTool *defaultTool = dynamic_cast<DefaultTool*>(tool());
0146         KIS_SAFE_ASSERT_RECOVER_RETURN(defaultTool);
0147 
0148         KoSelection * selection = defaultTool->koSelection();
0149 
0150         const bool useContainedMode = currentMode() == CoveringSelection;
0151 
0152         QList<KoShape *> shapes =
0153                 defaultTool->shapeManager()->
0154                         shapesAt(selectedRectangle(), true, useContainedMode);
0155 
0156         Q_FOREACH (KoShape * shape, shapes) {
0157                 if (!shape->isSelectable()) continue;
0158 
0159                 selection->select(shape);
0160             }
0161 
0162         tool()->canvas()->updateCanvas(selectedRectangle() | tool()->decorationsRect());
0163     }
0164 };
0165 #include <KoGradientBackground.h>
0166 #include "KoShapeGradientHandles.h"
0167 #include "ShapeGradientEditStrategy.h"
0168 
0169 class DefaultTool::MoveGradientHandleInteractionFactory : public KoInteractionStrategyFactory
0170 {
0171 public:
0172     MoveGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant,
0173                                          int priority, const QString &id, DefaultTool *_q)
0174         : KoInteractionStrategyFactory(priority, id),
0175           q(_q),
0176           m_fillVariant(fillVariant)
0177     {
0178     }
0179 
0180     KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override
0181     {
0182         m_currentHandle = handleAt(ev->point);
0183 
0184         if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) {
0185             KoShape *shape = onlyEditableShape();
0186             KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0);
0187 
0188             return new ShapeGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle.type, ev->point);
0189         }
0190 
0191         return 0;
0192     }
0193 
0194     bool hoverEvent(KoPointerEvent *ev) override
0195     {
0196         m_currentHandle = handleAt(ev->point);
0197         return false;
0198     }
0199 
0200     bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override
0201     {
0202         Q_UNUSED(painter);
0203         Q_UNUSED(converter);
0204         return false;
0205     }
0206 
0207     bool tryUseCustomCursor() override {
0208         if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) {
0209             q->useCursor(Qt::OpenHandCursor);
0210             return true;
0211         }
0212 
0213         return false;
0214     }
0215 
0216 private:
0217 
0218     KoShape* onlyEditableShape() const {
0219         KoSelection *selection = q->koSelection();
0220         QList<KoShape*> shapes = selection->selectedEditableShapes();
0221 
0222         KoShape *shape = 0;
0223         if (shapes.size() == 1) {
0224             shape = shapes.first();
0225         }
0226 
0227         return shape;
0228     }
0229 
0230     KoShapeGradientHandles::Handle handleAt(const QPointF &pos) {
0231         KoShapeGradientHandles::Handle result;
0232 
0233         KoShape *shape = onlyEditableShape();
0234         if (shape) {
0235             KoFlake::SelectionHandle globalHandle = q->handleAt(pos);
0236             const qreal distanceThresholdSq =
0237                 globalHandle == KoFlake::NoHandle ?
0238                     HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ;
0239 
0240             const KoViewConverter *converter = q->canvas()->viewConverter();
0241             const QPointF viewPoint = converter->documentToView(pos);
0242             qreal minDistanceSq = std::numeric_limits<qreal>::max();
0243 
0244             KoShapeGradientHandles sh(m_fillVariant, shape);
0245             auto handless = sh.handles();
0246             Q_FOREACH (const KoShapeGradientHandles::Handle &handle, sh.handles()) {
0247                 const QPointF handlePoint = converter->documentToView(handle.pos);
0248                 const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
0249 
0250                 if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) {
0251                     result = handle;
0252                     minDistanceSq = distanceSq;
0253                 }
0254             }
0255         }
0256 
0257         return result;
0258     }
0259 
0260 private:
0261     DefaultTool *q;
0262     KoFlake::FillVariant m_fillVariant;
0263     KoShapeGradientHandles::Handle m_currentHandle;
0264 };
0265 
0266 #include "KoShapeMeshGradientHandles.h"
0267 #include "ShapeMeshGradientEditStrategy.h"
0268 
0269 class DefaultTool::MoveMeshGradientHandleInteractionFactory: public KoInteractionStrategyFactory
0270 {
0271 public:
0272     MoveMeshGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant,
0273                                              int priority,
0274                                              const QString& id,
0275                                              DefaultTool* _q)
0276         : KoInteractionStrategyFactory(priority, id)
0277         , m_fillVariant(fillVariant)
0278         , q(_q)
0279     {
0280     }
0281 
0282     KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override
0283     {
0284         m_currentHandle = handleAt(ev->point);
0285         q->m_selectedMeshHandle = m_currentHandle;
0286         emit q->meshgradientHandleSelected(m_currentHandle);
0287 
0288 
0289         if (m_currentHandle.type != KoShapeMeshGradientHandles::Handle::None) {
0290             KoShape *shape = onlyEditableShape();
0291             KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0);
0292 
0293             return new ShapeMeshGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle, ev->point);
0294         }
0295 
0296         return nullptr;
0297     }
0298 
0299     bool hoverEvent(KoPointerEvent *ev) override
0300     {
0301         // for custom cursor
0302         KoShapeMeshGradientHandles::Handle handle = handleAt(ev->point);
0303 
0304         // refresh
0305         if (handle.type != m_currentHandle.type && handle.type == KoShapeMeshGradientHandles::Handle::None) {
0306             q->repaintDecorations();
0307         }
0308 
0309         m_currentHandle = handle;
0310         q->m_hoveredMeshHandle = m_currentHandle;
0311 
0312         // highlight the decoration which is being hovered
0313         if (m_currentHandle.type != KoShapeMeshGradientHandles::Handle::None) {
0314             q->repaintDecorations();
0315         }
0316         return false;
0317     }
0318 
0319     bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override
0320     {
0321         Q_UNUSED(painter);
0322         Q_UNUSED(converter);
0323         return false;
0324     }
0325 
0326     bool tryUseCustomCursor() override
0327     {
0328         if (m_currentHandle.type != KoShapeMeshGradientHandles::Handle::None) {
0329             q->useCursor(Qt::OpenHandCursor);
0330             return true;
0331         }
0332 
0333         return false;
0334     }
0335 
0336 
0337 private:
0338     KoShape* onlyEditableShape() const {
0339         // FIXME: copy of KoShapeGradientHandles
0340         KoSelection *selection = q->koSelection();
0341         QList<KoShape*> shapes = selection->selectedEditableShapes();
0342 
0343         KoShape *shape = 0;
0344         if (shapes.size() == 1) {
0345             shape = shapes.first();
0346         }
0347 
0348         return shape;
0349     }
0350 
0351     KoShapeMeshGradientHandles::Handle handleAt(const QPointF &pos) const
0352     {
0353         // FIXME: copy of KoShapeGradientHandles. use a template?
0354         KoShapeMeshGradientHandles::Handle result;
0355 
0356         KoShape *shape = onlyEditableShape();
0357         if (shape) {
0358             KoFlake::SelectionHandle globalHandle = q->handleAt(pos);
0359             const qreal distanceThresholdSq =
0360                 globalHandle == KoFlake::NoHandle ?
0361                     HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ;
0362 
0363             const KoViewConverter *converter = q->canvas()->viewConverter();
0364             const QPointF viewPoint = converter->documentToView(pos);
0365             qreal minDistanceSq = std::numeric_limits<qreal>::max();
0366 
0367             KoShapeMeshGradientHandles sh(m_fillVariant, shape);
0368 
0369             for (const auto& handle: sh.handles()) {
0370                 const QPointF handlePoint = converter->documentToView(handle.pos);
0371                 const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
0372 
0373                 if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) {
0374                     result = handle;
0375                     minDistanceSq = distanceSq;
0376                 }
0377             }
0378         }
0379 
0380         return result;
0381     }
0382 
0383 private:
0384     KoFlake::FillVariant m_fillVariant;
0385     KoShapeMeshGradientHandles::Handle m_currentHandle;
0386     DefaultTool *q;
0387 };
0388 
0389 class SelectionHandler : public KoToolSelection
0390 {
0391 public:
0392     SelectionHandler(DefaultTool *parent)
0393         : KoToolSelection(parent)
0394         , m_selection(parent->koSelection())
0395     {
0396     }
0397 
0398     bool hasSelection() override
0399     {
0400         if (m_selection) {
0401             return m_selection->count();
0402         }
0403         return false;
0404     }
0405 
0406 private:
0407     QPointer<KoSelection> m_selection;
0408 };
0409 
0410 DefaultTool::DefaultTool(KoCanvasBase *canvas, bool connectToSelectedShapesProxy)
0411     : KoInteractionTool(canvas)
0412     , m_lastHandle(KoFlake::NoHandle)
0413     , m_hotPosition(KoFlake::TopLeft)
0414     , m_mouseWasInsideHandles(false)
0415     , m_selectionHandler(new SelectionHandler(this))
0416     , m_tabbedOptionWidget(0)
0417 {
0418     setupActions();
0419 
0420     QPixmap rotatePixmap, shearPixmap;
0421     rotatePixmap.load(":/cursor_rotate.png");
0422     Q_ASSERT(!rotatePixmap.isNull());
0423     shearPixmap.load(":/cursor_shear.png");
0424     Q_ASSERT(!shearPixmap.isNull());
0425 
0426     m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45)));
0427     m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90)));
0428     m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135)));
0429     m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180)));
0430     m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225)));
0431     m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270)));
0432     m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315)));
0433     m_rotateCursors[7] = QCursor(rotatePixmap);
0434     /*
0435         m_rotateCursors[0] = QCursor(Qt::RotateNCursor);
0436         m_rotateCursors[1] = QCursor(Qt::RotateNECursor);
0437         m_rotateCursors[2] = QCursor(Qt::RotateECursor);
0438         m_rotateCursors[3] = QCursor(Qt::RotateSECursor);
0439         m_rotateCursors[4] = QCursor(Qt::RotateSCursor);
0440         m_rotateCursors[5] = QCursor(Qt::RotateSWCursor);
0441         m_rotateCursors[6] = QCursor(Qt::RotateWCursor);
0442         m_rotateCursors[7] = QCursor(Qt::RotateNWCursor);
0443     */
0444     m_shearCursors[0] = QCursor(shearPixmap);
0445     m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45)));
0446     m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90)));
0447     m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135)));
0448     m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180)));
0449     m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225)));
0450     m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270)));
0451     m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315)));
0452     m_sizeCursors[0] = Qt::SizeVerCursor;
0453     m_sizeCursors[1] = Qt::SizeBDiagCursor;
0454     m_sizeCursors[2] = Qt::SizeHorCursor;
0455     m_sizeCursors[3] = Qt::SizeFDiagCursor;
0456     m_sizeCursors[4] = Qt::SizeVerCursor;
0457     m_sizeCursors[5] = Qt::SizeBDiagCursor;
0458     m_sizeCursors[6] = Qt::SizeHorCursor;
0459     m_sizeCursors[7] = Qt::SizeFDiagCursor;
0460 
0461     if (connectToSelectedShapesProxy) {
0462         connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(updateActions()));
0463 
0464         connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(repaintDecorations()));
0465         connect(canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(repaintDecorations()));
0466     }
0467 }
0468 
0469 DefaultTool::~DefaultTool()
0470 {
0471 }
0472 
0473 void DefaultTool::slotActivateEditFillGradient(bool value)
0474 {
0475     if (value) {
0476         addInteractionFactory(
0477             new MoveGradientHandleInteractionFactory(KoFlake::Fill,
0478                                                      1, EditFillGradientFactoryId, this));
0479     } else {
0480         removeInteractionFactory(EditFillGradientFactoryId);
0481     }
0482     repaintDecorations();
0483 }
0484 
0485 void DefaultTool::slotActivateEditStrokeGradient(bool value)
0486 {
0487     if (value) {
0488         addInteractionFactory(
0489             new MoveGradientHandleInteractionFactory(KoFlake::StrokeFill,
0490                                                      0, EditStrokeGradientFactoryId, this));
0491     } else {
0492         removeInteractionFactory(EditStrokeGradientFactoryId);
0493     }
0494     repaintDecorations();
0495 }
0496 
0497 void DefaultTool::slotActivateEditFillMeshGradient(bool value)
0498 {
0499     if (value) {
0500         connect(this, SIGNAL(meshgradientHandleSelected(KoShapeMeshGradientHandles::Handle)),
0501                 m_tabbedOptionWidget, SLOT(slotMeshGradientHandleSelected(KoShapeMeshGradientHandles::Handle)));
0502         addInteractionFactory(
0503             new MoveMeshGradientHandleInteractionFactory(KoFlake::Fill, 1,
0504                                                          EditFillMeshGradientFactoryId, this));
0505     } else {
0506         disconnect(this, SIGNAL(meshgradientHandleSelected(KoShapeMeshGradientHandles::Handle)),
0507                    m_tabbedOptionWidget, SLOT(slotMeshGradientHandleSelected(KoShapeMeshGradientHandles::Handle)));
0508         removeInteractionFactory(EditFillMeshGradientFactoryId);
0509     }
0510 }
0511 
0512 void DefaultTool::slotResetMeshGradientState()
0513 {
0514     m_selectedMeshHandle = KoShapeMeshGradientHandles::Handle();
0515 }
0516 
0517 bool DefaultTool::wantsAutoScroll() const
0518 {
0519     return true;
0520 }
0521 
0522 void DefaultTool::addMappedAction(KisSignalMapper *mapper, const QString &actionId, int commandType)
0523 {
0524     QAction *a =action(actionId);
0525     connect(a, SIGNAL(triggered()), mapper, SLOT(map()));
0526     mapper->setMapping(a, commandType);
0527 }
0528 
0529 void DefaultTool::setupActions()
0530 {
0531     m_alignSignalsMapper = new KisSignalMapper(this);
0532 
0533     addMappedAction(m_alignSignalsMapper, "object_align_horizontal_left", KoShapeAlignCommand::HorizontalLeftAlignment);
0534     addMappedAction(m_alignSignalsMapper, "object_align_horizontal_center", KoShapeAlignCommand::HorizontalCenterAlignment);
0535     addMappedAction(m_alignSignalsMapper, "object_align_horizontal_right", KoShapeAlignCommand::HorizontalRightAlignment);
0536     addMappedAction(m_alignSignalsMapper, "object_align_vertical_top", KoShapeAlignCommand::VerticalTopAlignment);
0537     addMappedAction(m_alignSignalsMapper, "object_align_vertical_center", KoShapeAlignCommand::VerticalCenterAlignment);
0538     addMappedAction(m_alignSignalsMapper, "object_align_vertical_bottom", KoShapeAlignCommand::VerticalBottomAlignment);
0539 
0540     m_distributeSignalsMapper = new KisSignalMapper(this);
0541 
0542     addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_left", KoShapeDistributeCommand::HorizontalLeftDistribution);
0543     addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_center", KoShapeDistributeCommand::HorizontalCenterDistribution);
0544     addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_right", KoShapeDistributeCommand::HorizontalRightDistribution);
0545     addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_gaps", KoShapeDistributeCommand::HorizontalGapsDistribution);
0546 
0547     addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_top", KoShapeDistributeCommand::VerticalTopDistribution);
0548     addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_center", KoShapeDistributeCommand::VerticalCenterDistribution);
0549     addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_bottom", KoShapeDistributeCommand::VerticalBottomDistribution);
0550     addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_gaps", KoShapeDistributeCommand::VerticalGapsDistribution);
0551 
0552     m_transformSignalsMapper = new KisSignalMapper(this);
0553 
0554     addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_cw", TransformRotate90CW);
0555     addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_ccw", TransformRotate90CCW);
0556     addMappedAction(m_transformSignalsMapper, "object_transform_rotate_180", TransformRotate180);
0557     addMappedAction(m_transformSignalsMapper, "object_transform_mirror_horizontally", TransformMirrorX);
0558     addMappedAction(m_transformSignalsMapper, "object_transform_mirror_vertically", TransformMirrorY);
0559     addMappedAction(m_transformSignalsMapper, "object_transform_reset", TransformReset);
0560 
0561     m_booleanSignalsMapper = new KisSignalMapper(this);
0562 
0563     addMappedAction(m_booleanSignalsMapper, "object_unite", BooleanUnion);
0564     addMappedAction(m_booleanSignalsMapper, "object_intersect", BooleanIntersection);
0565     addMappedAction(m_booleanSignalsMapper, "object_subtract", BooleanSubtraction);
0566 
0567     m_contextMenu.reset(new QMenu());
0568 }
0569 
0570 qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation)
0571 {
0572     QPointF selectionCenter = koSelection()->absolutePosition();
0573     QPointF direction;
0574 
0575     switch (handle) {
0576     case KoFlake::TopMiddleHandle:
0577         if (useEdgeRotation) {
0578             direction = koSelection()->absolutePosition(KoFlake::TopRight)
0579                         - koSelection()->absolutePosition(KoFlake::TopLeft);
0580         } else {
0581             QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft);
0582             handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRight) - handlePosition);
0583             direction = handlePosition - selectionCenter;
0584         }
0585         break;
0586     case KoFlake::TopRightHandle:
0587         direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized()).toPointF();
0588         break;
0589     case KoFlake::RightMiddleHandle:
0590         if (useEdgeRotation) {
0591             direction = koSelection()->absolutePosition(KoFlake::BottomRight)
0592                         - koSelection()->absolutePosition(KoFlake::TopRight);
0593         } else {
0594             QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRight);
0595             handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition);
0596             direction = handlePosition - selectionCenter;
0597         }
0598         break;
0599     case KoFlake::BottomRightHandle:
0600         direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized()).toPointF();
0601         break;
0602     case KoFlake::BottomMiddleHandle:
0603         if (useEdgeRotation) {
0604             direction = koSelection()->absolutePosition(KoFlake::BottomLeft)
0605                         - koSelection()->absolutePosition(KoFlake::BottomRight);
0606         } else {
0607             QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeft);
0608             handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition);
0609             direction = handlePosition - selectionCenter;
0610         }
0611         break;
0612     case KoFlake::BottomLeftHandle:
0613         direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - selectionCenter;
0614         direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized()).toPointF();
0615         break;
0616     case KoFlake::LeftMiddleHandle:
0617         if (useEdgeRotation) {
0618             direction = koSelection()->absolutePosition(KoFlake::TopLeft)
0619                         - koSelection()->absolutePosition(KoFlake::BottomLeft);
0620         } else {
0621             QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft);
0622             handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeft) - handlePosition);
0623             direction = handlePosition - selectionCenter;
0624         }
0625         break;
0626     case KoFlake::TopLeftHandle:
0627         direction = koSelection()->absolutePosition(KoFlake::TopLeft) - selectionCenter;
0628         direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized()).toPointF();
0629         break;
0630     case KoFlake::NoHandle:
0631         return 0.0;
0632         break;
0633     }
0634 
0635     qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI;
0636 
0637     switch (handle) {
0638     case KoFlake::TopMiddleHandle:
0639         if (useEdgeRotation) {
0640             rotation -= 0.0;
0641         } else {
0642             rotation -= 270.0;
0643         }
0644         break;
0645     case KoFlake::TopRightHandle:
0646         rotation -= 315.0;
0647         break;
0648     case KoFlake::RightMiddleHandle:
0649         if (useEdgeRotation) {
0650             rotation -= 90.0;
0651         } else {
0652             rotation -= 0.0;
0653         }
0654         break;
0655     case KoFlake::BottomRightHandle:
0656         rotation -= 45.0;
0657         break;
0658     case KoFlake::BottomMiddleHandle:
0659         if (useEdgeRotation) {
0660             rotation -= 180.0;
0661         } else {
0662             rotation -= 90.0;
0663         }
0664         break;
0665     case KoFlake::BottomLeftHandle:
0666         rotation -= 135.0;
0667         break;
0668     case KoFlake::LeftMiddleHandle:
0669         if (useEdgeRotation) {
0670             rotation -= 270.0;
0671         } else {
0672             rotation -= 180.0;
0673         }
0674         break;
0675     case KoFlake::TopLeftHandle:
0676         rotation -= 225.0;
0677         break;
0678     default:
0679         ;
0680     }
0681 
0682     if (rotation < 0.0) {
0683         rotation += 360.0;
0684     }
0685 
0686     return rotation;
0687 }
0688 
0689 void DefaultTool::updateCursor()
0690 {
0691     if (tryUseCustomCursor()) return;
0692 
0693     QCursor cursor = Qt::ArrowCursor;
0694 
0695     QString statusText;
0696 
0697     KoSelection *selection = koSelection();
0698     if (selection && selection->count() > 0) { // has a selection
0699         bool editable = !selection->selectedEditableShapes().isEmpty();
0700 
0701         if (!m_mouseWasInsideHandles) {
0702             m_angle = rotationOfHandle(m_lastHandle, true);
0703             int rotOctant = 8 + int(8.5 + m_angle / 45);
0704 
0705             bool rotateHandle = false;
0706             bool shearHandle = false;
0707             switch (m_lastHandle) {
0708             case KoFlake::TopMiddleHandle:
0709                 cursor = m_shearCursors[(0 + rotOctant) % 8];
0710                 shearHandle = true;
0711                 break;
0712             case KoFlake::TopRightHandle:
0713                 cursor = m_rotateCursors[(1 + rotOctant) % 8];
0714                 rotateHandle = true;
0715                 break;
0716             case KoFlake::RightMiddleHandle:
0717                 cursor = m_shearCursors[(2 + rotOctant) % 8];
0718                 shearHandle = true;
0719                 break;
0720             case KoFlake::BottomRightHandle:
0721                 cursor = m_rotateCursors[(3 + rotOctant) % 8];
0722                 rotateHandle = true;
0723                 break;
0724             case KoFlake::BottomMiddleHandle:
0725                 cursor = m_shearCursors[(4 + rotOctant) % 8];
0726                 shearHandle = true;
0727                 break;
0728             case KoFlake::BottomLeftHandle:
0729                 cursor = m_rotateCursors[(5 + rotOctant) % 8];
0730                 rotateHandle = true;
0731                 break;
0732             case KoFlake::LeftMiddleHandle:
0733                 cursor = m_shearCursors[(6 + rotOctant) % 8];
0734                 shearHandle = true;
0735                 break;
0736             case KoFlake::TopLeftHandle:
0737                 cursor = m_rotateCursors[(7 + rotOctant) % 8];
0738                 rotateHandle = true;
0739                 break;
0740             case KoFlake::NoHandle:
0741                 cursor = Qt::ArrowCursor;
0742                 break;
0743             }
0744             if (rotateHandle) {
0745                 statusText = i18n("Left click rotates around center, right click around highlighted position.");
0746             }
0747             if (shearHandle) {
0748                 statusText = i18n("Click and drag to shear selection.");
0749             }
0750 
0751 
0752         } else {
0753             statusText = i18n("Click and drag to resize selection.");
0754             m_angle = rotationOfHandle(m_lastHandle, false);
0755             int rotOctant = 8 + int(8.5 + m_angle / 45);
0756             bool cornerHandle = false;
0757             switch (m_lastHandle) {
0758             case KoFlake::TopMiddleHandle:
0759                 cursor = m_sizeCursors[(0 + rotOctant) % 8];
0760                 break;
0761             case KoFlake::TopRightHandle:
0762                 cursor = m_sizeCursors[(1 + rotOctant) % 8];
0763                 cornerHandle = true;
0764                 break;
0765             case KoFlake::RightMiddleHandle:
0766                 cursor = m_sizeCursors[(2 + rotOctant) % 8];
0767                 break;
0768             case KoFlake::BottomRightHandle:
0769                 cursor = m_sizeCursors[(3 + rotOctant) % 8];
0770                 cornerHandle = true;
0771                 break;
0772             case KoFlake::BottomMiddleHandle:
0773                 cursor = m_sizeCursors[(4 + rotOctant) % 8];
0774                 break;
0775             case KoFlake::BottomLeftHandle:
0776                 cursor = m_sizeCursors[(5 + rotOctant) % 8];
0777                 cornerHandle = true;
0778                 break;
0779             case KoFlake::LeftMiddleHandle:
0780                 cursor = m_sizeCursors[(6 + rotOctant) % 8];
0781                 break;
0782             case KoFlake::TopLeftHandle:
0783                 cursor = m_sizeCursors[(7 + rotOctant) % 8];
0784                 cornerHandle = true;
0785                 break;
0786             case KoFlake::NoHandle:
0787                 cursor = Qt::SizeAllCursor;
0788                 statusText = i18n("Click and drag to move selection.");
0789                 break;
0790             }
0791             if (cornerHandle) {
0792                 statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position.");
0793             }
0794         }
0795         if (!editable) {
0796             cursor = Qt::ArrowCursor;
0797         }
0798     } else {
0799         // there used to be guides... :'''(
0800     }
0801     useCursor(cursor);
0802     if (currentStrategy() == 0) {
0803         emit statusTextChanged(statusText);
0804     }
0805 }
0806 
0807 void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter)
0808 {
0809     KoSelection *selection = koSelection();
0810     if (selection) {
0811         m_decorator.reset(new SelectionDecorator(canvas()->resourceManager()));
0812 
0813         {
0814             /**
0815              * Selection masks don't render the outline of the shapes, so we should
0816              * do that explicitly when rendering them via selection
0817              */
0818 
0819             KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
0820             KisNodeSP node = kisCanvas->viewManager()->nodeManager()->activeNode();
0821             const bool isSelectionMask = node && node->inherits("KisSelectionMask");
0822             m_decorator->setForceShapeOutlines(isSelectionMask);
0823         }
0824 
0825         m_decorator->setSelection(selection);
0826         m_decorator->setHandleRadius(handleRadius());
0827         m_decorator->setDecorationThickness(decorationThickness());
0828         m_decorator->setShowFillGradientHandles(hasInteractionFactory(EditFillGradientFactoryId));
0829         m_decorator->setShowStrokeFillGradientHandles(hasInteractionFactory(EditStrokeGradientFactoryId));
0830         m_decorator->setShowFillMeshGradientHandles(hasInteractionFactory(EditFillMeshGradientFactoryId));
0831         m_decorator->setCurrentMeshGradientHandles(m_selectedMeshHandle, m_hoveredMeshHandle);
0832         m_decorator->paint(painter, converter);
0833     }
0834 
0835     KoInteractionTool::paint(painter, converter);
0836 
0837     painter.save();
0838     painter.setTransform(converter.documentToView(), true);
0839     canvas()->snapGuide()->paint(painter, converter);
0840     painter.restore();
0841 }
0842 
0843 bool DefaultTool::isValidForCurrentLayer() const
0844 {
0845     // if the currently active node has a shape manager, then it is
0846     // probably our client :)
0847 
0848     KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
0849     return bool(kisCanvas->localShapeManager());
0850 }
0851 
0852 KoShapeManager *DefaultTool::shapeManager() const {
0853     return canvas()->shapeManager();
0854 }
0855 
0856 void DefaultTool::mousePressEvent(KoPointerEvent *event)
0857 {
0858     // this tool only works on a vector layer right now, so give a warning if another layer type is trying to use it
0859     if (!isValidForCurrentLayer()) {
0860         KisCanvas2 *kiscanvas = static_cast<KisCanvas2 *>(canvas());
0861         kiscanvas->viewManager()->showFloatingMessage(
0862                 i18n("This tool only works on vector layers. You probably want the move tool."),
0863                 QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter);
0864         return;
0865     }
0866 
0867     KoInteractionTool::mousePressEvent(event);
0868     updateCursor();
0869 }
0870 
0871 void DefaultTool::mouseMoveEvent(KoPointerEvent *event)
0872 {
0873     KoInteractionTool::mouseMoveEvent(event);
0874     if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) {
0875         QRectF bound = handlesSize();
0876 
0877         if (bound.contains(event->point)) {
0878             bool inside;
0879             KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside);
0880 
0881             if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) {
0882                 m_lastHandle = newDirection;
0883                 m_mouseWasInsideHandles = inside;
0884             }
0885         } else {
0886             m_lastHandle = KoFlake::NoHandle;
0887             m_mouseWasInsideHandles = false;
0888 
0889             // there used to be guides... :'''(
0890         }
0891     } else {
0892         // there used to be guides... :'''(
0893     }
0894 
0895 
0896     updateCursor();
0897 }
0898 
0899 QRectF DefaultTool::handlesSize()
0900 {
0901     KoSelection *selection = koSelection();
0902     if (!selection || !selection->count()) return QRectF();
0903 
0904     recalcSelectionBox(selection);
0905 
0906     QRectF bound = m_selectionOutline.boundingRect();
0907 
0908     // expansion Border
0909     if (!canvas() || !canvas()->viewConverter()) {
0910         return bound;
0911     }
0912 
0913     QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE));
0914     bound.adjust(-border.x(), -border.y(), border.x(), border.y());
0915     return bound;
0916 }
0917 
0918 void DefaultTool::mouseReleaseEvent(KoPointerEvent *event)
0919 {
0920     KoInteractionTool::mouseReleaseEvent(event);
0921     updateCursor();
0922 }
0923 
0924 void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event)
0925 {
0926     KoSelection *selection = koSelection();
0927 
0928     KoShape *shape = shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop);
0929     if (shape && selection && !selection->isSelected(shape)) {
0930 
0931         if (!(event->modifiers() & Qt::ShiftModifier)) {
0932             selection->deselectAll();
0933         }
0934 
0935         selection->select(shape);
0936     }
0937 
0938     explicitUserStrokeEndRequest();
0939 }
0940 
0941 bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers)
0942 {
0943     bool result = false;
0944 
0945     qreal x = 0.0, y = 0.0;
0946     if (direction == Qt::Key_Left) {
0947         x = -5;
0948     } else if (direction == Qt::Key_Right) {
0949         x = 5;
0950     } else if (direction == Qt::Key_Up) {
0951         y = -5;
0952     } else if (direction == Qt::Key_Down) {
0953         y = 5;
0954     }
0955 
0956     if (x != 0.0 || y != 0.0) { // actually move
0957 
0958         if ((modifiers & Qt::ShiftModifier) != 0) {
0959             x *= 10;
0960             y *= 10;
0961         } else if ((modifiers & Qt::AltModifier) != 0) { // more precise
0962             x /= 5;
0963             y /= 5;
0964         }
0965 
0966         QList<KoShape *> shapes = koSelection()->selectedEditableShapes();
0967 
0968         if (!shapes.isEmpty()) {
0969             canvas()->addCommand(new KoShapeMoveCommand(shapes, QPointF(x, y)));
0970             result = true;
0971         }
0972     }
0973 
0974     return result;
0975 }
0976 
0977 void DefaultTool::keyPressEvent(QKeyEvent *event)
0978 {
0979     KoInteractionTool::keyPressEvent(event);
0980     if (currentStrategy() == 0) {
0981         switch (event->key()) {
0982         case Qt::Key_Left:
0983         case Qt::Key_Right:
0984         case Qt::Key_Up:
0985         case Qt::Key_Down:
0986             if (moveSelection(event->key(), event->modifiers())) {
0987                 event->accept();
0988             }
0989             break;
0990         default:
0991             return;
0992         }
0993     }
0994 }
0995 
0996 QRectF DefaultTool::decorationsRect() const
0997 {
0998     QRectF dirtyRect;
0999 
1000     if (koSelection() && koSelection()->count() > 0) {
1001         /// TODO: avoid cons_cast by implementing proper
1002         ///       caching strategy inrecalcSelectionBox() and
1003         ///       handlesSize()
1004         dirtyRect = const_cast<DefaultTool*>(this)->handlesSize();
1005     }
1006 
1007     if (canvas()->snapGuide()->isSnapping()) {
1008         dirtyRect |= canvas()->snapGuide()->boundingRect();
1009     }
1010 
1011     return dirtyRect;
1012 }
1013 
1014 void DefaultTool::copy() const
1015 {
1016     // all the selected shapes, not only editable!
1017     QList<KoShape *> shapes = koSelection()->selectedShapes();
1018 
1019     if (!shapes.isEmpty()) {
1020         KoDrag drag;
1021         drag.setSvg(shapes);
1022         drag.addToClipboard();
1023     }
1024 }
1025 
1026 void DefaultTool::deleteSelection()
1027 {
1028     QList<KoShape *> shapes;
1029     foreach (KoShape *s, koSelection()->selectedShapes()) {
1030         if (s->isGeometryProtected()) {
1031             continue;
1032         }
1033         shapes << s;
1034     }
1035     if (!shapes.empty()) {
1036         canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes));
1037     }
1038 }
1039 
1040 bool DefaultTool::paste()
1041 {
1042     // we no longer have to do anything as tool Proxy will do it for us
1043     return false;
1044 }
1045 
1046 bool DefaultTool::selectAll()
1047 {
1048     Q_ASSERT(canvas());
1049     Q_ASSERT(canvas()->selectedShapesProxy());
1050     Q_FOREACH(KoShape *shape, canvas()->shapeManager()->shapes()) {
1051         if (!shape->isSelectable()) continue;
1052         canvas()->selectedShapesProxy()->selection()->select(shape);
1053     }
1054     repaintDecorations();
1055 
1056     return true;
1057 }
1058 
1059 void DefaultTool::deselect()
1060 {
1061     Q_ASSERT(canvas());
1062     Q_ASSERT(canvas()->selectedShapesProxy());
1063     canvas()->selectedShapesProxy()->selection()->deselectAll();
1064     repaintDecorations();
1065 }
1066 
1067 KoSelection *DefaultTool::koSelection() const
1068 {
1069     Q_ASSERT(canvas());
1070     Q_ASSERT(canvas()->selectedShapesProxy());
1071     return canvas()->selectedShapesProxy()->selection();
1072 }
1073 
1074 KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning)
1075 {
1076     // check for handles in this order; meaning that when handles overlap the one on top is chosen
1077     static const KoFlake::SelectionHandle handleOrder[] = {
1078         KoFlake::BottomRightHandle,
1079         KoFlake::TopLeftHandle,
1080         KoFlake::BottomLeftHandle,
1081         KoFlake::TopRightHandle,
1082         KoFlake::BottomMiddleHandle,
1083         KoFlake::RightMiddleHandle,
1084         KoFlake::LeftMiddleHandle,
1085         KoFlake::TopMiddleHandle,
1086         KoFlake::NoHandle
1087     };
1088 
1089     const KoViewConverter *converter = canvas()->viewConverter();
1090     KoSelection *selection = koSelection();
1091 
1092     if (!selection || !selection->count() || !converter) {
1093         return KoFlake::NoHandle;
1094     }
1095 
1096     recalcSelectionBox(selection);
1097 
1098     if (innerHandleMeaning) {
1099         QPainterPath path;
1100         path.addPolygon(m_selectionOutline);
1101         *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point));
1102     }
1103 
1104     const QPointF viewPoint = converter->documentToView(point);
1105 
1106     for (int i = 0; i < KoFlake::NoHandle; ++i) {
1107         KoFlake::SelectionHandle handle = handleOrder[i];
1108 
1109         const QPointF handlePoint = converter->documentToView(m_selectionBox[handle]);
1110         const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
1111 
1112         // if just inside the outline
1113         if (distanceSq < HANDLE_DISTANCE_SQ) {
1114 
1115             if (innerHandleMeaning) {
1116                 if (distanceSq < INNER_HANDLE_DISTANCE_SQ) {
1117                     *innerHandleMeaning = true;
1118                 }
1119             }
1120 
1121             return handle;
1122         }
1123     }
1124     return KoFlake::NoHandle;
1125 }
1126 
1127 void DefaultTool::recalcSelectionBox(KoSelection *selection)
1128 {
1129     KIS_ASSERT_RECOVER_RETURN(selection->count());
1130 
1131     QTransform matrix = selection->absoluteTransformation();
1132     m_selectionOutline = matrix.map(QPolygonF(selection->outlineRect()));
1133     m_angle = 0.0;
1134 
1135     QPolygonF outline = m_selectionOutline; //shorter name in the following :)
1136     m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2;
1137     m_selectionBox[KoFlake::TopRightHandle] = outline.value(1);
1138     m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2;
1139     m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2);
1140     m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2;
1141     m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3);
1142     m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2;
1143     m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0);
1144     if (selection->count() == 1) {
1145 #if 0        // TODO detect mirroring
1146         KoShape *s = koSelection()->firstSelectedShape();
1147 
1148         if (s->scaleX() < 0) { // vertically mirrored: swap left / right
1149             std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]);
1150             std::swap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]);
1151             std::swap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]);
1152         }
1153         if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom
1154             std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]);
1155             std::swap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]);
1156             std::swap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]);
1157         }
1158 #endif
1159     }
1160 }
1161 
1162 void DefaultTool::activate(const QSet<KoShape *> &shapes)
1163 {
1164     KoToolBase::activate(shapes);
1165 
1166     QAction *actionBringToFront = action("object_order_front");
1167     connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront()), Qt::UniqueConnection);
1168 
1169     QAction *actionRaise = action("object_order_raise");
1170     connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp()), Qt::UniqueConnection);
1171 
1172     QAction *actionLower = action("object_order_lower");
1173     connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown()));
1174 
1175     QAction *actionSendToBack = action("object_order_back");
1176     connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack()), Qt::UniqueConnection);
1177 
1178     QAction *actionGroupBottom = action("object_group");
1179     connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup()), Qt::UniqueConnection);
1180 
1181     QAction *actionUngroupBottom = action("object_ungroup");
1182     connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup()), Qt::UniqueConnection);
1183 
1184     QAction *actionSplit = action("object_split");
1185     connect(actionSplit, SIGNAL(triggered()), this, SLOT(selectionSplitShapes()), Qt::UniqueConnection);
1186 
1187     connect(m_alignSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionAlign(int)));
1188     connect(m_distributeSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionDistribute(int)));
1189     connect(m_transformSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionTransform(int)));
1190     connect(m_booleanSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionBooleanOp(int)));
1191 
1192     m_mouseWasInsideHandles = false;
1193     m_lastHandle = KoFlake::NoHandle;
1194     useCursor(Qt::ArrowCursor);
1195     repaintDecorations();
1196     updateActions();
1197 
1198     if (m_tabbedOptionWidget) {
1199         m_tabbedOptionWidget->activate();
1200     }
1201 }
1202 
1203 void DefaultTool::deactivate()
1204 {
1205     KoToolBase::deactivate();
1206 
1207     QAction *actionBringToFront = action("object_order_front");
1208     disconnect(actionBringToFront, 0, this, 0);
1209 
1210     QAction *actionRaise = action("object_order_raise");
1211     disconnect(actionRaise, 0, this, 0);
1212 
1213     QAction *actionLower = action("object_order_lower");
1214     disconnect(actionLower, 0, this, 0);
1215 
1216     QAction *actionSendToBack = action("object_order_back");
1217     disconnect(actionSendToBack, 0, this, 0);
1218 
1219     QAction *actionGroupBottom = action("object_group");
1220     disconnect(actionGroupBottom, 0, this, 0);
1221 
1222     QAction *actionUngroupBottom = action("object_ungroup");
1223     disconnect(actionUngroupBottom, 0, this, 0);
1224 
1225     QAction *actionSplit = action("object_split");
1226     disconnect(actionSplit, 0, this, 0);
1227 
1228     disconnect(m_alignSignalsMapper, 0, this, 0);
1229     disconnect(m_distributeSignalsMapper, 0, this, 0);
1230     disconnect(m_transformSignalsMapper, 0, this, 0);
1231     disconnect(m_booleanSignalsMapper, 0, this, 0);
1232 
1233 
1234     if (m_tabbedOptionWidget) {
1235         m_tabbedOptionWidget->deactivate();
1236     }
1237 }
1238 
1239 void DefaultTool::selectionGroup()
1240 {
1241     KoSelection *selection = koSelection();
1242     if (!selection) return;
1243 
1244     QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
1245     std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
1246     if (selectedShapes.isEmpty()) return;
1247 
1248     const int groupZIndex = selectedShapes.last()->zIndex();
1249 
1250     KoShapeGroup *group = new KoShapeGroup();
1251     group->setZIndex(groupZIndex);
1252     // TODO what if only one shape is left?
1253     KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes"));
1254     new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
1255     canvas()->shapeController()->addShapeDirect(group, 0, cmd);
1256     new KoShapeGroupCommand(group, selectedShapes, true, cmd);
1257     new KoKeepShapesSelectedCommand({}, {group}, canvas()->selectedShapesProxy(), true, cmd);
1258     canvas()->addCommand(cmd);
1259 
1260     // update selection so we can ungroup immediately again
1261     selection->deselectAll();
1262     selection->select(group);
1263 }
1264 
1265 void DefaultTool::selectionUngroup()
1266 {
1267     KoSelection *selection = koSelection();
1268     if (!selection) return;
1269 
1270     QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
1271     std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
1272 
1273     KUndo2Command *cmd = 0;
1274     QList<KoShape*> newShapes;
1275 
1276     // add a ungroup command for each found shape container to the macro command
1277     Q_FOREACH (KoShape *shape, selectedShapes) {
1278         KoShapeGroup *group = dynamic_cast<KoShapeGroup *>(shape);
1279         if (group) {
1280             if (!cmd) {
1281                 cmd = new KUndo2Command(kundo2_i18n("Ungroup shapes"));
1282                 new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
1283             }
1284             newShapes << group->shapes();
1285             new KoShapeUngroupCommand(group, group->shapes(),
1286                                       group->parent() ? QList<KoShape *>() : shapeManager()->topLevelShapes(),
1287                                       cmd);
1288             canvas()->shapeController()->removeShape(group, cmd);
1289         }
1290     }
1291     if (cmd) {
1292         new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd);
1293         canvas()->addCommand(cmd);
1294     }
1295 }
1296 
1297 void DefaultTool::selectionTransform(int transformAction)
1298 {
1299     KoSelection *selection = koSelection();
1300     if (!selection) return;
1301 
1302     QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1303     if (editableShapes.isEmpty()) {
1304         return;
1305     }
1306 
1307     QTransform applyTransform;
1308     bool shouldReset = false;
1309     KUndo2MagicString actionName = kundo2_noi18n("BUG: No transform action");
1310 
1311 
1312     switch (TransformActionType(transformAction)) {
1313     case TransformRotate90CW:
1314         applyTransform.rotate(90.0);
1315         actionName = kundo2_i18n("Rotate Object 90° CW");
1316         break;
1317     case TransformRotate90CCW:
1318         applyTransform.rotate(-90.0);
1319         actionName = kundo2_i18n("Rotate Object 90° CCW");
1320         break;
1321     case TransformRotate180:
1322         applyTransform.rotate(180.0);
1323         actionName = kundo2_i18n("Rotate Object 180°");
1324         break;
1325     case TransformMirrorX:
1326         applyTransform.scale(-1.0, 1.0);
1327         actionName = kundo2_i18n("Mirror Object Horizontally");
1328         break;
1329     case TransformMirrorY:
1330         applyTransform.scale(1.0, -1.0);
1331         actionName = kundo2_i18n("Mirror Object Vertically");
1332         break;
1333     case TransformReset:
1334         shouldReset = true;
1335         actionName = kundo2_i18n("Reset Object Transformations");
1336         break;
1337     }
1338 
1339     if (!shouldReset && applyTransform.isIdentity()) return;
1340 
1341     QList<QTransform> oldTransforms;
1342     QList<QTransform> newTransforms;
1343 
1344     const QRectF outlineRect = KoShape::absoluteOutlineRect(editableShapes);
1345     const QPointF centerPoint = outlineRect.center();
1346     const QTransform centerTrans = QTransform::fromTranslate(centerPoint.x(), centerPoint.y());
1347     const QTransform centerTransInv = QTransform::fromTranslate(-centerPoint.x(), -centerPoint.y());
1348 
1349     // we also add selection to the list of transformed shapes, so that its outline is updated correctly
1350     QList<KoShape*> transformedShapes = editableShapes;
1351     transformedShapes << selection;
1352 
1353     Q_FOREACH (KoShape *shape, transformedShapes) {
1354         oldTransforms.append(shape->transformation());
1355 
1356         QTransform t;
1357 
1358         if (!shouldReset) {
1359             const QTransform world = shape->absoluteTransformation();
1360             t =  world * centerTransInv * applyTransform * centerTrans * world.inverted() * shape->transformation();
1361         } else {
1362             const QPointF center = shape->outlineRect().center();
1363             const QPointF offset = shape->transformation().map(center) - center;
1364             t = QTransform::fromTranslate(offset.x(), offset.y());
1365         }
1366 
1367         newTransforms.append(t);
1368     }
1369 
1370     KoShapeTransformCommand *cmd = new KoShapeTransformCommand(transformedShapes, oldTransforms, newTransforms);
1371     cmd->setText(actionName);
1372     canvas()->addCommand(cmd);
1373 }
1374 
1375 void DefaultTool::selectionBooleanOp(int booleanOp)
1376 {
1377     KoSelection *selection = koSelection();
1378     if (!selection) return;
1379 
1380     QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1381     if (editableShapes.isEmpty()) {
1382         return;
1383     }
1384 
1385     QVector<QPainterPath> srcOutlines;
1386     QPainterPath dstOutline;
1387     KUndo2MagicString actionName = kundo2_noi18n("BUG: boolean action name");
1388 
1389     // TODO: implement a reference shape selection dialog!
1390     const int referenceShapeIndex = 0;
1391     KoShape *referenceShape = editableShapes[referenceShapeIndex];
1392 
1393     KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
1394     KIS_SAFE_ASSERT_RECOVER_RETURN(kisCanvas);
1395     const QTransform booleanWorkaroundTransform =
1396         KritaUtils::pathShapeBooleanSpaceWorkaround(kisCanvas->image());
1397 
1398     Q_FOREACH (KoShape *shape, editableShapes) {
1399         srcOutlines <<
1400             booleanWorkaroundTransform.map(
1401             shape->absoluteTransformation().map(
1402                 shape->outline()));
1403     }
1404 
1405     if (booleanOp == BooleanUnion) {
1406         Q_FOREACH (const QPainterPath &path, srcOutlines) {
1407             dstOutline |= path;
1408         }
1409         actionName = kundo2_i18n("Unite Shapes");
1410     } else if (booleanOp == BooleanIntersection) {
1411         for (int i = 0; i < srcOutlines.size(); i++) {
1412             if (i == 0) {
1413                 dstOutline = srcOutlines[i];
1414             } else {
1415                 dstOutline &= srcOutlines[i];
1416             }
1417         }
1418 
1419         // there is a bug in Qt, sometimes it leaves the resulting
1420         // outline open, so just close it explicitly.
1421         dstOutline.closeSubpath();
1422 
1423         actionName = kundo2_i18n("Intersect Shapes");
1424 
1425     } else if (booleanOp == BooleanSubtraction) {
1426         for (int i = 0; i < srcOutlines.size(); i++) {
1427             dstOutline = srcOutlines[referenceShapeIndex];
1428             if (i != referenceShapeIndex) {
1429                 dstOutline -= srcOutlines[i];
1430             }
1431         }
1432 
1433         actionName = kundo2_i18n("Subtract Shapes");
1434     }
1435 
1436     dstOutline = booleanWorkaroundTransform.inverted().map(dstOutline);
1437 
1438     KoShape *newShape = 0;
1439 
1440     if (!dstOutline.isEmpty()) {
1441         newShape = KoPathShape::createShapeFromPainterPath(dstOutline);
1442     }
1443 
1444     KUndo2Command *cmd = new KUndo2Command(actionName);
1445 
1446     new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
1447 
1448     QList<KoShape*> newSelectedShapes;
1449 
1450     if (newShape) {
1451         newShape->setBackground(referenceShape->background());
1452         newShape->setStroke(referenceShape->stroke());
1453         newShape->setZIndex(referenceShape->zIndex());
1454 
1455         KoShapeContainer *parent = referenceShape->parent();
1456         canvas()->shapeController()->addShapeDirect(newShape, parent, cmd);
1457 
1458         newSelectedShapes << newShape;
1459     }
1460 
1461     canvas()->shapeController()->removeShapes(editableShapes, cmd);
1462 
1463     new KoKeepShapesSelectedCommand({}, newSelectedShapes, canvas()->selectedShapesProxy(), true, cmd);
1464 
1465     canvas()->addCommand(cmd);
1466 }
1467 
1468 void DefaultTool::selectionSplitShapes()
1469 {
1470     KoSelection *selection = koSelection();
1471     if (!selection) return;
1472 
1473     QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1474     if (editableShapes.isEmpty()) {
1475         return;
1476     }
1477 
1478     KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Split Shapes"));
1479 
1480     new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
1481     QList<KoShape*> newShapes;
1482 
1483     Q_FOREACH (KoShape *shape, editableShapes) {
1484         KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
1485         if (!pathShape) return;
1486 
1487         QList<KoPathShape*> splitShapes;
1488         if (pathShape->separate(splitShapes)) {
1489             QList<KoShape*> normalShapes = implicitCastList<KoShape*>(splitShapes);
1490 
1491             KoShapeContainer *parent = shape->parent();
1492             canvas()->shapeController()->addShapesDirect(normalShapes, parent, cmd);
1493             canvas()->shapeController()->removeShape(shape, cmd);
1494             newShapes << normalShapes;
1495         }
1496     }
1497 
1498     new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd);
1499 
1500     canvas()->addCommand(cmd);
1501 }
1502 
1503 void DefaultTool::selectionAlign(int _align)
1504 {
1505     KoShapeAlignCommand::Align align =
1506         static_cast<KoShapeAlignCommand::Align>(_align);
1507 
1508     KoSelection *selection = koSelection();
1509     if (!selection) return;
1510 
1511     QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1512     if (editableShapes.isEmpty()) {
1513         return;
1514     }
1515 
1516     // TODO add an option to the widget so that one can align to the page
1517     // with multiple selected shapes too
1518 
1519     QRectF bb;
1520 
1521     // single selected shape is automatically aligned to document rect
1522     if (editableShapes.count() == 1) {
1523         if (!canvas()->resourceManager()->hasResource(KoCanvasResource::PageSize)) {
1524             return;
1525         }
1526         bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResource::PageSize));
1527     } else {
1528         bb = KoShape::absoluteOutlineRect(editableShapes);
1529     }
1530 
1531     KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb);
1532     canvas()->addCommand(cmd);
1533 }
1534 
1535 void DefaultTool::selectionDistribute(int _distribute)
1536 {
1537     KoShapeDistributeCommand::Distribute distribute =
1538         static_cast<KoShapeDistributeCommand::Distribute>(_distribute);
1539 
1540     KoSelection *selection = koSelection();
1541     if (!selection) return;
1542 
1543     QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1544     if (editableShapes.size() < 3) {
1545         return;
1546     }
1547 
1548     QRectF bb = KoShape::absoluteOutlineRect(editableShapes);
1549     KoShapeDistributeCommand *cmd = new KoShapeDistributeCommand(editableShapes, distribute, bb);
1550     canvas()->addCommand(cmd);
1551 }
1552 
1553 void DefaultTool::selectionBringToFront()
1554 {
1555     selectionReorder(KoShapeReorderCommand::BringToFront);
1556 }
1557 
1558 void DefaultTool::selectionMoveUp()
1559 {
1560     selectionReorder(KoShapeReorderCommand::RaiseShape);
1561 }
1562 
1563 void DefaultTool::selectionMoveDown()
1564 {
1565     selectionReorder(KoShapeReorderCommand::LowerShape);
1566 }
1567 
1568 void DefaultTool::selectionSendToBack()
1569 {
1570     selectionReorder(KoShapeReorderCommand::SendToBack);
1571 }
1572 
1573 void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order)
1574 {
1575     KoSelection *selection = koSelection();
1576     if (!selection) {
1577         return;
1578     }
1579 
1580     QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
1581     if (selectedShapes.isEmpty()) {
1582         return;
1583     }
1584 
1585     KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, shapeManager(), order);
1586     if (cmd) {
1587         canvas()->addCommand(cmd);
1588     }
1589 }
1590 
1591 QList<QPointer<QWidget> > DefaultTool::createOptionWidgets()
1592 {
1593     QList<QPointer<QWidget> > widgets;
1594 
1595     m_tabbedOptionWidget = new DefaultToolTabbedWidget(this);
1596 
1597     if (isActivated()) {
1598         m_tabbedOptionWidget->activate();
1599     }
1600     widgets.append(m_tabbedOptionWidget);
1601 
1602     connect(m_tabbedOptionWidget,
1603             SIGNAL(sigSwitchModeEditFillGradient(bool)),
1604             SLOT(slotActivateEditFillGradient(bool)));
1605 
1606     connect(m_tabbedOptionWidget,
1607             SIGNAL(sigSwitchModeEditStrokeGradient(bool)),
1608             SLOT(slotActivateEditStrokeGradient(bool)));
1609 
1610     connect(m_tabbedOptionWidget,
1611             SIGNAL(sigSwitchModeEditFillGradient(bool)),
1612             SLOT(slotActivateEditFillMeshGradient(bool)));
1613     // TODO: strokes!!
1614 
1615     connect(m_tabbedOptionWidget,
1616             SIGNAL(sigMeshGradientResetted()),
1617             SLOT(slotResetMeshGradientState()));
1618 
1619     return widgets;
1620 }
1621 
1622 void DefaultTool::canvasResourceChanged(int key, const QVariant &res)
1623 {
1624     if (key == HotPosition) {
1625         m_hotPosition = KoFlake::AnchorPosition(res.toInt());
1626         repaintDecorations();
1627     }
1628 }
1629 
1630 KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event)
1631 {
1632     KoSelection *selection = koSelection();
1633     if (!selection) return nullptr;
1634 
1635     bool insideSelection = false;
1636     KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection);
1637 
1638     bool editableShape = !selection->selectedEditableShapes().isEmpty();
1639 
1640     const bool selectMultiple = event->modifiers() & Qt::ShiftModifier;
1641     const bool selectNextInStack = event->modifiers() & Qt::ControlModifier;
1642     const bool avoidSelection = event->modifiers() & Qt::AltModifier;
1643 
1644     if (selectNextInStack) {
1645         // change the hot selection position when middle clicking on a handle
1646         KoFlake::AnchorPosition newHotPosition = m_hotPosition;
1647         switch (handle) {
1648         case KoFlake::TopMiddleHandle:
1649             newHotPosition = KoFlake::Top;
1650             break;
1651         case KoFlake::TopRightHandle:
1652             newHotPosition = KoFlake::TopRight;
1653             break;
1654         case KoFlake::RightMiddleHandle:
1655             newHotPosition = KoFlake::Right;
1656             break;
1657         case KoFlake::BottomRightHandle:
1658             newHotPosition = KoFlake::BottomRight;
1659             break;
1660         case KoFlake::BottomMiddleHandle:
1661             newHotPosition = KoFlake::Bottom;
1662             break;
1663         case KoFlake::BottomLeftHandle:
1664             newHotPosition = KoFlake::BottomLeft;
1665             break;
1666         case KoFlake::LeftMiddleHandle:
1667             newHotPosition = KoFlake::Left;
1668             break;
1669         case KoFlake::TopLeftHandle:
1670             newHotPosition = KoFlake::TopLeft;
1671             break;
1672         case KoFlake::NoHandle:
1673         default:
1674             // check if we had hit the center point
1675             const KoViewConverter *converter = canvas()->viewConverter();
1676             QPointF pt = converter->documentToView(event->point);
1677 
1678             // TODO: use calculated values instead!
1679             QPointF centerPt = converter->documentToView(selection->absolutePosition());
1680 
1681             if (kisSquareDistance(pt, centerPt) < HANDLE_DISTANCE_SQ) {
1682                 newHotPosition = KoFlake::Center;
1683             }
1684 
1685             break;
1686         }
1687 
1688         if (m_hotPosition != newHotPosition) {
1689             canvas()->resourceManager()->setResource(HotPosition, newHotPosition);
1690             return new NopInteractionStrategy(this);
1691         }
1692     }
1693 
1694     if (!avoidSelection && editableShape) {
1695         // manipulation of selected shapes goes first
1696         if (handle != KoFlake::NoHandle) {
1697             // resizing or shearing only with left mouse button
1698             if (insideSelection) {
1699                 bool forceUniformScaling = m_tabbedOptionWidget && m_tabbedOptionWidget->useUniformScaling();
1700                 return new ShapeResizeStrategy(this, selection, event->point, handle, forceUniformScaling);
1701             }
1702 
1703             if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle ||
1704                 handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) {
1705 
1706                 return new ShapeShearStrategy(this, selection, event->point, handle);
1707             }
1708 
1709             // rotating is allowed for right mouse button too
1710             if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle ||
1711                     handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) {
1712 
1713                 return new ShapeRotateStrategy(this, selection, event->point, event->buttons());
1714             }
1715         }
1716 
1717         if (!selectMultiple && !selectNextInStack) {
1718 
1719            if (insideSelection) {
1720                 return new ShapeMoveStrategy(this, selection, event->point);
1721             }
1722         }
1723     }
1724 
1725     KoShape *shape = shapeManager()->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop);
1726 
1727     if (avoidSelection || (!shape && handle == KoFlake::NoHandle)) {
1728         if (!selectMultiple) {
1729             selection->deselectAll();
1730         }
1731         return new SelectionInteractionStrategy(this, event->point, false);
1732     }
1733 
1734     if (selection->isSelected(shape)) {
1735         if (selectMultiple) {
1736             selection->deselect(shape);
1737         }
1738     } else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected
1739         if (!selectMultiple) {
1740             selection->deselectAll();
1741         }
1742         selection->select(shape);
1743         // tablet selection isn't precise and may lead to a move, preventing that
1744         if (event->isTabletEvent()) {
1745             return new NopInteractionStrategy(this);
1746         }
1747         return new ShapeMoveStrategy(this, selection, event->point);
1748     }
1749     return 0;
1750 }
1751 
1752 void DefaultTool::updateActions()
1753 {
1754     QList<KoShape*> editableShapes;
1755 
1756     if (koSelection()) {
1757         editableShapes = koSelection()->selectedEditableShapes();
1758     }
1759 
1760     const bool hasEditableShapes = !editableShapes.isEmpty();
1761 
1762     action("object_order_front")->setEnabled(hasEditableShapes);
1763     action("object_order_raise")->setEnabled(hasEditableShapes);
1764     action("object_order_lower")->setEnabled(hasEditableShapes);
1765     action("object_order_back")->setEnabled(hasEditableShapes);
1766 
1767     action("object_transform_rotate_90_cw")->setEnabled(hasEditableShapes);
1768     action("object_transform_rotate_90_ccw")->setEnabled(hasEditableShapes);
1769     action("object_transform_rotate_180")->setEnabled(hasEditableShapes);
1770     action("object_transform_mirror_horizontally")->setEnabled(hasEditableShapes);
1771     action("object_transform_mirror_vertically")->setEnabled(hasEditableShapes);
1772     action("object_transform_reset")->setEnabled(hasEditableShapes);
1773 
1774     const bool multipleSelected = editableShapes.size() > 1;
1775 
1776     const bool alignmentEnabled =
1777        multipleSelected ||
1778        (!editableShapes.isEmpty() &&
1779         canvas()->resourceManager()->hasResource(KoCanvasResource::PageSize));
1780 
1781     action("object_align_horizontal_left")->setEnabled(alignmentEnabled);
1782     action("object_align_horizontal_center")->setEnabled(alignmentEnabled);
1783     action("object_align_horizontal_right")->setEnabled(alignmentEnabled);
1784     action("object_align_vertical_top")->setEnabled(alignmentEnabled);
1785     action("object_align_vertical_center")->setEnabled(alignmentEnabled);
1786     action("object_align_vertical_bottom")->setEnabled(alignmentEnabled);
1787 
1788     const bool distributionEnabled = editableShapes.size() > 2;
1789 
1790     action("object_distribute_horizontal_left")->setEnabled(distributionEnabled);
1791     action("object_distribute_horizontal_center")->setEnabled(distributionEnabled);
1792     action("object_distribute_horizontal_right")->setEnabled(distributionEnabled);
1793     action("object_distribute_horizontal_gaps")->setEnabled(distributionEnabled);
1794 
1795     action("object_distribute_vertical_top")->setEnabled(distributionEnabled);
1796     action("object_distribute_vertical_center")->setEnabled(distributionEnabled);
1797     action("object_distribute_vertical_bottom")->setEnabled(distributionEnabled);
1798     action("object_distribute_vertical_gaps")->setEnabled(distributionEnabled);
1799 
1800     updateDistinctiveActions(editableShapes);
1801 
1802     emit selectionChanged(editableShapes.size());
1803 }
1804 
1805 void DefaultTool::updateDistinctiveActions(const QList<KoShape*> &editableShapes) {
1806     const bool multipleSelected = editableShapes.size() > 1;
1807 
1808     action("object_group")->setEnabled(multipleSelected);
1809 
1810     action("object_unite")->setEnabled(multipleSelected);
1811     action("object_intersect")->setEnabled(multipleSelected);
1812     action("object_subtract")->setEnabled(multipleSelected);
1813 
1814     bool hasShapesWithMultipleSegments = false;
1815     Q_FOREACH (KoShape *shape, editableShapes) {
1816             KoPathShape *pathShape = dynamic_cast<KoPathShape *>(shape);
1817             if (pathShape && pathShape->subpathCount() > 1) {
1818                 hasShapesWithMultipleSegments = true;
1819                 break;
1820             }
1821         }
1822     action("object_split")->setEnabled(hasShapesWithMultipleSegments);
1823 
1824 
1825     bool hasGroupShape = false;
1826             foreach (KoShape *shape, editableShapes) {
1827             if (dynamic_cast<KoShapeGroup *>(shape)) {
1828                 hasGroupShape = true;
1829                 break;
1830             }
1831         }
1832     action("object_ungroup")->setEnabled(hasGroupShape);
1833 }
1834 
1835 
1836 KoToolSelection *DefaultTool::selection()
1837 {
1838     return m_selectionHandler;
1839 }
1840 
1841 QMenu* DefaultTool::popupActionsMenu()
1842 {
1843     if (m_contextMenu) {
1844         m_contextMenu->clear();
1845 
1846         m_contextMenu->addSection(i18n("Vector Shape Actions"));
1847         m_contextMenu->addSeparator();
1848 
1849         QMenu *transform = m_contextMenu->addMenu(i18n("Transform"));
1850 
1851         transform->addAction(action("object_transform_rotate_90_cw"));
1852         transform->addAction(action("object_transform_rotate_90_ccw"));
1853         transform->addAction(action("object_transform_rotate_180"));
1854         transform->addSeparator();
1855         transform->addAction(action("object_transform_mirror_horizontally"));
1856         transform->addAction(action("object_transform_mirror_vertically"));
1857         transform->addSeparator();
1858         transform->addAction(action("object_transform_reset"));
1859 
1860         if (action("object_unite")->isEnabled() ||
1861             action("object_intersect")->isEnabled() ||
1862             action("object_subtract")->isEnabled() ||
1863             action("object_split")->isEnabled()) {
1864 
1865             QMenu *transform = m_contextMenu->addMenu(i18n("Logical Operations"));
1866             transform->addAction(action("object_unite"));
1867             transform->addAction(action("object_intersect"));
1868             transform->addAction(action("object_subtract"));
1869             transform->addAction(action("object_split"));
1870         }
1871 
1872         m_contextMenu->addSeparator();
1873 
1874         m_contextMenu->addAction(action("edit_cut"));
1875         m_contextMenu->addAction(action("edit_copy"));
1876         m_contextMenu->addAction(action("edit_paste"));
1877         m_contextMenu->addAction(action("paste_at"));
1878 
1879         m_contextMenu->addSeparator();
1880 
1881         m_contextMenu->addAction(action("object_order_front"));
1882         m_contextMenu->addAction(action("object_order_raise"));
1883         m_contextMenu->addAction(action("object_order_lower"));
1884         m_contextMenu->addAction(action("object_order_back"));
1885 
1886         if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) {
1887             m_contextMenu->addSeparator();
1888             m_contextMenu->addAction(action("object_group"));
1889             m_contextMenu->addAction(action("object_ungroup"));
1890         }
1891         m_contextMenu->addSeparator();
1892         m_contextMenu->addAction(action("convert_shapes_to_vector_selection"));
1893     }
1894 
1895     return m_contextMenu.data();
1896 }
1897 
1898 void DefaultTool::addTransformActions(QMenu *menu) const {
1899     menu->addAction(action("object_transform_rotate_90_cw"));
1900     menu->addAction(action("object_transform_rotate_90_ccw"));
1901     menu->addAction(action("object_transform_rotate_180"));
1902     menu->addSeparator();
1903     menu->addAction(action("object_transform_mirror_horizontally"));
1904     menu->addAction(action("object_transform_mirror_vertically"));
1905     menu->addSeparator();
1906     menu->addAction(action("object_transform_reset"));
1907 }
1908 
1909 void DefaultTool::explicitUserStrokeEndRequest()
1910 {
1911     QList<KoShape *> shapes = koSelection()->selectedEditableShapesAndDelegates();
1912     QString tool = KoToolManager::instance()->preferredToolForSelection(shapes);
1913     QTimer::singleShot(0, [tool = std::move(tool)]() {
1914         KoToolManager::instance()->switchToolRequested(tool);
1915     });
1916 }