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 }