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