File indexing completed on 2024-05-12 15:56:44
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2006-2008, 2010-2011 Thorsten Zachmann <zachmann@kde.org> 0003 SPDX-FileCopyrightText: 2006-2011 Jan Hambrecht <jaham@gmx.net> 0004 SPDX-FileCopyrightText: 2007-2009 Thomas Zander <zander@kde.org> 0005 SPDX-FileCopyrightText: 2011 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "KoPathShape.h" 0011 #include "KoPathShape_p.h" 0012 0013 #include "KoPathSegment.h" 0014 #include "KoPathPoint.h" 0015 #include "KoShapeStrokeModel.h" 0016 #include "KoPathShapeLoader.h" 0017 #include "KoShapeSavingContext.h" 0018 #include "KoShapeLoadingContext.h" 0019 #include "KoShapeShadow.h" 0020 #include "KoShapeBackground.h" 0021 #include "KoShapeContainer.h" 0022 #include "KoFilterEffectStack.h" 0023 #include "KoMarker.h" 0024 #include "KoShapeStroke.h" 0025 #include "KoInsets.h" 0026 0027 #include <KoUnit.h> 0028 #include "KisQPainterStateSaver.h" 0029 0030 #include <FlakeDebug.h> 0031 #include <QPainter> 0032 #include <QPainterPath> 0033 0034 #include "kis_global.h" 0035 0036 #include <qnumeric.h> // for qIsNaN 0037 static bool qIsNaNPoint(const QPointF &p) { 0038 return qIsNaN(p.x()) || qIsNaN(p.y()); 0039 } 0040 0041 KoPathShape::Private::Private() 0042 : fillRule(Qt::OddEvenFill) 0043 , autoFillMarkers(false) 0044 { 0045 } 0046 0047 KoPathShape::Private::Private(const Private &rhs) 0048 : fillRule(rhs.fillRule) 0049 , markersNew(rhs.markersNew) 0050 , autoFillMarkers(rhs.autoFillMarkers) 0051 { 0052 } 0053 0054 QRectF KoPathShape::Private::handleRect(const QPointF &p, qreal radius) const 0055 { 0056 return QRectF(p.x() - radius, p.y() - radius, 2*radius, 2*radius); 0057 } 0058 0059 0060 KoPathShape::KoPathShape() 0061 : KoTosContainer() 0062 , d(new Private) 0063 { 0064 } 0065 0066 KoPathShape::KoPathShape(const KoPathShape &rhs) 0067 : KoTosContainer(rhs) 0068 , d(new Private(*rhs.d)) 0069 { 0070 // local data cannot be shared via QSharedData because 0071 // every path point holds a pointer to the parent shape 0072 KoSubpathList subpaths; 0073 Q_FOREACH (KoSubpath *subPath, rhs.d->subpaths) { 0074 KoSubpath *clonedSubPath = new KoSubpath(); 0075 0076 Q_FOREACH (KoPathPoint *point, *subPath) { 0077 *clonedSubPath << new KoPathPoint(*point, this); 0078 } 0079 0080 subpaths << clonedSubPath; 0081 } 0082 d->subpaths = subpaths; 0083 } 0084 0085 KoPathShape::~KoPathShape() 0086 { 0087 clear(); 0088 } 0089 0090 KoShape *KoPathShape::cloneShape() const 0091 { 0092 return new KoPathShape(*this); 0093 } 0094 0095 void KoPathShape::clear() 0096 { 0097 Q_FOREACH (KoSubpath *subpath, d->subpaths) { 0098 Q_FOREACH (KoPathPoint *point, *subpath) 0099 delete point; 0100 delete subpath; 0101 } 0102 d->subpaths.clear(); 0103 0104 notifyPointsChanged(); 0105 } 0106 0107 void KoPathShape::paint(QPainter &painter) const 0108 { 0109 KisQPainterStateSaver saver(&painter); 0110 Q_UNUSED(saver); 0111 0112 QPainterPath path(outline()); 0113 path.setFillRule(d->fillRule); 0114 0115 if (background()) { 0116 background()->paint(painter, path); 0117 } 0118 //d->paintDebug(painter); 0119 } 0120 0121 0122 #ifndef NDEBUG 0123 void KoPathShape::Private::paintDebug(QPainter &painter) 0124 { 0125 KoSubpathList::const_iterator pathIt(subpaths.constBegin()); 0126 int i = 0; 0127 0128 QPen pen(Qt::black, 0); 0129 painter.save(); 0130 painter.setPen(pen); 0131 for (; pathIt != subpaths.constEnd(); ++pathIt) { 0132 KoSubpath::const_iterator it((*pathIt)->constBegin()); 0133 for (; it != (*pathIt)->constEnd(); ++it) { 0134 ++i; 0135 KoPathPoint *point = (*it); 0136 QRectF r(point->point(), QSizeF(5, 5)); 0137 r.translate(-2.5, -2.5); 0138 QPen pen(Qt::black, 0); 0139 painter.setPen(pen); 0140 if (point->activeControlPoint1() && point->activeControlPoint2()) { 0141 QBrush b(Qt::red); 0142 painter.setBrush(b); 0143 } else if (point->activeControlPoint1()) { 0144 QBrush b(Qt::yellow); 0145 painter.setBrush(b); 0146 } else if (point->activeControlPoint2()) { 0147 QBrush b(Qt::darkYellow); 0148 painter.setBrush(b); 0149 } 0150 painter.drawEllipse(r); 0151 } 0152 } 0153 painter.restore(); 0154 debugFlake << "nop =" << i; 0155 } 0156 0157 void KoPathShape::Private::debugPath() const 0158 { 0159 KoSubpathList::const_iterator pathIt(subpaths.constBegin()); 0160 for (; pathIt != subpaths.constEnd(); ++pathIt) { 0161 KoSubpath::const_iterator it((*pathIt)->constBegin()); 0162 for (; it != (*pathIt)->constEnd(); ++it) { 0163 debugFlake << "p:" << (*pathIt) << "," << *it << "," << (*it)->point() << "," << (*it)->properties(); 0164 } 0165 } 0166 } 0167 #endif 0168 0169 void KoPathShape::paintPoints(KisHandlePainterHelper &handlesHelper) 0170 { 0171 KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); 0172 0173 for (; pathIt != d->subpaths.constEnd(); ++pathIt) { 0174 KoSubpath::const_iterator it((*pathIt)->constBegin()); 0175 for (; it != (*pathIt)->constEnd(); ++it) 0176 (*it)->paint(handlesHelper, KoPathPoint::Node); 0177 } 0178 } 0179 0180 QRectF KoPathShape::outlineRect() const 0181 { 0182 return outline().boundingRect(); 0183 } 0184 0185 QPainterPath KoPathShape::outline() const 0186 { 0187 QPainterPath path; 0188 for (auto subpathIt = d->subpaths.constBegin(); subpathIt != d->subpaths.constEnd(); ++subpathIt) { 0189 const KoSubpath * subpath = *subpathIt; 0190 const KoPathPoint * lastPoint = subpath->constFirst(); 0191 bool activeCP = false; 0192 for (auto pointIt = subpath->constBegin(); pointIt != subpath->constEnd(); ++pointIt) { 0193 const KoPathPoint * currPoint = *pointIt; 0194 KoPathPoint::PointProperties currProperties = currPoint->properties(); 0195 if (currPoint == subpath->constFirst()) { 0196 if (currProperties & KoPathPoint::StartSubpath) { 0197 Q_ASSERT(!qIsNaNPoint(currPoint->point())); 0198 path.moveTo(currPoint->point()); 0199 } 0200 } else if (activeCP && currPoint->activeControlPoint1()) { 0201 Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); 0202 Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); 0203 Q_ASSERT(!qIsNaNPoint(currPoint->point())); 0204 path.cubicTo( 0205 lastPoint->controlPoint2(), 0206 currPoint->controlPoint1(), 0207 currPoint->point()); 0208 } else if (activeCP || currPoint->activeControlPoint1()) { 0209 Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); 0210 Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); 0211 path.quadTo( 0212 activeCP ? lastPoint->controlPoint2() : currPoint->controlPoint1(), 0213 currPoint->point()); 0214 } else { 0215 Q_ASSERT(!qIsNaNPoint(currPoint->point())); 0216 path.lineTo(currPoint->point()); 0217 } 0218 if (currProperties & KoPathPoint::CloseSubpath && currProperties & KoPathPoint::StopSubpath) { 0219 // add curve when there is a curve on the way to the first point 0220 KoPathPoint * firstPoint = subpath->first(); 0221 Q_ASSERT(!qIsNaNPoint(firstPoint->point())); 0222 if (currPoint->activeControlPoint2() && firstPoint->activeControlPoint1()) { 0223 path.cubicTo( 0224 currPoint->controlPoint2(), 0225 firstPoint->controlPoint1(), 0226 firstPoint->point()); 0227 } 0228 else if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { 0229 Q_ASSERT(!qIsNaNPoint(currPoint->point())); 0230 Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); 0231 path.quadTo( 0232 currPoint->activeControlPoint2() ? currPoint->controlPoint2() : firstPoint->controlPoint1(), 0233 firstPoint->point()); 0234 } 0235 path.closeSubpath(); 0236 } 0237 0238 if (currPoint->activeControlPoint2()) { 0239 activeCP = true; 0240 } else { 0241 activeCP = false; 0242 } 0243 lastPoint = currPoint; 0244 } 0245 } 0246 0247 return path; 0248 } 0249 0250 QRectF KoPathShape::boundingRect() const 0251 { 0252 const QTransform transform = absoluteTransformation(); 0253 0254 /** 0255 * First we approximate the insets of the stroke by rendering a fat bezier curve 0256 * with width set to the maximum inset of miters and markers. The are swept by this 0257 * curve will be a good approximation of the real curve bounding rect. 0258 */ 0259 qreal outlineSweepWidth = 0; 0260 0261 const QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(stroke()); 0262 if (lineBorder) { 0263 outlineSweepWidth = lineBorder->lineWidth(); 0264 } 0265 0266 if (stroke()) { 0267 KoInsets inset; 0268 stroke()->strokeInsets(this, inset); 0269 const qreal maxInset = std::max({inset.left, inset.top, inset.right, inset.bottom}); 0270 0271 // insets extend outside the shape, but width extends both inside and outside, 0272 // so we should multiply insets by 2.0 0273 outlineSweepWidth = std::max({outlineSweepWidth, 0274 2.0 * maxInset, 0275 2.0 * stroke()->strokeMaxMarkersInset(this)}); 0276 } 0277 0278 0279 0280 /// NOTE: stroking the entire shape might be too expensive, so try to 0281 /// estimate the bounds using insets only... 0282 0283 #if 0 0284 QPen pen(Qt::black, outlineSweepWidth); 0285 0286 // select round joins and caps to ensure it sweeps exactly 0287 // 'outlineSweepWidth' pixels in every possible 0288 pen.setJoinStyle(Qt::RoundJoin); 0289 pen.setCapStyle(Qt::RoundCap); 0290 QRectF bb = transform.map(pathStroke(pen)).boundingRect(); 0291 #endif 0292 0293 // add 10% extra update area around the doubled insets 0294 QRectF bb = transform.mapRect(kisGrowRect(outline().boundingRect(), 1.1 * 0.5 * outlineSweepWidth)); 0295 0296 if (shadow()) { 0297 KoInsets insets; 0298 shadow()->insets(insets); 0299 bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); 0300 } 0301 if (filterEffectStack()) { 0302 QRectF clipRect = filterEffectStack()->clipRectForBoundingRect(QRectF(QPointF(), size())); 0303 bb |= transform.mapRect(clipRect); 0304 } 0305 return bb; 0306 } 0307 0308 QSizeF KoPathShape::size() const 0309 { 0310 // don't call boundingRect here as it uses absoluteTransformation 0311 // which itself uses size() -> leads to infinite recursion 0312 return outlineRect().size(); 0313 } 0314 0315 void KoPathShape::setSize(const QSizeF &newSize) 0316 { 0317 QTransform matrix(resizeMatrix(newSize)); 0318 0319 KoShape::setSize(newSize); 0320 d->map(matrix); 0321 } 0322 0323 QTransform KoPathShape::resizeMatrix(const QSizeF & newSize) const 0324 { 0325 QSizeF oldSize = size(); 0326 if (oldSize.width() == 0.0) { 0327 oldSize.setWidth(0.000001); 0328 } 0329 if (oldSize.height() == 0.0) { 0330 oldSize.setHeight(0.000001); 0331 } 0332 0333 QSizeF sizeNew(newSize); 0334 if (sizeNew.width() == 0.0) { 0335 sizeNew.setWidth(0.000001); 0336 } 0337 if (sizeNew.height() == 0.0) { 0338 sizeNew.setHeight(0.000001); 0339 } 0340 0341 return QTransform(sizeNew.width() / oldSize.width(), 0, 0, sizeNew.height() / oldSize.height(), 0, 0); 0342 } 0343 0344 KoPathPoint * KoPathShape::moveTo(const QPointF &p) 0345 { 0346 KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StartSubpath | KoPathPoint::StopSubpath); 0347 KoSubpath * path = new KoSubpath; 0348 path->push_back(point); 0349 d->subpaths.push_back(path); 0350 notifyPointsChanged(); 0351 return point; 0352 } 0353 0354 KoPathPoint * KoPathShape::lineTo(const QPointF &p) 0355 { 0356 if (d->subpaths.empty()) { 0357 moveTo(QPointF(0, 0)); 0358 } 0359 KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); 0360 KoPathPoint * lastPoint = d->subpaths.last()->last(); 0361 updateLastPriv(&lastPoint); 0362 d->subpaths.last()->push_back(point); 0363 notifyPointsChanged(); 0364 return point; 0365 } 0366 0367 KoPathPoint * KoPathShape::curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p) 0368 { 0369 if (d->subpaths.empty()) { 0370 moveTo(QPointF(0, 0)); 0371 } 0372 KoPathPoint * lastPoint = d->subpaths.last()->last(); 0373 updateLastPriv(&lastPoint); 0374 lastPoint->setControlPoint2(c1); 0375 KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); 0376 point->setControlPoint1(c2); 0377 d->subpaths.last()->push_back(point); 0378 notifyPointsChanged(); 0379 return point; 0380 } 0381 0382 KoPathPoint * KoPathShape::curveTo(const QPointF &c, const QPointF &p) 0383 { 0384 if (d->subpaths.empty()) 0385 moveTo(QPointF(0, 0)); 0386 0387 KoPathPoint * lastPoint = d->subpaths.last()->last(); 0388 updateLastPriv(&lastPoint); 0389 lastPoint->setControlPoint2(c); 0390 KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); 0391 d->subpaths.last()->push_back(point); 0392 notifyPointsChanged(); 0393 return point; 0394 } 0395 0396 KoPathPoint * KoPathShape::arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle) 0397 { 0398 if (d->subpaths.empty()) { 0399 moveTo(QPointF(0, 0)); 0400 } 0401 0402 KoPathPoint * lastPoint = d->subpaths.last()->last(); 0403 if (lastPoint->properties() & KoPathPoint::CloseSubpath) { 0404 lastPoint = d->subpaths.last()->first(); 0405 } 0406 QPointF startpoint(lastPoint->point()); 0407 0408 KoPathPoint * newEndPoint = lastPoint; 0409 0410 QPointF curvePoints[12]; 0411 int pointCnt = arcToCurve(rx, ry, startAngle, sweepAngle, startpoint, curvePoints); 0412 for (int i = 0; i < pointCnt; i += 3) { 0413 newEndPoint = curveTo(curvePoints[i], curvePoints[i+1], curvePoints[i+2]); 0414 } 0415 return newEndPoint; 0416 } 0417 0418 int KoPathShape::arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF & offset, QPointF * curvePoints) const 0419 { 0420 int pointCnt = 0; 0421 0422 // check Parameters 0423 if (sweepAngle == 0.0) 0424 return pointCnt; 0425 0426 sweepAngle = qBound(-360.0, sweepAngle, 360.0); 0427 0428 if (rx == 0 || ry == 0) { 0429 //TODO 0430 } 0431 0432 // split angles bigger than 90° so that it gives a good approximation to the circle 0433 qreal parts = ceil(qAbs(sweepAngle / 90.0)); 0434 0435 qreal sa_rad = startAngle * M_PI / 180.0; 0436 qreal partangle = sweepAngle / parts; 0437 qreal endangle = startAngle + partangle; 0438 qreal se_rad = endangle * M_PI / 180.0; 0439 qreal sinsa = sin(sa_rad); 0440 qreal cossa = cos(sa_rad); 0441 qreal kappa = 4.0 / 3.0 * tan((se_rad - sa_rad) / 4); 0442 0443 // startpoint is at the last point is the path but when it is closed 0444 // it is at the first point 0445 QPointF startpoint(offset); 0446 0447 //center berechnen 0448 QPointF center(startpoint - QPointF(cossa * rx, -sinsa * ry)); 0449 0450 //debugFlake <<"kappa" << kappa <<"parts" << parts; 0451 0452 for (int part = 0; part < parts; ++part) { 0453 // start tangent 0454 curvePoints[pointCnt++] = QPointF(startpoint - QPointF(sinsa * rx * kappa, cossa * ry * kappa)); 0455 0456 qreal sinse = sin(se_rad); 0457 qreal cosse = cos(se_rad); 0458 0459 // end point 0460 QPointF endpoint(center + QPointF(cosse * rx, -sinse * ry)); 0461 // end tangent 0462 curvePoints[pointCnt++] = QPointF(endpoint - QPointF(-sinse * rx * kappa, -cosse * ry * kappa)); 0463 curvePoints[pointCnt++] = endpoint; 0464 0465 // set the endpoint as next start point 0466 startpoint = endpoint; 0467 sinsa = sinse; 0468 cossa = cosse; 0469 endangle += partangle; 0470 se_rad = endangle * M_PI / 180.0; 0471 } 0472 0473 return pointCnt; 0474 } 0475 0476 void KoPathShape::close() 0477 { 0478 if (d->subpaths.empty()) { 0479 return; 0480 } 0481 closeSubpathPriv(d->subpaths.last()); 0482 } 0483 0484 void KoPathShape::closeMerge() 0485 { 0486 if (d->subpaths.empty()) { 0487 return; 0488 } 0489 closeMergeSubpathPriv(d->subpaths.last()); 0490 } 0491 0492 QPointF KoPathShape::normalize() 0493 { 0494 QPointF tl(outline().boundingRect().topLeft()); 0495 QTransform matrix; 0496 matrix.translate(-tl.x(), -tl.y()); 0497 d->map(matrix); 0498 0499 // keep the top left point of the object 0500 applyTransformation(matrix.inverted()); 0501 shapeChangedPriv(ContentChanged); 0502 return tl; 0503 } 0504 0505 void KoPathShape::Private::map(const QTransform &matrix) 0506 { 0507 KoSubpathList::const_iterator pathIt(subpaths.constBegin()); 0508 for (; pathIt != subpaths.constEnd(); ++pathIt) { 0509 KoSubpath::const_iterator it((*pathIt)->constBegin()); 0510 for (; it != (*pathIt)->constEnd(); ++it) { 0511 // It's possible there are null points in the map... 0512 if (*it) { 0513 (*it)->map(matrix); 0514 } 0515 } 0516 } 0517 } 0518 0519 void KoPathShape::updateLastPriv(KoPathPoint **lastPoint) 0520 { 0521 // check if we are about to add a new point to a closed subpath 0522 if ((*lastPoint)->properties() & KoPathPoint::StopSubpath 0523 && (*lastPoint)->properties() & KoPathPoint::CloseSubpath) { 0524 // get the first point of the subpath 0525 KoPathPoint *subpathStart = d->subpaths.last()->first(); 0526 // clone the first point of the subpath... 0527 KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart, this); 0528 // ... and make it a normal point 0529 newLastPoint->setProperties(KoPathPoint::Normal); 0530 // now start a new subpath with the cloned start point 0531 KoSubpath *path = new KoSubpath; 0532 path->push_back(newLastPoint); 0533 d->subpaths.push_back(path); 0534 *lastPoint = newLastPoint; 0535 } else { 0536 // the subpath was not closed so the formerly last point 0537 // of the subpath is no end point anymore 0538 (*lastPoint)->unsetProperty(KoPathPoint::StopSubpath); 0539 } 0540 (*lastPoint)->unsetProperty(KoPathPoint::CloseSubpath); 0541 } 0542 0543 QList<KoPathPoint*> KoPathShape::pointsAt(const QRectF &r, const bool useControlPoints) const 0544 { 0545 QList<KoPathPoint*> result; 0546 0547 KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); 0548 for (; pathIt != d->subpaths.constEnd(); ++pathIt) { 0549 KoSubpath::const_iterator it((*pathIt)->constBegin()); 0550 for (; it != (*pathIt)->constEnd(); ++it) { 0551 if (r.contains((*it)->point())) 0552 result.append(*it); 0553 else if (useControlPoints) { 0554 if ((*it)->activeControlPoint1() && r.contains((*it)->controlPoint1())) 0555 result.append(*it); 0556 else if ((*it)->activeControlPoint2() && r.contains((*it)->controlPoint2())) 0557 result.append(*it); 0558 } 0559 } 0560 } 0561 return result; 0562 } 0563 0564 QList<KoPathSegment> KoPathShape::segmentsAt(const QRectF &r) const 0565 { 0566 QList<KoPathSegment> segments; 0567 int subpathCount = d->subpaths.count(); 0568 for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { 0569 KoSubpath * subpath = d->subpaths[subpathIndex]; 0570 int pointCount = subpath->count(); 0571 bool subpathClosed = isClosedSubpath(subpathIndex); 0572 for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) { 0573 if (pointIndex == (pointCount - 1) && ! subpathClosed) 0574 break; 0575 KoPathSegment s(subpath->at(pointIndex), subpath->at((pointIndex + 1) % pointCount)); 0576 QRectF controlRect = s.controlPointRect(); 0577 if (! r.intersects(controlRect) && ! controlRect.contains(r)) 0578 continue; 0579 QRectF bound = s.boundingRect(); 0580 if (! r.intersects(bound) && ! bound.contains(r)) 0581 continue; 0582 0583 segments.append(s); 0584 } 0585 } 0586 return segments; 0587 } 0588 0589 KoPathPointIndex KoPathShape::pathPointIndex(const KoPathPoint *point) const 0590 { 0591 for (int subpathIndex = 0; subpathIndex < d->subpaths.size(); ++subpathIndex) { 0592 KoSubpath * subpath = d->subpaths.at(subpathIndex); 0593 for (int pointPos = 0; pointPos < subpath->size(); ++pointPos) { 0594 if (subpath->at(pointPos) == point) { 0595 return KoPathPointIndex(subpathIndex, pointPos); 0596 } 0597 } 0598 } 0599 return KoPathPointIndex(-1, -1); 0600 } 0601 0602 KoPathPoint * KoPathShape::pointByIndex(const KoPathPointIndex &pointIndex) const 0603 { 0604 KoSubpath *subpath = d->subPath(pointIndex.first); 0605 0606 if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) 0607 return 0; 0608 0609 return subpath->at(pointIndex.second); 0610 } 0611 0612 KoPathSegment KoPathShape::segmentByIndex(const KoPathPointIndex &pointIndex) const 0613 { 0614 KoPathSegment segment(0, 0); 0615 0616 KoSubpath *subpath = d->subPath(pointIndex.first); 0617 0618 if (subpath != 0 && pointIndex.second >= 0 && pointIndex.second < subpath->size()) { 0619 KoPathPoint * point = subpath->at(pointIndex.second); 0620 int index = pointIndex.second; 0621 // check if we have a (closing) segment starting from the last point 0622 if ((index == subpath->size() - 1) && point->properties() & KoPathPoint::CloseSubpath) 0623 index = 0; 0624 else 0625 ++index; 0626 0627 if (index < subpath->size()) { 0628 segment = KoPathSegment(point, subpath->at(index)); 0629 } 0630 } 0631 return segment; 0632 } 0633 0634 int KoPathShape::pointCount() const 0635 { 0636 int i = 0; 0637 KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); 0638 for (; pathIt != d->subpaths.constEnd(); ++pathIt) { 0639 i += (*pathIt)->size(); 0640 } 0641 0642 return i; 0643 } 0644 0645 int KoPathShape::subpathCount() const 0646 { 0647 return d->subpaths.count(); 0648 } 0649 0650 int KoPathShape::subpathPointCount(int subpathIndex) const 0651 { 0652 KoSubpath *subpath = d->subPath(subpathIndex); 0653 0654 if (subpath == 0) 0655 return -1; 0656 0657 return subpath->size(); 0658 } 0659 0660 bool KoPathShape::isClosedSubpath(int subpathIndex) const 0661 { 0662 KoSubpath *subpath = d->subPath(subpathIndex); 0663 0664 if (subpath == 0) 0665 return false; 0666 0667 const bool firstClosed = subpath->first()->properties() & KoPathPoint::CloseSubpath; 0668 const bool lastClosed = subpath->last()->properties() & KoPathPoint::CloseSubpath; 0669 0670 return firstClosed && lastClosed; 0671 } 0672 0673 bool KoPathShape::insertPoint(KoPathPoint* point, const KoPathPointIndex &pointIndex) 0674 { 0675 KoSubpath *subpath = d->subPath(pointIndex.first); 0676 0677 if (subpath == 0 || pointIndex.second < 0 || pointIndex.second > subpath->size()) 0678 return false; 0679 0680 KoPathPoint::PointProperties properties = point->properties(); 0681 properties &= ~KoPathPoint::StartSubpath; 0682 properties &= ~KoPathPoint::StopSubpath; 0683 properties &= ~KoPathPoint::CloseSubpath; 0684 // check if new point starts subpath 0685 if (pointIndex.second == 0) { 0686 properties |= KoPathPoint::StartSubpath; 0687 // subpath was closed 0688 if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { 0689 // keep the path closed 0690 properties |= KoPathPoint::CloseSubpath; 0691 } 0692 // old first point does not start the subpath anymore 0693 subpath->first()->unsetProperty(KoPathPoint::StartSubpath); 0694 } 0695 // check if new point stops subpath 0696 else if (pointIndex.second == subpath->size()) { 0697 properties |= KoPathPoint::StopSubpath; 0698 // subpath was closed 0699 if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { 0700 // keep the path closed 0701 properties = properties | KoPathPoint::CloseSubpath; 0702 } 0703 // old last point does not end subpath anymore 0704 subpath->last()->unsetProperty(KoPathPoint::StopSubpath); 0705 } 0706 0707 point->setProperties(properties); 0708 point->setParent(this); 0709 subpath->insert(pointIndex.second , point); 0710 notifyPointsChanged(); 0711 0712 return true; 0713 } 0714 0715 KoPathPoint * KoPathShape::removePoint(const KoPathPointIndex &pointIndex) 0716 { 0717 KoSubpath *subpath = d->subPath(pointIndex.first); 0718 0719 if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) 0720 return 0; 0721 0722 KoPathPoint * point = subpath->takeAt(pointIndex.second); 0723 point->setParent(0); 0724 0725 //don't do anything (not even crash), if there was only one point 0726 if (pointCount()==0) { 0727 return point; 0728 } 0729 // check if we removed the first point 0730 else if (pointIndex.second == 0) { 0731 // first point removed, set new StartSubpath 0732 subpath->first()->setProperty(KoPathPoint::StartSubpath); 0733 // check if path was closed 0734 if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { 0735 // keep path closed 0736 subpath->first()->setProperty(KoPathPoint::CloseSubpath); 0737 } 0738 } 0739 // check if we removed the last point 0740 else if (pointIndex.second == subpath->size()) { // use size as point is already removed 0741 // last point removed, set new StopSubpath 0742 subpath->last()->setProperty(KoPathPoint::StopSubpath); 0743 // check if path was closed 0744 if (point->properties() & KoPathPoint::CloseSubpath) { 0745 // keep path closed 0746 subpath->last()->setProperty(KoPathPoint::CloseSubpath); 0747 } 0748 } 0749 0750 notifyPointsChanged(); 0751 0752 return point; 0753 } 0754 0755 bool KoPathShape::breakAfter(const KoPathPointIndex &pointIndex) 0756 { 0757 KoSubpath *subpath = d->subPath(pointIndex.first); 0758 0759 if (!subpath || pointIndex.second < 0 || pointIndex.second > subpath->size() - 2 0760 || isClosedSubpath(pointIndex.first)) 0761 return false; 0762 0763 KoSubpath * newSubpath = new KoSubpath; 0764 0765 int size = subpath->size(); 0766 for (int i = pointIndex.second + 1; i < size; ++i) { 0767 newSubpath->append(subpath->takeAt(pointIndex.second + 1)); 0768 } 0769 // now make the first point of the new subpath a starting node 0770 newSubpath->first()->setProperty(KoPathPoint::StartSubpath); 0771 // the last point of the old subpath is now an ending node 0772 subpath->last()->setProperty(KoPathPoint::StopSubpath); 0773 0774 // insert the new subpath after the broken one 0775 d->subpaths.insert(pointIndex.first + 1, newSubpath); 0776 notifyPointsChanged(); 0777 0778 return true; 0779 } 0780 0781 bool KoPathShape::join(int subpathIndex) 0782 { 0783 KoSubpath *subpath = d->subPath(subpathIndex); 0784 KoSubpath *nextSubpath = d->subPath(subpathIndex + 1); 0785 0786 if (!subpath || !nextSubpath || isClosedSubpath(subpathIndex) 0787 || isClosedSubpath(subpathIndex+1)) 0788 return false; 0789 0790 // the last point of the subpath does not end the subpath anymore 0791 subpath->last()->unsetProperty(KoPathPoint::StopSubpath); 0792 // the first point of the next subpath does not start a subpath anymore 0793 nextSubpath->first()->unsetProperty(KoPathPoint::StartSubpath); 0794 0795 // append the second subpath to the first 0796 Q_FOREACH (KoPathPoint * p, *nextSubpath) 0797 subpath->append(p); 0798 0799 // remove the nextSubpath from path 0800 d->subpaths.removeAt(subpathIndex + 1); 0801 0802 // delete it as it is no longer possible to use it 0803 delete nextSubpath; 0804 0805 notifyPointsChanged(); 0806 0807 return true; 0808 } 0809 0810 bool KoPathShape::moveSubpath(int oldSubpathIndex, int newSubpathIndex) 0811 { 0812 KoSubpath *subpath = d->subPath(oldSubpathIndex); 0813 0814 if (subpath == 0 || newSubpathIndex >= d->subpaths.size()) 0815 return false; 0816 0817 if (oldSubpathIndex == newSubpathIndex) 0818 return true; 0819 0820 d->subpaths.removeAt(oldSubpathIndex); 0821 d->subpaths.insert(newSubpathIndex, subpath); 0822 0823 notifyPointsChanged(); 0824 0825 return true; 0826 } 0827 0828 KoPathPointIndex KoPathShape::openSubpath(const KoPathPointIndex &pointIndex) 0829 { 0830 KoSubpath *subpath = d->subPath(pointIndex.first); 0831 0832 if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() 0833 || !isClosedSubpath(pointIndex.first)) 0834 return KoPathPointIndex(-1, -1); 0835 0836 KoPathPoint * oldStartPoint = subpath->first(); 0837 // the old starting node no longer starts the subpath 0838 oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); 0839 // the old end node no longer closes the subpath 0840 subpath->last()->unsetProperty(KoPathPoint::StopSubpath); 0841 0842 // reorder the subpath 0843 for (int i = 0; i < pointIndex.second; ++i) { 0844 subpath->append(subpath->takeFirst()); 0845 } 0846 // make the first point a start node 0847 subpath->first()->setProperty(KoPathPoint::StartSubpath); 0848 // make the last point an end node 0849 subpath->last()->setProperty(KoPathPoint::StopSubpath); 0850 0851 notifyPointsChanged(); 0852 0853 return pathPointIndex(oldStartPoint); 0854 } 0855 0856 KoPathPointIndex KoPathShape::closeSubpath(const KoPathPointIndex &pointIndex) 0857 { 0858 KoSubpath *subpath = d->subPath(pointIndex.first); 0859 0860 if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() 0861 || isClosedSubpath(pointIndex.first)) 0862 return KoPathPointIndex(-1, -1); 0863 0864 KoPathPoint * oldStartPoint = subpath->first(); 0865 // the old starting node no longer starts the subpath 0866 oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); 0867 // the old end node no longer ends the subpath 0868 subpath->last()->unsetProperty(KoPathPoint::StopSubpath); 0869 0870 // reorder the subpath 0871 for (int i = 0; i < pointIndex.second; ++i) { 0872 subpath->append(subpath->takeFirst()); 0873 } 0874 subpath->first()->setProperty(KoPathPoint::StartSubpath); 0875 subpath->last()->setProperty(KoPathPoint::StopSubpath); 0876 0877 closeSubpathPriv(subpath); 0878 0879 notifyPointsChanged(); 0880 0881 return pathPointIndex(oldStartPoint); 0882 } 0883 0884 bool KoPathShape::reverseSubpath(int subpathIndex) 0885 { 0886 KoSubpath *subpath = d->subPath(subpathIndex); 0887 0888 if (subpath == 0) 0889 return false; 0890 0891 int size = subpath->size(); 0892 for (int i = 0; i < size; ++i) { 0893 KoPathPoint *p = subpath->takeAt(i); 0894 p->reverse(); 0895 subpath->prepend(p); 0896 } 0897 0898 // adjust the position dependent properties 0899 KoPathPoint *first = subpath->first(); 0900 KoPathPoint *last = subpath->last(); 0901 0902 KoPathPoint::PointProperties firstProps = first->properties(); 0903 KoPathPoint::PointProperties lastProps = last->properties(); 0904 0905 firstProps |= KoPathPoint::StartSubpath; 0906 firstProps &= ~KoPathPoint::StopSubpath; 0907 lastProps |= KoPathPoint::StopSubpath; 0908 lastProps &= ~KoPathPoint::StartSubpath; 0909 if (firstProps & KoPathPoint::CloseSubpath) { 0910 firstProps |= KoPathPoint::CloseSubpath; 0911 lastProps |= KoPathPoint::CloseSubpath; 0912 } 0913 first->setProperties(firstProps); 0914 last->setProperties(lastProps); 0915 0916 notifyPointsChanged(); 0917 0918 return true; 0919 } 0920 0921 KoSubpath * KoPathShape::removeSubpath(int subpathIndex) 0922 { 0923 KoSubpath *subpath = d->subPath(subpathIndex); 0924 0925 if (subpath != 0) { 0926 Q_FOREACH (KoPathPoint* point, *subpath) { 0927 point->setParent(this); 0928 } 0929 d->subpaths.removeAt(subpathIndex); 0930 } 0931 0932 notifyPointsChanged(); 0933 0934 return subpath; 0935 } 0936 0937 bool KoPathShape::addSubpath(KoSubpath * subpath, int subpathIndex) 0938 { 0939 if (subpathIndex < 0 || subpathIndex > d->subpaths.size()) 0940 return false; 0941 0942 Q_FOREACH (KoPathPoint* point, *subpath) { 0943 point->setParent(this); 0944 } 0945 0946 d->subpaths.insert(subpathIndex, subpath); 0947 notifyPointsChanged(); 0948 0949 0950 return true; 0951 } 0952 int KoPathShape::combine(KoPathShape *path) 0953 { 0954 int insertSegmentPosition = -1; 0955 if (!path) return insertSegmentPosition; 0956 0957 QTransform pathMatrix = path->absoluteTransformation(); 0958 QTransform myMatrix = absoluteTransformation().inverted(); 0959 0960 Q_FOREACH (KoSubpath* subpath, path->d->subpaths) { 0961 KoSubpath *newSubpath = new KoSubpath(); 0962 0963 Q_FOREACH (KoPathPoint* point, *subpath) { 0964 KoPathPoint *newPoint = new KoPathPoint(*point, this); 0965 newPoint->map(pathMatrix); 0966 newPoint->map(myMatrix); 0967 newSubpath->append(newPoint); 0968 } 0969 d->subpaths.append(newSubpath); 0970 0971 if (insertSegmentPosition < 0) { 0972 insertSegmentPosition = d->subpaths.size() - 1; 0973 } 0974 } 0975 normalize(); 0976 0977 notifyPointsChanged(); 0978 return insertSegmentPosition; 0979 } 0980 0981 bool KoPathShape::separate(QList<KoPathShape*> & separatedPaths) 0982 { 0983 if (! d->subpaths.size()) 0984 return false; 0985 0986 QTransform myMatrix = absoluteTransformation(); 0987 0988 Q_FOREACH (KoSubpath* subpath, d->subpaths) { 0989 KoPathShape *shape = new KoPathShape(); 0990 0991 shape->setStroke(stroke()); 0992 shape->setBackground(background()); 0993 shape->setShapeId(shapeId()); 0994 shape->setZIndex(zIndex()); 0995 0996 KoSubpath *newSubpath = new KoSubpath(); 0997 0998 Q_FOREACH (KoPathPoint* point, *subpath) { 0999 KoPathPoint *newPoint = new KoPathPoint(*point, shape); 1000 newPoint->map(myMatrix); 1001 newSubpath->append(newPoint); 1002 } 1003 shape->d->subpaths.append(newSubpath); 1004 shape->normalize(); 1005 1006 // NOTE: shape cannot have any listeners yet, so no notification about 1007 // points modification is needed 1008 1009 separatedPaths.append(shape); 1010 } 1011 return true; 1012 } 1013 1014 void KoPathShape::closeSubpathPriv(KoSubpath *subpath) 1015 { 1016 if (! subpath) 1017 return; 1018 1019 subpath->last()->setProperty(KoPathPoint::CloseSubpath); 1020 subpath->first()->setProperty(KoPathPoint::CloseSubpath); 1021 1022 notifyPointsChanged(); 1023 } 1024 1025 void KoPathShape::closeMergeSubpathPriv(KoSubpath *subpath) 1026 { 1027 if (! subpath || subpath->size() < 2) 1028 return; 1029 1030 KoPathPoint * lastPoint = subpath->last(); 1031 KoPathPoint * firstPoint = subpath->first(); 1032 1033 // check if first and last points are coincident 1034 if (lastPoint->point() == firstPoint->point()) { 1035 // we are removing the current last point and 1036 // reuse its first control point if active 1037 firstPoint->setProperty(KoPathPoint::StartSubpath); 1038 firstPoint->setProperty(KoPathPoint::CloseSubpath); 1039 if (lastPoint->activeControlPoint1()) 1040 firstPoint->setControlPoint1(lastPoint->controlPoint1()); 1041 // remove last point 1042 delete subpath->takeLast(); 1043 // the new last point closes the subpath now 1044 lastPoint = subpath->last(); 1045 lastPoint->setProperty(KoPathPoint::StopSubpath); 1046 lastPoint->setProperty(KoPathPoint::CloseSubpath); 1047 1048 notifyPointsChanged(); 1049 } else { 1050 closeSubpathPriv(subpath); 1051 } 1052 } 1053 1054 const KoSubpathList &KoPathShape::subpaths() const 1055 { 1056 return d->subpaths; 1057 } 1058 1059 KoSubpathList &KoPathShape::subpaths() 1060 { 1061 return d->subpaths; 1062 } 1063 1064 void KoPathShape::map(const QTransform &matrix) 1065 { 1066 return d->map(matrix); 1067 } 1068 1069 KoSubpath *KoPathShape::Private::subPath(int subpathIndex) const 1070 { 1071 if (subpathIndex < 0 || subpathIndex >= subpaths.size()) 1072 return 0; 1073 1074 return subpaths.at(subpathIndex); 1075 } 1076 1077 QString KoPathShape::pathShapeId() const 1078 { 1079 return KoPathShapeId; 1080 } 1081 1082 QString KoPathShape::toString(const QTransform &matrix) const 1083 { 1084 QString pathString; 1085 1086 // iterate over all subpaths 1087 KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); 1088 for (; pathIt != d->subpaths.constEnd(); ++pathIt) { 1089 KoSubpath::const_iterator pointIt((*pathIt)->constBegin()); 1090 // keep a pointer to the first point of the subpath 1091 KoPathPoint *firstPoint(*pointIt); 1092 // keep a pointer to the previous point of the subpath 1093 KoPathPoint *lastPoint = firstPoint; 1094 // keep track if the previous point has an active control point 2 1095 bool activeControlPoint2 = false; 1096 1097 // iterate over all points of the current subpath 1098 for (; pointIt != (*pathIt)->constEnd(); ++pointIt) { 1099 KoPathPoint *currPoint(*pointIt); 1100 if (!currPoint) { 1101 qWarning() << "Found a zero point in the shape's path!"; 1102 continue; 1103 } 1104 // first point of subpath ? 1105 if (currPoint == firstPoint) { 1106 // are we starting a subpath ? 1107 if (currPoint->properties() & KoPathPoint::StartSubpath) { 1108 const QPointF p = matrix.map(currPoint->point()); 1109 pathString += QString("M%1 %2").arg(p.x()).arg(p.y()); 1110 } 1111 } 1112 // end point of curve segment ? 1113 else if (activeControlPoint2 || currPoint->activeControlPoint1()) { 1114 // check if we have a cubic or quadratic curve 1115 const bool isCubic = activeControlPoint2 && currPoint->activeControlPoint1(); 1116 KoPathSegment cubicSeg = isCubic ? KoPathSegment(lastPoint, currPoint) 1117 : KoPathSegment(lastPoint, currPoint).toCubic(); 1118 if (cubicSeg.first() && cubicSeg.second()) { 1119 const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); 1120 const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); 1121 const QPointF p = matrix.map(cubicSeg.second()->point()); 1122 pathString += QString("C%1 %2 %3 %4 %5 %6") 1123 .arg(cp1.x()).arg(cp1.y()) 1124 .arg(cp2.x()).arg(cp2.y()) 1125 .arg(p.x()).arg(p.y()); 1126 } 1127 } 1128 // end point of line segment! 1129 else { 1130 const QPointF p = matrix.map(currPoint->point()); 1131 pathString += QString("L%1 %2").arg(p.x()).arg(p.y()); 1132 } 1133 // last point closes subpath ? 1134 if (currPoint->properties() & KoPathPoint::StopSubpath 1135 && currPoint->properties() & KoPathPoint::CloseSubpath) { 1136 // add curve when there is a curve on the way to the first point 1137 if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { 1138 // check if we have a cubic or quadratic curve 1139 const bool isCubic = currPoint->activeControlPoint2() && firstPoint->activeControlPoint1(); 1140 KoPathSegment cubicSeg = isCubic ? KoPathSegment(currPoint, firstPoint) 1141 : KoPathSegment(currPoint, firstPoint).toCubic(); 1142 if (cubicSeg.first() && cubicSeg.second()) { 1143 const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); 1144 const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); 1145 1146 const QPointF p = matrix.map(cubicSeg.second()->point()); 1147 pathString += QString("C%1 %2 %3 %4 %5 %6") 1148 .arg(cp1.x()).arg(cp1.y()) 1149 .arg(cp2.x()).arg(cp2.y()) 1150 .arg(p.x()).arg(p.y()); 1151 } 1152 } 1153 pathString += QString("Z"); 1154 } 1155 1156 activeControlPoint2 = currPoint->activeControlPoint2(); 1157 lastPoint = currPoint; 1158 } 1159 } 1160 1161 return pathString; 1162 } 1163 1164 char nodeType(const KoPathPoint * point) 1165 { 1166 if (point->properties() & KoPathPoint::IsSmooth) { 1167 return 's'; 1168 } 1169 else if (point->properties() & KoPathPoint::IsSymmetric) { 1170 return 'z'; 1171 } 1172 else { 1173 return 'c'; 1174 } 1175 } 1176 1177 QString KoPathShape::nodeTypes() const 1178 { 1179 QString types; 1180 KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); 1181 for (; pathIt != d->subpaths.constEnd(); ++pathIt) { 1182 KoSubpath::const_iterator it((*pathIt)->constBegin()); 1183 for (; it != (*pathIt)->constEnd(); ++it) { 1184 if (it == (*pathIt)->constBegin()) { 1185 types.append('c'); 1186 } 1187 else { 1188 types.append(nodeType(*it)); 1189 } 1190 1191 if ((*it)->properties() & KoPathPoint::StopSubpath 1192 && (*it)->properties() & KoPathPoint::CloseSubpath) { 1193 KoPathPoint * firstPoint = (*pathIt)->first(); 1194 types.append(nodeType(firstPoint)); 1195 } 1196 } 1197 } 1198 return types; 1199 } 1200 1201 void updateNodeType(KoPathPoint * point, const QChar & nodeType) 1202 { 1203 if (nodeType == 's') { 1204 point->setProperty(KoPathPoint::IsSmooth); 1205 } 1206 else if (nodeType == 'z') { 1207 point->setProperty(KoPathPoint::IsSymmetric); 1208 } 1209 } 1210 1211 void KoPathShape::loadNodeTypes(const QString &nodeTypes) 1212 { 1213 QString::const_iterator nIt(nodeTypes.constBegin()); 1214 KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); 1215 for (; pathIt != d->subpaths.constEnd(); ++pathIt) { 1216 KoSubpath::const_iterator it((*pathIt)->constBegin()); 1217 for (; it != (*pathIt)->constEnd(); ++it, nIt++) { 1218 // be sure not to crash if there are not enough nodes in nodeTypes 1219 if (nIt == nodeTypes.constEnd()) { 1220 warnFlake << "not enough nodes in sodipodi:nodetypes"; 1221 return; 1222 } 1223 // the first node is always of type 'c' 1224 if (it != (*pathIt)->constBegin()) { 1225 updateNodeType(*it, *nIt); 1226 } 1227 1228 if ((*it)->properties() & KoPathPoint::StopSubpath 1229 && (*it)->properties() & KoPathPoint::CloseSubpath) { 1230 ++nIt; 1231 updateNodeType((*pathIt)->first(), *nIt); 1232 } 1233 } 1234 } 1235 } 1236 1237 Qt::FillRule KoPathShape::fillRule() const 1238 { 1239 return d->fillRule; 1240 } 1241 1242 void KoPathShape::setFillRule(Qt::FillRule fillRule) 1243 { 1244 d->fillRule = fillRule; 1245 } 1246 1247 KoPathShape * KoPathShape::createShapeFromPainterPath(const QPainterPath &path) 1248 { 1249 KoPathShape * shape = new KoPathShape(); 1250 1251 int elementCount = path.elementCount(); 1252 for (int i = 0; i < elementCount; i++) { 1253 QPainterPath::Element element = path.elementAt(i); 1254 switch (element.type) { 1255 case QPainterPath::MoveToElement: 1256 shape->moveTo(QPointF(element.x, element.y)); 1257 break; 1258 case QPainterPath::LineToElement: 1259 shape->lineTo(QPointF(element.x, element.y)); 1260 break; 1261 case QPainterPath::CurveToElement: 1262 shape->curveTo(QPointF(element.x, element.y), 1263 QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), 1264 QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y)); 1265 break; 1266 default: 1267 continue; 1268 } 1269 } 1270 1271 shape->setShapeId(KoPathShapeId); 1272 1273 //shape->normalize(); 1274 return shape; 1275 } 1276 1277 bool KoPathShape::hitTest(const QPointF &position) const 1278 { 1279 if (parent() && parent()->isClipped(this) && ! parent()->hitTest(position)) 1280 return false; 1281 1282 QPointF point = absoluteTransformation().inverted().map(position); 1283 const QPainterPath outlinePath = outline(); 1284 if (stroke()) { 1285 KoInsets insets; 1286 stroke()->strokeInsets(this, insets); 1287 QRectF roi(QPointF(-insets.left, -insets.top), QPointF(insets.right, insets.bottom)); 1288 1289 roi.moveCenter(point); 1290 if (outlinePath.intersects(roi) || outlinePath.contains(roi)) 1291 return true; 1292 } else { 1293 if (outlinePath.contains(point)) 1294 return true; 1295 } 1296 1297 // if there is no shadow we can as well just leave 1298 if (! shadow()) 1299 return false; 1300 1301 // the shadow has an offset to the shape, so we simply 1302 // check if the position minus the shadow offset hits the shape 1303 point = absoluteTransformation().inverted().map(position - shadow()->offset()); 1304 1305 return outlinePath.contains(point); 1306 } 1307 1308 void KoPathShape::setMarker(KoMarker *marker, KoFlake::MarkerPosition pos) 1309 { 1310 if (!marker && d->markersNew.contains(pos)) { 1311 d->markersNew.remove(pos); 1312 } else { 1313 d->markersNew[pos] = marker; 1314 } 1315 1316 notifyChanged(); 1317 shapeChangedPriv(StrokeChanged); 1318 } 1319 1320 KoMarker *KoPathShape::marker(KoFlake::MarkerPosition pos) const 1321 { 1322 return d->markersNew[pos].data(); 1323 } 1324 1325 bool KoPathShape::hasMarkers() const 1326 { 1327 return !d->markersNew.isEmpty(); 1328 } 1329 1330 bool KoPathShape::autoFillMarkers() const 1331 { 1332 return d->autoFillMarkers; 1333 } 1334 1335 void KoPathShape::setAutoFillMarkers(bool value) 1336 { 1337 d->autoFillMarkers = value; 1338 } 1339 1340 void KoPathShape::recommendPointSelectionChange(const QList<KoPathPointIndex> &newSelection) 1341 { 1342 Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) { 1343 PointSelectionChangeListener *pointListener = dynamic_cast<PointSelectionChangeListener*>(listener); 1344 if (pointListener) { 1345 pointListener->recommendPointSelectionChange(this, newSelection); 1346 } 1347 } 1348 } 1349 1350 void KoPathShape::notifyPointsChanged() 1351 { 1352 Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) { 1353 PointSelectionChangeListener *pointListener = dynamic_cast<PointSelectionChangeListener*>(listener); 1354 if (pointListener) { 1355 pointListener->notifyPathPointsChanged(this); 1356 } 1357 } 1358 } 1359 1360 QPainterPath KoPathShape::pathStroke(const QPen &pen) const 1361 { 1362 if (d->subpaths.isEmpty()) { 1363 return QPainterPath(); 1364 } 1365 QPainterPath pathOutline; 1366 1367 QPainterPathStroker stroker; 1368 stroker.setWidth(0); 1369 stroker.setJoinStyle(Qt::MiterJoin); 1370 stroker.setWidth(pen.widthF()); 1371 stroker.setJoinStyle(pen.joinStyle()); 1372 stroker.setMiterLimit(pen.miterLimit()); 1373 stroker.setCapStyle(pen.capStyle()); 1374 stroker.setDashOffset(pen.dashOffset()); 1375 stroker.setDashPattern(pen.dashPattern()); 1376 1377 QPainterPath path = stroker.createStroke(outline()); 1378 1379 pathOutline.addPath(path); 1380 pathOutline.setFillRule(Qt::WindingFill); 1381 1382 return pathOutline; 1383 } 1384 1385 void KoPathShape::PointSelectionChangeListener::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) 1386 { 1387 Q_UNUSED(type); 1388 Q_UNUSED(shape); 1389 }