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 }