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