File indexing completed on 2024-05-12 15:56:43

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2006 Thorsten Zachmann <zachmann@kde.org>
0003    SPDX-FileCopyrightText: 2006-2008 Jan Hambrecht <jaham@gmx.net>
0004    SPDX-FileCopyrightText: 2007 Thomas Zander <zander@kde.org>
0005 
0006    SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "KoPathPoint.h"
0010 #include "KoPathShape.h"
0011 
0012 #include <FlakeDebug.h>
0013 #include <QPainter>
0014 #include <QPointF>
0015 #include <KisHandlePainterHelper.h>
0016 
0017 #include <math.h>
0018 
0019 #include <qnumeric.h> // for qIsNaN
0020 static bool qIsNaNPoint(const QPointF &p) {
0021     return qIsNaN(p.x()) || qIsNaN(p.y());
0022 }
0023 
0024 class Q_DECL_HIDDEN KoPathPoint::Private
0025 {
0026 public:
0027     Private()
0028             : shape(0), properties(Normal)
0029             , activeControlPoint1(false), activeControlPoint2(false) {}
0030     KoPathShape * shape;
0031     QPointF point;
0032     QPointF controlPoint1;
0033     QPointF controlPoint2;
0034     PointProperties properties;
0035     bool activeControlPoint1;
0036     bool activeControlPoint2;
0037 };
0038 
0039 KoPathPoint::KoPathPoint(const KoPathPoint &pathPoint)
0040         : d(new Private())
0041 {
0042     d->shape = 0;
0043     d->point = pathPoint.d->point;
0044     d->controlPoint1 = pathPoint.d->controlPoint1;
0045     d->controlPoint2 = pathPoint.d->controlPoint2;
0046     d->properties = pathPoint.d->properties;
0047     d->activeControlPoint1 = pathPoint.d->activeControlPoint1;
0048     d->activeControlPoint2 = pathPoint.d->activeControlPoint2;
0049 }
0050 
0051 KoPathPoint::KoPathPoint(const KoPathPoint &pathPoint, KoPathShape *newParent)
0052     : KoPathPoint(pathPoint)
0053 {
0054     d->shape = newParent;
0055 }
0056 
0057 KoPathPoint::KoPathPoint()
0058         : d(new Private())
0059 {
0060 }
0061 
0062 KoPathPoint::KoPathPoint(KoPathShape * path, const QPointF &point, PointProperties properties)
0063         : d(new Private())
0064 {
0065     d->shape = path;
0066     d->point = point;
0067     d->controlPoint1 = point;
0068     d->controlPoint2 = point;
0069     d->properties = properties;
0070 }
0071 
0072 KoPathPoint::~KoPathPoint()
0073 {
0074     delete d;
0075 }
0076 
0077 KoPathPoint &KoPathPoint::operator=(const KoPathPoint &rhs)
0078 {
0079     if (this == &rhs)
0080         return (*this);
0081 
0082     d->shape = rhs.d->shape;
0083     d->point = rhs.d->point;
0084     d->controlPoint1 = rhs.d->controlPoint1;
0085     d->controlPoint2 = rhs.d->controlPoint2;
0086     d->properties = rhs.d->properties;
0087     d->activeControlPoint1 = rhs.d->activeControlPoint1;
0088     d->activeControlPoint2 = rhs.d->activeControlPoint2;
0089 
0090     return (*this);
0091 }
0092 
0093 bool KoPathPoint::operator == (const KoPathPoint &rhs) const
0094 {
0095     if (d->point != rhs.d->point)
0096         return false;
0097     if (d->controlPoint1 != rhs.d->controlPoint1)
0098         return false;
0099     if (d->controlPoint2 != rhs.d->controlPoint2)
0100         return false;
0101     if (d->properties != rhs.d->properties)
0102         return false;
0103     if (d->activeControlPoint1 != rhs.d->activeControlPoint1)
0104         return false;
0105     if (d->activeControlPoint2 != rhs.d->activeControlPoint2)
0106         return false;
0107     return true;
0108 }
0109 
0110 void KoPathPoint::setPoint(const QPointF &point)
0111 {
0112     d->point = point;
0113     if (d->shape)
0114         d->shape->notifyChanged();
0115 }
0116 
0117 void KoPathPoint::setControlPoint1(const QPointF &point)
0118 {
0119     if (qIsNaNPoint(point)) return;
0120 
0121     d->controlPoint1 = point;
0122     d->activeControlPoint1 = true;
0123     if (d->shape)
0124         d->shape->notifyChanged();
0125 }
0126 
0127 void KoPathPoint::setControlPoint2(const QPointF &point)
0128 {
0129     if (qIsNaNPoint(point)) return;
0130 
0131     d->controlPoint2 = point;
0132     d->activeControlPoint2 = true;
0133     if (d->shape)
0134         d->shape->notifyChanged();
0135 }
0136 
0137 void KoPathPoint::removeControlPoint1()
0138 {
0139     d->activeControlPoint1 = false;
0140     d->properties &= ~IsSmooth;
0141     d->properties &= ~IsSymmetric;
0142     if (d->shape)
0143         d->shape->notifyChanged();
0144 }
0145 
0146 void KoPathPoint::removeControlPoint2()
0147 {
0148     d->activeControlPoint2 = false;
0149     d->properties &= ~IsSmooth;
0150     d->properties &= ~IsSymmetric;
0151     if (d->shape)
0152         d->shape->notifyChanged();
0153 }
0154 
0155 void KoPathPoint::setProperties(PointProperties properties)
0156 {
0157     d->properties = properties;
0158     // CloseSubpath only allowed with StartSubpath or StopSubpath
0159     if ((d->properties & StartSubpath) == 0 && (d->properties & StopSubpath) == 0)
0160         d->properties &= ~CloseSubpath;
0161 
0162     if (! activeControlPoint1() || ! activeControlPoint2()) {
0163         // strip smooth and symmetric flags if point has not two control points
0164         d->properties &= ~IsSmooth;
0165         d->properties &= ~IsSymmetric;
0166     }
0167 
0168     if (d->shape)
0169         d->shape->notifyChanged();
0170 }
0171 
0172 void KoPathPoint::setProperty(PointProperty property)
0173 {
0174     switch (property) {
0175     case StartSubpath:
0176     case StopSubpath:
0177     case CloseSubpath:
0178         // nothing special to do here
0179         break;
0180     case IsSmooth:
0181         d->properties &= ~IsSymmetric;
0182         break;
0183     case IsSymmetric:
0184         d->properties &= ~IsSmooth;
0185         break;
0186     default: return;
0187     }
0188 
0189     d->properties |= property;
0190 
0191     if (! activeControlPoint1() || ! activeControlPoint2()) {
0192         // strip smooth and symmetric flags if point has not two control points
0193         d->properties &= ~IsSymmetric;
0194         d->properties &= ~IsSmooth;
0195     }
0196 }
0197 
0198 void KoPathPoint::unsetProperty(PointProperty property)
0199 {
0200     switch (property) {
0201     case StartSubpath:
0202         if (d->properties & StartSubpath && (d->properties & StopSubpath) == 0)
0203             d->properties &= ~CloseSubpath;
0204         break;
0205     case StopSubpath:
0206         if (d->properties & StopSubpath && (d->properties & StartSubpath) == 0)
0207             d->properties &= ~CloseSubpath;
0208         break;
0209     case CloseSubpath:
0210         if (d->properties & StartSubpath || d->properties & StopSubpath) {
0211             d->properties &= ~IsSmooth;
0212             d->properties &= ~IsSymmetric;
0213         }
0214         break;
0215     case IsSmooth:
0216     case IsSymmetric:
0217         // no others depend on these
0218         break;
0219     default: return;
0220     }
0221     d->properties &= ~property;
0222 }
0223 
0224 bool KoPathPoint::activeControlPoint1() const
0225 {
0226     // only start point on closed subpaths can have a controlPoint1
0227     if ((d->properties & StartSubpath) && (d->properties & CloseSubpath) == 0)
0228         return false;
0229 
0230     return d->activeControlPoint1;
0231 }
0232 
0233 bool KoPathPoint::activeControlPoint2() const
0234 {
0235     // only end point on closed subpaths can have a controlPoint2
0236     if ((d->properties & StopSubpath) && (d->properties & CloseSubpath) == 0)
0237         return false;
0238 
0239     return d->activeControlPoint2;
0240 }
0241 
0242 void KoPathPoint::map(const QTransform &matrix)
0243 {
0244     d->point = matrix.map(d->point);
0245     d->controlPoint1 = matrix.map(d->controlPoint1);
0246     d->controlPoint2 = matrix.map(d->controlPoint2);
0247 
0248     if (d->shape)
0249         d->shape->notifyChanged();
0250 }
0251 
0252 void KoPathPoint::paint(KisHandlePainterHelper &handlesHelper, PointTypes types, bool active)
0253 {
0254     bool drawControlPoint1 = types & ControlPoint1 && (!active || activeControlPoint1());
0255     bool drawControlPoint2 = types & ControlPoint2 && (!active || activeControlPoint2());
0256 
0257     // draw lines at the bottom
0258     if (drawControlPoint2) {
0259         handlesHelper.drawConnectionLine(point(), controlPoint2());
0260     }
0261 
0262     if (drawControlPoint1) {
0263         handlesHelper.drawConnectionLine(point(), controlPoint1());
0264     }
0265 
0266     // the point is lowest
0267     if (types & Node) {
0268         if (properties() & IsSmooth) {
0269             handlesHelper.drawHandleCircle(point());
0270         } else if (properties() & IsSymmetric) {
0271             handlesHelper.drawHandleRect(point());
0272         } else {
0273             handlesHelper.drawGradientHandle(point());
0274         }
0275     }
0276 
0277     // then comes control point 2
0278     if (drawControlPoint2) {
0279         handlesHelper.drawHandleSmallCircle(controlPoint2());
0280     }
0281 
0282     // then comes control point 1
0283     if (drawControlPoint1) {
0284         handlesHelper.drawHandleSmallCircle(controlPoint1());
0285     }
0286 }
0287 
0288 void KoPathPoint::setParent(KoPathShape* parent)
0289 {
0290     // don't set to zero
0291     //Q_ASSERT(parent);
0292     d->shape = parent;
0293 }
0294 
0295 QRectF KoPathPoint::boundingRect(bool active) const
0296 {
0297     QRectF rect(d->point, QSize(1, 1));
0298     if (!active && activeControlPoint1()) {
0299         QRectF r1(d->point, QSize(1, 1));
0300         r1.setBottomRight(d->controlPoint1);
0301         rect = rect.united(r1);
0302     }
0303     if (!active && activeControlPoint2()) {
0304         QRectF r2(d->point, QSize(1, 1));
0305         r2.setBottomRight(d->controlPoint2);
0306         rect = rect.united(r2);
0307     }
0308     if (d->shape)
0309         return d->shape->shapeToDocument(rect);
0310     else
0311         return rect;
0312 }
0313 
0314 void KoPathPoint::reverse()
0315 {
0316     std::swap(d->controlPoint1, d->controlPoint2);
0317     std::swap(d->activeControlPoint1, d->activeControlPoint2);
0318     PointProperties newProps = Normal;
0319     newProps |= d->properties & IsSmooth;
0320     newProps |= d->properties & IsSymmetric;
0321     newProps |= d->properties & StartSubpath;
0322     newProps |= d->properties & StopSubpath;
0323     newProps |= d->properties & CloseSubpath;
0324     d->properties = newProps;
0325 }
0326 
0327 bool KoPathPoint::isSmooth(KoPathPoint * prev, KoPathPoint * next) const
0328 {
0329     QPointF t1, t2;
0330 
0331     if (activeControlPoint1()) {
0332         t1 = point() - controlPoint1();
0333     } else {
0334         // we need the previous path point but there is none provided
0335         if (! prev)
0336             return false;
0337         if (prev->activeControlPoint2())
0338             t1 = point() - prev->controlPoint2();
0339         else
0340             t1 = point() - prev->point();
0341     }
0342 
0343     if (activeControlPoint2()) {
0344         t2 = controlPoint2() - point();
0345     } else {
0346         // we need the next path point but there is none provided
0347         if (! next)
0348             return false;
0349         if (next->activeControlPoint1())
0350             t2 = next->controlPoint1() - point();
0351         else
0352             t2 = next->point() - point();
0353     }
0354 
0355     // normalize tangent vectors
0356     qreal l1 = sqrt(t1.x() * t1.x() + t1.y() * t1.y());
0357     qreal l2 = sqrt(t2.x() * t2.x() + t2.y() * t2.y());
0358     if (qFuzzyCompare(l1 + 1, qreal(1.0)) || qFuzzyCompare(l2 + 1, qreal(1.0)))
0359         return true;
0360 
0361     t1 /= l1;
0362     t2 /= l2;
0363 
0364     qreal scalar = t1.x() * t2.x() + t1.y() * t2.y();
0365     // tangents are parallel if t1*t2 = |t1|*|t2|
0366     return qFuzzyCompare(scalar, qreal(1.0));
0367 }
0368 
0369 KoPathPoint::PointProperties KoPathPoint::properties() const
0370 {
0371     return d->properties;
0372 }
0373 
0374 QPointF KoPathPoint::point() const
0375 {
0376     return d->point;
0377 }
0378 
0379 QPointF KoPathPoint::controlPoint1() const
0380 {
0381     return d->controlPoint1;
0382 }
0383 
0384 QPointF KoPathPoint::controlPoint2() const
0385 {
0386     return d->controlPoint2;
0387 }
0388 
0389 KoPathShape * KoPathPoint::parent() const
0390 {
0391     return d->shape;
0392 }