Krita Source Code Documentation
Loading...
Searching...
No Matches
DefaultTool.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2
3 SPDX-FileCopyrightText: 2006-2008 Thorsten Zachmann <zachmann@kde.org>
4 SPDX-FileCopyrightText: 2006-2010 Thomas Zander <zander@kde.org>
5 SPDX-FileCopyrightText: 2008-2009 Jan Hambrecht <jaham@gmx.net>
6 SPDX-FileCopyrightText: 2008 C. Boemann <cbo@boemann.dk>
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9*/
10
11#include "DefaultTool.h"
14#include "SelectionDecorator.h"
15#include "ShapeMoveStrategy.h"
16#include "ShapeRotateStrategy.h"
17#include "ShapeShearStrategy.h"
18#include "ShapeResizeStrategy.h"
19
20#include <KoPointerEvent.h>
21#include <KoToolSelection.h>
22#include <KoToolManager.h>
23#include <KoSelection.h>
24#include <KoShapeController.h>
25#include <KoShapeManager.h>
27#include <KoShapeGroup.h>
28#include <KoShapeLayer.h>
29#include <KoPathShape.h>
30#include <KoDrag.h>
31#include <KoCanvasBase.h>
34#include <KoSvgTextShape.h>
48
49#include <KoSnapGuide.h>
51#include "kis_action_registry.h"
52#include "kis_node.h"
53#include "kis_node_manager.h"
54#include "KisViewManager.h"
55#include "kis_canvas2.h"
61
63
64#include <KoIcon.h>
65
66#include <QPainterPath>
67#include <QPointer>
68#include <QAction>
69#include <QKeyEvent>
70#include <QTimer>
71#include <QActionGroup>
72#include <KisSignalMapper.h>
73#include <KoResourcePaths.h>
74
75#include <KoCanvasController.h>
76#include <kactioncollection.h>
77#include <QMenu>
78
79#include <math.h>
80#include "kis_assert.h"
81#include "kis_global.h"
82#include "kis_debug.h"
83#include "krita_utils.h"
84
85#include <QVector2D>
86
87#define HANDLE_DISTANCE 10
88#define HANDLE_DISTANCE_SQ (HANDLE_DISTANCE * HANDLE_DISTANCE)
89
90#define INNER_HANDLE_DISTANCE_SQ 16
91
92namespace {
93static const QString EditFillGradientFactoryId = "edit_fill_gradient";
94static const QString EditStrokeGradientFactoryId = "edit_stroke_gradient";
95static const QString EditFillMeshGradientFactoryId = "edit_fill_meshgradient";
96
97enum TransformActionType {
98 TransformRotate90CW,
99 TransformRotate90CCW,
100 TransformRotate180,
101 TransformMirrorX,
102 TransformMirrorY,
103 TransformReset
104};
105
106enum BooleanOp {
107 BooleanUnion,
108 BooleanIntersection,
109 BooleanSubtraction
110};
111
112}
113
115{
116public:
118 : KoInteractionStrategy(parent)
119 {
120 }
121
123 {
124 return 0;
125 }
126
127 void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {}
128 void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {}
129
130 void paint(QPainter &painter, const KoViewConverter &converter) override {
131 Q_UNUSED(painter);
132 Q_UNUSED(converter);
133 }
134};
135
137{
138public:
139 explicit SelectionInteractionStrategy(KoToolBase *parent, const QPointF &clicked, bool useSnapToGrid)
140 : KoShapeRubberSelectStrategy(parent, clicked, useSnapToGrid)
141 {
142 }
143
144 void paint(QPainter &painter, const KoViewConverter &converter) override {
145 KoShapeRubberSelectStrategy::paint(painter, converter);
146 }
147
148 void cancelInteraction() override
149 {
150 tool()->canvas()->updateCanvas(selectedRectangle() | tool()->decorationsRect());
151 }
152
153 void finishInteraction(Qt::KeyboardModifiers modifiers = QFlags<Qt::KeyboardModifier>()) override
154 {
155 Q_UNUSED(modifiers);
156 DefaultTool *defaultTool = dynamic_cast<DefaultTool*>(tool());
158
159 KoSelection * selection = defaultTool->koSelection();
160
161 const bool useContainedMode = currentMode() == CoveringSelection;
162
163 QList<KoShape *> shapes =
164 defaultTool->shapeManager()->
165 shapesAt(selectedRectangle(), true, useContainedMode);
166
167 Q_FOREACH (KoShape * shape, shapes) {
168 if (!shape->isSelectable()) continue;
169
170 selection->select(shape);
171 }
172
173 tool()->canvas()->updateCanvas(selectedRectangle() | tool()->decorationsRect());
174 }
175};
176#include <KoGradientBackground.h>
179
181{
182public:
184 int priority, const QString &id, DefaultTool *_q)
186 q(_q),
187 m_fillVariant(fillVariant)
188 {
189 }
190
204
205 bool hoverEvent(KoPointerEvent *ev) override
206 {
208 return false;
209 }
210
211 bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override
212 {
213 Q_UNUSED(painter);
214 Q_UNUSED(converter);
215 return false;
216 }
217
218 bool tryUseCustomCursor() override {
220 q->useCursor(Qt::OpenHandCursor);
221 return true;
222 }
223
224 return false;
225 }
226
227private:
228
231 QList<KoShape*> shapes = selection->selectedEditableShapes();
232
233 KoShape *shape = 0;
234 if (shapes.size() == 1) {
235 shape = shapes.first();
236 }
237
238 return shape;
239 }
240
243
244 KoShape *shape = onlyEditableShape();
245 if (shape) {
246 KoFlake::SelectionHandle globalHandle = q->handleAt(pos);
247 const qreal distanceThresholdSq =
248 globalHandle == KoFlake::NoHandle ?
250
251 const KoViewConverter *converter = q->canvas()->viewConverter();
252 const QPointF viewPoint = converter->documentToView(pos);
253 qreal minDistanceSq = std::numeric_limits<qreal>::max();
254
256 auto handless = sh.handles();
257 Q_FOREACH (const KoShapeGradientHandles::Handle &handle, sh.handles()) {
258 const QPointF handlePoint = converter->documentToView(handle.pos);
259 const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
260
261 if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) {
262 result = handle;
263 minDistanceSq = distanceSq;
264 }
265 }
266 }
267
268 return result;
269 }
270
271private:
275};
276
279
281{
282public:
284 int priority,
285 const QString& id,
286 DefaultTool* _q)
288 , m_fillVariant(fillVariant)
289 , q(_q)
290 {
291 }
292
309
310 bool hoverEvent(KoPointerEvent *ev) override
311 {
312 // for custom cursor
314
315 // refresh
318 }
319
320 m_currentHandle = handle;
322
323 // highlight the decoration which is being hovered
326 }
327 return false;
328 }
329
330 bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override
331 {
332 Q_UNUSED(painter);
333 Q_UNUSED(converter);
334 return false;
335 }
336
337 bool tryUseCustomCursor() override
338 {
340 q->useCursor(Qt::OpenHandCursor);
341 return true;
342 }
343
344 return false;
345 }
346
347
348private:
350 // FIXME: copy of KoShapeGradientHandles
352 QList<KoShape*> shapes = selection->selectedEditableShapes();
353
354 KoShape *shape = 0;
355 if (shapes.size() == 1) {
356 shape = shapes.first();
357 }
358
359 return shape;
360 }
361
363 {
364 // FIXME: copy of KoShapeGradientHandles. use a template?
366
367 KoShape *shape = onlyEditableShape();
368 if (shape) {
369 KoFlake::SelectionHandle globalHandle = q->handleAt(pos);
370 const qreal distanceThresholdSq =
371 globalHandle == KoFlake::NoHandle ?
373
374 const KoViewConverter *converter = q->canvas()->viewConverter();
375 const QPointF viewPoint = converter->documentToView(pos);
376 qreal minDistanceSq = std::numeric_limits<qreal>::max();
377
379
380 for (const auto& handle: sh.handles()) {
381 const QPointF handlePoint = converter->documentToView(handle.pos);
382 const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
383
384 if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) {
385 result = handle;
386 minDistanceSq = distanceSq;
387 }
388 }
389 }
390
391 return result;
392 }
393
394private:
398};
399
401{
402public:
404 : KoToolSelection(parent)
405 , m_selection(parent->koSelection())
406 {
407 }
408
409 bool hasSelection() override
410 {
411 if (m_selection) {
412 return m_selection->count();
413 }
414 return false;
415 }
416
417private:
419};
420
421DefaultTool::DefaultTool(KoCanvasBase *canvas, bool connectToSelectedShapesProxy)
422 : KoInteractionTool(canvas)
423 , m_lastHandle(KoFlake::NoHandle)
424 , m_hotPosition(KoFlake::TopLeft)
425 , m_mouseWasInsideHandles(false)
426 , m_textOutlineHelper(new KoSvgTextShapeOutlineHelper(canvas))
427 , m_selectionHandler(new SelectionHandler(this))
428 , m_tabbedOptionWidget(0)
429 , m_textPropertyInterface(new DefaultToolTextPropertiesInterface(this))
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(selectionChanged()), m_textPropertyInterface, SLOT(slotSelectionChanged()));
479 connect(canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(repaintDecorations()));
480 }
481
482 m_textOutlineHelper->setDrawBoundingRect(false);
483 m_textOutlineHelper->setDrawShapeOutlines(true);
484}
485
489
491{
492 if (value) {
495 1, EditFillGradientFactoryId, this));
496 } else {
497 removeInteractionFactory(EditFillGradientFactoryId);
498 }
500}
501
503{
504 if (value) {
507 0, EditStrokeGradientFactoryId, this));
508 } else {
509 removeInteractionFactory(EditStrokeGradientFactoryId);
510 }
512}
513
515{
516 if (value) {
518 m_tabbedOptionWidget, SLOT(slotMeshGradientHandleSelected(KoShapeMeshGradientHandles::Handle)));
521 EditFillMeshGradientFactoryId, this));
522 } else {
524 m_tabbedOptionWidget, SLOT(slotMeshGradientHandleSelected(KoShapeMeshGradientHandles::Handle)));
525 removeInteractionFactory(EditFillMeshGradientFactoryId);
526 }
527}
528
533
535{
537
538 if (shapes.isEmpty()) return;
539
541 KUndo2Command *parentCommand = new KUndo2Command();
542 bool convertableShape = false;
544 Q_FOREACH(KoShape *shape, shapes) {
545 KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shape);
546 if (textShape && textShape->textType() != type) {
547 KoSvgConvertTextTypeCommand *cmd = new KoSvgConvertTextTypeCommand(textShape, type, 0, parentCommand);
548 if (!convertableShape) {
549 convertableShape = true;
550 parentCommand->setText(cmd->text());
551 }
553 }
554 }
555
557 if (convertableShape) {
558 canvas()->addCommand(parentCommand);
559 }
560}
561
563{
564 KoSvgTextShape *textShape = nullptr;
565 QList<KoShape*> shapes;
567 if (!selection) return;
568
569 QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
570 std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
571 if (selectedShapes.isEmpty()) return;
572
573 Q_FOREACH(KoShape *shape, selectedShapes) {
574 KoSvgTextShape *text = dynamic_cast<KoSvgTextShape*>(shape);
575 if (text && !textShape) {
576 textShape = text;
577 } else if (dynamic_cast<KoPathShape*>(shape)) {
578 shapes.append(shape);
579 }
580 }
581 if (!textShape) return;
582 if (shapes.isEmpty()) return;
583
584 KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Add shapes to text flow."));
585
586 if (textShape->textType() != KoSvgTextShape::InlineWrap) {
587 new KoSvgConvertTextTypeCommand(textShape, KoSvgTextShape::PreformattedText, 0, parentCommand);
588 }
589 KoSvgTextRemoveShapeCommand::removeContourShapesFromFlow(textShape, parentCommand, false, true);
590
591 new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, parentCommand);
592 Q_FOREACH(KoShape *shape, shapes) {
593 new KoSvgTextAddShapeCommand(textShape, shape, true, parentCommand);
594 }
595 new KoKeepShapesSelectedCommand({}, {textShape}, canvas()->selectedShapesProxy(), true, parentCommand);
596
597 canvas()->addCommand(parentCommand);
598 selection->deselectAll();
599 selection->select(textShape);
600}
601
603{
604 KoSvgTextShape *textShape = nullptr;
605 KoPathShape *textPath = nullptr;
607 if (!selection) return;
608
609 QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
610 std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
611 if (selectedShapes.isEmpty()) return;
612
613 Q_FOREACH(KoShape *shape, selectedShapes) {
614 KoSvgTextShape *text = dynamic_cast<KoSvgTextShape*>(shape);
615 if (text && !textShape) {
616 textShape = text;
617 } else if (KoPathShape *path = dynamic_cast<KoPathShape*>(shape)){
618 textPath = path;
619 }
620 if (textShape && textPath) {
621 break;
622 }
623 }
624 if (!(textShape && textPath)) return;
625
626 KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Put Text On Path"));
627
628 new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, parentCommand);
629 if (textShape->textType() != KoSvgTextShape::InlineWrap) {
630 new KoSvgConvertTextTypeCommand(textShape, KoSvgTextShape::PreformattedText, 0, parentCommand);
631 }
632
637 KoSvgTextRemoveShapeCommand::removeContourShapesFromFlow(textShape, parentCommand, true, true);
638 new KoSvgTextSetTextPathOnRangeCommand(textShape, textPath, 0, textShape->posForIndex(textShape->plainText().size()), parentCommand);
639
640 new KoKeepShapesSelectedCommand({}, {textShape}, canvas()->selectedShapesProxy(), true, parentCommand);
641
642 canvas()->addCommand(parentCommand);
643 selection->deselectAll();
644 selection->select(textShape);
645}
646
648{
649 KoSvgTextShape *textShape = nullptr;
650 QList<KoShape*> shapes;
652 if (!selection) return;
653
654 QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
655 std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
656 if (selectedShapes.isEmpty()) return;
657
658 Q_FOREACH(KoShape *shape, selectedShapes) {
659 KoSvgTextShape *text = dynamic_cast<KoSvgTextShape*>(shape);
660 if (text && !textShape) {
661 textShape = text;
662 } else if (dynamic_cast<KoPathShape*>(shape)) {
663 shapes.append(shape);
664 }
665 }
666 if (!textShape) return;
667 if (shapes.isEmpty()) return;
668
669 KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Subtract shapes from text flow."));
670
671 if (textShape->textType() == KoSvgTextShape::InlineWrap) {
672 new KoSvgConvertTextTypeCommand(textShape, KoSvgTextShape::PreformattedText, 0, parentCommand);
673 }
674 KoSvgTextRemoveShapeCommand::removeContourShapesFromFlow(textShape, parentCommand, false, true);
675
676 new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, parentCommand);
677 Q_FOREACH(KoShape *shape, shapes) {
678 new KoSvgTextAddShapeCommand(textShape, shape, false, parentCommand);
679 }
680 new KoKeepShapesSelectedCommand({}, {textShape}, canvas()->selectedShapesProxy(), true, parentCommand);
681
682 canvas()->addCommand(parentCommand);
683 selection->deselectAll();
684 selection->select(textShape);
685}
686
691
693{
695 if (!textShape) return;
697 if (!selection) return;
698
699 QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
700 if (selectedShapes.isEmpty()) return;
701
702 KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Remove shapes from text flow."));
703
704 new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, parentCommand);
705 Q_FOREACH(KoShape *shape, selectedShapes) {
706 if (!textShape->shapeInContours(shape)) continue;
707 new KoSvgTextRemoveShapeCommand(textShape, shape, parentCommand);
708 }
709 new KoKeepShapesSelectedCommand({}, {selectedShapes}, canvas()->selectedShapesProxy(), true, parentCommand);
710
711 canvas()->addCommand(parentCommand);
712}
713
715{
717 if (!textShape) return;
719 if (!selection) return;
720
721 QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
722 if (selectedShapes.isEmpty()) return;
723 KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Toggle Flow Shape Type"));
724
725 bool addToCanvas = false;
726 Q_FOREACH(KoShape *shape, selectedShapes) {
727 if (!textShape->shapesInside().contains(shape)
728 && !textShape->shapesSubtract().contains(shape)) continue;
729 addToCanvas = true;
730 new KoSvgTextFlipShapeContourTypeCommand(textShape, shape, parentCommand);
731 }
732 if (addToCanvas) {
733 canvas()->addCommand(parentCommand);
734 }
735}
736
738{
740 if (!textShape) return;
742 if (!selection) return;
743
744 QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
745 if (selectedShapes.isEmpty()) return;
746
747 KUndo2Command *parentCommand = new KUndo2Command();
749 parentCommand->setText(kundo2_i18n("Set Flow Shape as First"));
751 parentCommand->setText(kundo2_i18n("Decrease Flow Shape Index"));
753 parentCommand->setText(kundo2_i18n("Increase Flow Shape Index"));
754 } else {
755 parentCommand->setText(kundo2_i18n("Set Flow Shape as Last"));
756 }
757
758 QList<KoShape *> shapesInside;
759 Q_FOREACH(KoShape *shape, selectedShapes) {
760 if (!textShape->shapesInside().contains(shape)) continue;
761 shapesInside.append(shape);
762 }
763
764 if (!shapesInside.isEmpty()) {
765 new KoSvgTextReorderShapeInsideCommand(textShape, shapesInside, KoSvgTextReorderShapeInsideCommand::MoveShapeType(type), parentCommand);
766 canvas()->addCommand(parentCommand);
767 }
768}
769
771{
772 return m_textOutlineHelper->updateTextContourMode();
773}
774
776{
777 return true;
778}
779
780void DefaultTool::addMappedAction(KisSignalMapper *mapper, const QString &actionId, int commandType)
781{
782 QAction *a =action(actionId);
783 connect(a, SIGNAL(triggered()), mapper, SLOT(map()));
784 mapper->setMapping(a, commandType);
785}
786
788{
790
797
799
804
809
811
812 addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_cw", TransformRotate90CW);
813 addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_ccw", TransformRotate90CCW);
814 addMappedAction(m_transformSignalsMapper, "object_transform_rotate_180", TransformRotate180);
815 addMappedAction(m_transformSignalsMapper, "object_transform_mirror_horizontally", TransformMirrorX);
816 addMappedAction(m_transformSignalsMapper, "object_transform_mirror_vertically", TransformMirrorY);
817 addMappedAction(m_transformSignalsMapper, "object_transform_reset", TransformReset);
818
820
821 addMappedAction(m_booleanSignalsMapper, "object_unite", BooleanUnion);
822 addMappedAction(m_booleanSignalsMapper, "object_intersect", BooleanIntersection);
823 addMappedAction(m_booleanSignalsMapper, "object_subtract", BooleanSubtraction);
824
829
830 if (!action("text_type_preformatted")->actionGroup()) {
831 QActionGroup *textTypeActions = new QActionGroup(this);
832 textTypeActions->addAction(action("text_type_preformatted"));
833 textTypeActions->addAction(action("text_type_inline_wrap"));
834 textTypeActions->addAction(action("text_type_pre_positioned"));
835 textTypeActions->setExclusive(true);
836 }
837
839
844
845 m_contextMenu.reset(new QMenu());
846}
847
849{
850 QPointF selectionCenter = koSelection()->absolutePosition();
851 QPointF direction;
852
853 switch (handle) {
855 if (useEdgeRotation) {
858 } else {
859 QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft);
860 handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRight) - handlePosition);
861 direction = handlePosition - selectionCenter;
862 }
863 break;
865 direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized()).toPointF();
866 break;
868 if (useEdgeRotation) {
871 } else {
872 QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRight);
873 handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition);
874 direction = handlePosition - selectionCenter;
875 }
876 break;
878 direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized()).toPointF();
879 break;
881 if (useEdgeRotation) {
884 } else {
885 QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeft);
886 handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition);
887 direction = handlePosition - selectionCenter;
888 }
889 break;
891 direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - selectionCenter;
892 direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized()).toPointF();
893 break;
895 if (useEdgeRotation) {
898 } else {
899 QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft);
900 handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeft) - handlePosition);
901 direction = handlePosition - selectionCenter;
902 }
903 break;
905 direction = koSelection()->absolutePosition(KoFlake::TopLeft) - selectionCenter;
906 direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized()).toPointF();
907 break;
909 return 0.0;
910 break;
911 }
912
913 qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI;
914
915 switch (handle) {
917 if (useEdgeRotation) {
918 rotation -= 0.0;
919 } else {
920 rotation -= 270.0;
921 }
922 break;
924 rotation -= 315.0;
925 break;
927 if (useEdgeRotation) {
928 rotation -= 90.0;
929 } else {
930 rotation -= 0.0;
931 }
932 break;
934 rotation -= 45.0;
935 break;
937 if (useEdgeRotation) {
938 rotation -= 180.0;
939 } else {
940 rotation -= 90.0;
941 }
942 break;
944 rotation -= 135.0;
945 break;
947 if (useEdgeRotation) {
948 rotation -= 270.0;
949 } else {
950 rotation -= 180.0;
951 }
952 break;
954 rotation -= 225.0;
955 break;
956 default:
957 ;
958 }
959
960 if (rotation < 0.0) {
961 rotation += 360.0;
962 }
963
964 return rotation;
965}
966
968{
969 if (tryUseCustomCursor()) return;
970
971 QCursor cursor = Qt::ArrowCursor;
972
973 QString statusText;
974
976 if (selection && selection->count() > 0) { // has a selection
977 bool editable = !selection->selectedEditableShapes().isEmpty();
978
981 int rotOctant = 8 + int(8.5 + m_angle / 45);
982
983 bool rotateHandle = false;
984 bool shearHandle = false;
985 switch (m_lastHandle) {
987 cursor = m_shearCursors[(0 + rotOctant) % 8];
988 shearHandle = true;
989 break;
991 cursor = m_rotateCursors[(1 + rotOctant) % 8];
992 rotateHandle = true;
993 break;
995 cursor = m_shearCursors[(2 + rotOctant) % 8];
996 shearHandle = true;
997 break;
999 cursor = m_rotateCursors[(3 + rotOctant) % 8];
1000 rotateHandle = true;
1001 break;
1003 cursor = m_shearCursors[(4 + rotOctant) % 8];
1004 shearHandle = true;
1005 break;
1007 cursor = m_rotateCursors[(5 + rotOctant) % 8];
1008 rotateHandle = true;
1009 break;
1011 cursor = m_shearCursors[(6 + rotOctant) % 8];
1012 shearHandle = true;
1013 break;
1015 cursor = m_rotateCursors[(7 + rotOctant) % 8];
1016 rotateHandle = true;
1017 break;
1018 case KoFlake::NoHandle:
1019 cursor = Qt::ArrowCursor;
1020 break;
1021 }
1022 if (rotateHandle) {
1023 statusText = i18n("Left click rotates around center, right click around highlighted position.");
1024 }
1025 if (shearHandle) {
1026 statusText = i18n("Click and drag to shear selection.");
1027 }
1028
1029
1030 } else {
1031 statusText = i18n("Click and drag to resize selection.");
1033 int rotOctant = 8 + int(8.5 + m_angle / 45);
1034 bool cornerHandle = false;
1035 switch (m_lastHandle) {
1037 cursor = m_sizeCursors[(0 + rotOctant) % 8];
1038 break;
1040 cursor = m_sizeCursors[(1 + rotOctant) % 8];
1041 cornerHandle = true;
1042 break;
1044 cursor = m_sizeCursors[(2 + rotOctant) % 8];
1045 break;
1047 cursor = m_sizeCursors[(3 + rotOctant) % 8];
1048 cornerHandle = true;
1049 break;
1051 cursor = m_sizeCursors[(4 + rotOctant) % 8];
1052 break;
1054 cursor = m_sizeCursors[(5 + rotOctant) % 8];
1055 cornerHandle = true;
1056 break;
1058 cursor = m_sizeCursors[(6 + rotOctant) % 8];
1059 break;
1061 cursor = m_sizeCursors[(7 + rotOctant) % 8];
1062 cornerHandle = true;
1063 break;
1064 case KoFlake::NoHandle:
1065 cursor = Qt::SizeAllCursor;
1066 statusText = i18n("Click and drag to move selection.");
1067 break;
1068 }
1069 if (cornerHandle) {
1070 statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position.");
1071 }
1072 }
1073 if (!editable) {
1074 cursor = Qt::ArrowCursor;
1075 }
1076 } else {
1077 // there used to be guides... :'''(
1078 }
1080 if (currentStrategy() == 0) {
1081 Q_EMIT statusTextChanged(statusText);
1082 }
1083}
1084
1085void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter)
1086{
1088 if (selection) {
1089 m_decorator.reset(new SelectionDecorator(canvas()->resourceManager()));
1090
1091 {
1097 KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
1098 KisNodeSP node = kisCanvas->viewManager()->nodeManager()->activeNode();
1099 const bool isSelectionMask = node && node->inherits("KisSelectionMask");
1100 m_decorator->setForceShapeOutlines(isSelectionMask);
1101
1102
1103 }
1104
1105 m_decorator->setSelection(selection);
1106 m_decorator->setHandleRadius(handleRadius());
1107 m_decorator->setDecorationThickness(decorationThickness());
1108 m_decorator->setShowFillGradientHandles(hasInteractionFactory(EditFillGradientFactoryId));
1109 m_decorator->setShowStrokeFillGradientHandles(hasInteractionFactory(EditStrokeGradientFactoryId));
1110 m_decorator->setShowFillMeshGradientHandles(hasInteractionFactory(EditFillMeshGradientFactoryId));
1111 m_decorator->setCurrentMeshGradientHandles(m_selectedMeshHandle, m_hoveredMeshHandle);
1112 m_decorator->paint(painter, converter);
1113 }
1114
1115 m_textOutlineHelper->setHandleRadius(handleRadius());
1116 m_textOutlineHelper->setDecorationThickness(decorationThickness());
1117 m_textOutlineHelper->paint(&painter, converter);
1118
1119 KoInteractionTool::paint(painter, converter);
1120
1121 painter.save();
1122 painter.setTransform(converter.documentToView(), true);
1123 canvas()->snapGuide()->paint(painter, converter);
1124 painter.restore();
1125}
1126
1128{
1129 // if the currently active node has a shape manager, then it is
1130 // probably our client :)
1131
1132 KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
1133 return bool(kisCanvas->localShapeManager());
1134}
1135
1139
1141{
1142 // this tool only works on a vector layer right now, so give a warning if another layer type is trying to use it
1143 if (!isValidForCurrentLayer()) {
1144 KisCanvas2 *kiscanvas = static_cast<KisCanvas2 *>(canvas());
1145 kiscanvas->viewManager()->showFloatingMessage(
1146 i18n("This tool only works on vector layers. You probably want the move tool."),
1147 QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter);
1148 return;
1149 }
1150
1151 if (KoSvgTextShape *shape = m_textOutlineHelper->contourModeButtonHovered(event->point)) {
1152 m_textOutlineHelper->toggleTextContourMode(shape);
1153 updateActions();
1154 event->accept();
1155 updateCursor();
1156 return;
1157 }
1159 updateCursor();
1160}
1161
1163{
1165 if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) {
1166 QRectF bound = handlesSize();
1167
1168 if (bound.contains(event->point)) {
1169 bool inside;
1170 KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside);
1171
1172 if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) {
1173 m_lastHandle = newDirection;
1174 m_mouseWasInsideHandles = inside;
1175 }
1176 } else {
1179
1180 // there used to be guides... :'''(
1181 }
1182 } else {
1183 // there used to be guides... :'''(
1184 }
1185
1186
1187 updateCursor();
1188}
1189
1191{
1193 if (!selection || !selection->count()) return QRectF();
1194
1196
1197 QRectF bound = m_selectionOutline.boundingRect();
1198
1199 // expansion Border
1200 if (!canvas() || !canvas()->viewConverter()) {
1201 return bound;
1202 }
1203
1204 QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE));
1205 bound.adjust(-border.x(), -border.y(), border.x(), border.y());
1206 return bound;
1207}
1208
1214
1216{
1218
1220 if (shape && selection && !selection->isSelected(shape)) {
1221
1222 if (!(event->modifiers() & Qt::ShiftModifier)) {
1223 selection->deselectAll();
1224 }
1225
1226 selection->select(shape);
1227 }
1228
1230}
1231
1232bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers)
1233{
1234 bool result = false;
1235
1236 qreal x = 0.0, y = 0.0;
1237 if (direction == Qt::Key_Left) {
1238 x = -5;
1239 } else if (direction == Qt::Key_Right) {
1240 x = 5;
1241 } else if (direction == Qt::Key_Up) {
1242 y = -5;
1243 } else if (direction == Qt::Key_Down) {
1244 y = 5;
1245 }
1246
1247 if (x != 0.0 || y != 0.0) { // actually move
1248
1249 if ((modifiers & Qt::ShiftModifier) != 0) {
1250 x *= 10;
1251 y *= 10;
1252 } else if ((modifiers & Qt::AltModifier) != 0) { // more precise
1253 x /= 5;
1254 y /= 5;
1255 }
1256
1258
1259 if (!shapes.isEmpty()) {
1260 canvas()->addCommand(new KoShapeMoveCommand(shapes, QPointF(x, y)));
1261 result = true;
1262 }
1263 }
1264
1265 return result;
1266}
1267
1268void DefaultTool::keyPressEvent(QKeyEvent *event)
1269{
1271 if (currentStrategy() == 0) {
1272 switch (event->key()) {
1273 case Qt::Key_Left:
1274 case Qt::Key_Right:
1275 case Qt::Key_Up:
1276 case Qt::Key_Down:
1277 if (moveSelection(event->key(), event->modifiers())) {
1278 event->accept();
1279 }
1280 break;
1281 default:
1282 return;
1283 }
1284 }
1285}
1286
1288{
1289 QRectF dirtyRect;
1290
1291 if (koSelection() && koSelection()->count() > 0) {
1295 dirtyRect = const_cast<DefaultTool*>(this)->handlesSize();
1296 }
1297
1298 if (canvas()->snapGuide()->isSnapping()) {
1299 dirtyRect |= canvas()->snapGuide()->boundingRect();
1300 }
1301 dirtyRect |= m_textOutlineHelper->decorationRect();
1302
1303 return dirtyRect;
1304}
1305
1307{
1308 // all the selected shapes, not only editable!
1310
1311 if (!shapes.isEmpty()) {
1312 KoDrag drag;
1313 drag.setSvg(shapes);
1314 drag.addToClipboard();
1315 }
1316}
1317
1319{
1320 QList<KoShape *> shapes;
1321 foreach (KoShape *s, koSelection()->selectedShapes()) {
1322 if (s->isGeometryProtected()) {
1323 continue;
1324 }
1325 shapes << s;
1326 }
1327 if (!shapes.empty()) {
1328 canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes));
1329 }
1330}
1331
1333{
1334 // we no longer have to do anything as tool Proxy will do it for us
1335 return false;
1336}
1337
1339{
1340 Q_ASSERT(canvas());
1341 Q_ASSERT(canvas()->selectedShapesProxy());
1342 Q_FOREACH(KoShape *shape, canvas()->shapeManager()->shapes()) {
1343 if (!shape->isSelectable()) continue;
1345 }
1347
1348 return true;
1349}
1350
1352{
1353 Q_ASSERT(canvas());
1354 Q_ASSERT(canvas()->selectedShapesProxy());
1357}
1358
1360{
1361 Q_ASSERT(canvas());
1362 Q_ASSERT(canvas()->selectedShapesProxy());
1363 return canvas()->selectedShapesProxy()->selection();
1364}
1365
1366KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning)
1367{
1368 // check for handles in this order; meaning that when handles overlap the one on top is chosen
1369 static const KoFlake::SelectionHandle handleOrder[] = {
1379 };
1380
1381 const KoViewConverter *converter = canvas()->viewConverter();
1383
1384 if (!selection || !selection->count() || !converter) {
1385 return KoFlake::NoHandle;
1386 }
1387
1389
1390 if (innerHandleMeaning) {
1391 QPainterPath path;
1392 path.addPolygon(m_selectionOutline);
1393 *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point));
1394 }
1395
1396 const QPointF viewPoint = converter->documentToView(point);
1397
1398 for (int i = 0; i < KoFlake::NoHandle; ++i) {
1399 KoFlake::SelectionHandle handle = handleOrder[i];
1400
1401 const QPointF handlePoint = converter->documentToView(m_selectionBox[handle]);
1402 const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
1403
1404 // if just inside the outline
1405 if (distanceSq < HANDLE_DISTANCE_SQ) {
1406
1407 if (innerHandleMeaning) {
1408 if (distanceSq < INNER_HANDLE_DISTANCE_SQ) {
1409 *innerHandleMeaning = true;
1410 }
1411 }
1412
1413 return handle;
1414 }
1415 }
1416 return KoFlake::NoHandle;
1417}
1418
1420{
1422
1423 QTransform matrix = selection->absoluteTransformation();
1424 m_selectionOutline = matrix.map(QPolygonF(selection->outlineRect()));
1425 m_angle = 0.0;
1426
1427 QPolygonF outline = m_selectionOutline; //shorter name in the following :)
1428 m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2;
1429 m_selectionBox[KoFlake::TopRightHandle] = outline.value(1);
1430 m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2;
1431 m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2);
1432 m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2;
1433 m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3);
1434 m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2;
1435 m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0);
1436 if (selection->count() == 1) {
1437#if 0 // TODO detect mirroring
1439
1440 if (s->scaleX() < 0) { // vertically mirrored: swap left / right
1444 }
1445 if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom
1449 }
1450#endif
1451 }
1452}
1453
1454void DefaultTool::activate(const QSet<KoShape *> &shapes)
1455{
1456 KoToolBase::activate(shapes);
1457
1458 QAction *actionBringToFront = action("object_order_front");
1459 connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront()), Qt::UniqueConnection);
1460
1461 QAction *actionRaise = action("object_order_raise");
1462 connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp()), Qt::UniqueConnection);
1463
1464 QAction *actionLower = action("object_order_lower");
1465 connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown()));
1466
1467 QAction *actionSendToBack = action("object_order_back");
1468 connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack()), Qt::UniqueConnection);
1469
1470 QAction *actionGroupBottom = action("object_group");
1471 connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup()), Qt::UniqueConnection);
1472
1473 QAction *actionUngroupBottom = action("object_ungroup");
1474 connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup()), Qt::UniqueConnection);
1475
1476 QAction *actionSplit = action("object_split");
1477 connect(actionSplit, SIGNAL(triggered()), this, SLOT(selectionSplitShapes()), Qt::UniqueConnection);
1478
1479 connect(m_alignSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionAlign(int)));
1480 connect(m_distributeSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionDistribute(int)));
1481 connect(m_transformSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionTransform(int)));
1482 connect(m_booleanSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionBooleanOp(int)));
1483 connect(m_textTypeSignalsMapper, SIGNAL(mapped(int)), SLOT(slotChangeTextType(int)));
1484 connect(m_textFlowSignalsMapper, SIGNAL(mapped(int)), SLOT(slotReorderFlowShapes(int)));
1485
1486 QAction *actionTextInside = action("add_shape_to_flow_area");
1487 connect(actionTextInside, SIGNAL(triggered()), this, SLOT(slotAddShapesToFlow()), Qt::UniqueConnection);
1488
1489 QAction *actionTextSubtract = action("subtract_shape_from_flow_area");
1490 connect(actionTextSubtract, SIGNAL(triggered()), this, SLOT(slotSubtractShapesFromFlow()), Qt::UniqueConnection);
1491
1492 QAction *actionTextOnPath = action("put_text_on_path");
1493 connect(actionTextOnPath, SIGNAL(triggered()), this, SLOT(slotPutTextOnPath()), Qt::UniqueConnection);
1494
1495 QAction *actionTextRemoveFlow = action("remove_shapes_from_text_flow");
1496 connect(actionTextRemoveFlow, SIGNAL(triggered()), this, SLOT(slotRemoveShapesFromFlow()), Qt::UniqueConnection);
1497
1498 QAction *actionTextFlowToggle = action("flow_shape_type_toggle");
1499 connect(actionTextFlowToggle, SIGNAL(triggered()), this, SLOT(slotToggleFlowShapeType()), Qt::UniqueConnection);
1500
1503 useCursor(Qt::ArrowCursor);
1505 updateActions();
1506
1507 const KisCanvas2 *canvas2 = qobject_cast<const KisCanvas2 *>(this->canvas());
1508 if (canvas2) {
1511 }
1512
1515 }
1516}
1517
1519{
1521
1522 QAction *actionBringToFront = action("object_order_front");
1523 disconnect(actionBringToFront, 0, this, 0);
1524
1525 QAction *actionRaise = action("object_order_raise");
1526 disconnect(actionRaise, 0, this, 0);
1527
1528 QAction *actionLower = action("object_order_lower");
1529 disconnect(actionLower, 0, this, 0);
1530
1531 QAction *actionSendToBack = action("object_order_back");
1532 disconnect(actionSendToBack, 0, this, 0);
1533
1534 QAction *actionGroupBottom = action("object_group");
1535 disconnect(actionGroupBottom, 0, this, 0);
1536
1537 QAction *actionUngroupBottom = action("object_ungroup");
1538 disconnect(actionUngroupBottom, 0, this, 0);
1539
1540 QAction *actionSplit = action("object_split");
1541 disconnect(actionSplit, 0, this, 0);
1542
1543 disconnect(m_alignSignalsMapper, 0, this, 0);
1544 disconnect(m_distributeSignalsMapper, 0, this, 0);
1545 disconnect(m_transformSignalsMapper, 0, this, 0);
1546 disconnect(m_booleanSignalsMapper, 0, this, 0);
1547 disconnect(m_textTypeSignalsMapper, 0, this, 0);
1548 disconnect(m_textFlowSignalsMapper, 0, this, 0);
1549
1550 QAction *actionTextInside = action("add_shape_to_flow_area");
1551 disconnect(actionTextInside, 0, this, 0);
1552 QAction *actionTextSubtract = action("subtract_shape_from_flow_area");
1553 disconnect(actionTextSubtract, 0, this, 0);
1554 QAction *actionTextOnPath = action("put_text_on_path");
1555 disconnect(actionTextOnPath, 0, this, 0);
1556 QAction *actionTextRemoveFlow = action("remove_shapes_from_text_flow");
1557 disconnect(actionTextRemoveFlow, 0, this, 0);
1558 QAction *actionTextFlowToggle = action("flow_shape_type_toggle");
1559 disconnect(actionTextFlowToggle, 0, this, 0);
1560
1561 const KisCanvas2 *canvas2 = qobject_cast<const KisCanvas2 *>(this->canvas());
1562 if (canvas2) {
1565 }
1566
1569 }
1570}
1571
1573{
1575 if (!selection) return;
1576
1577 QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
1578 std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
1579 if (selectedShapes.isEmpty()) return;
1580
1581 const int groupZIndex = selectedShapes.last()->zIndex();
1582
1583 KoShapeGroup *group = new KoShapeGroup();
1584 group->setZIndex(groupZIndex);
1585 // TODO what if only one shape is left?
1586 KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes"));
1587 new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
1588 canvas()->shapeController()->addShapeDirect(group, 0, cmd);
1589 new KoShapeGroupCommand(group, selectedShapes, true, cmd);
1590 new KoKeepShapesSelectedCommand({}, {group}, canvas()->selectedShapesProxy(), true, cmd);
1591 canvas()->addCommand(cmd);
1592
1593 // update selection so we can ungroup immediately again
1594 selection->deselectAll();
1595 selection->select(group);
1596}
1597
1599{
1601 if (!selection) return;
1602
1603 QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
1604 std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
1605
1606 KUndo2Command *cmd = 0;
1607 QList<KoShape*> newShapes;
1608
1609 // add a ungroup command for each found shape container to the macro command
1610 Q_FOREACH (KoShape *shape, selectedShapes) {
1611 KoShapeGroup *group = dynamic_cast<KoShapeGroup *>(shape);
1612 if (group) {
1613 if (!cmd) {
1614 cmd = new KUndo2Command(kundo2_i18n("Ungroup shapes"));
1615 new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
1616 }
1617 newShapes << group->shapes();
1618 new KoShapeUngroupCommand(group, group->shapes(),
1620 cmd);
1621 canvas()->shapeController()->removeShape(group, cmd);
1622 }
1623 }
1624 if (cmd) {
1625 new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd);
1626 canvas()->addCommand(cmd);
1627 }
1628}
1629
1630void DefaultTool::selectionTransform(int transformAction)
1631{
1633 if (!selection) return;
1634
1635 QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1636 if (editableShapes.isEmpty()) {
1637 return;
1638 }
1639
1640 QTransform applyTransform;
1641 bool shouldReset = false;
1642 KUndo2MagicString actionName = kundo2_noi18n("BUG: No transform action");
1643
1644
1645 switch (TransformActionType(transformAction)) {
1646 case TransformRotate90CW:
1647 applyTransform.rotate(90.0);
1648 actionName = kundo2_i18n("Rotate Object 90° CW");
1649 break;
1650 case TransformRotate90CCW:
1651 applyTransform.rotate(-90.0);
1652 actionName = kundo2_i18n("Rotate Object 90° CCW");
1653 break;
1654 case TransformRotate180:
1655 applyTransform.rotate(180.0);
1656 actionName = kundo2_i18n("Rotate Object 180°");
1657 break;
1658 case TransformMirrorX:
1659 applyTransform.scale(-1.0, 1.0);
1660 actionName = kundo2_i18n("Mirror Object Horizontally");
1661 break;
1662 case TransformMirrorY:
1663 applyTransform.scale(1.0, -1.0);
1664 actionName = kundo2_i18n("Mirror Object Vertically");
1665 break;
1666 case TransformReset:
1667 shouldReset = true;
1668 actionName = kundo2_i18n("Reset Object Transformations");
1669 break;
1670 }
1671
1672 if (!shouldReset && applyTransform.isIdentity()) return;
1673
1674 QList<QTransform> oldTransforms;
1675 QList<QTransform> newTransforms;
1676
1677 const QRectF outlineRect = KoShape::absoluteOutlineRect(editableShapes);
1678 const QPointF centerPoint = outlineRect.center();
1679 const QTransform centerTrans = QTransform::fromTranslate(centerPoint.x(), centerPoint.y());
1680 const QTransform centerTransInv = QTransform::fromTranslate(-centerPoint.x(), -centerPoint.y());
1681
1682 // we also add selection to the list of transformed shapes, so that its outline is updated correctly
1683 QList<KoShape*> transformedShapes = editableShapes;
1684 transformedShapes << selection;
1685
1686 Q_FOREACH (KoShape *shape, transformedShapes) {
1687 oldTransforms.append(shape->transformation());
1688
1689 QTransform t;
1690
1691 if (!shouldReset) {
1692 const QTransform world = shape->absoluteTransformation();
1693 t = world * centerTransInv * applyTransform * centerTrans * world.inverted() * shape->transformation();
1694 } else {
1695 const QPointF center = shape->outlineRect().center();
1696 const QPointF offset = shape->transformation().map(center) - center;
1697 t = QTransform::fromTranslate(offset.x(), offset.y());
1698 }
1699
1700 newTransforms.append(t);
1701 }
1702
1703 KoShapeTransformCommand *cmd = new KoShapeTransformCommand(transformedShapes, oldTransforms, newTransforms);
1704 cmd->setText(actionName);
1705 canvas()->addCommand(cmd);
1706}
1707
1709{
1711 if (!selection) return;
1712
1713 QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1714 if (editableShapes.isEmpty()) {
1715 return;
1716 }
1717
1718 QVector<QPainterPath> srcOutlines;
1719 QPainterPath dstOutline;
1720 KUndo2MagicString actionName = kundo2_noi18n("BUG: boolean action name");
1721
1722 // TODO: implement a reference shape selection dialog!
1723 const int referenceShapeIndex = 0;
1724 KoShape *referenceShape = editableShapes[referenceShapeIndex];
1725
1726 KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
1728 const QTransform booleanWorkaroundTransform =
1730
1731 Q_FOREACH (KoShape *shape, editableShapes) {
1732 srcOutlines <<
1733 booleanWorkaroundTransform.map(
1734 shape->absoluteTransformation().map(
1735 shape->outline()));
1736 }
1737
1738 if (booleanOp == BooleanUnion) {
1739 Q_FOREACH (const QPainterPath &path, srcOutlines) {
1740 dstOutline |= path;
1741 }
1742 actionName = kundo2_i18n("Unite Shapes");
1743 } else if (booleanOp == BooleanIntersection) {
1744 for (int i = 0; i < srcOutlines.size(); i++) {
1745 if (i == 0) {
1746 dstOutline = srcOutlines[i];
1747 } else {
1748 dstOutline &= srcOutlines[i];
1749 }
1750 }
1751
1752 // there is a bug in Qt, sometimes it leaves the resulting
1753 // outline open, so just close it explicitly.
1754 dstOutline.closeSubpath();
1755
1756 actionName = kundo2_i18n("Intersect Shapes");
1757
1758 } else if (booleanOp == BooleanSubtraction) {
1759 for (int i = 0; i < srcOutlines.size(); i++) {
1760 dstOutline = srcOutlines[referenceShapeIndex];
1761 if (i != referenceShapeIndex) {
1762 dstOutline -= srcOutlines[i];
1763 }
1764 }
1765
1766 actionName = kundo2_i18n("Subtract Shapes");
1767 }
1768
1769 dstOutline = booleanWorkaroundTransform.inverted().map(dstOutline);
1770
1771 KoShape *newShape = 0;
1772
1773 if (!dstOutline.isEmpty()) {
1774 newShape = KoPathShape::createShapeFromPainterPath(dstOutline);
1775 }
1776
1777 KUndo2Command *cmd = new KUndo2Command(actionName);
1778
1779 new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
1780
1781 QList<KoShape*> newSelectedShapes;
1782
1783 if (newShape) {
1784 newShape->setBackground(referenceShape->background());
1785 newShape->setStroke(referenceShape->stroke());
1786 newShape->setZIndex(referenceShape->zIndex());
1787
1788 KoShapeContainer *parent = referenceShape->parent();
1789 canvas()->shapeController()->addShapeDirect(newShape, parent, cmd);
1790
1791 newSelectedShapes << newShape;
1792 }
1793
1794 canvas()->shapeController()->removeShapes(editableShapes, cmd);
1795
1796 new KoKeepShapesSelectedCommand({}, newSelectedShapes, canvas()->selectedShapesProxy(), true, cmd);
1797
1798 canvas()->addCommand(cmd);
1799}
1800
1802{
1804 if (!selection) return;
1805
1806 QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1807 if (editableShapes.isEmpty()) {
1808 return;
1809 }
1810
1811 KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Split Shapes"));
1812
1813 new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
1814 QList<KoShape*> newShapes;
1815
1816 Q_FOREACH (KoShape *shape, editableShapes) {
1817 KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
1818 if (!pathShape) return;
1819
1820 QList<KoPathShape*> splitShapes;
1821 if (pathShape->separate(splitShapes)) {
1822 QList<KoShape*> normalShapes = implicitCastList<KoShape*>(splitShapes);
1823
1824 KoShapeContainer *parent = shape->parent();
1825 canvas()->shapeController()->addShapesDirect(normalShapes, parent, cmd);
1826 canvas()->shapeController()->removeShape(shape, cmd);
1827 newShapes << normalShapes;
1828 }
1829 }
1830
1831 new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd);
1832
1833 canvas()->addCommand(cmd);
1834}
1835
1837{
1839 static_cast<KoShapeAlignCommand::Align>(_align);
1840
1842 if (!selection) return;
1843
1844 QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1845 if (editableShapes.isEmpty()) {
1846 return;
1847 }
1848
1849 // TODO add an option to the widget so that one can align to the page
1850 // with multiple selected shapes too
1851
1852 QRectF bb;
1853
1854 // single selected shape is automatically aligned to document rect
1855 if (editableShapes.count() == 1) {
1856 if (!canvas()->resourceManager()->hasResource(KoCanvasResource::PageSize)) {
1857 return;
1858 }
1859 bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResource::PageSize));
1860 } else {
1861 bb = KoShape::absoluteOutlineRect(editableShapes);
1862 }
1863
1864 KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb);
1865 canvas()->addCommand(cmd);
1866}
1867
1869{
1871 static_cast<KoShapeDistributeCommand::Distribute>(_distribute);
1872
1874 if (!selection) return;
1875
1876 QList<KoShape *> editableShapes = selection->selectedEditableShapes();
1877 if (editableShapes.size() < 3) {
1878 return;
1879 }
1880
1881 QRectF bb = KoShape::absoluteOutlineRect(editableShapes);
1882 KoShapeDistributeCommand *cmd = new KoShapeDistributeCommand(editableShapes, distribute, bb);
1883 canvas()->addCommand(cmd);
1884}
1885
1890
1895
1900
1905
1907{
1909 if (!selection) {
1910 return;
1911 }
1912
1913 QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
1914 if (selectedShapes.isEmpty()) {
1915 return;
1916 }
1917
1918 KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, shapeManager(), order);
1919 if (cmd) {
1920 canvas()->addCommand(cmd);
1921 }
1922}
1923
1925{
1926 QList<QPointer<QWidget> > widgets;
1927
1929
1930 if (isActivated()) {
1932 }
1933 widgets.append(m_tabbedOptionWidget);
1934
1935 connect(m_tabbedOptionWidget,
1936 SIGNAL(sigSwitchModeEditFillGradient(bool)),
1937 SLOT(slotActivateEditFillGradient(bool)));
1938
1939 connect(m_tabbedOptionWidget,
1940 SIGNAL(sigSwitchModeEditStrokeGradient(bool)),
1941 SLOT(slotActivateEditStrokeGradient(bool)));
1942
1943 connect(m_tabbedOptionWidget,
1944 SIGNAL(sigSwitchModeEditFillGradient(bool)),
1946 // TODO: strokes!!
1947
1948 connect(m_tabbedOptionWidget,
1949 SIGNAL(sigMeshGradientResetted()),
1951
1952 return widgets;
1953}
1954
1955void DefaultTool::canvasResourceChanged(int key, const QVariant &res)
1956{
1957 if (key == HotPosition) {
1960 }
1961}
1962
1964{
1966 if (!selection) return nullptr;
1967
1968 bool insideSelection = false;
1969 KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection);
1970
1971 bool editableShape = !selection->selectedEditableShapes().isEmpty();
1972
1973 const bool selectMultiple = event->modifiers() & Qt::ShiftModifier;
1974 const bool selectNextInStack = event->modifiers() & Qt::ControlModifier;
1975 const bool avoidSelection = event->modifiers() & Qt::AltModifier;
1976
1977 if (selectNextInStack) {
1978 // change the hot selection position when middle clicking on a handle
1979 KoFlake::AnchorPosition newHotPosition = m_hotPosition;
1980 switch (handle) {
1982 newHotPosition = KoFlake::Top;
1983 break;
1985 newHotPosition = KoFlake::TopRight;
1986 break;
1988 newHotPosition = KoFlake::Right;
1989 break;
1991 newHotPosition = KoFlake::BottomRight;
1992 break;
1994 newHotPosition = KoFlake::Bottom;
1995 break;
1997 newHotPosition = KoFlake::BottomLeft;
1998 break;
2000 newHotPosition = KoFlake::Left;
2001 break;
2003 newHotPosition = KoFlake::TopLeft;
2004 break;
2005 case KoFlake::NoHandle:
2006 default:
2007 // check if we had hit the center point
2008 const KoViewConverter *converter = canvas()->viewConverter();
2009 QPointF pt = converter->documentToView(event->point);
2010
2011 // TODO: use calculated values instead!
2012 QPointF centerPt = converter->documentToView(selection->absolutePosition());
2013
2014 if (kisSquareDistance(pt, centerPt) < HANDLE_DISTANCE_SQ) {
2015 newHotPosition = KoFlake::Center;
2016 }
2017
2018 break;
2019 }
2020
2021 if (m_hotPosition != newHotPosition) {
2022 canvas()->resourceManager()->setResource(HotPosition, newHotPosition);
2023 return new NopInteractionStrategy(this);
2024 }
2025 }
2026
2027 if (!avoidSelection && editableShape) {
2028 // manipulation of selected shapes goes first
2029 if (handle != KoFlake::NoHandle) {
2030 // resizing or shearing only with left mouse button
2031 if (insideSelection) {
2032 bool forceUniformScaling = m_tabbedOptionWidget && m_tabbedOptionWidget->useUniformScaling();
2033 return new ShapeResizeStrategy(this, selection, event->point, handle, forceUniformScaling);
2034 }
2035
2036 if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle ||
2038
2039 return new ShapeShearStrategy(this, selection, event->point, handle);
2040 }
2041
2042 // rotating is allowed for right mouse button too
2043 if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle ||
2045
2046 return new ShapeRotateStrategy(this, selection, event->point, event->buttons());
2047 }
2048 }
2049
2050 if (!selectMultiple && !selectNextInStack) {
2051
2052 if (insideSelection) {
2053 return new ShapeMoveStrategy(this, selection, event->point);
2054 }
2055 }
2056 }
2057
2058 KoShape *shape = shapeManager()->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop);
2059
2060 if (avoidSelection || (!shape && handle == KoFlake::NoHandle)) {
2061 if (!selectMultiple) {
2062 selection->deselectAll();
2063 }
2064 return new SelectionInteractionStrategy(this, event->point, false);
2065 }
2066
2067 if (selection->isSelected(shape)) {
2068 if (selectMultiple) {
2069 selection->deselect(shape);
2070 }
2071 } else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected
2072 if (!selectMultiple) {
2073 selection->deselectAll();
2074 }
2075 selection->select(shape);
2076 // tablet selection isn't precise and may lead to a move, preventing that
2077 if (event->isTabletEvent()) {
2078 return new NopInteractionStrategy(this);
2079 }
2080 return new ShapeMoveStrategy(this, selection, event->point);
2081 }
2082 return 0;
2083}
2084
2086{
2087 QList<KoShape*> editableShapes;
2088
2089 if (koSelection()) {
2090 editableShapes = koSelection()->selectedEditableShapes();
2091 }
2092
2093 const bool hasEditableShapes = !editableShapes.isEmpty();
2094
2095 action("object_order_front")->setEnabled(hasEditableShapes);
2096 action("object_order_raise")->setEnabled(hasEditableShapes);
2097 action("object_order_lower")->setEnabled(hasEditableShapes);
2098 action("object_order_back")->setEnabled(hasEditableShapes);
2099
2100 action("object_transform_rotate_90_cw")->setEnabled(hasEditableShapes);
2101 action("object_transform_rotate_90_ccw")->setEnabled(hasEditableShapes);
2102 action("object_transform_rotate_180")->setEnabled(hasEditableShapes);
2103 action("object_transform_mirror_horizontally")->setEnabled(hasEditableShapes);
2104 action("object_transform_mirror_vertically")->setEnabled(hasEditableShapes);
2105 action("object_transform_reset")->setEnabled(hasEditableShapes);
2106
2107 const bool multipleSelected = editableShapes.size() > 1;
2108
2109 const bool alignmentEnabled =
2110 multipleSelected ||
2111 (!editableShapes.isEmpty() &&
2113
2114 action("object_align_horizontal_left")->setEnabled(alignmentEnabled);
2115 action("object_align_horizontal_center")->setEnabled(alignmentEnabled);
2116 action("object_align_horizontal_right")->setEnabled(alignmentEnabled);
2117 action("object_align_vertical_top")->setEnabled(alignmentEnabled);
2118 action("object_align_vertical_center")->setEnabled(alignmentEnabled);
2119 action("object_align_vertical_bottom")->setEnabled(alignmentEnabled);
2120
2121 const bool distributionEnabled = editableShapes.size() > 2;
2122
2123 action("object_distribute_horizontal_left")->setEnabled(distributionEnabled);
2124 action("object_distribute_horizontal_center")->setEnabled(distributionEnabled);
2125 action("object_distribute_horizontal_right")->setEnabled(distributionEnabled);
2126 action("object_distribute_horizontal_gaps")->setEnabled(distributionEnabled);
2127
2128 action("object_distribute_vertical_top")->setEnabled(distributionEnabled);
2129 action("object_distribute_vertical_center")->setEnabled(distributionEnabled);
2130 action("object_distribute_vertical_bottom")->setEnabled(distributionEnabled);
2131 action("object_distribute_vertical_gaps")->setEnabled(distributionEnabled);
2132
2133 /* Handling the text actions */
2134 bool textShape = false;
2135 bool otherShapes = false;
2136 bool shapesInside = false;
2138 const bool editFlowShapes = bool(currentTextShapeGroup);
2139 Q_FOREACH(KoShape *shape, editableShapes) {
2140 KoSvgTextShape *text = dynamic_cast<KoSvgTextShape *>(shape);
2141 if (text && !textShape) {
2142 textShape = true;
2143 } else {
2144 otherShapes = true;
2145 if (editFlowShapes) {
2146 if (!shapesInside && currentTextShapeGroup->shapesInside().contains(shape)) {
2147 shapesInside = true;
2148 }
2149 }
2150 }
2151 if (textShape && otherShapes) break;
2152 }
2153 const bool editContours = textShape && otherShapes;
2154
2155 action("add_shape_to_flow_area")->setEnabled(editContours);
2156 action("subtract_shape_from_flow_area")->setEnabled(editContours);
2157 action("put_text_on_path")->setEnabled(editContours);
2158 action("remove_shapes_from_text_flow")->setEnabled(editFlowShapes);
2159 action("flow_shape_type_toggle")->setEnabled(editFlowShapes);
2160 action("flow_shape_order_back")->setEnabled(shapesInside);
2161 action("flow_shape_order_earlier")->setEnabled(shapesInside);
2162 action("flow_shape_order_later")->setEnabled(shapesInside);
2163 action("flow_shape_order_front")->setEnabled(shapesInside);
2164
2165 updateDistinctiveActions(editableShapes);
2166
2167 Q_EMIT selectionChanged(editableShapes.size());
2168}
2169
2171 const bool multipleSelected = editableShapes.size() > 1;
2172
2173 action("object_group")->setEnabled(multipleSelected);
2174
2175 action("object_unite")->setEnabled(multipleSelected);
2176 action("object_intersect")->setEnabled(multipleSelected);
2177 action("object_subtract")->setEnabled(multipleSelected);
2178
2179 bool hasShapesWithMultipleSegments = false;
2180 Q_FOREACH (KoShape *shape, editableShapes) {
2181 KoPathShape *pathShape = dynamic_cast<KoPathShape *>(shape);
2182 if (pathShape && pathShape->subpathCount() > 1) {
2183 hasShapesWithMultipleSegments = true;
2184 break;
2185 }
2186 }
2187 action("object_split")->setEnabled(hasShapesWithMultipleSegments);
2188
2189
2190 bool hasGroupShape = false;
2191 foreach (KoShape *shape, editableShapes) {
2192 if (dynamic_cast<KoShapeGroup *>(shape)) {
2193 hasGroupShape = true;
2194 break;
2195 }
2196 }
2197 action("object_ungroup")->setEnabled(hasGroupShape);
2198
2199 bool enablePreformatted = false;
2200 bool enablePrePositioned = false;
2201 bool enableInlineWrapped = false;
2202 bool text = false;
2203 Q_FOREACH (KoShape *shape, editableShapes) {
2204 KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape *>(shape);
2205 if (textShape) {
2206 text = true;
2207 if (textShape->textType() != KoSvgTextShape::PreformattedText && !enablePreformatted) {
2208 enablePreformatted = true;
2209 }
2210 if (textShape && textShape->textType() != KoSvgTextShape::PrePositionedText && !enablePrePositioned) {
2211 enablePrePositioned = true;
2212 }
2213 if (textShape && textShape->textType() != KoSvgTextShape::InlineWrap && !enableInlineWrapped) {
2214 enableInlineWrapped = true;
2215 }
2216 }
2217 }
2218 QActionGroup *group = action("text_type_preformatted")->actionGroup();
2219 group->setEnabled(true);
2220 group->setExclusive(false);
2221 Q_FOREACH (QAction *a, group->actions()) {
2222 a->setCheckable(false);
2223 }
2224
2225 action("text_type_preformatted")->setEnabled(enablePreformatted);
2226 action("text_type_pre_positioned")->setEnabled(enablePrePositioned);
2227 action("text_type_inline_wrap")->setEnabled(enableInlineWrapped);
2228}
2229
2230
2235
2237{
2238 if (m_contextMenu) {
2239 m_contextMenu->clear();
2240
2241 m_contextMenu->addSection(i18n("Vector Shape Actions"));
2242 m_contextMenu->addSeparator();
2243
2244 QMenu *transform = m_contextMenu->addMenu(i18n("Transform"));
2245
2246 transform->addAction(action("object_transform_rotate_90_cw"));
2247 transform->addAction(action("object_transform_rotate_90_ccw"));
2248 transform->addAction(action("object_transform_rotate_180"));
2249 transform->addSeparator();
2250 transform->addAction(action("object_transform_mirror_horizontally"));
2251 transform->addAction(action("object_transform_mirror_vertically"));
2252 transform->addSeparator();
2253 transform->addAction(action("object_transform_reset"));
2254
2255 if (action("object_unite")->isEnabled() ||
2256 action("object_intersect")->isEnabled() ||
2257 action("object_subtract")->isEnabled() ||
2258 action("object_split")->isEnabled()) {
2259
2260 QMenu *transform = m_contextMenu->addMenu(i18n("Logical Operations"));
2261 transform->addAction(action("object_unite"));
2262 transform->addAction(action("object_intersect"));
2263 transform->addAction(action("object_subtract"));
2264 transform->addAction(action("object_split"));
2265 }
2266
2267 m_contextMenu->addSeparator();
2268
2269 m_contextMenu->addAction(action("edit_cut"));
2270 m_contextMenu->addAction(action("edit_copy"));
2271 m_contextMenu->addAction(action("edit_paste"));
2272 m_contextMenu->addAction(action("paste_at"));
2273
2274 m_contextMenu->addSeparator();
2275
2276 m_contextMenu->addAction(action("object_order_front"));
2277 m_contextMenu->addAction(action("object_order_raise"));
2278 m_contextMenu->addAction(action("object_order_lower"));
2279 m_contextMenu->addAction(action("object_order_back"));
2280
2281 if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) {
2282 m_contextMenu->addSeparator();
2283 m_contextMenu->addAction(action("object_group"));
2284 m_contextMenu->addAction(action("object_ungroup"));
2285 }
2286 m_contextMenu->addSeparator();
2287 m_contextMenu->addAction(action("convert_shapes_to_vector_selection"));
2288
2289 m_contextMenu->addSeparator();
2290 QMenu *text = m_contextMenu->addMenu(i18n("Text"));
2291 text->addAction(action("add_shape_to_flow_area"));
2292 text->addAction(action("subtract_shape_from_flow_area"));
2293 text->addAction(action("put_text_on_path"));
2294 text->addSeparator();
2295 text->addAction(action("text_type_preformatted"));
2296 text->addAction(action("text_type_inline_wrap"));
2297 text->addAction(action("text_type_pre_positioned"));
2298 text->addSeparator();
2299 text->addAction(action("remove_shapes_from_text_flow"));
2300 text->addAction(action("flow_shape_type_toggle"));
2301 text->addSeparator();
2302 text->addAction(action("flow_shape_order_back"));
2303 text->addAction(action("flow_shape_order_earlier"));
2304 text->addAction(action("flow_shape_order_later"));
2305 text->addAction(action("flow_shape_order_front"));
2306 }
2307
2308 return m_contextMenu.data();
2309}
2310
2311void DefaultTool::addTransformActions(QMenu *menu) const {
2312 menu->addAction(action("object_transform_rotate_90_cw"));
2313 menu->addAction(action("object_transform_rotate_90_ccw"));
2314 menu->addAction(action("object_transform_rotate_180"));
2315 menu->addSeparator();
2316 menu->addAction(action("object_transform_mirror_horizontally"));
2317 menu->addAction(action("object_transform_mirror_vertically"));
2318 menu->addSeparator();
2319 menu->addAction(action("object_transform_reset"));
2320}
2321
2323{
2325 QString tool = KoToolManager::instance()->preferredToolForSelection(shapes);
2326 QTimer::singleShot(0, [tool = std::move(tool)]() {
2328 });
2329}
2330
2332
2334 : parent(parent)
2335 , compressor(10, KisSignalCompressor::POSTPONE){}
2336
2340};
2341
2344 , d(new Private(parent))
2345{
2346 connect(&d->compressor, SIGNAL(timeout()), this, SIGNAL(textSelectionChanged()));
2347}
2348
2353
2355{
2357 if (!d->parent->selection()->hasSelection()) return props;
2358
2359 QList<KoShape*> shapes = d->shapes;
2360 for (auto it = shapes.begin(); it != shapes.end(); it++) {
2361 KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(*it);
2362 if (!textShape) continue;
2363 KoSvgTextProperties p = textShape->textProperties();
2364 props.append(p);
2365 }
2366
2367 return props;
2368}
2369
2374
2379
2380void DefaultToolTextPropertiesInterface::setPropertiesOnSelected(KoSvgTextProperties properties, QSet<KoSvgTextProperties::PropertyId> removeProperties)
2381{
2382 if (d->shapes.isEmpty()) return;
2383 KUndo2Command *cmd = new KoShapeMergeTextPropertiesCommand(d->shapes, properties, removeProperties);
2384 if (cmd) {
2385 d->parent->canvas()->addCommand(cmd);
2386 }
2387}
2388
2389void DefaultToolTextPropertiesInterface::setCharacterPropertiesOnSelected(KoSvgTextProperties properties, QSet<KoSvgTextProperties::PropertyId> removeProperties)
2390{
2391 Q_UNUSED(properties)
2392 Q_UNUSED(removeProperties)
2393 return;
2394}
2395
2397{
2398 return false;
2399}
2400
2405
2407{
2408 Q_UNUSED(pos)
2409 Q_UNUSED(anchor)
2410 d->compressor.start();
2411}
2412
2414{
2415 d->compressor.start();
2416}
2417
2419{
2420 Q_UNUSED(type)
2421 Q_UNUSED(shape)
2422 d->compressor.start();
2423}
2424
2426{
2427 Q_FOREACH(KoShape *shape, d->shapes) {
2428 shape->removeShapeChangeListener(this);
2429 }
2430 d->shapes.clear();
2431}
2432
2434{
2435 if (d->parent->updateTextContourMode()) return;
2436 Q_FOREACH(KoShape *shape, d->shapes) {
2437 if (!shape) continue;
2438 shape->removeShapeChangeListener(this);
2439 }
2440
2441 auto *textShapeGroup = d->parent->tryFetchCurrentShapeManagerOwnerTextShape();
2442
2443 if (textShapeGroup) {
2444 d->shapes = {textShapeGroup};
2445 } else {
2446 d->shapes = d->parent->canvas()->selectedShapesProxy()->selection()->selectedEditableShapes();
2447 }
2448
2449 Q_FOREACH(KoShape *shape, d->shapes) {
2450 if (!shape) continue;
2451 shape->addShapeChangeListener(this);
2452 }
2453 d->compressor.start();
2454}
#define HANDLE_DISTANCE
#define INNER_HANDLE_DISTANCE_SQ
#define HANDLE_DISTANCE_SQ
float value(const T *src, size_t ch)
const Params2D p
KoShapeGradientHandles::Handle m_currentHandle
bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override
MoveGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant, int priority, const QString &id, DefaultTool *_q)
KoInteractionStrategy * createStrategy(KoPointerEvent *ev) override
KoShapeGradientHandles::Handle handleAt(const QPointF &pos)
bool hoverEvent(KoPointerEvent *ev) override
KoInteractionStrategy * createStrategy(KoPointerEvent *ev) override
KoShapeMeshGradientHandles::Handle handleAt(const QPointF &pos) const
MoveMeshGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant, int priority, const QString &id, DefaultTool *_q)
bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override
KoShapeMeshGradientHandles::Handle m_currentHandle
virtual void updateDistinctiveActions(const QList< KoShape * > &editableShapes)
void addMappedAction(KisSignalMapper *mapper, const QString &actionId, int type)
KoFlake::SelectionHandle m_lastHandle
virtual bool isValidForCurrentLayer() const
void selectionSendToBack()
void explicitUserStrokeEndRequest() override
explicitUserStrokeEndRequest is called by the input manager when the user presses Enter key or any eq...
void slotActivateEditStrokeGradient(bool value)
void canvasResourceChanged(int key, const QVariant &res) override
bool m_mouseWasInsideHandles
QScopedPointer< QMenu > m_contextMenu
KoFlake::AnchorPosition m_hotPosition
KisSignalMapper * m_textTypeSignalsMapper
void deleteSelection() override
reimplemented
void selectionDistribute(int _distribute)
bool paste() override
reimplemented
void deselect() override
reimplemented
KisSignalMapper * m_booleanSignalsMapper
void updateActions()
Update actions on selection change.
KisSignalMapper * m_distributeSignalsMapper
QCursor m_shearCursors[8]
KoFlake::SelectionHandle handleAt(const QPointF &point, bool *innerHandleMeaning=0)
void keyPressEvent(QKeyEvent *event) override
~DefaultTool() override
QCursor m_sizeCursors[8]
bool moveSelection(int direction, Qt::KeyboardModifiers modifiers)
KoShapeMeshGradientHandles::Handle m_selectedMeshHandle
void mousePressEvent(KoPointerEvent *event) override
bool wantsAutoScroll() const override
DefaultToolTabbedWidget * m_tabbedOptionWidget
void slotRemoveShapesFromFlow()
virtual KoSelection * koSelection() const
DefaultTool(KoCanvasBase *canvas, bool connectToSelectedShapesProxy=false)
void slotAddShapesToFlow()
qreal rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation)
Returns rotation angle of given handle of the current selection.
void slotReorderFlowShapes(int type)
QList< QPointer< QWidget > > createOptionWidgets() override
QRectF decorationsRect() const override
void mouseMoveEvent(KoPointerEvent *event) override
void mouseDoubleClickEvent(KoPointerEvent *event) override
friend class SelectionInteractionStrategy
void selectionTransform(int transformAction)
void addTransformActions(QMenu *menu) const
QPointF m_selectionBox[8]
KoToolSelection * selection() override
reimplemented
void slotActivateEditFillMeshGradient(bool value)
void slotSubtractShapesFromFlow()
void slotPutTextOnPath()
void selectionGroup()
QMenu * popupActionsMenu() override
QRectF handlesSize()
Returns selection rectangle adjusted by handle proximity threshold.
void paint(QPainter &painter, const KoViewConverter &converter) override
void deactivate() override
DefaultToolTextPropertiesInterface * m_textPropertyInterface
bool selectAll() override
reimplemented
void selectionAlign(int _align)
void selectionMoveDown()
void slotActivateEditFillGradient(bool value)
void slotToggleFlowShapeType()
bool updateTextContourMode()
void recalcSelectionBox(KoSelection *selection)
void selectionBooleanOp(int booleanOp)
QScopedPointer< KoSvgTextShapeOutlineHelper > m_textOutlineHelper
KisSignalMapper * m_alignSignalsMapper
KisSignalMapper * m_textFlowSignalsMapper
QCursor m_rotateCursors[8]
void meshgradientHandleSelected(KoShapeMeshGradientHandles::Handle)
QScopedPointer< SelectionDecorator > m_decorator
void slotResetMeshGradientState()
void updateCursor()
void setupActions()
void activate(const QSet< KoShape * > &shapes) override
KisSignalMapper * m_transformSignalsMapper
void selectionReorder(KoShapeReorderCommand::MoveShapeType order)
void mouseReleaseEvent(KoPointerEvent *event) override
void copy() const override
reimplemented
KoToolSelection * m_selectionHandler
void selectionBringToFront()
QPolygonF m_selectionOutline
virtual KoShapeManager * shapeManager() const
void selectionUngroup()
KoShapeMeshGradientHandles::Handle m_hoveredMeshHandle
KoSvgTextShape * tryFetchCurrentShapeManagerOwnerTextShape() const
void slotChangeTextType(int index)
void selectionSplitShapes()
void selectionMoveUp()
KoInteractionStrategy * createStrategy(KoPointerEvent *event) override
void setText(const KUndo2MagicString &text)
KUndo2MagicString text() const
KisImageWSP image() const
KoShapeManager * localShapeManager() const
KisViewManager * viewManager() const
KisNodeSP activeNode()
Convenience function to get the active layer or mask.
The KisSignalMapper class bundles signals from identifiable senders.
void setMapping(QObject *sender, int id)
void setTextPropertiesInterface(KoSvgTextPropertiesInterface *interface)
setTextPropertiesInterface set the text properties interface. This should be done on tool activation....
KisNodeManager * nodeManager() const
The node manager handles everything about nodes.
KisTextPropertiesManager * textPropertyManager() const
void showFloatingMessage(const QString &message, const QIcon &icon, int timeout=4500, KisFloatingMessage::Priority priority=KisFloatingMessage::Medium, int alignment=Qt::AlignCenter|Qt::TextWordWrap)
shows a floating message in the top right corner of the canvas
virtual KoShape * currentShapeManagerOwnerShape() const
the shape that owns the currently active shape manager
KoSnapGuide * snapGuide
QPointer< KoShapeController > shapeController
virtual KoShapeManager * shapeManager() const =0
virtual const KoViewConverter * viewConverter() const =0
virtual void updateCanvas(const QRectF &rc)=0
virtual void addCommand(KUndo2Command *command)=0
QPointer< KoCanvasResourceProvider > resourceManager
virtual KoSelectedShapesProxy * selectedShapesProxy() const =0
selectedShapesProxy() is a special interface for keeping a persistent connections to selectionChanged...
bool setSvg(const QList< KoShape * > shapes)
Definition KoDrag.cpp:47
void addToClipboard()
Definition KoDrag.cpp:86
void mouseReleaseEvent(KoPointerEvent *event) override
void mouseMoveEvent(KoPointerEvent *event) override
bool hasInteractionFactory(const QString &id)
void removeInteractionFactory(const QString &id)
void paint(QPainter &painter, const KoViewConverter &converter) override
void mousePressEvent(KoPointerEvent *event) override
void addInteractionFactory(KoInteractionStrategyFactory *factory)
void keyPressEvent(QKeyEvent *event) override
KoInteractionStrategy * currentStrategy()
The position of a path point within a path shape.
Definition KoPathShape.h:63
bool separate(QList< KoPathShape * > &separatedPaths)
Creates separate path shapes, one for each existing subpath.
int subpathCount() const
Returns the number of subpaths in the path.
static KoPathShape * createShapeFromPainterPath(const QPainterPath &path)
Creates path shape from given QPainterPath.
Qt::MouseButtons buttons() const
return buttons pressed (see QMouseEvent::buttons());
bool isTabletEvent() const
Qt::KeyboardModifiers modifiers() const
QPointF point
The point in document coordinates.
virtual KoSelection * selection()=0
void deselectAll()
clear the selections list
const QList< KoShape * > selectedEditableShapesAndDelegates() const
void select(KoShape *shape)
const QList< KoShape * > selectedEditableShapes() const
KoShape * firstSelectedShape() const
const QList< KoShape * > selectedShapes() const
The undo / redo command for aligning shapes.
Align
The different alignment options for this command.
@ VerticalCenterAlignment
Align centered vertically.
@ HorizontalLeftAlignment
Align left.
@ HorizontalCenterAlignment
Align Centered horizontally.
@ VerticalBottomAlignment
Align bottom.
@ HorizontalRightAlignment
Align Right.
QList< KoShape * > shapes() const
The undo / redo command for distributing shapes.
Distribute
The different options to distribute with this command.
@ HorizontalRightDistribution
Horizontal Right.
@ VerticalCenterDistribution
Vertical centered.
@ HorizontalGapsDistribution
Horizontal Gaps.
@ VerticalBottomDistribution
Vertical bottom.
@ HorizontalCenterDistribution
Horizontal centered.
@ HorizontalLeftDistribution
Horizontal Left.
QVector< Handle > handles() const
The undo / redo command for grouping shapes.
QList< KoShape * > topLevelShapes() const
KoShape * shapeAt(const QPointF &position, KoFlake::ShapeSelection selection=KoFlake::ShapeOnTop, bool omitHiddenShapes=true)
The KoShapeMergeTextPropertiesCommand class This sets text properties on given text shapes....
QVector< Handle > handles() const
get all nodes in the mesh, don't use this for drawing the path but use path()
The undo / redo command for shape moving.
MoveShapeType
An enum for defining what kind of reordering to use.
@ RaiseShape
raise the selected shape to the level that it is above the shape that is on top of it.
@ SendToBack
Lower the selected shape to be below all other shapes.
@ LowerShape
Lower the selected shape to the level that it is below the shape that is below it.
@ BringToFront
Raise the selected shape to be on top of all shapes.
static KoShapeReorderCommand * createCommand(const QList< KoShape * > &shapes, KoShapeManager *manager, MoveShapeType move, KUndo2Command *parent=0)
virtual SelectionMode currentMode() const
void paint(QPainter &painter, const KoViewConverter &converter) override
The undo / redo command for ungrouping shapes.
virtual QRectF outlineRect() const
Definition KoShape.cpp:566
void addShapeChangeListener(ShapeChangeListener *listener)
Definition KoShape.cpp:1157
void setZIndex(qint16 zIndex)
Definition KoShape.cpp:787
virtual QPainterPath outline() const
Definition KoShape.cpp:559
bool isSelectable() const
Definition KoShape.cpp:837
QPointF absolutePosition(KoFlake::AnchorPosition anchor=KoFlake::Center) const
Definition KoShape.cpp:573
void removeShapeChangeListener(ShapeChangeListener *listener)
Definition KoShape.cpp:1165
virtual KoShapeStrokeModelSP stroke() const
Definition KoShape.cpp:890
static bool compareShapeZIndex(KoShape *s1, KoShape *s2)
Definition KoShape.cpp:393
bool isGeometryProtected() const
Definition KoShape.cpp:847
QRectF absoluteOutlineRect() const
Definition KoShape.cpp:321
KoShapeContainer * parent() const
Definition KoShape.cpp:862
virtual void setStroke(KoShapeStrokeModelSP stroke)
Definition KoShape.cpp:904
QTransform absoluteTransformation() const
Definition KoShape.cpp:335
virtual void setBackground(QSharedPointer< KoShapeBackground > background)
Definition KoShape.cpp:751
ChangeType
Used by shapeChanged() to select which change was made.
Definition KoShape.h:92
virtual QSharedPointer< KoShapeBackground > background() const
Definition KoShape.cpp:759
QTransform transformation() const
Returns the shapes local transformation matrix.
Definition KoShape.cpp:383
qint16 zIndex() const
Definition KoShape.cpp:529
void paint(QPainter &painter, const KoViewConverter &converter)
paints the guide
QRectF boundingRect()
returns the bounding rect of the guide
bool isSnapping() const
returns if snapping is enabled
The SvgConvertTextTypeCommand class This command allows textshapes to be converted between preformatt...
The KoSvgTextPropertiesInterface class.
void textSelectionChanged()
Emit to signal to KisTextPropertiesManager to call getSelectedProperties.
static void removeContourShapesFromFlow(KoSvgTextShape *textShape, KUndo2Command *parent, bool textInShape, bool textPaths)
removeContourShapesFromFlow Create a command to remove all contour shapes of a certain type from the ...
The KoSvgTextReorderShapeInsideCommand class Within a text shape, the order of the shapes inside dete...
The KoSvgTextShapeOutlineHelper class helper class that draws the text outlines and contour mode butt...
@ PreformattedText
Text-on-Path falls under this or PrePositionedText depending on collapse of lines.
@ TextInShape
Uses shape-inside to wrap and preserves spaces.
@ InlineWrap
Uses inline size to wrap and preserves spaces.
QList< KoShape * > shapesInside
bool shapeInContours(KoShape *shape)
shapeInContours
TextType textType() const
textType This enum gives an indication of what kind of text this shape is. The different text types a...
int posForIndex(int index, bool firstIndex=false, bool skipSynthetic=false) const
posForIndex Get the cursor position for a given index in a string.
QList< KoShape * > shapesSubtract
KoSvgTextProperties textProperties() const
KoCanvasBase * canvas() const
Returns the canvas the tool is working on.
void selectionChanged(bool hasSelection)
QRectF handlePaintRect(const QPointF &position) const
QCursor cursor() const
return the last emitted cursor
void statusTextChanged(const QString &statusText)
virtual void repaintDecorations()
int handleRadius() const
Convenience function to get the current handle radius.
void useCursor(const QCursor &cursor)
virtual void activate(const QSet< KoShape * > &shapes)
virtual void deactivate()
QAction * action(const QString &name) const
bool isActivated() const
int decorationThickness() const
decorationThickness The minimum thickness for tool decoration lines, this is derived from the screen ...
void switchToolRequested(const QString &id)
QString preferredToolForSelection(const QList< KoShape * > &shapes)
static KoToolManager * instance()
Return the toolmanager singleton.
virtual QPointF viewToDocument(const QPointF &viewPoint) const
virtual QPointF documentToView(const QPointF &documentPoint) const
KUndo2Command * createCommand() override
void paint(QPainter &painter, const KoViewConverter &converter) override
void finishInteraction(Qt::KeyboardModifiers) override
NopInteractionStrategy(KoToolBase *parent)
void handleMouseMove(const QPointF &, Qt::KeyboardModifiers) override
QPointer< KoSelection > m_selection
SelectionHandler(DefaultTool *parent)
bool hasSelection() override
return true if the tool currently has something selected that can be copied or deleted.
void cancelInteraction() override
void finishInteraction(Qt::KeyboardModifiers modifiers=QFlags< Qt::KeyboardModifier >()) override
void paint(QPainter &painter, const KoViewConverter &converter) override
SelectionInteractionStrategy(KoToolBase *parent, const QPointF &clicked, bool useSnapToGrid)
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
qreal kisSquareDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:194
#define M_PI
Definition kis_global.h:111
bool isSelectionMask(KisNodeSP node)
KUndo2MagicString kundo2_i18n(const char *text)
KUndo2MagicString kundo2_noi18n(const QString &text)
@ PageSize
The size of the (current) page in postscript points.
@ ShapeOnTop
return the shape highest z-ordering, regardless of selection.
Definition KoFlake.h:74
@ NextUnselected
return the first unselected directly under a selected shape, or the top most one if nothing is select...
Definition KoFlake.h:73
AnchorPosition
Definition KoFlake.h:85
@ Left
Definition KoFlake.h:89
@ Right
Definition KoFlake.h:91
@ Bottom
Definition KoFlake.h:93
@ BottomRight
Definition KoFlake.h:94
@ Top
Definition KoFlake.h:87
@ TopRight
Definition KoFlake.h:88
@ TopLeft
Definition KoFlake.h:86
@ BottomLeft
Definition KoFlake.h:92
@ Center
Definition KoFlake.h:90
FillVariant
Definition KoFlake.h:28
@ StrokeFill
Definition KoFlake.h:30
@ Fill
Definition KoFlake.h:29
SelectionHandle
Enum determining which handle is meant, used in KoInteractionTool.
Definition KoFlake.h:55
@ BottomRightHandle
The handle that is at the bottom right of a selection.
Definition KoFlake.h:59
@ BottomLeftHandle
The handle that is at the bottom left of a selection.
Definition KoFlake.h:61
@ RightMiddleHandle
The handle that is at the right - center of a selection.
Definition KoFlake.h:58
@ TopRightHandle
The handle that is at the top - right of a selection.
Definition KoFlake.h:57
@ TopLeftHandle
The handle that is at the top left of a selection.
Definition KoFlake.h:63
@ LeftMiddleHandle
The handle that is at the left center of a selection.
Definition KoFlake.h:62
@ NoHandle
Value to indicate no handle.
Definition KoFlake.h:64
@ TopMiddleHandle
The handle that is at the top - center of a selection.
Definition KoFlake.h:56
@ BottomMiddleHandle
The handle that is at the bottom center of a selection.
Definition KoFlake.h:60
QTransform pathShapeBooleanSpaceWorkaround(KisImageSP image)
Interface to interact with the text property manager.
virtual bool spanSelection() override
Whether the tool is currently selecting a set of characters instead of whole paragraphs.
virtual bool characterPropertiesEnabled() override
Whether character selections are possible at all.
virtual QList< KoSvgTextProperties > getSelectedProperties() override
getSelectedProperties
const QScopedPointer< Private > d
virtual void notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) override
DefaultToolTextPropertiesInterface(DefaultTool *parent)
virtual KoSvgTextProperties getInheritedProperties() override
getInheritedProperties The properties that should be visible when a given property isn't available in...
virtual void notifyCursorPosChanged(int pos, int anchor) override
virtual void notifyMarkupChanged() override
virtual void setCharacterPropertiesOnSelected(KoSvgTextProperties properties, QSet< KoSvgTextProperties::PropertyId > removeProperties=QSet< KoSvgTextProperties::PropertyId >()) override
setCharacterPropertiesOnSelected This sets the properties for a character selection instead of the fu...
virtual QList< KoSvgTextProperties > getCharacterProperties() override
getSelectedProperties
virtual void setPropertiesOnSelected(KoSvgTextProperties properties, QSet< KoSvgTextProperties::PropertyId > removeProperties=QSet< KoSvgTextProperties::PropertyId >()) override
setPropertiesOnSelected This sets the properties on the selection. The implementation is responsible ...