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 }