File indexing completed on 2024-12-22 04:09:14

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2006-2012 Jan Hambrecht <jaham@gmx.net>
0003  * SPDX-FileCopyrightText: 2006, 2007 Thorsten Zachmann <zachmann@kde.org>
0004  * SPDX-FileCopyrightText: 2007, 2010 Thomas Zander <zander@kde.org>
0005  * SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
0006  *
0007  * SPDX-License-Identifier: LGPL-2.0-or-later
0008  */
0009 
0010 #include "KoPathTool.h"
0011 #include "KoCanvasBase.h"
0012 #include "KoDocumentResourceManager.h"
0013 #include "KoParameterChangeStrategy.h"
0014 #include "KoParameterShape.h"
0015 #include "KoPathPoint.h"
0016 #include "KoPathPointRubberSelectStrategy.h"
0017 #include "KoPathSegmentChangeStrategy.h"
0018 #include "KoPathShape_p.h"
0019 #include "KoPathToolHandle.h"
0020 #include "KoPointerEvent.h"
0021 #include "KoSelectedShapesProxy.h"
0022 #include "KoSelection.h"
0023 #include "KoShapeController.h"
0024 #include "KoShapeGroup.h"
0025 #include "KoShapeManager.h"
0026 #include "KoSnapGuide.h"
0027 #include "KoToolBase_p.h"
0028 #include "KoToolManager.h"
0029 #include "KoViewConverter.h"
0030 #include "PathToolOptionWidget.h"
0031 #include "commands/KoParameterToPathCommand.h"
0032 #include "commands/KoPathBreakAtPointCommand.h"
0033 #include "commands/KoPathPointInsertCommand.h"
0034 #include "commands/KoPathPointRemoveCommand.h"
0035 #include "commands/KoPathPointTypeCommand.h"
0036 #include "commands/KoPathSegmentBreakCommand.h"
0037 #include "commands/KoPathSegmentTypeCommand.h"
0038 #include "commands/KoSubpathJoinCommand.h"
0039 #include "kis_action_registry.h"
0040 #include "kis_command_utils.h"
0041 #include "kis_pointer_utils.h"
0042 #include <KisHandlePainterHelper.h>
0043 #include <KoShapeStrokeModel.h>
0044 #include <commands/KoKeepShapesSelectedCommand.h>
0045 #include <commands/KoMultiPathPointJoinCommand.h>
0046 #include <commands/KoMultiPathPointMergeCommand.h>
0047 #include <commands/KoShapeGroupCommand.h>
0048 #include <text/KoSvgTextShape.h>
0049 
0050 #include <KoIcon.h>
0051 
0052 #include <QMenu>
0053 #include <QAction>
0054 #include <FlakeDebug.h>
0055 #include <klocalizedstring.h>
0056 #include <QPainter>
0057 #include <QPainterPath>
0058 #include <QBitmap>
0059 #include <QTabWidget>
0060 
0061 #include <math.h>
0062 
0063 // helper function to calculate the squared distance between two points
0064 qreal squaredDistance(const QPointF& p1, const QPointF &p2)
0065 {
0066     qreal dx = p1.x()-p2.x();
0067     qreal dy = p1.y()-p2.y();
0068     return dx*dx + dy*dy;
0069 }
0070 
0071 struct KoPathTool::PathSegment {
0072     PathSegment()
0073         : path(0), segmentStart(0), positionOnSegment(0)
0074     {
0075     }
0076 
0077     bool isValid() {
0078         return  path && segmentStart;
0079     }
0080 
0081     KoPathShape *path;
0082     KoPathPoint *segmentStart;
0083     qreal positionOnSegment;
0084 };
0085 
0086 KoPathTool::KoPathTool(KoCanvasBase *canvas)
0087     : KoToolBase(canvas)
0088     , m_pointSelection(this)
0089 {
0090     m_actionPathPointCorner = action("pathpoint-corner");
0091     m_actionPathPointSmooth = action("pathpoint-smooth");
0092     m_actionPathPointSymmetric = action("pathpoint-symmetric");
0093     m_actionCurvePoint = action("pathpoint-curve");
0094     m_actionLinePoint = action("pathpoint-line");
0095     m_actionLineSegment = action("pathsegment-line");
0096     m_actionCurveSegment = action("pathsegment-curve");
0097     m_actionAddPoint = action("pathpoint-insert");
0098     m_actionRemovePoint = action("pathpoint-remove");
0099     m_actionBreakPoint = action("path-break-point");
0100     m_actionBreakSegment = action("path-break-segment");
0101     m_actionJoinSegment = action("pathpoint-join");
0102     m_actionMergePoints = action("pathpoint-merge");
0103     m_actionConvertToPath = action("convert-to-path");
0104 
0105     m_contextMenu.reset(new QMenu());
0106 
0107     m_selectCursor = QCursor(QIcon(":/cursor-needle.svg").pixmap(32), 0, 0);
0108     m_moveCursor = QCursor(QIcon(":/cursor-needle-move.svg").pixmap(32), 0, 0);
0109 
0110     connect(&m_pointSelection, SIGNAL(selectionChanged()), SLOT(repaintDecorations()));
0111 }
0112 
0113 KoPathTool::~KoPathTool()
0114 {
0115 }
0116 
0117 QList<QPointer<QWidget> >  KoPathTool::createOptionWidgets()
0118 {
0119     QList<QPointer<QWidget> > list;
0120 
0121     PathToolOptionWidget * toolOptions = new PathToolOptionWidget(this);
0122     connect(this, SIGNAL(typeChanged(int)), toolOptions, SLOT(setSelectionType(int)));
0123     connect(this, SIGNAL(singleShapeChanged(KoPathShape*)), toolOptions, SLOT(setCurrentShape(KoPathShape*)));
0124     connect(toolOptions, SIGNAL(sigRequestUpdateActions()), this, SLOT(updateActions()));
0125     updateOptionsWidget();
0126     toolOptions->setWindowTitle(i18n("Edit Shape"));
0127     list.append(toolOptions);
0128 
0129     return list;
0130 }
0131 
0132 void KoPathTool::pointTypeChangedCorner() {
0133     pointTypeChanged(KoPathPointTypeCommand::Corner);
0134 }
0135 void KoPathTool::pointTypeChangedSmooth() {
0136     pointTypeChanged(KoPathPointTypeCommand::Smooth);
0137 }
0138 void KoPathTool::pointTypeChangedSymmetric() {
0139     pointTypeChanged(KoPathPointTypeCommand::Symmetric);
0140 }
0141 
0142 void KoPathTool::pointTypeChanged(KoPathPointTypeCommand::PointType type)
0143 {
0144     Q_D(KoToolBase);
0145     if (m_pointSelection.hasSelection()) {
0146         QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
0147 
0148         KUndo2Command *initialConversionCommand = createPointToCurveCommand(selectedPoints);
0149 
0150         // conversion should happen before the c-tor
0151         // of KoPathPointTypeCommand is executed!
0152         if (initialConversionCommand) {
0153             initialConversionCommand->redo();
0154         }
0155 
0156         KUndo2Command *command =
0157                 new KoPathPointTypeCommand(selectedPoints, type);
0158 
0159         if (initialConversionCommand) {
0160             using namespace KisCommandUtils;
0161             CompositeCommand *parent = new CompositeCommand();
0162             parent->setText(command->text());
0163             parent->addCommand(new SkipFirstRedoWrapper(initialConversionCommand));
0164             parent->addCommand(command);
0165             command = parent;
0166         }
0167 
0168         d->canvas->addCommand(command);
0169     }
0170 }
0171 
0172 void KoPathTool::insertPoints()
0173 {
0174     Q_D(KoToolBase);
0175     QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
0176     if (segments.size() == 1) {
0177         qreal positionInSegment = 0.5;
0178         if (m_activeSegment && m_activeSegment->isValid()) {
0179             positionInSegment = m_activeSegment->positionOnSegment;
0180         }
0181 
0182         KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, positionInSegment);
0183         d->canvas->addCommand(cmd);
0184 
0185         // TODO: this construction is dangerous. The canvas can remove the command right after
0186         //       it has been added to it!
0187         m_pointSelection.clear();
0188         foreach (KoPathPoint * p, cmd->insertedPoints()) {
0189             m_pointSelection.add(p, false);
0190         }
0191     }
0192 }
0193 
0194 void KoPathTool::removePoints()
0195 {
0196     Q_D(KoToolBase);
0197     if (m_pointSelection.size() > 0) {
0198         KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(m_pointSelection.selectedPointsData(), d->canvas->shapeController());
0199         PointHandle *pointHandle = dynamic_cast<PointHandle*>(m_activeHandle.data());
0200         if (pointHandle && m_pointSelection.contains(pointHandle->activePoint())) {
0201             m_activeHandle.reset();
0202         }
0203         clearActivePointSelectionReferences();
0204         d->canvas->addCommand(cmd);
0205     }
0206 }
0207 
0208 void KoPathTool::pointToLine()
0209 {
0210     Q_D(KoToolBase);
0211     if (m_pointSelection.hasSelection()) {
0212         QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
0213         QList<KoPathPointData> pointToChange;
0214 
0215         QList<KoPathPointData>::const_iterator it(selectedPoints.constBegin());
0216         for (; it != selectedPoints.constEnd(); ++it) {
0217             KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex);
0218             if (point && (point->activeControlPoint1() || point->activeControlPoint2()))
0219                 pointToChange.append(*it);
0220         }
0221 
0222         if (! pointToChange.isEmpty()) {
0223             d->canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Line));
0224         }
0225     }
0226 }
0227 
0228 void KoPathTool::pointToCurve()
0229 {
0230     Q_D(KoToolBase);
0231     if (m_pointSelection.hasSelection()) {
0232         QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
0233 
0234         KUndo2Command *command = createPointToCurveCommand(selectedPoints);
0235 
0236         if (command) {
0237             d->canvas->addCommand(command);
0238         }
0239     }
0240 }
0241 
0242 KUndo2Command* KoPathTool::createPointToCurveCommand(const QList<KoPathPointData> &points)
0243 {
0244     KUndo2Command *command = 0;
0245     QList<KoPathPointData> pointToChange;
0246 
0247     QList<KoPathPointData>::const_iterator it(points.constBegin());
0248     for (; it != points.constEnd(); ++it) {
0249         KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex);
0250         if (point && (! point->activeControlPoint1() || ! point->activeControlPoint2()))
0251             pointToChange.append(*it);
0252     }
0253 
0254     if (!pointToChange.isEmpty()) {
0255         command = new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Curve);
0256     }
0257 
0258     return command;
0259 }
0260 
0261 void KoPathTool::segmentToLine()
0262 {
0263     Q_D(KoToolBase);
0264     if (m_pointSelection.size() > 1) {
0265         QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
0266         if (segments.size() > 0) {
0267             d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Line));
0268         }
0269     }
0270 }
0271 
0272 void KoPathTool::segmentToCurve()
0273 {
0274     Q_D(KoToolBase);
0275     if (m_pointSelection.size() > 1) {
0276         QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
0277         if (segments.size() > 0) {
0278             d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Curve));
0279         }
0280     }
0281 }
0282 
0283 void KoPathTool::convertToPath()
0284 {
0285     Q_D(KoToolBase);
0286 
0287     KoSelection *selection = canvas()->selectedShapesProxy()->selection();
0288 
0289     QList<KoParameterShape*> parameterShapes;
0290 
0291     Q_FOREACH (KoShape *shape, m_pointSelection.selectedShapes()) {
0292         KoParameterShape * parametric = dynamic_cast<KoParameterShape*>(shape);
0293         if (parametric && parametric->isParametricShape()) {
0294             parameterShapes.append(parametric);
0295         }
0296     }
0297 
0298     if (!parameterShapes.isEmpty()) {
0299         d->canvas->addCommand(new KoParameterToPathCommand(parameterShapes));
0300     }
0301 
0302     QList<KoSvgTextShape*> textShapes;
0303     Q_FOREACH (KoShape *shape, selection->selectedEditableShapes()) {
0304         if (KoSvgTextShape *text = dynamic_cast<KoSvgTextShape*>(shape)) {
0305             textShapes.append(text);
0306         }
0307     }
0308 
0309     if (!textShapes.isEmpty()) {
0310         KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Convert to Path")); // TODO: reuse the text from KoParameterToPathCommand
0311         const QList<KoShape*> oldSelectedShapes = implicitCastList<KoShape*>(textShapes);
0312 
0313 
0314         new KoKeepShapesSelectedCommand(oldSelectedShapes, {}, canvas()->selectedShapesProxy(),
0315                                         KisCommandUtils::FlipFlopCommand::State::INITIALIZING, cmd);
0316 
0317         QList<KoShape*> newSelectedShapes;
0318         Q_FOREACH (KoSvgTextShape *shape, textShapes) {
0319             KoShapeGroup *groupShape = new KoShapeGroup();
0320             KoShapeGroupCommand groupCmd(groupShape, shape->textOutline(), false);
0321             groupCmd.redo();
0322 
0323             groupShape->setZIndex(shape->zIndex());
0324             groupShape->setTransformation(shape->absoluteTransformation());
0325 
0326             KoShapeContainer *parent = shape->parent();
0327             canvas()->shapeController()->addShapeDirect(groupShape, parent, cmd);
0328 
0329             newSelectedShapes << groupShape;
0330         }
0331 
0332         canvas()->shapeController()->removeShapes(oldSelectedShapes, cmd);
0333 
0334         new KoKeepShapesSelectedCommand({}, newSelectedShapes, canvas()->selectedShapesProxy(),
0335                                         KisCommandUtils::FlipFlopCommand::State::FINALIZING, cmd);
0336 
0337         canvas()->addCommand(cmd);
0338     }
0339 
0340     updateOptionsWidget();
0341 }
0342 
0343 namespace {
0344 bool checkCanJoinToPoints(const KoPathPointData & pd1, const KoPathPointData & pd2)
0345 {
0346     const KoPathPointIndex & index1 = pd1.pointIndex;
0347     const KoPathPointIndex & index2 = pd2.pointIndex;
0348 
0349     KoPathShape *path1 = pd1.pathShape;
0350     KoPathShape *path2 = pd2.pathShape;
0351 
0352     // check if subpaths are already closed
0353     if (path1->isClosedSubpath(index1.first) || path2->isClosedSubpath(index2.first))
0354         return false;
0355 
0356     // check if first point is an endpoint
0357     if (index1.second != 0 && index1.second != path1->subpathPointCount(index1.first)-1)
0358         return false;
0359 
0360     // check if second point is an endpoint
0361     if (index2.second != 0 && index2.second != path2->subpathPointCount(index2.first)-1)
0362         return false;
0363 
0364     return true;
0365 }
0366 }
0367 
0368 void KoPathTool::mergePointsImpl(bool doJoin)
0369 {
0370     Q_D(KoToolBase);
0371 
0372     if (m_pointSelection.size() != 2)
0373         return;
0374 
0375     QList<KoPathPointData> pointData = m_pointSelection.selectedPointsData();
0376     if (pointData.size() != 2) return;
0377 
0378     const KoPathPointData & pd1 = pointData.at(0);
0379     const KoPathPointData & pd2 = pointData.at(1);
0380 
0381     if (!checkCanJoinToPoints(pd1, pd2)) {
0382         return;
0383     }
0384 
0385     clearActivePointSelectionReferences();
0386 
0387     KUndo2Command *cmd = 0;
0388 
0389     if (doJoin) {
0390         cmd = new KoMultiPathPointJoinCommand(pd1, pd2, d->canvas->shapeController()->documentBase(), d->canvas->shapeManager()->selection());
0391     } else {
0392         cmd = new KoMultiPathPointMergeCommand(pd1, pd2, d->canvas->shapeController()->documentBase(), d->canvas->shapeManager()->selection());
0393     }
0394     d->canvas->addCommand(cmd);
0395 }
0396 
0397 void KoPathTool::joinPoints()
0398 {
0399     mergePointsImpl(true);
0400 }
0401 
0402 void KoPathTool::mergePoints()
0403 {
0404     mergePointsImpl(false);
0405 }
0406 
0407 void KoPathTool::breakAtPoint()
0408 {
0409     Q_D(KoToolBase);
0410     if (m_pointSelection.hasSelection()) {
0411         d->canvas->addCommand(new KoPathBreakAtPointCommand(m_pointSelection.selectedPointsData()));
0412     }
0413 }
0414 
0415 void KoPathTool::breakAtSegment()
0416 {
0417     Q_D(KoToolBase);
0418     // only try to break a segment when 2 points of the same object are selected
0419     if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) {
0420         QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
0421         if (segments.size() == 1) {
0422             d->canvas->addCommand(new KoPathSegmentBreakCommand(segments.at(0)));
0423         }
0424     }
0425 }
0426 
0427 void KoPathTool::paint(QPainter &painter, const KoViewConverter &converter)
0428 {
0429     Q_D(KoToolBase);
0430 
0431     Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) {
0432         KisHandlePainterHelper helper =
0433                 KoShape::createHandlePainterHelperView(&painter, shape, converter, handleRadius(), decorationThickness());
0434         helper.setHandleStyle(KisHandleStyle::primarySelection());
0435 
0436         KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
0437         if (parameterShape && parameterShape->isParametricShape()) {
0438             parameterShape->paintHandles(helper);
0439         } else {
0440             shape->paintPoints(helper);
0441         }
0442 
0443         if (!shape->stroke() || !shape->stroke()->isVisible()) {
0444             helper.setHandleStyle(KisHandleStyle::secondarySelection());
0445             helper.drawPath(shape->outline());
0446         }
0447     }
0448 
0449     if (m_currentStrategy) {
0450         painter.save();
0451         m_currentStrategy->paint(painter, converter);
0452         painter.restore();
0453     }
0454 
0455     m_pointSelection.paint(painter, converter, handleRadius());
0456 
0457     if (m_activeHandle) {
0458         if (m_activeHandle->check(m_pointSelection.selectedShapes())) {
0459             m_activeHandle->paint(painter, converter, handleRadius(), decorationThickness());
0460         } else {
0461             m_activeHandle.reset();
0462         }
0463     } else if (m_activeSegment && m_activeSegment->isValid()) {
0464 
0465         KoPathShape *shape = m_activeSegment->path;
0466 
0467         // if the stroke is invisible, then we already painted the outline of the shape!
0468         if (shape->stroke() && shape->stroke()->isVisible()) {
0469             KoPathPointIndex index = shape->pathPointIndex(m_activeSegment->segmentStart);
0470             KoPathSegment segment = shape->segmentByIndex(index).toCubic();
0471 
0472             KIS_SAFE_ASSERT_RECOVER_RETURN(segment.isValid());
0473 
0474             KisHandlePainterHelper helper =
0475                     KoShape::createHandlePainterHelperView(&painter, shape, converter, handleRadius(), decorationThickness());
0476             helper.setHandleStyle(KisHandleStyle::secondarySelection());
0477 
0478             QPainterPath path;
0479             path.moveTo(segment.first()->point());
0480             path.cubicTo(segment.first()->controlPoint2(),
0481                          segment.second()->controlPoint1(),
0482                          segment.second()->point());
0483 
0484             helper.drawPath(path);
0485         }
0486     }
0487 
0488 
0489 
0490     if (m_currentStrategy) {
0491         painter.save();
0492         painter.setTransform(converter.documentToView(), true);
0493         d->canvas->snapGuide()->paint(painter, converter);
0494         painter.restore();
0495     }
0496 }
0497 
0498 QRectF KoPathTool::decorationsRect() const
0499 {
0500     const_cast<KoPathToolSelection&>(m_pointSelection).update();
0501 
0502     QRectF newDecorationsRect;
0503 
0504     Q_FOREACH (KoShape *shape, m_pointSelection.selectedShapes()) {
0505         newDecorationsRect |= kisGrowRect(shape->boundingRect(), handleDocRadius());
0506     }
0507 
0508     Q_FOREACH(const KoPathPoint *point, m_pointSelection.selectedPoints()) {
0509         newDecorationsRect |= kisGrowRect(point->boundingRect(false), handleDocRadius());
0510     }
0511 
0512     if (m_activeHandle) {
0513         newDecorationsRect |= kisGrowRect(m_activeHandle->boundingRect(), handleDocRadius());
0514     }
0515 
0516     if (m_activeSegment) {
0517         KoPathPointIndex index = m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart);
0518         KoPathSegment segment = m_activeSegment->path->segmentByIndex(index);
0519 
0520         QRectF rect = segment.boundingRect();
0521         rect = m_activeSegment->path->shapeToDocument(rect);
0522 
0523         newDecorationsRect |= kisGrowRect(rect, handleDocRadius());
0524     }
0525 
0526     return newDecorationsRect;
0527 }
0528 
0529 void KoPathTool::repaintDecorations()
0530 {
0531     KoToolBase::repaintDecorations();
0532     updateOptionsWidget();
0533 }
0534 
0535 void KoPathTool::mousePressEvent(KoPointerEvent *event)
0536 {
0537     // we are moving if we hit a point and use the left mouse button
0538     event->ignore();
0539     if (m_activeHandle) {
0540         m_currentStrategy.reset(m_activeHandle->handleMousePress(event));
0541         event->accept();
0542     } else {
0543         if (event->button() & Qt::LeftButton) {
0544 
0545             // check if we hit a path segment
0546             if (m_activeSegment && m_activeSegment->isValid()) {
0547 
0548                 KoPathShape *shape = m_activeSegment->path;
0549                 KoPathPointIndex index = shape->pathPointIndex(m_activeSegment->segmentStart);
0550                 KoPathSegment segment = shape->segmentByIndex(index);
0551 
0552                 m_pointSelection.add(segment.first(), !(event->modifiers() & Qt::ShiftModifier));
0553                 m_pointSelection.add(segment.second(), false);
0554 
0555                 KoPathPointData data(shape, index);
0556                 m_currentStrategy.reset(new KoPathSegmentChangeStrategy(this, event->point, data, m_activeSegment->positionOnSegment));
0557                 event->accept();
0558             } else {
0559 
0560                 KoShapeManager *shapeManager = canvas()->shapeManager();
0561                 KoSelection *selection = shapeManager->selection();
0562 
0563                 KoShape *shape = shapeManager->shapeAt(event->point, KoFlake::ShapeOnTop);
0564 
0565                 if (shape && !selection->isSelected(shape)) {
0566 
0567                     if (!(event->modifiers() & Qt::ShiftModifier)) {
0568                         selection->deselectAll();
0569                     }
0570 
0571                     selection->select(shape);
0572                 } else {
0573                     KIS_ASSERT_RECOVER_RETURN(m_currentStrategy == 0);
0574                     m_currentStrategy.reset(new KoPathPointRubberSelectStrategy(this, event->point));
0575                     event->accept();
0576                 }
0577             }
0578         }
0579     }
0580 }
0581 
0582 void KoPathTool::mouseMoveEvent(KoPointerEvent *event)
0583 {
0584     if (event->button() & Qt::RightButton)
0585         return;
0586 
0587     if (m_currentStrategy) {
0588         m_lastPoint = event->point;
0589         m_currentStrategy->handleMouseMove(event->point, event->modifiers());
0590 
0591         repaintDecorations();
0592 
0593         return;
0594     }
0595 
0596     if (m_activeSegment) {
0597         m_activeSegment.reset();
0598         repaintDecorations();
0599     }
0600 
0601     Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) {
0602         QRectF roi = shape->documentToShape(handleGrabRect(event->point));
0603         KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
0604         if (parameterShape && parameterShape->isParametricShape()) {
0605             int handleId = parameterShape->handleIdAt(roi);
0606             if (handleId != -1) {
0607                 useCursor(m_moveCursor);
0608                 emit statusTextChanged(i18n("Drag to move handle."));
0609 
0610                 m_activeHandle.reset(new ParameterHandle(this, parameterShape, handleId));
0611                 repaintDecorations();
0612                 return;
0613             }
0614         } else {
0615             QList<KoPathPoint*> points = shape->pointsAt(roi, true);
0616             if (! points.empty()) {
0617                 // find the nearest control point from all points within the roi
0618                 KoPathPoint * bestPoint = 0;
0619                 KoPathPoint::PointType bestPointType = KoPathPoint::Node;
0620                 qreal minDistance = HUGE_VAL;
0621                 Q_FOREACH (KoPathPoint *p, points) {
0622                     // the node point must be hit if the point is not selected yet
0623                     if (! m_pointSelection.contains(p) && ! roi.contains(p->point()))
0624                         continue;
0625 
0626                     // check for the control points first as otherwise it is no longer
0627                     // possible to change the control points when they are the same as the point
0628                     if (p->activeControlPoint1() && roi.contains(p->controlPoint1())) {
0629                         qreal dist = squaredDistance(roi.center(), p->controlPoint1());
0630                         if (dist < minDistance) {
0631                             bestPoint = p;
0632                             bestPointType = KoPathPoint::ControlPoint1;
0633                             minDistance = dist;
0634                         }
0635                     }
0636 
0637                     if (p->activeControlPoint2() && roi.contains(p->controlPoint2())) {
0638                         qreal dist = squaredDistance(roi.center(), p->controlPoint2());
0639                         if (dist < minDistance) {
0640                             bestPoint = p;
0641                             bestPointType = KoPathPoint::ControlPoint2;
0642                             minDistance = dist;
0643                         }
0644                     }
0645 
0646                     // check the node point at last
0647                     qreal dist = squaredDistance(roi.center(), p->point());
0648                     if (dist < minDistance) {
0649                         bestPoint = p;
0650                         bestPointType = KoPathPoint::Node;
0651                         minDistance = dist;
0652                     }
0653                 }
0654 
0655                 if (! bestPoint) {
0656                     repaintDecorations();
0657                     return;
0658                 }
0659 
0660                 useCursor(m_moveCursor);
0661                 if (bestPointType == KoPathPoint::Node)
0662                     emit statusTextChanged(i18n("Drag to move point. Shift click to change point type."));
0663                 else
0664                     emit statusTextChanged(i18n("Drag to move control point."));
0665 
0666                 PointHandle *prev = dynamic_cast<PointHandle*>(m_activeHandle.data());
0667                 if (prev && prev->activePoint() == bestPoint && prev->activePointType() == bestPointType)
0668                     return; // no change;
0669 
0670                 m_activeHandle.reset(new PointHandle(this, bestPoint, bestPointType));
0671                 repaintDecorations();
0672                 return;
0673             }
0674         }
0675     }
0676 
0677     useCursor(m_selectCursor);
0678 
0679     if (m_activeHandle) {
0680         m_activeHandle.reset();
0681         repaintDecorations();
0682     }
0683 
0684     PathSegment *hoveredSegment = segmentAtPoint(event->point);
0685     if(hoveredSegment) {
0686         useCursor(Qt::PointingHandCursor);
0687         emit statusTextChanged(i18n("Drag to change curve directly. Double click to insert new path point."));
0688         m_activeSegment.reset(hoveredSegment);
0689         repaintDecorations();
0690     } else {
0691         uint selectedPointCount = m_pointSelection.size();
0692         if (selectedPointCount == 0)
0693             emit statusTextChanged(QString());
0694         else if (selectedPointCount == 1)
0695             emit statusTextChanged(i18n("Press B to break path at selected point."));
0696         else
0697             emit statusTextChanged(i18n("Press B to break path at selected segments."));
0698     }
0699 }
0700 
0701 void KoPathTool::mouseReleaseEvent(KoPointerEvent *event)
0702 {
0703     Q_D(KoToolBase);
0704     if (m_currentStrategy) {
0705         const bool hadNoSelection = !m_pointSelection.hasSelection();
0706         m_currentStrategy->finishInteraction(event->modifiers());
0707         KUndo2Command *command = m_currentStrategy->createCommand();
0708         if (command)
0709             d->canvas->addCommand(command);
0710         if (hadNoSelection && dynamic_cast<KoPathPointRubberSelectStrategy*>(m_currentStrategy.data())
0711                 && !m_pointSelection.hasSelection()) {
0712             // the click didn't do anything at all. Allow it to be used by others.
0713             event->ignore();
0714         }
0715         m_currentStrategy.reset();
0716         repaintDecorations();
0717     }
0718 }
0719 
0720 void KoPathTool::keyPressEvent(QKeyEvent *event)
0721 {
0722     if (m_currentStrategy) {
0723         switch (event->key()) {
0724         case Qt::Key_Control:
0725         case Qt::Key_Alt:
0726         case Qt::Key_Shift:
0727         case Qt::Key_Meta:
0728             if (! event->isAutoRepeat()) {
0729                 m_currentStrategy->handleMouseMove(m_lastPoint, event->modifiers());
0730             }
0731             break;
0732         case Qt::Key_Escape:
0733             m_currentStrategy->cancelInteraction();
0734             m_currentStrategy.reset();
0735             break;
0736         default:
0737             event->ignore();
0738             return;
0739         }
0740     } else {
0741         switch (event->key()) {
0742 #ifndef NDEBUG
0743 //        case Qt::Key_D:
0744 //            if (m_pointSelection.objectCount() == 1) {
0745 //                QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
0746 //                KoPathShapePrivate *p = static_cast<KoPathShapePrivate*>(selectedPoints[0].pathShape->priv());
0747 //                p->debugPath();
0748 //            }
0749 //            break;
0750 #endif
0751         case Qt::Key_B:
0752             if (m_pointSelection.size() == 1)
0753                 breakAtPoint();
0754             else if (m_pointSelection.size() >= 2)
0755                 breakAtSegment();
0756             break;
0757         default:
0758             event->ignore();
0759             return;
0760         }
0761     }
0762     event->accept();
0763 }
0764 
0765 void KoPathTool::keyReleaseEvent(QKeyEvent *event)
0766 {
0767     if (m_currentStrategy) {
0768         switch (event->key()) {
0769         case Qt::Key_Control:
0770         case Qt::Key_Alt:
0771         case Qt::Key_Shift:
0772         case Qt::Key_Meta:
0773             if (! event->isAutoRepeat()) {
0774                 m_currentStrategy->handleMouseMove(m_lastPoint, Qt::NoModifier);
0775             }
0776             break;
0777         default:
0778             break;
0779         }
0780     }
0781     event->accept();
0782 }
0783 
0784 void KoPathTool::mouseDoubleClickEvent(KoPointerEvent *event)
0785 {
0786     Q_D(KoToolBase);
0787     event->ignore();
0788 
0789     // check if we are doing something else at the moment
0790     if (m_currentStrategy) return;
0791 
0792     if (!m_activeHandle && m_activeSegment && m_activeSegment->isValid()) {
0793         QList<KoPathPointData> segments;
0794         segments.append(
0795                     KoPathPointData(m_activeSegment->path,
0796                                     m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart)));
0797 
0798         KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, m_activeSegment->positionOnSegment);
0799         d->canvas->addCommand(cmd);
0800 
0801         m_pointSelection.clear();
0802         foreach (KoPathPoint * p, cmd->insertedPoints()) {
0803             m_pointSelection.add(p, false);
0804         }
0805         updateActions();
0806         event->accept();
0807     } else if (!m_activeHandle && !m_activeSegment) {
0808         explicitUserStrokeEndRequest();
0809         event->accept();
0810     }
0811 }
0812 
0813 KoPathTool::PathSegment* KoPathTool::segmentAtPoint(const QPointF &point)
0814 {
0815     // the max allowed distance from a segment
0816     const QRectF grabRoi = handleGrabRect(point);
0817     const qreal distanceThreshold = 0.5 * KisAlgebra2D::maxDimension(grabRoi);
0818 
0819     QScopedPointer<PathSegment> segment(new PathSegment);
0820 
0821     Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) {
0822         KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
0823         if (parameterShape && parameterShape->isParametricShape())
0824             continue;
0825 
0826         // convert document point to shape coordinates
0827         const QPointF p = shape->documentToShape(point);
0828         // our region of interest, i.e. a region around our mouse position
0829         const QRectF roi = shape->documentToShape(grabRoi);
0830 
0831         qreal minDistance = std::numeric_limits<qreal>::max();
0832 
0833         // check all segments of this shape which intersect the region of interest
0834         const QList<KoPathSegment> segments = shape->segmentsAt(roi);
0835 
0836         foreach (const KoPathSegment &s, segments) {
0837             const qreal nearestPointParam = s.nearestPoint(p);
0838             const QPointF nearestPoint = s.pointAt(nearestPointParam);
0839             const qreal distance = kisDistance(p, nearestPoint);
0840 
0841             // are we within the allowed distance ?
0842             if (distance > distanceThreshold)
0843                 continue;
0844             // are we closer to the last closest point ?
0845             if (distance < minDistance) {
0846                 segment->path = shape;
0847                 segment->segmentStart = s.first();
0848                 segment->positionOnSegment = nearestPointParam;
0849             }
0850         }
0851     }
0852 
0853     if (!segment->isValid()) {
0854         segment.reset();
0855     }
0856 
0857     return segment.take();
0858 }
0859 
0860 void KoPathTool::activate(const QSet<KoShape*> &shapes)
0861 {
0862     KoToolBase::activate(shapes);
0863 
0864     Q_D(KoToolBase);
0865 
0866     d->canvas->snapGuide()->reset();
0867 
0868     useCursor(m_selectCursor);
0869     m_canvasConnections.addConnection(d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));
0870     m_canvasConnections.addConnection(d->canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(updateActions()));
0871 
0872     m_canvasConnections.addConnection(d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(repaintDecorations()));
0873     m_canvasConnections.addConnection(d->canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(repaintDecorations()));
0874     m_shapeFillResourceConnector.connectToCanvas(d->canvas);
0875 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
0876     initializeWithShapes(QList<KoShape*>(shapes.begin(), shapes.end()));
0877 #else
0878     initializeWithShapes(QList<KoShape*>::fromSet(shapes));
0879 #endif
0880     connect(m_actionCurvePoint, SIGNAL(triggered()), this, SLOT(pointToCurve()), Qt::UniqueConnection);
0881     connect(m_actionLinePoint, SIGNAL(triggered()), this, SLOT(pointToLine()), Qt::UniqueConnection);
0882     connect(m_actionLineSegment, SIGNAL(triggered()), this, SLOT(segmentToLine()), Qt::UniqueConnection);
0883     connect(m_actionCurveSegment, SIGNAL(triggered()), this, SLOT(segmentToCurve()), Qt::UniqueConnection);
0884     connect(m_actionAddPoint, SIGNAL(triggered()), this, SLOT(insertPoints()), Qt::UniqueConnection);
0885     connect(m_actionRemovePoint, SIGNAL(triggered()), this, SLOT(removePoints()), Qt::UniqueConnection);
0886     connect(m_actionBreakPoint, SIGNAL(triggered()), this, SLOT(breakAtPoint()), Qt::UniqueConnection);
0887     connect(m_actionBreakSegment, SIGNAL(triggered()), this, SLOT(breakAtSegment()), Qt::UniqueConnection);
0888     connect(m_actionJoinSegment, SIGNAL(triggered()), this, SLOT(joinPoints()), Qt::UniqueConnection);
0889     connect(m_actionMergePoints, SIGNAL(triggered()), this, SLOT(mergePoints()), Qt::UniqueConnection);
0890     connect(m_actionConvertToPath, SIGNAL(triggered()), this, SLOT(convertToPath()), Qt::UniqueConnection);
0891     connect(m_actionPathPointCorner, SIGNAL(triggered()), this, SLOT(pointTypeChangedCorner()), Qt::UniqueConnection);
0892     connect(m_actionPathPointSmooth, SIGNAL(triggered()), this, SLOT(pointTypeChangedSmooth()), Qt::UniqueConnection);
0893     connect(m_actionPathPointSymmetric, SIGNAL(triggered()), this, SLOT(pointTypeChangedSymmetric()), Qt::UniqueConnection);
0894     connect(&m_pointSelection, SIGNAL(selectionChanged()), this, SLOT(pointSelectionChanged()), Qt::UniqueConnection);
0895 
0896 }
0897 
0898 void KoPathTool::slotSelectionChanged()
0899 {
0900     Q_D(KoToolBase);
0901     QList<KoShape*> shapes =
0902             d->canvas->selectedShapesProxy()->selection()->selectedEditableShapesAndDelegates();
0903 
0904     initializeWithShapes(shapes);
0905 }
0906 
0907 void KoPathTool::notifyPathPointsChanged(KoPathShape *shape)
0908 {
0909     Q_UNUSED(shape);
0910 
0911     // active handle and selection might have already become invalid, so just
0912     // delete them without dereferencing anything...
0913 
0914     m_activeHandle.reset();
0915     m_activeSegment.reset();
0916 }
0917 
0918 void KoPathTool::clearActivePointSelectionReferences()
0919 {
0920     m_activeHandle.reset();
0921     m_activeSegment.reset();
0922     m_pointSelection.clear();
0923 }
0924 
0925 void KoPathTool::initializeWithShapes(const QList<KoShape*> shapes)
0926 {
0927     QList<KoPathShape*> selectedShapes;
0928     Q_FOREACH (KoShape *shape, shapes) {
0929         KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
0930 
0931         if (pathShape && pathShape->isShapeEditable()) {
0932             selectedShapes.append(pathShape);
0933         }
0934     }
0935 
0936     if (selectedShapes != m_pointSelection.selectedShapes()) {
0937         clearActivePointSelectionReferences();
0938         m_pointSelection.setSelectedShapes(selectedShapes);
0939         repaintDecorations();
0940     }
0941 
0942     updateOptionsWidget();
0943     updateActions();
0944 }
0945 
0946 void KoPathTool::updateOptionsWidget()
0947 {
0948     PathToolOptionWidget::Types type;
0949     QList<KoPathShape*> selectedShapes = m_pointSelection.selectedShapes();
0950     Q_FOREACH (KoPathShape *shape, selectedShapes) {
0951         KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
0952         type |= parameterShape && parameterShape->isParametricShape() ?
0953                     PathToolOptionWidget::ParametricShape : PathToolOptionWidget::PlainPath;
0954     }
0955 
0956     emit singleShapeChanged(selectedShapes.size() == 1 ? selectedShapes.first() : 0);
0957     emit typeChanged(type);
0958 }
0959 
0960 void KoPathTool::updateActions()
0961 {
0962     QList<KoPathPointData> pointData = m_pointSelection.selectedPointsData();
0963 
0964     bool canBreakAtPoint = false;
0965 
0966     bool hasNonSmoothPoints = false;
0967     bool hasNonSymmetricPoints = false;
0968     bool hasNonSplitPoints = false;
0969 
0970     bool hasNonLinePoints = false;
0971     bool hasNonCurvePoints = false;
0972 
0973     bool canJoinSubpaths = false;
0974 
0975     if (!pointData.isEmpty()) {
0976         Q_FOREACH (const KoPathPointData &pd, pointData) {
0977             const int subpathIndex = pd.pointIndex.first;
0978             const int pointIndex = pd.pointIndex.second;
0979 
0980             canBreakAtPoint |= pd.pathShape->isClosedSubpath(subpathIndex) ||
0981                     (pointIndex > 0 && pointIndex < pd.pathShape->subpathPointCount(subpathIndex) - 1);
0982 
0983             KoPathPoint *point = pd.pathShape->pointByIndex(pd.pointIndex);
0984 
0985             hasNonSmoothPoints |= !(point->properties() & KoPathPoint::IsSmooth);
0986             hasNonSymmetricPoints |= !(point->properties() & KoPathPoint::IsSymmetric);
0987             hasNonSplitPoints |=
0988                     point->properties() & KoPathPoint::IsSymmetric ||
0989                     point->properties() & KoPathPoint::IsSmooth;
0990 
0991             hasNonLinePoints |= point->activeControlPoint1() || point->activeControlPoint2();
0992             hasNonCurvePoints |= !point->activeControlPoint1() && !point->activeControlPoint2();
0993         }
0994 
0995         if (pointData.size() == 2) {
0996             const KoPathPointData & pd1 = pointData.at(0);
0997             const KoPathPointData & pd2 = pointData.at(1);
0998 
0999             canJoinSubpaths = checkCanJoinToPoints(pd1, pd2);
1000         }
1001     }
1002 
1003     m_actionPathPointCorner->setEnabled(hasNonSplitPoints);
1004     m_actionPathPointSmooth->setEnabled(hasNonSmoothPoints);
1005     m_actionPathPointSymmetric->setEnabled(hasNonSymmetricPoints);
1006 
1007     m_actionRemovePoint->setEnabled(!pointData.isEmpty());
1008 
1009     m_actionBreakPoint->setEnabled(canBreakAtPoint);
1010 
1011     m_actionCurvePoint->setEnabled(hasNonCurvePoints);
1012     m_actionLinePoint->setEnabled(hasNonLinePoints);
1013 
1014     m_actionJoinSegment->setEnabled(canJoinSubpaths);
1015     m_actionMergePoints->setEnabled(canJoinSubpaths);
1016 
1017     QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
1018 
1019 
1020     bool canSplitAtSegment = false;
1021     bool canConvertSegmentToLine = false;
1022     bool canConvertSegmentToCurve= false;
1023 
1024     if (!segments.isEmpty()) {
1025 
1026         canSplitAtSegment = segments.size() == 1;
1027 
1028         bool hasLines = false;
1029         bool hasCurves = false;
1030 
1031         Q_FOREACH (const KoPathPointData &pd, segments) {
1032             KoPathSegment segment = pd.pathShape->segmentByIndex(pd.pointIndex);
1033             hasLines |= segment.degree() == 1;
1034             hasCurves |= segment.degree() > 1;
1035         }
1036 
1037         canConvertSegmentToLine = !segments.isEmpty() && hasCurves;
1038         canConvertSegmentToCurve= !segments.isEmpty() && hasLines;
1039     }
1040 
1041     m_actionAddPoint->setEnabled(canSplitAtSegment);
1042 
1043     m_actionLineSegment->setEnabled(canConvertSegmentToLine);
1044     m_actionCurveSegment->setEnabled(canConvertSegmentToCurve);
1045 
1046     m_actionBreakSegment->setEnabled(canSplitAtSegment);
1047 
1048     KoSelection *selection = canvas()->selectedShapesProxy()->selection();
1049     bool haveConvertibleShapes = false;
1050     Q_FOREACH (KoShape *shape, selection->selectedEditableShapes()) {
1051         KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
1052         KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shape);
1053         if (textShape ||
1054                 (parameterShape && parameterShape->isParametricShape())) {
1055 
1056             haveConvertibleShapes = true;
1057             break;
1058         }
1059     }
1060     m_actionConvertToPath->setEnabled(haveConvertibleShapes);
1061 }
1062 
1063 void KoPathTool::deactivate()
1064 {
1065     Q_D(KoToolBase);
1066 
1067     m_shapeFillResourceConnector.disconnect();
1068     m_canvasConnections.clear();
1069     m_pointSelection.clear();
1070     m_pointSelection.setSelectedShapes(QList<KoPathShape*>());
1071     m_activeHandle.reset();
1072     m_activeSegment.reset();
1073     m_currentStrategy.reset();
1074     d->canvas->snapGuide()->reset();
1075 
1076     disconnect(m_actionCurvePoint, 0, this, 0);
1077     disconnect(m_actionLinePoint, 0, this, 0);
1078     disconnect(m_actionLineSegment, 0, this, 0);
1079     disconnect(m_actionCurveSegment, 0, this, 0);
1080     disconnect(m_actionAddPoint, 0, this, 0);
1081     disconnect(m_actionRemovePoint, 0, this, 0);
1082     disconnect(m_actionBreakPoint, 0, this, 0);
1083     disconnect(m_actionBreakSegment, 0, this, 0);
1084     disconnect(m_actionJoinSegment, 0, this, 0);
1085     disconnect(m_actionMergePoints, 0, this, 0);
1086     disconnect(m_actionConvertToPath, 0, this, 0);
1087     disconnect(m_actionPathPointCorner, 0, this, 0);
1088     disconnect(m_actionPathPointSmooth, 0, this, 0);
1089     disconnect(m_actionPathPointSymmetric, 0, this, 0);
1090     disconnect(&m_pointSelection, 0, this, 0);
1091 
1092     KoToolBase::deactivate();
1093 }
1094 
1095 void KoPathTool::canvasResourceChanged(int key, const QVariant & /*res*/)
1096 {
1097     if (key == KoCanvasResource::HandleRadius || key == KoCanvasResource::DecorationThickness) {
1098         repaintDecorations();
1099     }
1100 }
1101 
1102 void KoPathTool::pointSelectionChanged()
1103 {
1104     Q_D(KoToolBase);
1105     updateActions();
1106 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
1107     d->canvas->snapGuide()->setIgnoredPathPoints(QList<KoPathPoint*>(m_pointSelection.selectedPoints().begin(), m_pointSelection.selectedPoints().end()));
1108 #else
1109     d->canvas->snapGuide()->setIgnoredPathPoints(QList<KoPathPoint*>::fromSet(m_pointSelection.selectedPoints()));
1110 #endif
1111     emit selectionChanged(m_pointSelection.hasSelection());
1112 }
1113 
1114 namespace {
1115 void addActionsGroupIfEnabled(QMenu *menu, QAction *a1, QAction *a2)
1116 {
1117     if (a1->isEnabled() || a2->isEnabled()) {
1118         menu->addAction(a1);
1119         menu->addAction(a2);
1120         menu->addSeparator();
1121     }
1122 }
1123 
1124 void addActionsGroupIfEnabled(QMenu *menu, QAction *a1, QAction *a2, QAction *a3)
1125 {
1126     if (a1->isEnabled() || a2->isEnabled()) {
1127         menu->addAction(a1);
1128         menu->addAction(a2);
1129         menu->addAction(a3);
1130         menu->addSeparator();
1131     }
1132 }
1133 }
1134 
1135 QMenu *KoPathTool::popupActionsMenu()
1136 {
1137     if (m_activeHandle) {
1138         m_activeHandle->trySelectHandle();
1139     }
1140 
1141     if (m_activeSegment && m_activeSegment->isValid()) {
1142         KoPathShape *shape = m_activeSegment->path;
1143         KoPathSegment segment = shape->segmentByIndex(shape->pathPointIndex(m_activeSegment->segmentStart));
1144 
1145         m_pointSelection.add(segment.first(), true);
1146         m_pointSelection.add(segment.second(), false);
1147     }
1148 
1149     if (m_contextMenu) {
1150         m_contextMenu->clear();
1151 
1152         addActionsGroupIfEnabled(m_contextMenu.data(),
1153                                  m_actionPathPointCorner,
1154                                  m_actionPathPointSmooth,
1155                                  m_actionPathPointSymmetric);
1156 
1157         addActionsGroupIfEnabled(m_contextMenu.data(),
1158                                  m_actionCurvePoint,
1159                                  m_actionLinePoint);
1160 
1161         addActionsGroupIfEnabled(m_contextMenu.data(),
1162                                  m_actionAddPoint,
1163                                  m_actionRemovePoint);
1164 
1165         addActionsGroupIfEnabled(m_contextMenu.data(),
1166                                  m_actionLineSegment,
1167                                  m_actionCurveSegment);
1168 
1169         addActionsGroupIfEnabled(m_contextMenu.data(),
1170                                  m_actionBreakPoint,
1171                                  m_actionBreakSegment);
1172 
1173         addActionsGroupIfEnabled(m_contextMenu.data(),
1174                                  m_actionJoinSegment,
1175                                  m_actionMergePoints);
1176 
1177         m_contextMenu->addAction(m_actionConvertToPath);
1178 
1179         m_contextMenu->addSeparator();
1180     }
1181 
1182     return m_contextMenu.data();
1183 }
1184 
1185 void KoPathTool::deleteSelection()
1186 {
1187     removePoints();
1188 }
1189 
1190 KoToolSelection * KoPathTool::selection()
1191 {
1192     return &m_pointSelection;
1193 }
1194 
1195 void KoPathTool::requestUndoDuringStroke()
1196 {
1197     // noop!
1198 }
1199 
1200 void KoPathTool::requestStrokeCancellation()
1201 {
1202     explicitUserStrokeEndRequest();
1203 }
1204 
1205 void KoPathTool::requestStrokeEnd()
1206 {
1207     // noop!
1208 }
1209 
1210 void KoPathTool::explicitUserStrokeEndRequest()
1211 {
1212     KoToolManager::instance()->switchToolRequested("InteractionTool");
1213 }
1214 
1215 bool KoPathTool::selectAll()
1216 {
1217     m_pointSelection.selectAll();
1218     repaintDecorations();
1219     return true;
1220 }
1221 
1222 void KoPathTool::deselect()
1223 {
1224     clearActivePointSelectionReferences();
1225     repaintDecorations();
1226 }