1 /* This file is part of the KDE project
2 
3    Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
4    Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
5    Copyright (C) 2008-2009 Jan Hambrecht <jaham@gmx.net>
6    Copyright (C) 2008 C. Boemann <cbo@boemann.dk>
7 
8    This library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Library General Public
10    License as published by the Free Software Foundation; either
11    version 2 of the License, or (at your option) any later version.
12 
13    This library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Library General Public License for more details.
17 
18    You should have received a copy of the GNU Library General Public License
19    along with this library; see the file COPYING.LIB.  If not, write to
20    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22 */
23 
24 #include "DefaultTool.h"
25 #include "DefaultToolGeometryWidget.h"
26 #include "DefaultToolTabbedWidget.h"
27 #include "SelectionDecorator.h"
28 #include "ShapeMoveStrategy.h"
29 #include "ShapeRotateStrategy.h"
30 #include "ShapeShearStrategy.h"
31 #include "ShapeResizeStrategy.h"
32 
33 #include <KoPointerEvent.h>
34 #include <KoToolSelection.h>
35 #include <KoToolManager.h>
36 #include <KoSelection.h>
37 #include <KoShapeController.h>
38 #include <KoShapeManager.h>
39 #include <KoSelectedShapesProxy.h>
40 #include <KoShapeGroup.h>
41 #include <KoShapeLayer.h>
42 #include <KoShapeOdfSaveHelper.h>
43 #include <KoPathShape.h>
44 #include <KoDrag.h>
45 #include <KoCanvasBase.h>
46 #include <KoCanvasResourceProvider.h>
47 #include <KoShapeRubberSelectStrategy.h>
48 #include <commands/KoShapeMoveCommand.h>
49 #include <commands/KoShapeTransformCommand.h>
50 #include <commands/KoShapeDeleteCommand.h>
51 #include <commands/KoShapeCreateCommand.h>
52 #include <commands/KoShapeGroupCommand.h>
53 #include <commands/KoShapeUngroupCommand.h>
54 #include <commands/KoShapeDistributeCommand.h>
55 #include <commands/KoKeepShapesSelectedCommand.h>
56 #include <KoSnapGuide.h>
57 #include <KoStrokeConfigWidget.h>
58 #include "kis_action_registry.h"
59 #include "kis_node.h"
60 #include "kis_node_manager.h"
61 #include "KisViewManager.h"
62 #include "kis_canvas2.h"
63 #include "kis_canvas_resource_provider.h"
64 #include <KoInteractionStrategyFactory.h>
65 
66 #include "kis_document_aware_spin_box_unit_manager.h"
67 
68 #include <KoIcon.h>
69 
70 #include <QPainterPath>
71 #include <QPointer>
72 #include <QAction>
73 #include <QKeyEvent>
74 #include <KisSignalMapper.h>
75 #include <KoResourcePaths.h>
76 
77 #include <KoCanvasController.h>
78 #include <kactioncollection.h>
79 #include <QMenu>
80 
81 #include <math.h>
82 #include "kis_assert.h"
83 #include "kis_global.h"
84 #include "kis_debug.h"
85 #include "krita_utils.h"
86 
87 #include <QVector2D>
88 
89 #define HANDLE_DISTANCE 10
90 #define HANDLE_DISTANCE_SQ (HANDLE_DISTANCE * HANDLE_DISTANCE)
91 
92 #define INNER_HANDLE_DISTANCE_SQ 16
93 
94 namespace {
95 static const QString EditFillGradientFactoryId = "edit_fill_gradient";
96 static const QString EditStrokeGradientFactoryId = "edit_stroke_gradient";
97 static const QString EditFillMeshGradientFactoryId = "edit_fill_meshgradient";
98 
99 enum TransformActionType {
100     TransformRotate90CW,
101     TransformRotate90CCW,
102     TransformRotate180,
103     TransformMirrorX,
104     TransformMirrorY,
105     TransformReset
106 };
107 
108 enum BooleanOp {
109     BooleanUnion,
110     BooleanIntersection,
111     BooleanSubtraction
112 };
113 
114 }
115 
116 class NopInteractionStrategy : public KoInteractionStrategy
117 {
118 public:
NopInteractionStrategy(KoToolBase * parent)119     explicit NopInteractionStrategy(KoToolBase *parent)
120         : KoInteractionStrategy(parent)
121     {
122     }
123 
createCommand()124     KUndo2Command *createCommand() override
125     {
126         return 0;
127     }
128 
handleMouseMove(const QPointF &,Qt::KeyboardModifiers)129     void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {}
finishInteraction(Qt::KeyboardModifiers)130     void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {}
131 
paint(QPainter & painter,const KoViewConverter & converter)132     void paint(QPainter &painter, const KoViewConverter &converter) override {
133         Q_UNUSED(painter);
134         Q_UNUSED(converter);
135     }
136 };
137 
138 class SelectionInteractionStrategy : public KoShapeRubberSelectStrategy
139 {
140 public:
SelectionInteractionStrategy(KoToolBase * parent,const QPointF & clicked,bool useSnapToGrid)141     explicit SelectionInteractionStrategy(KoToolBase *parent, const QPointF &clicked, bool useSnapToGrid)
142         : KoShapeRubberSelectStrategy(parent, clicked, useSnapToGrid)
143     {
144     }
145 
paint(QPainter & painter,const KoViewConverter & converter)146     void paint(QPainter &painter, const KoViewConverter &converter) override {
147         KoShapeRubberSelectStrategy::paint(painter, converter);
148     }
149 
cancelInteraction()150     void cancelInteraction() override
151     {
152         tool()->canvas()->updateCanvas(selectedRectangle() | tool()->decorationsRect());
153     }
154 
finishInteraction(Qt::KeyboardModifiers modifiers=0)155     void finishInteraction(Qt::KeyboardModifiers modifiers = 0) override
156     {
157         Q_UNUSED(modifiers);
158         DefaultTool *defaultTool = dynamic_cast<DefaultTool*>(tool());
159         KIS_SAFE_ASSERT_RECOVER_RETURN(defaultTool);
160 
161         KoSelection * selection = defaultTool->koSelection();
162 
163         const bool useContainedMode = currentMode() == CoveringSelection;
164 
165         QList<KoShape *> shapes =
166                 defaultTool->shapeManager()->
167                         shapesAt(selectedRectangle(), true, useContainedMode);
168 
169         Q_FOREACH (KoShape * shape, shapes) {
170                 if (!shape->isSelectable()) continue;
171 
172                 selection->select(shape);
173             }
174 
175         tool()->canvas()->updateCanvas(selectedRectangle() | tool()->decorationsRect());
176     }
177 };
178 #include <KoGradientBackground.h>
179 #include "KoShapeGradientHandles.h"
180 #include "ShapeGradientEditStrategy.h"
181 
182 class DefaultTool::MoveGradientHandleInteractionFactory : public KoInteractionStrategyFactory
183 {
184 public:
MoveGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant,int priority,const QString & id,DefaultTool * _q)185     MoveGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant,
186                                          int priority, const QString &id, DefaultTool *_q)
187         : KoInteractionStrategyFactory(priority, id),
188           q(_q),
189           m_fillVariant(fillVariant)
190     {
191     }
192 
createStrategy(KoPointerEvent * ev)193     KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override
194     {
195         m_currentHandle = handleAt(ev->point);
196 
197         if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) {
198             KoShape *shape = onlyEditableShape();
199             KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0);
200 
201             return new ShapeGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle.type, ev->point);
202         }
203 
204         return 0;
205     }
206 
hoverEvent(KoPointerEvent * ev)207     bool hoverEvent(KoPointerEvent *ev) override
208     {
209         m_currentHandle = handleAt(ev->point);
210         return false;
211     }
212 
paintOnHover(QPainter & painter,const KoViewConverter & converter)213     bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override
214     {
215         Q_UNUSED(painter);
216         Q_UNUSED(converter);
217         return false;
218     }
219 
tryUseCustomCursor()220     bool tryUseCustomCursor() override {
221         if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) {
222             q->useCursor(Qt::OpenHandCursor);
223             return true;
224         }
225 
226         return false;
227     }
228 
229 private:
230 
onlyEditableShape() const231     KoShape* onlyEditableShape() const {
232         KoSelection *selection = q->koSelection();
233         QList<KoShape*> shapes = selection->selectedEditableShapes();
234 
235         KoShape *shape = 0;
236         if (shapes.size() == 1) {
237             shape = shapes.first();
238         }
239 
240         return shape;
241     }
242 
handleAt(const QPointF & pos)243     KoShapeGradientHandles::Handle handleAt(const QPointF &pos) {
244         KoShapeGradientHandles::Handle result;
245 
246         KoShape *shape = onlyEditableShape();
247         if (shape) {
248             KoFlake::SelectionHandle globalHandle = q->handleAt(pos);
249             const qreal distanceThresholdSq =
250                 globalHandle == KoFlake::NoHandle ?
251                     HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ;
252 
253             const KoViewConverter *converter = q->canvas()->viewConverter();
254             const QPointF viewPoint = converter->documentToView(pos);
255             qreal minDistanceSq = std::numeric_limits<qreal>::max();
256 
257             KoShapeGradientHandles sh(m_fillVariant, shape);
258             auto handless = sh.handles();
259             Q_FOREACH (const KoShapeGradientHandles::Handle &handle, sh.handles()) {
260                 const QPointF handlePoint = converter->documentToView(handle.pos);
261                 const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
262 
263                 if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) {
264                     result = handle;
265                     minDistanceSq = distanceSq;
266                 }
267             }
268         }
269 
270         return result;
271     }
272 
273 private:
274     DefaultTool *q;
275     KoFlake::FillVariant m_fillVariant;
276     KoShapeGradientHandles::Handle m_currentHandle;
277 };
278 
279 #include "KoShapeMeshGradientHandles.h"
280 #include "ShapeMeshGradientEditStrategy.h"
281 
282 class DefaultTool::MoveMeshGradientHandleInteractionFactory: public KoInteractionStrategyFactory
283 {
284 public:
MoveMeshGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant,int priority,const QString & id,DefaultTool * _q)285     MoveMeshGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant,
286                                              int priority,
287                                              const QString& id,
288                                              DefaultTool* _q)
289         : KoInteractionStrategyFactory(priority, id)
290         , m_fillVariant(fillVariant)
291         , q(_q)
292     {
293     }
294 
createStrategy(KoPointerEvent * ev)295     KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override
296     {
297         m_currentHandle = handleAt(ev->point);
298         q->m_selectedMeshHandle = m_currentHandle;
299         emit q->meshgradientHandleSelected(m_currentHandle);
300 
301 
302         if (m_currentHandle.type != KoShapeMeshGradientHandles::Handle::None) {
303             KoShape *shape = onlyEditableShape();
304             KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0);
305 
306             return new ShapeMeshGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle, ev->point);
307         }
308 
309         return nullptr;
310     }
311 
hoverEvent(KoPointerEvent * ev)312     bool hoverEvent(KoPointerEvent *ev) override
313     {
314         // for custom cursor
315         KoShapeMeshGradientHandles::Handle handle = handleAt(ev->point);
316 
317         // refresh
318         if (handle.type != m_currentHandle.type && handle.type == KoShapeMeshGradientHandles::Handle::None) {
319             q->repaintDecorations();
320         }
321 
322         m_currentHandle = handle;
323         q->m_hoveredMeshHandle = m_currentHandle;
324 
325         // highlight the decoration which is being hovered
326         if (m_currentHandle.type != KoShapeMeshGradientHandles::Handle::None) {
327             q->repaintDecorations();
328         }
329         return false;
330     }
331 
paintOnHover(QPainter & painter,const KoViewConverter & converter)332     bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override
333     {
334         Q_UNUSED(painter);
335         Q_UNUSED(converter);
336         return false;
337     }
338 
tryUseCustomCursor()339     bool tryUseCustomCursor() override
340     {
341         if (m_currentHandle.type != KoShapeMeshGradientHandles::Handle::None) {
342             q->useCursor(Qt::OpenHandCursor);
343             return true;
344         }
345 
346         return false;
347     }
348 
349 
350 private:
onlyEditableShape() const351     KoShape* onlyEditableShape() const {
352         // FIXME: copy of KoShapeGradientHandles
353         KoSelection *selection = q->koSelection();
354         QList<KoShape*> shapes = selection->selectedEditableShapes();
355 
356         KoShape *shape = 0;
357         if (shapes.size() == 1) {
358             shape = shapes.first();
359         }
360 
361         return shape;
362     }
363 
handleAt(const QPointF & pos) const364     KoShapeMeshGradientHandles::Handle handleAt(const QPointF &pos) const
365     {
366         // FIXME: copy of KoShapeGradientHandles. use a template?
367         KoShapeMeshGradientHandles::Handle result;
368 
369         KoShape *shape = onlyEditableShape();
370         if (shape) {
371             KoFlake::SelectionHandle globalHandle = q->handleAt(pos);
372             const qreal distanceThresholdSq =
373                 globalHandle == KoFlake::NoHandle ?
374                     HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ;
375 
376             const KoViewConverter *converter = q->canvas()->viewConverter();
377             const QPointF viewPoint = converter->documentToView(pos);
378             qreal minDistanceSq = std::numeric_limits<qreal>::max();
379 
380             KoShapeMeshGradientHandles sh(m_fillVariant, shape);
381 
382             for (const auto& handle: sh.handles()) {
383                 const QPointF handlePoint = converter->documentToView(handle.pos);
384                 const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
385 
386                 if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) {
387                     result = handle;
388                     minDistanceSq = distanceSq;
389                 }
390             }
391         }
392 
393         return result;
394     }
395 
396 private:
397     KoFlake::FillVariant m_fillVariant;
398     KoShapeMeshGradientHandles::Handle m_currentHandle;
399     DefaultTool *q;
400 };
401 
402 class SelectionHandler : public KoToolSelection
403 {
404 public:
SelectionHandler(DefaultTool * parent)405     SelectionHandler(DefaultTool *parent)
406         : KoToolSelection(parent)
407         , m_selection(parent->koSelection())
408     {
409     }
410 
hasSelection()411     bool hasSelection() override
412     {
413         if (m_selection) {
414             return m_selection->count();
415         }
416         return false;
417     }
418 
419 private:
420     QPointer<KoSelection> m_selection;
421 };
422 
DefaultTool(KoCanvasBase * canvas,bool connectToSelectedShapesProxy)423 DefaultTool::DefaultTool(KoCanvasBase *canvas, bool connectToSelectedShapesProxy)
424     : KoInteractionTool(canvas)
425     , m_lastHandle(KoFlake::NoHandle)
426     , m_hotPosition(KoFlake::TopLeft)
427     , m_mouseWasInsideHandles(false)
428     , m_selectionHandler(new SelectionHandler(this))
429     , m_tabbedOptionWidget(0)
430 {
431     setupActions();
432 
433     QPixmap rotatePixmap, shearPixmap;
434     rotatePixmap.load(":/cursor_rotate.png");
435     Q_ASSERT(!rotatePixmap.isNull());
436     shearPixmap.load(":/cursor_shear.png");
437     Q_ASSERT(!shearPixmap.isNull());
438 
439     m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45)));
440     m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90)));
441     m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135)));
442     m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180)));
443     m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225)));
444     m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270)));
445     m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315)));
446     m_rotateCursors[7] = QCursor(rotatePixmap);
447     /*
448         m_rotateCursors[0] = QCursor(Qt::RotateNCursor);
449         m_rotateCursors[1] = QCursor(Qt::RotateNECursor);
450         m_rotateCursors[2] = QCursor(Qt::RotateECursor);
451         m_rotateCursors[3] = QCursor(Qt::RotateSECursor);
452         m_rotateCursors[4] = QCursor(Qt::RotateSCursor);
453         m_rotateCursors[5] = QCursor(Qt::RotateSWCursor);
454         m_rotateCursors[6] = QCursor(Qt::RotateWCursor);
455         m_rotateCursors[7] = QCursor(Qt::RotateNWCursor);
456     */
457     m_shearCursors[0] = QCursor(shearPixmap);
458     m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45)));
459     m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90)));
460     m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135)));
461     m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180)));
462     m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225)));
463     m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270)));
464     m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315)));
465     m_sizeCursors[0] = Qt::SizeVerCursor;
466     m_sizeCursors[1] = Qt::SizeBDiagCursor;
467     m_sizeCursors[2] = Qt::SizeHorCursor;
468     m_sizeCursors[3] = Qt::SizeFDiagCursor;
469     m_sizeCursors[4] = Qt::SizeVerCursor;
470     m_sizeCursors[5] = Qt::SizeBDiagCursor;
471     m_sizeCursors[6] = Qt::SizeHorCursor;
472     m_sizeCursors[7] = Qt::SizeFDiagCursor;
473 
474     if (connectToSelectedShapesProxy) {
475         connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(updateActions()));
476 
477         connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(repaintDecorations()));
478         connect(canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(repaintDecorations()));
479     }
480 }
481 
~DefaultTool()482 DefaultTool::~DefaultTool()
483 {
484 }
485 
slotActivateEditFillGradient(bool value)486 void DefaultTool::slotActivateEditFillGradient(bool value)
487 {
488     if (value) {
489         addInteractionFactory(
490             new MoveGradientHandleInteractionFactory(KoFlake::Fill,
491                                                      1, EditFillGradientFactoryId, this));
492     } else {
493         removeInteractionFactory(EditFillGradientFactoryId);
494     }
495     repaintDecorations();
496 }
497 
slotActivateEditStrokeGradient(bool value)498 void DefaultTool::slotActivateEditStrokeGradient(bool value)
499 {
500     if (value) {
501         addInteractionFactory(
502             new MoveGradientHandleInteractionFactory(KoFlake::StrokeFill,
503                                                      0, EditStrokeGradientFactoryId, this));
504     } else {
505         removeInteractionFactory(EditStrokeGradientFactoryId);
506     }
507     repaintDecorations();
508 }
509 
slotActivateEditFillMeshGradient(bool value)510 void DefaultTool::slotActivateEditFillMeshGradient(bool value)
511 {
512     if (value) {
513         connect(this, SIGNAL(meshgradientHandleSelected(KoShapeMeshGradientHandles::Handle)),
514                 m_tabbedOptionWidget, SLOT(slotMeshGradientHandleSelected(KoShapeMeshGradientHandles::Handle)));
515         addInteractionFactory(
516             new MoveMeshGradientHandleInteractionFactory(KoFlake::Fill, 1,
517                                                          EditFillMeshGradientFactoryId, this));
518     } else {
519         disconnect(this, SIGNAL(meshgradientHandleSelected(KoShapeMeshGradientHandles::Handle)),
520                    m_tabbedOptionWidget, SLOT(slotMeshGradientHandleSelected(KoShapeMeshGradientHandles::Handle)));
521         removeInteractionFactory(EditFillMeshGradientFactoryId);
522     }
523 }
524 
slotResetMeshGradientState()525 void DefaultTool::slotResetMeshGradientState()
526 {
527     m_selectedMeshHandle = KoShapeMeshGradientHandles::Handle();
528 }
529 
wantsAutoScroll() const530 bool DefaultTool::wantsAutoScroll() const
531 {
532     return true;
533 }
534 
addMappedAction(KisSignalMapper * mapper,const QString & actionId,int commandType)535 void DefaultTool::addMappedAction(KisSignalMapper *mapper, const QString &actionId, int commandType)
536 {
537     QAction *a =action(actionId);
538     connect(a, SIGNAL(triggered()), mapper, SLOT(map()));
539     mapper->setMapping(a, commandType);
540 }
541 
setupActions()542 void DefaultTool::setupActions()
543 {
544     m_alignSignalsMapper = new KisSignalMapper(this);
545 
546     addMappedAction(m_alignSignalsMapper, "object_align_horizontal_left", KoShapeAlignCommand::HorizontalLeftAlignment);
547     addMappedAction(m_alignSignalsMapper, "object_align_horizontal_center", KoShapeAlignCommand::HorizontalCenterAlignment);
548     addMappedAction(m_alignSignalsMapper, "object_align_horizontal_right", KoShapeAlignCommand::HorizontalRightAlignment);
549     addMappedAction(m_alignSignalsMapper, "object_align_vertical_top", KoShapeAlignCommand::VerticalTopAlignment);
550     addMappedAction(m_alignSignalsMapper, "object_align_vertical_center", KoShapeAlignCommand::VerticalCenterAlignment);
551     addMappedAction(m_alignSignalsMapper, "object_align_vertical_bottom", KoShapeAlignCommand::VerticalBottomAlignment);
552 
553     m_distributeSignalsMapper = new KisSignalMapper(this);
554 
555     addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_left", KoShapeDistributeCommand::HorizontalLeftDistribution);
556     addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_center", KoShapeDistributeCommand::HorizontalCenterDistribution);
557     addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_right", KoShapeDistributeCommand::HorizontalRightDistribution);
558     addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_gaps", KoShapeDistributeCommand::HorizontalGapsDistribution);
559 
560     addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_top", KoShapeDistributeCommand::VerticalTopDistribution);
561     addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_center", KoShapeDistributeCommand::VerticalCenterDistribution);
562     addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_bottom", KoShapeDistributeCommand::VerticalBottomDistribution);
563     addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_gaps", KoShapeDistributeCommand::VerticalGapsDistribution);
564 
565     m_transformSignalsMapper = new KisSignalMapper(this);
566 
567     addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_cw", TransformRotate90CW);
568     addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_ccw", TransformRotate90CCW);
569     addMappedAction(m_transformSignalsMapper, "object_transform_rotate_180", TransformRotate180);
570     addMappedAction(m_transformSignalsMapper, "object_transform_mirror_horizontally", TransformMirrorX);
571     addMappedAction(m_transformSignalsMapper, "object_transform_mirror_vertically", TransformMirrorY);
572     addMappedAction(m_transformSignalsMapper, "object_transform_reset", TransformReset);
573 
574     m_booleanSignalsMapper = new KisSignalMapper(this);
575 
576     addMappedAction(m_booleanSignalsMapper, "object_unite", BooleanUnion);
577     addMappedAction(m_booleanSignalsMapper, "object_intersect", BooleanIntersection);
578     addMappedAction(m_booleanSignalsMapper, "object_subtract", BooleanSubtraction);
579 
580     m_contextMenu.reset(new QMenu());
581 }
582 
rotationOfHandle(KoFlake::SelectionHandle handle,bool useEdgeRotation)583 qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation)
584 {
585     QPointF selectionCenter = koSelection()->absolutePosition();
586     QPointF direction;
587 
588     switch (handle) {
589     case KoFlake::TopMiddleHandle:
590         if (useEdgeRotation) {
591             direction = koSelection()->absolutePosition(KoFlake::TopRight)
592                         - koSelection()->absolutePosition(KoFlake::TopLeft);
593         } else {
594             QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft);
595             handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRight) - handlePosition);
596             direction = handlePosition - selectionCenter;
597         }
598         break;
599     case KoFlake::TopRightHandle:
600         direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized()).toPointF();
601         break;
602     case KoFlake::RightMiddleHandle:
603         if (useEdgeRotation) {
604             direction = koSelection()->absolutePosition(KoFlake::BottomRight)
605                         - koSelection()->absolutePosition(KoFlake::TopRight);
606         } else {
607             QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRight);
608             handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition);
609             direction = handlePosition - selectionCenter;
610         }
611         break;
612     case KoFlake::BottomRightHandle:
613         direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized()).toPointF();
614         break;
615     case KoFlake::BottomMiddleHandle:
616         if (useEdgeRotation) {
617             direction = koSelection()->absolutePosition(KoFlake::BottomLeft)
618                         - koSelection()->absolutePosition(KoFlake::BottomRight);
619         } else {
620             QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeft);
621             handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition);
622             direction = handlePosition - selectionCenter;
623         }
624         break;
625     case KoFlake::BottomLeftHandle:
626         direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - selectionCenter;
627         direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized()).toPointF();
628         break;
629     case KoFlake::LeftMiddleHandle:
630         if (useEdgeRotation) {
631             direction = koSelection()->absolutePosition(KoFlake::TopLeft)
632                         - koSelection()->absolutePosition(KoFlake::BottomLeft);
633         } else {
634             QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft);
635             handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeft) - handlePosition);
636             direction = handlePosition - selectionCenter;
637         }
638         break;
639     case KoFlake::TopLeftHandle:
640         direction = koSelection()->absolutePosition(KoFlake::TopLeft) - selectionCenter;
641         direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized()).toPointF();
642         break;
643     case KoFlake::NoHandle:
644         return 0.0;
645         break;
646     }
647 
648     qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI;
649 
650     switch (handle) {
651     case KoFlake::TopMiddleHandle:
652         if (useEdgeRotation) {
653             rotation -= 0.0;
654         } else {
655             rotation -= 270.0;
656         }
657         break;
658     case KoFlake::TopRightHandle:
659         rotation -= 315.0;
660         break;
661     case KoFlake::RightMiddleHandle:
662         if (useEdgeRotation) {
663             rotation -= 90.0;
664         } else {
665             rotation -= 0.0;
666         }
667         break;
668     case KoFlake::BottomRightHandle:
669         rotation -= 45.0;
670         break;
671     case KoFlake::BottomMiddleHandle:
672         if (useEdgeRotation) {
673             rotation -= 180.0;
674         } else {
675             rotation -= 90.0;
676         }
677         break;
678     case KoFlake::BottomLeftHandle:
679         rotation -= 135.0;
680         break;
681     case KoFlake::LeftMiddleHandle:
682         if (useEdgeRotation) {
683             rotation -= 270.0;
684         } else {
685             rotation -= 180.0;
686         }
687         break;
688     case KoFlake::TopLeftHandle:
689         rotation -= 225.0;
690         break;
691     default:
692         ;
693     }
694 
695     if (rotation < 0.0) {
696         rotation += 360.0;
697     }
698 
699     return rotation;
700 }
701 
updateCursor()702 void DefaultTool::updateCursor()
703 {
704     if (tryUseCustomCursor()) return;
705 
706     QCursor cursor = Qt::ArrowCursor;
707 
708     QString statusText;
709 
710     KoSelection *selection = koSelection();
711     if (selection && selection->count() > 0) { // has a selection
712         bool editable = !selection->selectedEditableShapes().isEmpty();
713 
714         if (!m_mouseWasInsideHandles) {
715             m_angle = rotationOfHandle(m_lastHandle, true);
716             int rotOctant = 8 + int(8.5 + m_angle / 45);
717 
718             bool rotateHandle = false;
719             bool shearHandle = false;
720             switch (m_lastHandle) {
721             case KoFlake::TopMiddleHandle:
722                 cursor = m_shearCursors[(0 + rotOctant) % 8];
723                 shearHandle = true;
724                 break;
725             case KoFlake::TopRightHandle:
726                 cursor = m_rotateCursors[(1 + rotOctant) % 8];
727                 rotateHandle = true;
728                 break;
729             case KoFlake::RightMiddleHandle:
730                 cursor = m_shearCursors[(2 + rotOctant) % 8];
731                 shearHandle = true;
732                 break;
733             case KoFlake::BottomRightHandle:
734                 cursor = m_rotateCursors[(3 + rotOctant) % 8];
735                 rotateHandle = true;
736                 break;
737             case KoFlake::BottomMiddleHandle:
738                 cursor = m_shearCursors[(4 + rotOctant) % 8];
739                 shearHandle = true;
740                 break;
741             case KoFlake::BottomLeftHandle:
742                 cursor = m_rotateCursors[(5 + rotOctant) % 8];
743                 rotateHandle = true;
744                 break;
745             case KoFlake::LeftMiddleHandle:
746                 cursor = m_shearCursors[(6 + rotOctant) % 8];
747                 shearHandle = true;
748                 break;
749             case KoFlake::TopLeftHandle:
750                 cursor = m_rotateCursors[(7 + rotOctant) % 8];
751                 rotateHandle = true;
752                 break;
753             case KoFlake::NoHandle:
754                 cursor = Qt::ArrowCursor;
755                 break;
756             }
757             if (rotateHandle) {
758                 statusText = i18n("Left click rotates around center, right click around highlighted position.");
759             }
760             if (shearHandle) {
761                 statusText = i18n("Click and drag to shear selection.");
762             }
763 
764 
765         } else {
766             statusText = i18n("Click and drag to resize selection.");
767             m_angle = rotationOfHandle(m_lastHandle, false);
768             int rotOctant = 8 + int(8.5 + m_angle / 45);
769             bool cornerHandle = false;
770             switch (m_lastHandle) {
771             case KoFlake::TopMiddleHandle:
772                 cursor = m_sizeCursors[(0 + rotOctant) % 8];
773                 break;
774             case KoFlake::TopRightHandle:
775                 cursor = m_sizeCursors[(1 + rotOctant) % 8];
776                 cornerHandle = true;
777                 break;
778             case KoFlake::RightMiddleHandle:
779                 cursor = m_sizeCursors[(2 + rotOctant) % 8];
780                 break;
781             case KoFlake::BottomRightHandle:
782                 cursor = m_sizeCursors[(3 + rotOctant) % 8];
783                 cornerHandle = true;
784                 break;
785             case KoFlake::BottomMiddleHandle:
786                 cursor = m_sizeCursors[(4 + rotOctant) % 8];
787                 break;
788             case KoFlake::BottomLeftHandle:
789                 cursor = m_sizeCursors[(5 + rotOctant) % 8];
790                 cornerHandle = true;
791                 break;
792             case KoFlake::LeftMiddleHandle:
793                 cursor = m_sizeCursors[(6 + rotOctant) % 8];
794                 break;
795             case KoFlake::TopLeftHandle:
796                 cursor = m_sizeCursors[(7 + rotOctant) % 8];
797                 cornerHandle = true;
798                 break;
799             case KoFlake::NoHandle:
800                 cursor = Qt::SizeAllCursor;
801                 statusText = i18n("Click and drag to move selection.");
802                 break;
803             }
804             if (cornerHandle) {
805                 statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position.");
806             }
807         }
808         if (!editable) {
809             cursor = Qt::ArrowCursor;
810         }
811     } else {
812         // there used to be guides... :'''(
813     }
814     useCursor(cursor);
815     if (currentStrategy() == 0) {
816         emit statusTextChanged(statusText);
817     }
818 }
819 
paint(QPainter & painter,const KoViewConverter & converter)820 void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter)
821 {
822     KoSelection *selection = koSelection();
823     if (selection) {
824         m_decorator.reset(new SelectionDecorator(canvas()->resourceManager()));
825 
826         {
827             /**
828              * Selection masks don't render the outline of the shapes, so we should
829              * do that explicitly when rendering them via selection
830              */
831 
832             KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
833             KisNodeSP node = kisCanvas->viewManager()->nodeManager()->activeNode();
834             const bool isSelectionMask = node && node->inherits("KisSelectionMask");
835             m_decorator->setForceShapeOutlines(isSelectionMask);
836         }
837 
838         m_decorator->setSelection(selection);
839         m_decorator->setHandleRadius(handleRadius());
840         m_decorator->setShowFillGradientHandles(hasInteractioFactory(EditFillGradientFactoryId));
841         m_decorator->setShowStrokeFillGradientHandles(hasInteractioFactory(EditStrokeGradientFactoryId));
842         m_decorator->setShowFillMeshGradientHandles(hasInteractioFactory(EditFillMeshGradientFactoryId));
843         m_decorator->setCurrentMeshGradientHandles(m_selectedMeshHandle, m_hoveredMeshHandle);
844         m_decorator->paint(painter, converter);
845     }
846 
847     KoInteractionTool::paint(painter, converter);
848 
849     painter.save();
850     painter.setTransform(converter.documentToView(), true);
851     canvas()->snapGuide()->paint(painter, converter);
852     painter.restore();
853 }
854 
isValidForCurrentLayer() const855 bool DefaultTool::isValidForCurrentLayer() const
856 {
857     // if the currently active node has a shape manager, then it is
858     // probably our client :)
859 
860     KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
861     return bool(kisCanvas->localShapeManager());
862 }
863 
shapeManager() const864 KoShapeManager *DefaultTool::shapeManager() const {
865     return canvas()->shapeManager();
866 }
867 
mousePressEvent(KoPointerEvent * event)868 void DefaultTool::mousePressEvent(KoPointerEvent *event)
869 {
870     // this tool only works on a vector layer right now, so give a warning if another layer type is trying to use it
871     if (!isValidForCurrentLayer()) {
872         KisCanvas2 *kiscanvas = static_cast<KisCanvas2 *>(canvas());
873         kiscanvas->viewManager()->showFloatingMessage(
874                 i18n("This tool only works on vector layers. You probably want the move tool."),
875                 QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter);
876         return;
877     }
878 
879     KoInteractionTool::mousePressEvent(event);
880     updateCursor();
881 }
882 
mouseMoveEvent(KoPointerEvent * event)883 void DefaultTool::mouseMoveEvent(KoPointerEvent *event)
884 {
885     KoInteractionTool::mouseMoveEvent(event);
886     if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) {
887         QRectF bound = handlesSize();
888 
889         if (bound.contains(event->point)) {
890             bool inside;
891             KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside);
892 
893             if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) {
894                 m_lastHandle = newDirection;
895                 m_mouseWasInsideHandles = inside;
896             }
897         } else {
898             m_lastHandle = KoFlake::NoHandle;
899             m_mouseWasInsideHandles = false;
900 
901             // there used to be guides... :'''(
902         }
903     } else {
904         // there used to be guides... :'''(
905     }
906 
907 
908     updateCursor();
909 }
910 
handlesSize()911 QRectF DefaultTool::handlesSize()
912 {
913     KoSelection *selection = koSelection();
914     if (!selection || !selection->count()) return QRectF();
915 
916     recalcSelectionBox(selection);
917 
918     QRectF bound = m_selectionOutline.boundingRect();
919 
920     // expansion Border
921     if (!canvas() || !canvas()->viewConverter()) {
922         return bound;
923     }
924 
925     QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE));
926     bound.adjust(-border.x(), -border.y(), border.x(), border.y());
927     return bound;
928 }
929 
mouseReleaseEvent(KoPointerEvent * event)930 void DefaultTool::mouseReleaseEvent(KoPointerEvent *event)
931 {
932     KoInteractionTool::mouseReleaseEvent(event);
933     updateCursor();
934 }
935 
mouseDoubleClickEvent(KoPointerEvent * event)936 void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event)
937 {
938     KoSelection *selection = koSelection();
939 
940     KoShape *shape = shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop);
941     if (shape && selection && !selection->isSelected(shape)) {
942 
943         if (!(event->modifiers() & Qt::ShiftModifier)) {
944             selection->deselectAll();
945         }
946 
947         selection->select(shape);
948     }
949 
950     explicitUserStrokeEndRequest();
951 }
952 
moveSelection(int direction,Qt::KeyboardModifiers modifiers)953 bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers)
954 {
955     bool result = false;
956 
957     qreal x = 0.0, y = 0.0;
958     if (direction == Qt::Key_Left) {
959         x = -5;
960     } else if (direction == Qt::Key_Right) {
961         x = 5;
962     } else if (direction == Qt::Key_Up) {
963         y = -5;
964     } else if (direction == Qt::Key_Down) {
965         y = 5;
966     }
967 
968     if (x != 0.0 || y != 0.0) { // actually move
969 
970         if ((modifiers & Qt::ShiftModifier) != 0) {
971             x *= 10;
972             y *= 10;
973         } else if ((modifiers & Qt::AltModifier) != 0) { // more precise
974             x /= 5;
975             y /= 5;
976         }
977 
978         QList<KoShape *> shapes = koSelection()->selectedEditableShapes();
979 
980         if (!shapes.isEmpty()) {
981             canvas()->addCommand(new KoShapeMoveCommand(shapes, QPointF(x, y)));
982             result = true;
983         }
984     }
985 
986     return result;
987 }
988 
keyPressEvent(QKeyEvent * event)989 void DefaultTool::keyPressEvent(QKeyEvent *event)
990 {
991     KoInteractionTool::keyPressEvent(event);
992     if (currentStrategy() == 0) {
993         switch (event->key()) {
994         case Qt::Key_Left:
995         case Qt::Key_Right:
996         case Qt::Key_Up:
997         case Qt::Key_Down:
998             if (moveSelection(event->key(), event->modifiers())) {
999                 event->accept();
1000             }
1001             break;
1002         case Qt::Key_1:
1003         case Qt::Key_2:
1004         case Qt::Key_3:
1005         case Qt::Key_4:
1006         case Qt::Key_5:
1007             canvas()->resourceManager()->setResource(HotPosition, event->key() - Qt::Key_1);
1008             event->accept();
1009             break;
1010         default:
1011             return;
1012         }
1013     }
1014 }
1015 
decorationsRect() const1016 QRectF DefaultTool::decorationsRect() const
1017 {
1018     QRectF dirtyRect;
1019 
1020     if (koSelection() && koSelection()->count() > 0) {
1021         /// TODO: avoid cons_cast by implementing proper
1022         ///       caching strategy inrecalcSelectionBox() and
1023         ///       handlesSize()
1024         dirtyRect = const_cast<DefaultTool*>(this)->handlesSize();
1025     }
1026 
1027     if (canvas()->snapGuide()->isSnapping()) {
1028         dirtyRect |= canvas()->snapGuide()->boundingRect();
1029     }
1030 
1031     return dirtyRect;
1032 }
1033 
copy() const1034 void DefaultTool::copy() const
1035 {
1036     // all the selected shapes, not only editable!
1037     QList<KoShape *> shapes = koSelection()->selectedShapes();
1038 
1039     if (!shapes.isEmpty()) {
1040         KoDrag drag;
1041         drag.setSvg(shapes);
1042         drag.addToClipboard();
1043     }
1044 }
1045 
deleteSelection()1046 void DefaultTool::deleteSelection()
1047 {
1048     QList<KoShape *> shapes;
1049     foreach (KoShape *s, koSelection()->selectedShapes()) {
1050         if (s->isGeometryProtected()) {
1051             continue;
1052         }
1053         shapes << s;
1054     }
1055     if (!shapes.empty()) {
1056         canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes));
1057     }
1058 }
1059 
paste()1060 bool DefaultTool::paste()
1061 {
1062     // we no longer have to do anything as tool Proxy will do it for us
1063     return false;
1064 }
1065 
koSelection() const1066 KoSelection *DefaultTool::koSelection() const
1067 {
1068     Q_ASSERT(canvas());
1069     Q_ASSERT(canvas()->selectedShapesProxy());
1070     return canvas()->selectedShapesProxy()->selection();
1071 }
1072 
handleAt(const QPointF & point,bool * innerHandleMeaning)1073 KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning)
1074 {
1075     // check for handles in this order; meaning that when handles overlap the one on top is chosen
1076     static const KoFlake::SelectionHandle handleOrder[] = {
1077         KoFlake::BottomRightHandle,
1078         KoFlake::TopLeftHandle,
1079         KoFlake::BottomLeftHandle,
1080         KoFlake::TopRightHandle,
1081         KoFlake::BottomMiddleHandle,
1082         KoFlake::RightMiddleHandle,
1083         KoFlake::LeftMiddleHandle,
1084         KoFlake::TopMiddleHandle,
1085         KoFlake::NoHandle
1086     };
1087 
1088     const KoViewConverter *converter = canvas()->viewConverter();
1089     KoSelection *selection = koSelection();
1090 
1091     if (!selection || !selection->count() || !converter) {
1092         return KoFlake::NoHandle;
1093     }
1094 
1095     recalcSelectionBox(selection);
1096 
1097     if (innerHandleMeaning) {
1098         QPainterPath path;
1099         path.addPolygon(m_selectionOutline);
1100         *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point));
1101     }
1102 
1103     const QPointF viewPoint = converter->documentToView(point);
1104 
1105     for (int i = 0; i < KoFlake::NoHandle; ++i) {
1106         KoFlake::SelectionHandle handle = handleOrder[i];
1107 
1108         const QPointF handlePoint = converter->documentToView(m_selectionBox[handle]);
1109         const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
1110 
1111         // if just inside the outline
1112         if (distanceSq < HANDLE_DISTANCE_SQ) {
1113 
1114             if (innerHandleMeaning) {
1115                 if (distanceSq < INNER_HANDLE_DISTANCE_SQ) {
1116                     *innerHandleMeaning = true;
1117                 }
1118             }
1119 
1120             return handle;
1121         }
1122     }
1123     return KoFlake::NoHandle;
1124 }
1125 
recalcSelectionBox(KoSelection * selection)1126 void DefaultTool::recalcSelectionBox(KoSelection *selection)
1127 {
1128     KIS_ASSERT_RECOVER_RETURN(selection->count());
1129 
1130     QTransform matrix = selection->absoluteTransformation();
1131     m_selectionOutline = matrix.map(QPolygonF(selection->outlineRect()));
1132     m_angle = 0.0;
1133 
1134     QPolygonF outline = m_selectionOutline; //shorter name in the following :)
1135     m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2;
1136     m_selectionBox[KoFlake::TopRightHandle] = outline.value(1);
1137     m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2;
1138     m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2);
1139     m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2;
1140     m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3);
1141     m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2;
1142     m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0);
1143     if (selection->count() == 1) {
1144 #if 0        // TODO detect mirroring
1145         KoShape *s = koSelection()->firstSelectedShape();
1146 
1147         if (s->scaleX() < 0) { // vertically mirrored: swap left / right
1148             std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]);
1149             std::swap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]);
1150             std::swap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]);
1151         }
1152         if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom
1153             std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]);
1154             std::swap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]);
1155             std::swap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]);
1156         }
1157 #endif
1158     }
1159 }
1160 
activate(ToolActivation activation,const QSet<KoShape * > & shapes)1161 void DefaultTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
1162 {
1163     KoToolBase::activate(activation, shapes);
1164 
1165     QAction *actionBringToFront = action("object_order_front");
1166     connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront()), Qt::UniqueConnection);
1167 
1168     QAction *actionRaise = action("object_order_raise");
1169     connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp()), Qt::UniqueConnection);
1170 
1171     QAction *actionLower = action("object_order_lower");
1172     connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown()));
1173 
1174     QAction *actionSendToBack = action("object_order_back");
1175     connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack()), Qt::UniqueConnection);
1176 
1177     QAction *actionGroupBottom = action("object_group");
1178     connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup()), Qt::UniqueConnection);
1179 
1180     QAction *actionUngroupBottom = action("object_ungroup");
1181     connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup()), Qt::UniqueConnection);
1182 
1183     QAction *actionSplit = action("object_split");
1184     connect(actionSplit, SIGNAL(triggered()), this, SLOT(selectionSplitShapes()), Qt::UniqueConnection);
1185 
1186     connect(m_alignSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionAlign(int)));
1187     connect(m_distributeSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionDistribute(int)));
1188     connect(m_transformSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionTransform(int)));
1189     connect(m_booleanSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionBooleanOp(int)));
1190 
1191     m_mouseWasInsideHandles = false;
1192     m_lastHandle = KoFlake::NoHandle;
1193     useCursor(Qt::ArrowCursor);
1194     repaintDecorations();
1195     updateActions();
1196 
1197     if (m_tabbedOptionWidget) {
1198         m_tabbedOptionWidget->activate();
1199     }
1200 }
1201 
deactivate()1202 void DefaultTool::deactivate()
1203 {
1204     KoToolBase::deactivate();
1205 
1206     QAction *actionBringToFront = action("object_order_front");
1207     disconnect(actionBringToFront, 0, this, 0);
1208 
1209     QAction *actionRaise = action("object_order_raise");
1210     disconnect(actionRaise, 0, this, 0);
1211 
1212     QAction *actionLower = action("object_order_lower");
1213     disconnect(actionLower, 0, this, 0);
1214 
1215     QAction *actionSendToBack = action("object_order_back");
1216     disconnect(actionSendToBack, 0, this, 0);
1217 
1218     QAction *actionGroupBottom = action("object_group");
1219     disconnect(actionGroupBottom, 0, this, 0);
1220 
1221     QAction *actionUngroupBottom = action("object_ungroup");
1222     disconnect(actionUngroupBottom, 0, this, 0);
1223 
1224     QAction *actionSplit = action("object_split");
1225     disconnect(actionSplit, 0, this, 0);
1226 
1227     disconnect(m_alignSignalsMapper, 0, this, 0);
1228     disconnect(m_distributeSignalsMapper, 0, this, 0);
1229     disconnect(m_transformSignalsMapper, 0, this, 0);
1230     disconnect(m_booleanSignalsMapper, 0, this, 0);
1231 
1232 
1233     if (m_tabbedOptionWidget) {
1234         m_tabbedOptionWidget->deactivate();
1235     }
1236 }
1237 
selectionGroup()1238 void DefaultTool::selectionGroup()
1239 {
1240     KoSelection *selection = koSelection();
1241     if (!selection) return;
1242 
1243     QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
1244     std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
1245     if (selectedShapes.isEmpty()) return;
1246 
1247     const int groupZIndex = selectedShapes.last()->zIndex();
1248 
1249     KoShapeGroup *group = new KoShapeGroup();
1250     group->setZIndex(groupZIndex);
1251     // TODO what if only one shape is left?
1252     KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes"));
1253     new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
1254     canvas()->shapeController()->addShapeDirect(group, 0, cmd);
1255     new KoShapeGroupCommand(group, selectedShapes, true, cmd);
1256     new KoKeepShapesSelectedCommand({}, {group}, canvas()->selectedShapesProxy(), true, cmd);
1257     canvas()->addCommand(cmd);
1258 
1259     // update selection so we can ungroup immediately again
1260     selection->deselectAll();
1261     selection->select(group);
1262 }
1263 
selectionUngroup()1264 void DefaultTool::selectionUngroup()
1265 {
1266     KoSelection *selection = koSelection();
1267     if (!selection) return;
1268 
1269     QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
1270     std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
1271 
1272     KUndo2Command *cmd = 0;
1273     QList<KoShape*> newShapes;
1274 
1275     // add a ungroup command for each found shape container to the macro command
1276     Q_FOREACH (KoShape *shape, selectedShapes) {
1277         KoShapeGroup *group = dynamic_cast<KoShapeGroup *>(shape);
1278         if (group) {
1279             if (!cmd) {
1280                 cmd = new KUndo2Command(kundo2_i18n("Ungroup shapes"));
1281                 new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
1282             }
1283             newShapes << group->shapes();
1284             new KoShapeUngroupCommand(group, group->shapes(),
1285                                       group->parent() ? QList<KoShape *>() : shapeManager()->topLevelShapes(),
1286                                       cmd);
1287             canvas()->shapeController()->removeShape(group, cmd);
1288         }
1289     }
1290     if (cmd) {
1291         new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd);
1292         canvas()->addCommand(cmd);
1293     }
1294 }
1295 
selectionTransform(int transformAction)1296 void DefaultTool::selectionTransform(int transformAction)
1297 {
1298     KoSelection *selection = koSelection();
1299     if (!selection) return;
1300 
1301     QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1302     if (editableShapes.isEmpty()) {
1303         return;
1304     }
1305 
1306     QTransform applyTransform;
1307     bool shouldReset = false;
1308     KUndo2MagicString actionName = kundo2_noi18n("BUG: No transform action");
1309 
1310 
1311     switch (TransformActionType(transformAction)) {
1312     case TransformRotate90CW:
1313         applyTransform.rotate(90.0);
1314         actionName = kundo2_i18n("Rotate Object 90° CW");
1315         break;
1316     case TransformRotate90CCW:
1317         applyTransform.rotate(-90.0);
1318         actionName = kundo2_i18n("Rotate Object 90° CCW");
1319         break;
1320     case TransformRotate180:
1321         applyTransform.rotate(180.0);
1322         actionName = kundo2_i18n("Rotate Object 180°");
1323         break;
1324     case TransformMirrorX:
1325         applyTransform.scale(-1.0, 1.0);
1326         actionName = kundo2_i18n("Mirror Object Horizontally");
1327         break;
1328     case TransformMirrorY:
1329         applyTransform.scale(1.0, -1.0);
1330         actionName = kundo2_i18n("Mirror Object Vertically");
1331         break;
1332     case TransformReset:
1333         shouldReset = true;
1334         actionName = kundo2_i18n("Reset Object Transformations");
1335         break;
1336     }
1337 
1338     if (!shouldReset && applyTransform.isIdentity()) return;
1339 
1340     QList<QTransform> oldTransforms;
1341     QList<QTransform> newTransforms;
1342 
1343     const QRectF outlineRect = KoShape::absoluteOutlineRect(editableShapes);
1344     const QPointF centerPoint = outlineRect.center();
1345     const QTransform centerTrans = QTransform::fromTranslate(centerPoint.x(), centerPoint.y());
1346     const QTransform centerTransInv = QTransform::fromTranslate(-centerPoint.x(), -centerPoint.y());
1347 
1348     // we also add selection to the list of transformed shapes, so that its outline is updated correctly
1349     QList<KoShape*> transformedShapes = editableShapes;
1350     transformedShapes << selection;
1351 
1352     Q_FOREACH (KoShape *shape, transformedShapes) {
1353         oldTransforms.append(shape->transformation());
1354 
1355         QTransform t;
1356 
1357         if (!shouldReset) {
1358             const QTransform world = shape->absoluteTransformation();
1359             t =  world * centerTransInv * applyTransform * centerTrans * world.inverted() * shape->transformation();
1360         } else {
1361             const QPointF center = shape->outlineRect().center();
1362             const QPointF offset = shape->transformation().map(center) - center;
1363             t = QTransform::fromTranslate(offset.x(), offset.y());
1364         }
1365 
1366         newTransforms.append(t);
1367     }
1368 
1369     KoShapeTransformCommand *cmd = new KoShapeTransformCommand(transformedShapes, oldTransforms, newTransforms);
1370     cmd->setText(actionName);
1371     canvas()->addCommand(cmd);
1372 }
1373 
selectionBooleanOp(int booleanOp)1374 void DefaultTool::selectionBooleanOp(int booleanOp)
1375 {
1376     KoSelection *selection = koSelection();
1377     if (!selection) return;
1378 
1379     QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1380     if (editableShapes.isEmpty()) {
1381         return;
1382     }
1383 
1384     QVector<QPainterPath> srcOutlines;
1385     QPainterPath dstOutline;
1386     KUndo2MagicString actionName = kundo2_noi18n("BUG: boolean action name");
1387 
1388     // TODO: implement a reference shape selection dialog!
1389     const int referenceShapeIndex = 0;
1390     KoShape *referenceShape = editableShapes[referenceShapeIndex];
1391 
1392     KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
1393     KIS_SAFE_ASSERT_RECOVER_RETURN(kisCanvas);
1394     const QTransform booleanWorkaroundTransform =
1395         KritaUtils::pathShapeBooleanSpaceWorkaround(kisCanvas->image());
1396 
1397     Q_FOREACH (KoShape *shape, editableShapes) {
1398         srcOutlines <<
1399             booleanWorkaroundTransform.map(
1400             shape->absoluteTransformation().map(
1401                 shape->outline()));
1402     }
1403 
1404     if (booleanOp == BooleanUnion) {
1405         Q_FOREACH (const QPainterPath &path, srcOutlines) {
1406             dstOutline |= path;
1407         }
1408         actionName = kundo2_i18n("Unite Shapes");
1409     } else if (booleanOp == BooleanIntersection) {
1410         for (int i = 0; i < srcOutlines.size(); i++) {
1411             if (i == 0) {
1412                 dstOutline = srcOutlines[i];
1413             } else {
1414                 dstOutline &= srcOutlines[i];
1415             }
1416         }
1417 
1418         // there is a bug in Qt, sometimes it leaves the resulting
1419         // outline open, so just close it explicitly.
1420         dstOutline.closeSubpath();
1421 
1422         actionName = kundo2_i18n("Intersect Shapes");
1423 
1424     } else if (booleanOp == BooleanSubtraction) {
1425         for (int i = 0; i < srcOutlines.size(); i++) {
1426             dstOutline = srcOutlines[referenceShapeIndex];
1427             if (i != referenceShapeIndex) {
1428                 dstOutline -= srcOutlines[i];
1429             }
1430         }
1431 
1432         actionName = kundo2_i18n("Subtract Shapes");
1433     }
1434 
1435     dstOutline = booleanWorkaroundTransform.inverted().map(dstOutline);
1436 
1437     KoShape *newShape = 0;
1438 
1439     if (!dstOutline.isEmpty()) {
1440         newShape = KoPathShape::createShapeFromPainterPath(dstOutline);
1441     }
1442 
1443     KUndo2Command *cmd = new KUndo2Command(actionName);
1444 
1445     new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
1446 
1447     QList<KoShape*> newSelectedShapes;
1448 
1449     if (newShape) {
1450         newShape->setBackground(referenceShape->background());
1451         newShape->setStroke(referenceShape->stroke());
1452         newShape->setZIndex(referenceShape->zIndex());
1453 
1454         KoShapeContainer *parent = referenceShape->parent();
1455         canvas()->shapeController()->addShapeDirect(newShape, parent, cmd);
1456 
1457         newSelectedShapes << newShape;
1458     }
1459 
1460     canvas()->shapeController()->removeShapes(editableShapes, cmd);
1461 
1462     new KoKeepShapesSelectedCommand({}, newSelectedShapes, canvas()->selectedShapesProxy(), true, cmd);
1463 
1464     canvas()->addCommand(cmd);
1465 }
1466 
selectionSplitShapes()1467 void DefaultTool::selectionSplitShapes()
1468 {
1469     KoSelection *selection = koSelection();
1470     if (!selection) return;
1471 
1472     QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1473     if (editableShapes.isEmpty()) {
1474         return;
1475     }
1476 
1477     KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Split Shapes"));
1478 
1479     new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
1480     QList<KoShape*> newShapes;
1481 
1482     Q_FOREACH (KoShape *shape, editableShapes) {
1483         KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
1484         if (!pathShape) return;
1485 
1486         QList<KoPathShape*> splitShapes;
1487         if (pathShape->separate(splitShapes)) {
1488             QList<KoShape*> normalShapes = implicitCastList<KoShape*>(splitShapes);
1489 
1490             KoShapeContainer *parent = shape->parent();
1491             canvas()->shapeController()->addShapesDirect(normalShapes, parent, cmd);
1492             canvas()->shapeController()->removeShape(shape, cmd);
1493             newShapes << normalShapes;
1494         }
1495     }
1496 
1497     new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd);
1498 
1499     canvas()->addCommand(cmd);
1500 }
1501 
selectionAlign(int _align)1502 void DefaultTool::selectionAlign(int _align)
1503 {
1504     KoShapeAlignCommand::Align align =
1505         static_cast<KoShapeAlignCommand::Align>(_align);
1506 
1507     KoSelection *selection = koSelection();
1508     if (!selection) return;
1509 
1510     QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1511     if (editableShapes.isEmpty()) {
1512         return;
1513     }
1514 
1515     // TODO add an option to the widget so that one can align to the page
1516     // with multiple selected shapes too
1517 
1518     QRectF bb;
1519 
1520     // single selected shape is automatically aligned to document rect
1521     if (editableShapes.count() == 1) {
1522         if (!canvas()->resourceManager()->hasResource(KoCanvasResourceProvider::PageSize)) {
1523             return;
1524         }
1525         bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResourceProvider::PageSize));
1526     } else {
1527         bb = KoShape::absoluteOutlineRect(editableShapes);
1528     }
1529 
1530     KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb);
1531     canvas()->addCommand(cmd);
1532 }
1533 
selectionDistribute(int _distribute)1534 void DefaultTool::selectionDistribute(int _distribute)
1535 {
1536     KoShapeDistributeCommand::Distribute distribute =
1537         static_cast<KoShapeDistributeCommand::Distribute>(_distribute);
1538 
1539     KoSelection *selection = koSelection();
1540     if (!selection) return;
1541 
1542     QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1543     if (editableShapes.size() < 3) {
1544         return;
1545     }
1546 
1547     QRectF bb = KoShape::absoluteOutlineRect(editableShapes);
1548     KoShapeDistributeCommand *cmd = new KoShapeDistributeCommand(editableShapes, distribute, bb);
1549     canvas()->addCommand(cmd);
1550 }
1551 
selectionBringToFront()1552 void DefaultTool::selectionBringToFront()
1553 {
1554     selectionReorder(KoShapeReorderCommand::BringToFront);
1555 }
1556 
selectionMoveUp()1557 void DefaultTool::selectionMoveUp()
1558 {
1559     selectionReorder(KoShapeReorderCommand::RaiseShape);
1560 }
1561 
selectionMoveDown()1562 void DefaultTool::selectionMoveDown()
1563 {
1564     selectionReorder(KoShapeReorderCommand::LowerShape);
1565 }
1566 
selectionSendToBack()1567 void DefaultTool::selectionSendToBack()
1568 {
1569     selectionReorder(KoShapeReorderCommand::SendToBack);
1570 }
1571 
selectionReorder(KoShapeReorderCommand::MoveShapeType order)1572 void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order)
1573 {
1574     KoSelection *selection = koSelection();
1575     if (!selection) {
1576         return;
1577     }
1578 
1579     QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
1580     if (selectedShapes.isEmpty()) {
1581         return;
1582     }
1583 
1584     KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, shapeManager(), order);
1585     if (cmd) {
1586         canvas()->addCommand(cmd);
1587     }
1588 }
1589 
createOptionWidgets()1590 QList<QPointer<QWidget> > DefaultTool::createOptionWidgets()
1591 {
1592     QList<QPointer<QWidget> > widgets;
1593 
1594     m_tabbedOptionWidget = new DefaultToolTabbedWidget(this);
1595 
1596     if (isActivated()) {
1597         m_tabbedOptionWidget->activate();
1598     }
1599     widgets.append(m_tabbedOptionWidget);
1600 
1601     connect(m_tabbedOptionWidget,
1602             SIGNAL(sigSwitchModeEditFillGradient(bool)),
1603             SLOT(slotActivateEditFillGradient(bool)));
1604 
1605     connect(m_tabbedOptionWidget,
1606             SIGNAL(sigSwitchModeEditStrokeGradient(bool)),
1607             SLOT(slotActivateEditStrokeGradient(bool)));
1608 
1609     connect(m_tabbedOptionWidget,
1610             SIGNAL(sigSwitchModeEditFillGradient(bool)),
1611             SLOT(slotActivateEditFillMeshGradient(bool)));
1612     // TODO: strokes!!
1613 
1614     connect(m_tabbedOptionWidget,
1615             SIGNAL(sigMeshGradientResetted()),
1616             SLOT(slotResetMeshGradientState()));
1617 
1618     return widgets;
1619 }
1620 
canvasResourceChanged(int key,const QVariant & res)1621 void DefaultTool::canvasResourceChanged(int key, const QVariant &res)
1622 {
1623     if (key == HotPosition) {
1624         m_hotPosition = KoFlake::AnchorPosition(res.toInt());
1625         repaintDecorations();
1626     }
1627 }
1628 
createStrategy(KoPointerEvent * event)1629 KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event)
1630 {
1631     KoSelection *selection = koSelection();
1632     if (!selection) return nullptr;
1633 
1634     bool insideSelection = false;
1635     KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection);
1636 
1637     bool editableShape = !selection->selectedEditableShapes().isEmpty();
1638 
1639     const bool selectMultiple = event->modifiers() & Qt::ShiftModifier;
1640     const bool selectNextInStack = event->modifiers() & Qt::ControlModifier;
1641     const bool avoidSelection = event->modifiers() & Qt::AltModifier;
1642 
1643     if (selectNextInStack) {
1644         // change the hot selection position when middle clicking on a handle
1645         KoFlake::AnchorPosition newHotPosition = m_hotPosition;
1646         switch (handle) {
1647         case KoFlake::TopMiddleHandle:
1648             newHotPosition = KoFlake::Top;
1649             break;
1650         case KoFlake::TopRightHandle:
1651             newHotPosition = KoFlake::TopRight;
1652             break;
1653         case KoFlake::RightMiddleHandle:
1654             newHotPosition = KoFlake::Right;
1655             break;
1656         case KoFlake::BottomRightHandle:
1657             newHotPosition = KoFlake::BottomRight;
1658             break;
1659         case KoFlake::BottomMiddleHandle:
1660             newHotPosition = KoFlake::Bottom;
1661             break;
1662         case KoFlake::BottomLeftHandle:
1663             newHotPosition = KoFlake::BottomLeft;
1664             break;
1665         case KoFlake::LeftMiddleHandle:
1666             newHotPosition = KoFlake::Left;
1667             break;
1668         case KoFlake::TopLeftHandle:
1669             newHotPosition = KoFlake::TopLeft;
1670             break;
1671         case KoFlake::NoHandle:
1672         default:
1673             // check if we had hit the center point
1674             const KoViewConverter *converter = canvas()->viewConverter();
1675             QPointF pt = converter->documentToView(event->point);
1676 
1677             // TODO: use calculated values instead!
1678             QPointF centerPt = converter->documentToView(selection->absolutePosition());
1679 
1680             if (kisSquareDistance(pt, centerPt) < HANDLE_DISTANCE_SQ) {
1681                 newHotPosition = KoFlake::Center;
1682             }
1683 
1684             break;
1685         }
1686 
1687         if (m_hotPosition != newHotPosition) {
1688             canvas()->resourceManager()->setResource(HotPosition, newHotPosition);
1689             return new NopInteractionStrategy(this);
1690         }
1691     }
1692 
1693     if (!avoidSelection && editableShape) {
1694         // manipulation of selected shapes goes first
1695         if (handle != KoFlake::NoHandle) {
1696             // resizing or shearing only with left mouse button
1697             if (insideSelection) {
1698                 bool forceUniformScaling = m_tabbedOptionWidget && m_tabbedOptionWidget->useUniformScaling();
1699                 return new ShapeResizeStrategy(this, selection, event->point, handle, forceUniformScaling);
1700             }
1701 
1702             if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle ||
1703                 handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) {
1704 
1705                 return new ShapeShearStrategy(this, selection, event->point, handle);
1706             }
1707 
1708             // rotating is allowed for right mouse button too
1709             if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle ||
1710                     handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) {
1711 
1712                 return new ShapeRotateStrategy(this, selection, event->point, event->buttons());
1713             }
1714         }
1715 
1716         if (!selectMultiple && !selectNextInStack) {
1717 
1718            if (insideSelection) {
1719                 return new ShapeMoveStrategy(this, selection, event->point);
1720             }
1721         }
1722     }
1723 
1724     KoShape *shape = shapeManager()->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop);
1725 
1726     if (avoidSelection || (!shape && handle == KoFlake::NoHandle)) {
1727         if (!selectMultiple) {
1728             selection->deselectAll();
1729         }
1730         return new SelectionInteractionStrategy(this, event->point, false);
1731     }
1732 
1733     if (selection->isSelected(shape)) {
1734         if (selectMultiple) {
1735             selection->deselect(shape);
1736         }
1737     } else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected
1738         if (!selectMultiple) {
1739             selection->deselectAll();
1740         }
1741         selection->select(shape);
1742         // tablet selection isn't precise and may lead to a move, preventing that
1743         if (event->isTabletEvent()) {
1744             return new NopInteractionStrategy(this);
1745         }
1746         return new ShapeMoveStrategy(this, selection, event->point);
1747     }
1748     return 0;
1749 }
1750 
updateActions()1751 void DefaultTool::updateActions()
1752 {
1753     QList<KoShape*> editableShapes;
1754 
1755     if (koSelection()) {
1756         editableShapes = koSelection()->selectedEditableShapes();
1757     }
1758 
1759     const bool hasEditableShapes = !editableShapes.isEmpty();
1760 
1761     action("object_order_front")->setEnabled(hasEditableShapes);
1762     action("object_order_raise")->setEnabled(hasEditableShapes);
1763     action("object_order_lower")->setEnabled(hasEditableShapes);
1764     action("object_order_back")->setEnabled(hasEditableShapes);
1765 
1766     action("object_transform_rotate_90_cw")->setEnabled(hasEditableShapes);
1767     action("object_transform_rotate_90_ccw")->setEnabled(hasEditableShapes);
1768     action("object_transform_rotate_180")->setEnabled(hasEditableShapes);
1769     action("object_transform_mirror_horizontally")->setEnabled(hasEditableShapes);
1770     action("object_transform_mirror_vertically")->setEnabled(hasEditableShapes);
1771     action("object_transform_reset")->setEnabled(hasEditableShapes);
1772 
1773     const bool multipleSelected = editableShapes.size() > 1;
1774 
1775     const bool alignmentEnabled =
1776        multipleSelected ||
1777        (!editableShapes.isEmpty() &&
1778         canvas()->resourceManager()->hasResource(KoCanvasResourceProvider::PageSize));
1779 
1780     action("object_align_horizontal_left")->setEnabled(alignmentEnabled);
1781     action("object_align_horizontal_center")->setEnabled(alignmentEnabled);
1782     action("object_align_horizontal_right")->setEnabled(alignmentEnabled);
1783     action("object_align_vertical_top")->setEnabled(alignmentEnabled);
1784     action("object_align_vertical_center")->setEnabled(alignmentEnabled);
1785     action("object_align_vertical_bottom")->setEnabled(alignmentEnabled);
1786 
1787     const bool distributionEnabled = editableShapes.size() > 2;
1788 
1789     action("object_distribute_horizontal_left")->setEnabled(distributionEnabled);
1790     action("object_distribute_horizontal_center")->setEnabled(distributionEnabled);
1791     action("object_distribute_horizontal_right")->setEnabled(distributionEnabled);
1792     action("object_distribute_horizontal_gaps")->setEnabled(distributionEnabled);
1793 
1794     action("object_distribute_vertical_top")->setEnabled(distributionEnabled);
1795     action("object_distribute_vertical_center")->setEnabled(distributionEnabled);
1796     action("object_distribute_vertical_bottom")->setEnabled(distributionEnabled);
1797     action("object_distribute_vertical_gaps")->setEnabled(distributionEnabled);
1798 
1799     updateDistinctiveActions(editableShapes);
1800 
1801     emit selectionChanged(editableShapes.size());
1802 }
1803 
updateDistinctiveActions(const QList<KoShape * > & editableShapes)1804 void DefaultTool::updateDistinctiveActions(const QList<KoShape*> &editableShapes) {
1805     const bool multipleSelected = editableShapes.size() > 1;
1806 
1807     action("object_group")->setEnabled(multipleSelected);
1808 
1809     action("object_unite")->setEnabled(multipleSelected);
1810     action("object_intersect")->setEnabled(multipleSelected);
1811     action("object_subtract")->setEnabled(multipleSelected);
1812 
1813     bool hasShapesWithMultipleSegments = false;
1814     Q_FOREACH (KoShape *shape, editableShapes) {
1815             KoPathShape *pathShape = dynamic_cast<KoPathShape *>(shape);
1816             if (pathShape && pathShape->subpathCount() > 1) {
1817                 hasShapesWithMultipleSegments = true;
1818                 break;
1819             }
1820         }
1821     action("object_split")->setEnabled(hasShapesWithMultipleSegments);
1822 
1823 
1824     bool hasGroupShape = false;
1825             foreach (KoShape *shape, editableShapes) {
1826             if (dynamic_cast<KoShapeGroup *>(shape)) {
1827                 hasGroupShape = true;
1828                 break;
1829             }
1830         }
1831     action("object_ungroup")->setEnabled(hasGroupShape);
1832 }
1833 
1834 
selection()1835 KoToolSelection *DefaultTool::selection()
1836 {
1837     return m_selectionHandler;
1838 }
1839 
popupActionsMenu()1840 QMenu* DefaultTool::popupActionsMenu()
1841 {
1842     if (m_contextMenu) {
1843         m_contextMenu->clear();
1844 
1845         m_contextMenu->addSection(i18n("Vector Shape Actions"));
1846         m_contextMenu->addSeparator();
1847 
1848         QMenu *transform = m_contextMenu->addMenu(i18n("Transform"));
1849 
1850         transform->addAction(action("object_transform_rotate_90_cw"));
1851         transform->addAction(action("object_transform_rotate_90_ccw"));
1852         transform->addAction(action("object_transform_rotate_180"));
1853         transform->addSeparator();
1854         transform->addAction(action("object_transform_mirror_horizontally"));
1855         transform->addAction(action("object_transform_mirror_vertically"));
1856         transform->addSeparator();
1857         transform->addAction(action("object_transform_reset"));
1858 
1859         if (action("object_unite")->isEnabled() ||
1860             action("object_intersect")->isEnabled() ||
1861             action("object_subtract")->isEnabled() ||
1862             action("object_split")->isEnabled()) {
1863 
1864             QMenu *transform = m_contextMenu->addMenu(i18n("Logical Operations"));
1865             transform->addAction(action("object_unite"));
1866             transform->addAction(action("object_intersect"));
1867             transform->addAction(action("object_subtract"));
1868             transform->addAction(action("object_split"));
1869         }
1870 
1871         m_contextMenu->addSeparator();
1872 
1873         m_contextMenu->addAction(action("edit_cut"));
1874         m_contextMenu->addAction(action("edit_copy"));
1875         m_contextMenu->addAction(action("edit_paste"));
1876 
1877         m_contextMenu->addSeparator();
1878 
1879         m_contextMenu->addAction(action("object_order_front"));
1880         m_contextMenu->addAction(action("object_order_raise"));
1881         m_contextMenu->addAction(action("object_order_lower"));
1882         m_contextMenu->addAction(action("object_order_back"));
1883 
1884         if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) {
1885             m_contextMenu->addSeparator();
1886             m_contextMenu->addAction(action("object_group"));
1887             m_contextMenu->addAction(action("object_ungroup"));
1888         }
1889 
1890 
1891     }
1892 
1893     return m_contextMenu.data();
1894 }
1895 
addTransformActions(QMenu * menu) const1896 void DefaultTool::addTransformActions(QMenu *menu) const {
1897     menu->addAction(action("object_transform_rotate_90_cw"));
1898     menu->addAction(action("object_transform_rotate_90_ccw"));
1899     menu->addAction(action("object_transform_rotate_180"));
1900     menu->addSeparator();
1901     menu->addAction(action("object_transform_mirror_horizontally"));
1902     menu->addAction(action("object_transform_mirror_vertically"));
1903     menu->addSeparator();
1904     menu->addAction(action("object_transform_reset"));
1905 }
1906 
explicitUserStrokeEndRequest()1907 void DefaultTool::explicitUserStrokeEndRequest()
1908 {
1909     QList<KoShape *> shapes = koSelection()->selectedEditableShapesAndDelegates();
1910     emit activateTemporary(KoToolManager::instance()->preferredToolForSelection(shapes));
1911 }
1912