File indexing completed on 2024-06-09 04:20:44

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2006, 2008 Jan Hambrecht <jaham@gmx.net>
0003  * SPDX-FileCopyrightText: 2006, 2007 Thorsten Zachmann <zachmann@kde.org>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "KoPathPointTypeCommand.h"
0009 
0010 #include <klocalizedstring.h>
0011 #include <math.h>
0012 
0013 #include "KoPathSegment.h"
0014 
0015 KoPathPointTypeCommand::KoPathPointTypeCommand(
0016     const QList<KoPathPointData> & pointDataList,
0017     PointType pointType,
0018     KUndo2Command *parent)
0019         : KoPathBaseCommand(parent)
0020         , m_pointType(pointType)
0021 {
0022     QList<KoPathPointData>::const_iterator it(pointDataList.begin());
0023     for (; it != pointDataList.end(); ++it) {
0024         KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex);
0025         if (point) {
0026             PointData pointData(*it);
0027             pointData.m_oldControlPoint1 = it->pathShape->shapeToDocument(point->controlPoint1());
0028             pointData.m_oldControlPoint2 = it->pathShape->shapeToDocument(point->controlPoint2());
0029             pointData.m_oldProperties = point->properties();
0030             pointData.m_hadControlPoint1 = point->activeControlPoint1();
0031             pointData.m_hadControlPoint2 = point->activeControlPoint2();
0032             m_oldPointData.append(pointData);
0033             m_shapes.insert(it->pathShape);
0034         }
0035     }
0036     setText(kundo2_i18n("Set point type"));
0037 }
0038 
0039 KoPathPointTypeCommand::~KoPathPointTypeCommand()
0040 {
0041 }
0042 
0043 void KoPathPointTypeCommand::redo()
0044 {
0045     KUndo2Command::redo();
0046     repaint(false);
0047     m_additionalPointData.clear();
0048 
0049     QList<PointData>::iterator it(m_oldPointData.begin());
0050     for (; it != m_oldPointData.end(); ++it) {
0051         KoPathPoint *point = it->m_pointData.pathShape->pointByIndex(it->m_pointData.pointIndex);
0052         KoPathPoint::PointProperties properties = point->properties();
0053 
0054         switch (m_pointType) {
0055         case Line: {
0056             point->removeControlPoint1();
0057             point->removeControlPoint2();
0058             break;
0059         }
0060         case Curve: {
0061             KoPathPointIndex pointIndex = it->m_pointData.pointIndex;
0062             KoPathPointIndex prevIndex;
0063             KoPathPointIndex nextIndex;
0064             KoPathShape * path = it->m_pointData.pathShape;
0065             // get previous path node
0066             if (pointIndex.second > 0)
0067                 prevIndex = KoPathPointIndex(pointIndex.first, pointIndex.second - 1);
0068             else if (pointIndex.second == 0 && path->isClosedSubpath(pointIndex.first))
0069                 prevIndex = KoPathPointIndex(pointIndex.first, path->subpathPointCount(pointIndex.first) - 1);
0070             // get next node
0071             if (pointIndex.second < path->subpathPointCount(pointIndex.first) - 1)
0072                 nextIndex = KoPathPointIndex(pointIndex.first, pointIndex.second + 1);
0073             else if (pointIndex.second < path->subpathPointCount(pointIndex.first) - 1
0074                      && path->isClosedSubpath(pointIndex.first))
0075                 nextIndex = KoPathPointIndex(pointIndex.first, 0);
0076 
0077             KoPathPoint * prevPoint = path->pointByIndex(prevIndex);
0078             KoPathPoint * nextPoint = path->pointByIndex(nextIndex);
0079 
0080             if (prevPoint && ! point->activeControlPoint1() && appendPointData(KoPathPointData(path, prevIndex))) {
0081                 KoPathSegment cubic = KoPathSegment(prevPoint, point).toCubic();
0082                 if (prevPoint->activeControlPoint2()) {
0083                     prevPoint->setControlPoint2(cubic.first()->controlPoint2());
0084                     point->setControlPoint1(cubic.second()->controlPoint1());
0085                 } else
0086                     point->setControlPoint1(cubic.second()->controlPoint1());
0087             }
0088             if (nextPoint && ! point->activeControlPoint2() && appendPointData(KoPathPointData(path, nextIndex))) {
0089                 KoPathSegment cubic = KoPathSegment(point, nextPoint).toCubic();
0090                 if (nextPoint->activeControlPoint1()) {
0091                     point->setControlPoint2(cubic.first()->controlPoint2());
0092                     nextPoint->setControlPoint1(cubic.second()->controlPoint1());
0093                 } else
0094                     point->setControlPoint2(cubic.first()->controlPoint2());
0095             }
0096             point->setProperties(properties);
0097             break;
0098         }
0099         case Symmetric: {
0100             properties &= ~KoPathPoint::IsSmooth;
0101             properties |= KoPathPoint::IsSymmetric;
0102 
0103             // calculate vector from node point to first control point and normalize it
0104             QPointF directionC1 = point->controlPoint1() - point->point();
0105             qreal dirLengthC1 = sqrt(directionC1.x() * directionC1.x() + directionC1.y() * directionC1.y());
0106             directionC1 /= dirLengthC1;
0107             // calculate vector from node point to second control point and normalize it
0108             QPointF directionC2 = point->controlPoint2() - point->point();
0109             qreal dirLengthC2 = sqrt(directionC2.x() * directionC2.x() + directionC2.y() * directionC2.y());
0110             directionC2 /= dirLengthC2;
0111             // calculate the average distance of the control points to the node point
0112             qreal averageLength = 0.5 * (dirLengthC1 + dirLengthC2);
0113             // compute position of the control points so that they lie on a line going through the node point
0114             // the new distance of the control points is the average distance to the node point
0115             point->setControlPoint1(point->point() + 0.5 * averageLength * (directionC1 - directionC2));
0116             point->setControlPoint2(point->point() + 0.5 * averageLength * (directionC2 - directionC1));
0117             point->setProperties(properties);
0118         }
0119         break;
0120         case Smooth: {
0121             makeCubicPointSmooth(point);
0122         }
0123         break;
0124         case Corner:
0125         default:
0126             properties &= ~KoPathPoint::IsSymmetric;
0127             properties &= ~KoPathPoint::IsSmooth;
0128             point->setProperties(properties);
0129             break;
0130         }
0131     }
0132     repaint(true);
0133 }
0134 
0135 void KoPathPointTypeCommand::undo()
0136 {
0137     KUndo2Command::undo();
0138     repaint(false);
0139 
0140     /*
0141     QList<PointData>::iterator it(m_oldPointData.begin());
0142     for (; it != m_oldPointData.end(); ++it)
0143     {
0144         KoPathShape *pathShape = it->m_pointData.pathShape;
0145         KoPathPoint *point = pathShape->pointByIndex(it->m_pointData.pointIndex);
0146 
0147         point->setProperties(it->m_oldProperties);
0148         if (it->m_hadControlPoint1)
0149             point->setControlPoint1(pathShape->documentToShape(it->m_oldControlPoint1));
0150         else
0151             point->removeControlPoint1();
0152         if (it->m_hadControlPoint2)
0153             point->setControlPoint2(pathShape->documentToShape(it->m_oldControlPoint2));
0154         else
0155             point->removeControlPoint2();
0156     }
0157     */
0158     undoChanges(m_oldPointData);
0159     undoChanges(m_additionalPointData);
0160 
0161     repaint(true);
0162 }
0163 
0164 void KoPathPointTypeCommand::makeCubicPointSmooth(KoPathPoint *point)
0165 {
0166     KoPathPoint::PointProperties properties = point->properties();
0167 
0168     properties &= ~KoPathPoint::IsSymmetric;
0169     properties |= KoPathPoint::IsSmooth;
0170 
0171     // calculate vector from node point to first control point and normalize it
0172     QPointF directionC1 = point->controlPoint1() - point->point();
0173     qreal dirLengthC1 = sqrt(directionC1.x() * directionC1.x() + directionC1.y() * directionC1.y());
0174     directionC1 /= dirLengthC1;
0175     // calculate vector from node point to second control point and normalize it
0176     QPointF directionC2 = point->controlPoint2() - point->point();
0177     qreal dirLengthC2 = sqrt(directionC2.x() * directionC2.x() + directionC2.y() * directionC2.y());
0178     directionC2 /= dirLengthC2;
0179     // compute position of the control points so that they lie on a line going through the node point
0180     // the new distance of the control points is the average distance to the node point
0181     point->setControlPoint1(point->point() + 0.5 * dirLengthC1 * (directionC1 - directionC2));
0182     point->setControlPoint2(point->point() + 0.5 * dirLengthC2 * (directionC2 - directionC1));
0183 
0184     point->setProperties(properties);
0185 }
0186 
0187 void KoPathPointTypeCommand::undoChanges(const QList<PointData> &data)
0188 {
0189     QList<PointData>::const_iterator it(data.begin());
0190     for (; it != data.end(); ++it) {
0191         KoPathShape *pathShape = it->m_pointData.pathShape;
0192         KoPathPoint *point = pathShape->pointByIndex(it->m_pointData.pointIndex);
0193 
0194         point->setProperties(it->m_oldProperties);
0195         if (it->m_hadControlPoint1)
0196             point->setControlPoint1(pathShape->documentToShape(it->m_oldControlPoint1));
0197         else
0198             point->removeControlPoint1();
0199         if (it->m_hadControlPoint2)
0200             point->setControlPoint2(pathShape->documentToShape(it->m_oldControlPoint2));
0201         else
0202             point->removeControlPoint2();
0203     }
0204 }
0205 
0206 bool KoPathPointTypeCommand::appendPointData(KoPathPointData data)
0207 {
0208     KoPathPoint *point = data.pathShape->pointByIndex(data.pointIndex);
0209     if (! point)
0210         return false;
0211 
0212     PointData pointData(data);
0213     pointData.m_oldControlPoint1 = data.pathShape->shapeToDocument(point->controlPoint1());
0214     pointData.m_oldControlPoint2 = data.pathShape->shapeToDocument(point->controlPoint2());
0215     pointData.m_oldProperties = point->properties();
0216     pointData.m_hadControlPoint1 = point->activeControlPoint1();
0217     pointData.m_hadControlPoint2 = point->activeControlPoint2();
0218 
0219     m_additionalPointData.append(pointData);
0220 
0221     return true;
0222 }