Warning, file /office/calligra/libs/flake/KoConnectionShape.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* This file is part of the KDE project 0002 * Copyright (C) 2007 Boudewijn Rempt <boud@kde.org> 0003 * Copyright (C) 2007,2009 Thorsten Zachmann <zachmann@kde.org> 0004 * Copyright (C) 2007,2009,2010 Jan Hambrecht <jaham@gmx.net> 0005 * 0006 * This library is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU Library General Public 0008 * License as published by the Free Software Foundation; either 0009 * version 2 of the License, or (at your option) any later version. 0010 * 0011 * This library is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 * Library General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU Library General Public License 0017 * along with this library; see the file COPYING.LIB. If not, write to 0018 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0019 * Boston, MA 02110-1301, USA. 0020 */ 0021 0022 #include "KoConnectionShape.h" 0023 #include "KoConnectionShape_p.h" 0024 0025 #include "KoViewConverter.h" 0026 #include "KoShapeLoadingContext.h" 0027 #include "KoShapeSavingContext.h" 0028 #include "KoConnectionShapeLoadingUpdater.h" 0029 #include "KoPathShapeLoader.h" 0030 #include "KoPathPoint.h" 0031 #include "KoShapeBackground.h" 0032 #include <KoXmlReader.h> 0033 #include <KoXmlWriter.h> 0034 #include <KoXmlNS.h> 0035 #include <KoUnit.h> 0036 #include <QPainter> 0037 #include <QPainterPath> 0038 0039 #include <FlakeDebug.h> 0040 0041 KoConnectionShapePrivate::KoConnectionShapePrivate(KoConnectionShape *q) 0042 : KoParameterShapePrivate(q), 0043 shape1(0), 0044 shape2(0), 0045 connectionPointId1(-1), 0046 connectionPointId2(-1), 0047 connectionType(KoConnectionShape::Standard), 0048 forceUpdate(false), 0049 hasCustomPath(false) 0050 { 0051 } 0052 0053 QPointF KoConnectionShapePrivate::escapeDirection(int handleId) const 0054 { 0055 Q_Q(const KoConnectionShape); 0056 QPointF direction; 0057 if (handleConnected(handleId)) { 0058 KoShape *attachedShape = handleId == KoConnectionShape::StartHandle ? shape1 : shape2; 0059 int connectionPointId = handleId == KoConnectionShape::StartHandle ? connectionPointId1 : connectionPointId2; 0060 KoConnectionPoint::EscapeDirection ed = attachedShape->connectionPoint(connectionPointId).escapeDirection; 0061 if (ed == KoConnectionPoint::AllDirections) { 0062 QPointF handlePoint = q->shapeToDocument(handles[handleId]); 0063 QPointF centerPoint = attachedShape->absolutePosition(KoFlake::CenteredPosition); 0064 0065 /* 0066 * Determine the best escape direction from the position of the handle point 0067 * and the position and orientation of the attached shape. 0068 * The idea is to define 4 sectors, one for each edge of the attached shape. 0069 * Each sector starts at the center point of the attached shape and has it 0070 * left and right edge going through the two points which define the edge. 0071 * Then we check which sector contains our handle point, for which we can 0072 * simply calculate the corresponding direction which is orthogonal to the 0073 * corresponding bounding box edge. 0074 * From that we derive the escape direction from looking at the main coordinate 0075 * of the orthogonal direction. 0076 */ 0077 // define our edge points in the right order 0078 const KoFlake::Position corners[4] = { 0079 KoFlake::BottomRightCorner, 0080 KoFlake::BottomLeftCorner, 0081 KoFlake::TopLeftCorner, 0082 KoFlake::TopRightCorner 0083 }; 0084 0085 QPointF vHandle = handlePoint-centerPoint; 0086 for (int i = 0; i < 4; ++i) { 0087 // first point of bounding box edge 0088 QPointF p1 = attachedShape->absolutePosition(corners[i]); 0089 // second point of bounding box edge 0090 QPointF p2 = attachedShape->absolutePosition(corners[(i+1)%4]); 0091 // check on which side of the first sector edge our second sector edge is 0092 const qreal c0 = crossProd(p1-centerPoint, p2-centerPoint); 0093 // check on which side of the first sector edge our handle point is 0094 const qreal c1 = crossProd(p1-centerPoint, vHandle); 0095 // second egde and handle point must be on the same side of first edge 0096 if ((c0 < 0 && c1 > 0) || (c0 > 0 && c1 < 0)) 0097 continue; 0098 // check on which side of the handle point our second sector edge is 0099 const qreal c2 = crossProd(vHandle, p2-centerPoint); 0100 // second edge must be on the same side of the handle point as on first edge 0101 if ((c0 < 0 && c2 > 0) || (c0 > 0 && c2 < 0)) 0102 continue; 0103 // now we found the correct edge 0104 QPointF vDir = 0.5 *(p1+p2) - centerPoint; 0105 // look at coordinate with the greatest absolute value 0106 // and construct our escape direction accordingly 0107 const qreal xabs = qAbs<qreal>(vDir.x()); 0108 const qreal yabs = qAbs<qreal>(vDir.y()); 0109 if (xabs > yabs) { 0110 direction.rx() = vDir.x() > 0 ? 1.0 : -1.0; 0111 direction.ry() = 0.0; 0112 } else { 0113 direction.rx() = 0.0; 0114 direction.ry() = vDir.y() > 0 ? 1.0 : -1.0; 0115 } 0116 break; 0117 } 0118 } else if (ed == KoConnectionPoint::HorizontalDirections) { 0119 QPointF handlePoint = q->shapeToDocument(handles[handleId]); 0120 QPointF centerPoint = attachedShape->absolutePosition(KoFlake::CenteredPosition); 0121 // use horizontal direction pointing away from center point 0122 if (handlePoint.x() < centerPoint.x()) 0123 direction = QPointF(-1.0, 0.0); 0124 else 0125 direction = QPointF(1.0, 0.0); 0126 } else if (ed == KoConnectionPoint::VerticalDirections) { 0127 QPointF handlePoint = q->shapeToDocument(handles[handleId]); 0128 QPointF centerPoint = attachedShape->absolutePosition(KoFlake::CenteredPosition); 0129 // use vertical direction pointing away from center point 0130 if (handlePoint.y() < centerPoint.y()) 0131 direction = QPointF(0.0, -1.0); 0132 else 0133 direction = QPointF(0.0, 1.0); 0134 } else if (ed == KoConnectionPoint::LeftDirection) { 0135 direction = QPointF(-1.0, 0.0); 0136 } else if (ed == KoConnectionPoint::RightDirection) { 0137 direction = QPointF(1.0, 0.0); 0138 } else if (ed == KoConnectionPoint::UpDirection) { 0139 direction = QPointF(0.0, -1.0); 0140 } else if (ed == KoConnectionPoint::DownDirection) { 0141 direction = QPointF(0.0, 1.0); 0142 } 0143 0144 // transform escape direction by using our own transformation matrix 0145 QTransform invMatrix = q->absoluteTransformation(0).inverted(); 0146 direction = invMatrix.map(direction) - invMatrix.map(QPointF()); 0147 direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y()); 0148 } 0149 0150 return direction; 0151 } 0152 0153 bool KoConnectionShapePrivate::intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect) 0154 { 0155 qreal sp1 = scalarProd(d1, p2 - p1); 0156 if (sp1 < 0.0) 0157 return false; 0158 0159 qreal sp2 = scalarProd(d2, p1 - p2); 0160 if (sp2 < 0.0) 0161 return false; 0162 0163 // use cross product to check if rays intersects at all 0164 qreal cp = crossProd(d1, d2); 0165 if (cp == 0.0) { 0166 // rays are parallel or coincident 0167 if (p1.x() == p2.x() && d1.x() == 0.0 && d1.y() != d2.y()) { 0168 // vertical, coincident 0169 isect = 0.5 * (p1 + p2); 0170 } else if (p1.y() == p2.y() && d1.y() == 0.0 && d1.x() != d2.x()) { 0171 // horizontal, coincident 0172 isect = 0.5 * (p1 + p2); 0173 } else { 0174 return false; 0175 } 0176 } else { 0177 // they are intersecting normally 0178 isect = p1 + sp1 * d1; 0179 } 0180 0181 return true; 0182 } 0183 0184 QPointF KoConnectionShapePrivate::perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2) 0185 { 0186 QPointF perpendicular(d1.y(), -d1.x()); 0187 qreal sp = scalarProd(perpendicular, p2 - p1); 0188 if (sp < 0.0) 0189 perpendicular *= -1.0; 0190 0191 return perpendicular; 0192 } 0193 0194 void KoConnectionShapePrivate::normalPath(const qreal MinimumEscapeLength) 0195 { 0196 // Clear the path to build it again. 0197 path.clear(); 0198 path.append(handles[KoConnectionShape::StartHandle]); 0199 0200 QVector<QPointF> edges1; 0201 QVector<QPointF> edges2; 0202 0203 QPointF direction1 = escapeDirection(KoConnectionShape::StartHandle); 0204 QPointF direction2 = escapeDirection(KoConnectionShape::EndHandle); 0205 0206 QPointF edgePoint1 = handles[KoConnectionShape::StartHandle] + MinimumEscapeLength * direction1; 0207 QPointF edgePoint2 = handles[KoConnectionShape::EndHandle] + MinimumEscapeLength * direction2; 0208 0209 edges1.append(edgePoint1); 0210 edges2.prepend(edgePoint2); 0211 0212 if (handleConnected(KoConnectionShape::StartHandle) && handleConnected(KoConnectionShape::EndHandle)) { 0213 QPointF intersection; 0214 bool connected = false; 0215 do { 0216 // first check if directions from current edge points intersect 0217 if (intersects(edgePoint1, direction1, edgePoint2, direction2, intersection)) { 0218 // directions intersect, we have another edge point and be done 0219 edges1.append(intersection); 0220 break; 0221 } 0222 0223 // check if we are going toward the other handle 0224 qreal sp = scalarProd(direction1, edgePoint2 - edgePoint1); 0225 if (sp >= 0.0) { 0226 // if we are having the same direction, go all the way toward 0227 // the other handle, else only go half the way 0228 if (direction1 == direction2) 0229 edgePoint1 += sp * direction1; 0230 else 0231 edgePoint1 += 0.5 * sp * direction1; 0232 edges1.append(edgePoint1); 0233 // switch direction 0234 direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2); 0235 } else { 0236 // we are not going into the same direction, so switch direction 0237 direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2); 0238 } 0239 } while (! connected); 0240 } 0241 0242 path.append(edges1); 0243 path.append(edges2); 0244 0245 path.append(handles[KoConnectionShape::EndHandle]); 0246 } 0247 0248 qreal KoConnectionShapePrivate::scalarProd(const QPointF &v1, const QPointF &v2) const 0249 { 0250 return v1.x() * v2.x() + v1.y() * v2.y(); 0251 } 0252 0253 qreal KoConnectionShapePrivate::crossProd(const QPointF &v1, const QPointF &v2) const 0254 { 0255 return v1.x() * v2.y() - v1.y() * v2.x(); 0256 } 0257 0258 bool KoConnectionShapePrivate::handleConnected(int handleId) const 0259 { 0260 if (handleId == KoConnectionShape::StartHandle && shape1 && connectionPointId1 >= 0) 0261 return true; 0262 if (handleId == KoConnectionShape::EndHandle && shape2 && connectionPointId2 >= 0) 0263 return true; 0264 0265 return false; 0266 } 0267 0268 void KoConnectionShape::updateConnections() 0269 { 0270 Q_D(KoConnectionShape); 0271 bool updateHandles = false; 0272 0273 if (d->handleConnected(StartHandle)) { 0274 if (d->shape1->hasConnectionPoint(d->connectionPointId1)) { 0275 // map connection point into our shape coordinates 0276 QPointF p = documentToShape(d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position)); 0277 if (d->handles[StartHandle] != p) { 0278 d->handles[StartHandle] = p; 0279 updateHandles = true; 0280 } 0281 } 0282 } 0283 if (d->handleConnected(EndHandle)) { 0284 if (d->shape2->hasConnectionPoint(d->connectionPointId2)) { 0285 // map connection point into our shape coordinates 0286 QPointF p = documentToShape(d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position)); 0287 if (d->handles[EndHandle] != p) { 0288 d->handles[EndHandle] = p; 0289 updateHandles = true; 0290 } 0291 } 0292 } 0293 0294 if (updateHandles || d->forceUpdate) { 0295 update(); // ugly, for repainting the connection we just changed 0296 updatePath(QSizeF()); 0297 update(); // ugly, for repainting the connection we just changed 0298 d->forceUpdate = false; 0299 } 0300 } 0301 0302 KoConnectionShape::KoConnectionShape() 0303 : KoParameterShape(*(new KoConnectionShapePrivate(this))) 0304 { 0305 Q_D(KoConnectionShape); 0306 d->handles.push_back(QPointF(0, 0)); 0307 d->handles.push_back(QPointF(140, 140)); 0308 0309 moveTo(d->handles[StartHandle]); 0310 lineTo(d->handles[EndHandle]); 0311 0312 updatePath(QSizeF(140, 140)); 0313 0314 clearConnectionPoints(); 0315 } 0316 0317 KoConnectionShape::~KoConnectionShape() 0318 { 0319 Q_D(KoConnectionShape); 0320 if (d->shape1) 0321 d->shape1->removeDependee(this); 0322 if (d->shape2) 0323 d->shape2->removeDependee(this); 0324 } 0325 0326 void KoConnectionShape::saveOdf(KoShapeSavingContext & context) const 0327 { 0328 Q_D(const KoConnectionShape); 0329 context.xmlWriter().startElement("draw:connector"); 0330 saveOdfAttributes(context, OdfMandatories | OdfAdditionalAttributes); 0331 0332 switch (d->connectionType) { 0333 case Lines: 0334 context.xmlWriter().addAttribute("draw:type", "lines"); 0335 break; 0336 case Straight: 0337 context.xmlWriter().addAttribute("draw:type", "line"); 0338 break; 0339 case Curve: 0340 context.xmlWriter().addAttribute("draw:type", "curve"); 0341 break; 0342 default: 0343 context.xmlWriter().addAttribute("draw:type", "standard"); 0344 break; 0345 } 0346 0347 if (d->shape1) { 0348 context.xmlWriter().addAttribute("draw:start-shape", context.xmlid(d->shape1, "shape", KoElementReference::Counter).toString()); 0349 context.xmlWriter().addAttribute("draw:start-glue-point", d->connectionPointId1); 0350 } else { 0351 QPointF p(shapeToDocument(d->handles[StartHandle]) * context.shapeOffset(this)); 0352 context.xmlWriter().addAttributePt("svg:x1", p.x()); 0353 context.xmlWriter().addAttributePt("svg:y1", p.y()); 0354 } 0355 if (d->shape2) { 0356 context.xmlWriter().addAttribute("draw:end-shape", context.xmlid(d->shape2, "shape", KoElementReference::Counter).toString()); 0357 context.xmlWriter().addAttribute("draw:end-glue-point", d->connectionPointId2); 0358 } else { 0359 QPointF p(shapeToDocument(d->handles[EndHandle]) * context.shapeOffset(this)); 0360 context.xmlWriter().addAttributePt("svg:x2", p.x()); 0361 context.xmlWriter().addAttributePt("svg:y2", p.y()); 0362 } 0363 0364 // write the path data 0365 context.xmlWriter().addAttribute("svg:d", toString()); 0366 saveOdfAttributes(context, OdfViewbox); 0367 0368 saveOdfCommonChildElements(context); 0369 saveText(context); 0370 0371 context.xmlWriter().endElement(); 0372 } 0373 0374 bool KoConnectionShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) 0375 { 0376 Q_D(KoConnectionShape); 0377 loadOdfAttributes(element, context, OdfMandatories | OdfCommonChildElements | OdfAdditionalAttributes); 0378 0379 QString type = element.attributeNS(KoXmlNS::draw, "type", "standard"); 0380 if (type == "lines") 0381 d->connectionType = Lines; 0382 else if (type == "line") 0383 d->connectionType = Straight; 0384 else if (type == "curve") 0385 d->connectionType = Curve; 0386 else 0387 d->connectionType = Standard; 0388 0389 // reset connection point indices 0390 d->connectionPointId1 = -1; 0391 d->connectionPointId2 = -1; 0392 // reset connected shapes 0393 d->shape1 = 0; 0394 d->shape2 = 0; 0395 0396 if (element.hasAttributeNS(KoXmlNS::draw, "start-shape")) { 0397 d->connectionPointId1 = element.attributeNS(KoXmlNS::draw, "start-glue-point", QString()).toInt(); 0398 QString shapeId1 = element.attributeNS(KoXmlNS::draw, "start-shape", QString()); 0399 debugFlake << "references start-shape" << shapeId1 << "at glue-point" << d->connectionPointId1; 0400 d->shape1 = context.shapeById(shapeId1); 0401 if (d->shape1) { 0402 debugFlake << "start-shape was already loaded"; 0403 d->shape1->addDependee(this); 0404 if (d->shape1->hasConnectionPoint(d->connectionPointId1)) { 0405 debugFlake << "connecting to start-shape"; 0406 d->handles[StartHandle] = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position); 0407 debugFlake << "start handle position =" << d->handles[StartHandle]; 0408 } 0409 } else { 0410 debugFlake << "start-shape not loaded yet, deferring connection"; 0411 context.updateShape(shapeId1, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::First)); 0412 } 0413 } else { 0414 d->handles[StartHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", QString()))); 0415 d->handles[StartHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", QString()))); 0416 } 0417 0418 if (element.hasAttributeNS(KoXmlNS::draw, "end-shape")) { 0419 d->connectionPointId2 = element.attributeNS(KoXmlNS::draw, "end-glue-point", "").toInt(); 0420 QString shapeId2 = element.attributeNS(KoXmlNS::draw, "end-shape", ""); 0421 debugFlake << "references end-shape " << shapeId2 << "at glue-point" << d->connectionPointId2; 0422 d->shape2 = context.shapeById(shapeId2); 0423 if (d->shape2) { 0424 debugFlake << "end-shape was already loaded"; 0425 d->shape2->addDependee(this); 0426 if (d->shape2->hasConnectionPoint(d->connectionPointId2)) { 0427 debugFlake << "connecting to end-shape"; 0428 d->handles[EndHandle] = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position); 0429 debugFlake << "end handle position =" << d->handles[EndHandle]; 0430 } 0431 } else { 0432 debugFlake << "end-shape not loaded yet, deferring connection"; 0433 context.updateShape(shapeId2, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::Second)); 0434 } 0435 } else { 0436 d->handles[EndHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", QString()))); 0437 d->handles[EndHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", QString()))); 0438 } 0439 0440 QString skew = element.attributeNS(KoXmlNS::draw, "line-skew", QString()); 0441 QStringList skewValues = skew.simplified().split(' ', QString::SkipEmptyParts); 0442 // TODO apply skew values once we support them 0443 0444 // load the path data if there is any 0445 d->hasCustomPath = element.hasAttributeNS(KoXmlNS::svg, "d"); 0446 if (d->hasCustomPath) { 0447 KoPathShapeLoader loader(this); 0448 loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); 0449 if (m_subpaths.size() > 0) { 0450 QRectF viewBox = loadOdfViewbox(element); 0451 if (viewBox.isEmpty()) { 0452 // there should be a viewBox to transform the path data 0453 // if there is none, use the bounding rectangle of the parsed path 0454 viewBox = outline().boundingRect(); 0455 } 0456 // convert path to viewbox coordinates to have a bounding rect of (0,0 1x1) 0457 // which can later be fitted back into the target rect once we have all 0458 // the required information 0459 QTransform viewMatrix; 0460 viewMatrix.scale(viewBox.width() ? static_cast<qreal>(1.0) / viewBox.width() : 1.0, 0461 viewBox.height() ? static_cast<qreal>(1.0) / viewBox.height() : 1.0); 0462 viewMatrix.translate(-viewBox.left(), -viewBox.top()); 0463 d->map(viewMatrix); 0464 0465 // trigger finishing the connections in case we have all data 0466 // otherwise it gets called again once the shapes we are 0467 // connected to are loaded 0468 } 0469 else { 0470 d->hasCustomPath = false; 0471 } 0472 finishLoadingConnection(); 0473 } else { 0474 d->forceUpdate = true; 0475 updateConnections(); 0476 } 0477 0478 loadText(element, context); 0479 0480 return true; 0481 } 0482 0483 void KoConnectionShape::finishLoadingConnection() 0484 { 0485 Q_D(KoConnectionShape); 0486 0487 if (d->hasCustomPath) { 0488 const bool loadingFinished1 = d->connectionPointId1 >= 0 ? d->shape1 != 0 : true; 0489 const bool loadingFinished2 = d->connectionPointId2 >= 0 ? d->shape2 != 0 : true; 0490 if (loadingFinished1 && loadingFinished2) { 0491 QPointF p1, p2; 0492 if (d->handleConnected(StartHandle)) { 0493 if (d->shape1->hasConnectionPoint(d->connectionPointId1)) { 0494 p1 = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position); 0495 } 0496 } else { 0497 p1 = d->handles[StartHandle]; 0498 } 0499 if (d->handleConnected(EndHandle)) { 0500 if (d->shape2->hasConnectionPoint(d->connectionPointId2)) { 0501 p2 = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position); 0502 } 0503 } else { 0504 p2 = d->handles[EndHandle]; 0505 } 0506 0507 QPointF relativeBegin = m_subpaths.first()->first()->point(); 0508 QPointF relativeEnd = m_subpaths.last()->last()->point(); 0509 0510 QPointF diffRelative(relativeBegin - relativeEnd); 0511 QPointF diffAbsolute(p1 - p2); 0512 0513 qreal factorX = diffRelative.x() ? diffAbsolute.x() / diffRelative.x(): 1.0; 0514 qreal factorY = diffRelative.y() ? diffAbsolute.y() / diffRelative.y(): 1.0; 0515 0516 p1.setX(p1.x() - relativeBegin.x() * factorX); 0517 p1.setY(p1.y() - relativeBegin.y() * factorY); 0518 p2.setX(p2.x() + (1 - relativeEnd.x()) * factorX); 0519 p2.setY(p2.y() + (1 - relativeEnd.y()) * factorY); 0520 0521 QRectF targetRect = QRectF(p1, p2).normalized(); 0522 0523 // transform the normalized coordinates back to our target rectangle 0524 QTransform viewMatrix; 0525 viewMatrix.translate(targetRect.x(), targetRect.y()); 0526 viewMatrix.scale(targetRect.width(), targetRect.height()); 0527 d->map(viewMatrix); 0528 0529 // pretend we are during a forced update, so normalize() 0530 // will not trigger an updateConnections() call 0531 d->forceUpdate = true; 0532 normalize(); 0533 d->forceUpdate = false; 0534 } 0535 } else { 0536 updateConnections(); 0537 } 0538 } 0539 0540 void KoConnectionShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) 0541 { 0542 Q_UNUSED(modifiers); 0543 Q_D(KoConnectionShape); 0544 0545 if (handleId >= d->handles.size()) 0546 return; 0547 0548 d->handles[handleId] = point; 0549 } 0550 0551 void KoConnectionShape::updatePath(const QSizeF &size) 0552 { 0553 Q_UNUSED(size); 0554 Q_D(KoConnectionShape); 0555 0556 clear(); 0557 // Do not create a path when all handles point to the same point. 0558 bool equal = true; 0559 const QPointF first = d->handles.value(0); 0560 for (int i = 1; equal && i < d->handles.count(); ++i) { 0561 equal = d->handles[i] == first; 0562 } 0563 if (equal) { 0564 return; 0565 } 0566 const qreal MinimumEscapeLength = (qreal)20.; 0567 switch (d->connectionType) { 0568 case Standard: { 0569 d->normalPath(MinimumEscapeLength); 0570 if (d->path.count() != 0){ 0571 moveTo(d->path[0]); 0572 for (int index = 1; index < d->path.count(); ++index) 0573 lineTo(d->path[index]); 0574 } 0575 0576 break; 0577 } 0578 case Lines: { 0579 QPointF direction1 = d->escapeDirection(0); 0580 QPointF direction2 = d->escapeDirection(d->handles.count() - 1); 0581 moveTo(d->handles[StartHandle]); 0582 if (! direction1.isNull()) 0583 lineTo(d->handles[StartHandle] + MinimumEscapeLength * direction1); 0584 if (! direction2.isNull()) 0585 lineTo(d->handles[EndHandle] + MinimumEscapeLength * direction2); 0586 lineTo(d->handles[EndHandle]); 0587 break; 0588 } 0589 case Straight: 0590 moveTo(d->handles[StartHandle]); 0591 lineTo(d->handles[EndHandle]); 0592 break; 0593 case Curve: 0594 // TODO 0595 QPointF direction1 = d->escapeDirection(0); 0596 QPointF direction2 = d->escapeDirection(d->handles.count() - 1); 0597 moveTo(d->handles[StartHandle]); 0598 if (! direction1.isNull() && ! direction2.isNull()) { 0599 QPointF curvePoint1 = d->handles[StartHandle] + 5.0 * MinimumEscapeLength * direction1; 0600 QPointF curvePoint2 = d->handles[EndHandle] + 5.0 * MinimumEscapeLength * direction2; 0601 curveTo(curvePoint1, curvePoint2, d->handles[EndHandle]); 0602 } else { 0603 lineTo(d->handles[EndHandle]); 0604 } 0605 break; 0606 } 0607 normalize(); 0608 } 0609 0610 bool KoConnectionShape::connectFirst(KoShape * shape1, int connectionPointId) 0611 { 0612 Q_D(KoConnectionShape); 0613 // refuse to connect to a shape that depends on us (e.g. a artistic text shape) 0614 if (hasDependee(shape1)) 0615 return false; 0616 0617 if (shape1) { 0618 // check if the connection point does exist 0619 if (!shape1->hasConnectionPoint(connectionPointId)) 0620 return false; 0621 // do not connect to the same connection point twice 0622 if (d->shape2 == shape1 && d->connectionPointId2 == connectionPointId) 0623 return false; 0624 } 0625 0626 if (d->shape1) 0627 d->shape1->removeDependee(this); 0628 d->shape1 = shape1; 0629 if (d->shape1) 0630 d->shape1->addDependee(this); 0631 0632 d->connectionPointId1 = connectionPointId; 0633 0634 return true; 0635 } 0636 0637 bool KoConnectionShape::connectSecond(KoShape * shape2, int connectionPointId) 0638 { 0639 Q_D(KoConnectionShape); 0640 // refuse to connect to a shape that depends on us (e.g. a artistic text shape) 0641 if (hasDependee(shape2)) 0642 return false; 0643 0644 if (shape2) { 0645 // check if the connection point does exist 0646 if (!shape2->hasConnectionPoint(connectionPointId)) 0647 return false; 0648 // do not connect to the same connection point twice 0649 if (d->shape1 == shape2 && d->connectionPointId1 == connectionPointId) 0650 return false; 0651 } 0652 0653 if (d->shape2) 0654 d->shape2->removeDependee(this); 0655 d->shape2 = shape2; 0656 if (d->shape2) 0657 d->shape2->addDependee(this); 0658 0659 d->connectionPointId2 = connectionPointId; 0660 0661 return true; 0662 } 0663 0664 KoShape *KoConnectionShape::firstShape() const 0665 { 0666 Q_D(const KoConnectionShape); 0667 return d->shape1; 0668 } 0669 0670 int KoConnectionShape::firstConnectionId() const 0671 { 0672 Q_D(const KoConnectionShape); 0673 return d->connectionPointId1; 0674 } 0675 0676 KoShape *KoConnectionShape::secondShape() const 0677 { 0678 Q_D(const KoConnectionShape); 0679 return d->shape2; 0680 } 0681 0682 int KoConnectionShape::secondConnectionId() const 0683 { 0684 Q_D(const KoConnectionShape); 0685 return d->connectionPointId2; 0686 } 0687 0688 KoConnectionShape::Type KoConnectionShape::type() const 0689 { 0690 Q_D(const KoConnectionShape); 0691 return d->connectionType; 0692 } 0693 0694 void KoConnectionShape::setType(Type connectionType) 0695 { 0696 Q_D(KoConnectionShape); 0697 d->connectionType = connectionType; 0698 updatePath(size()); 0699 } 0700 0701 void KoConnectionShape::shapeChanged(ChangeType type, KoShape *shape) 0702 { 0703 Q_D(KoConnectionShape); 0704 0705 KoTosContainer::shapeChanged(type, shape); 0706 // check if we are during a forced update 0707 const bool updateIsActive = d->forceUpdate; 0708 0709 switch (type) { 0710 case PositionChanged: 0711 case RotationChanged: 0712 case ShearChanged: 0713 case ScaleChanged: 0714 case GenericMatrixChange: 0715 case ParameterChanged: 0716 if (isParametricShape() && shape == 0) 0717 d->forceUpdate = true; 0718 break; 0719 case Deleted: 0720 if (shape != d->shape1 && shape != d->shape2) 0721 return; 0722 if (shape == d->shape1) 0723 connectFirst(0, -1); 0724 if (shape == d->shape2) 0725 connectSecond(0, -1); 0726 break; 0727 case ConnectionPointChanged: 0728 if (shape == d->shape1 && !shape->hasConnectionPoint(d->connectionPointId1)) { 0729 connectFirst(0, -1); 0730 } else if ( shape == d->shape2 && !shape->hasConnectionPoint(d->connectionPointId2)){ 0731 connectSecond(0, -1); 0732 } else { 0733 d->forceUpdate = true; 0734 } 0735 break; 0736 case BackgroundChanged: 0737 { 0738 // connection shape should not have a background 0739 QSharedPointer<KoShapeBackground> fill = background(); 0740 if (fill) { 0741 setBackground(QSharedPointer<KoShapeBackground>(0)); 0742 } 0743 return; 0744 } 0745 default: 0746 return; 0747 } 0748 0749 // the connection was moved while it is connected to some other shapes 0750 const bool connectionChanged = !shape && (d->shape1 || d->shape2); 0751 // one of the connected shape has moved 0752 const bool connectedShapeChanged = shape && (shape == d->shape1 || shape == d->shape2); 0753 0754 if (!updateIsActive && (connectionChanged || connectedShapeChanged) && isParametricShape()) 0755 updateConnections(); 0756 0757 // reset the forced update flag 0758 d->forceUpdate = false; 0759 } 0760 0761 QString KoConnectionShape::pathShapeId() const 0762 { 0763 return KOCONNECTIONSHAPEID; 0764 }