1 /* This file is part of the KDE project
2  * Copyright (C) 2007 Boudewijn Rempt <boud@kde.org>
3  * Copyright (C) 2007,2009 Thorsten Zachmann <zachmann@kde.org>
4  * Copyright (C) 2007,2009,2010  Jan Hambrecht <jaham@gmx.net>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #include "KoConnectionShape.h"
23 #include "KoConnectionShape_p.h"
24 
25 #include "KoViewConverter.h"
26 #include "KoShapeLoadingContext.h"
27 #include "KoShapeSavingContext.h"
28 #include "KoConnectionShapeLoadingUpdater.h"
29 #include "KoPathShapeLoader.h"
30 #include "KoPathPoint.h"
31 #include "KoShapeBackground.h"
32 #include <KoXmlReader.h>
33 #include <KoXmlWriter.h>
34 #include <KoXmlNS.h>
35 #include <KoUnit.h>
36 #include <QPainter>
37 #include <QPainterPath>
38 
39 #include <FlakeDebug.h>
40 
KoConnectionShapePrivate(KoConnectionShape * q)41 KoConnectionShapePrivate::KoConnectionShapePrivate(KoConnectionShape *q)
42     : KoParameterShapePrivate(q),
43     shape1(0),
44     shape2(0),
45     connectionPointId1(-1),
46     connectionPointId2(-1),
47     connectionType(KoConnectionShape::Standard),
48     forceUpdate(false),
49     hasCustomPath(false)
50 {
51 }
52 
escapeDirection(int handleId) const53 QPointF KoConnectionShapePrivate::escapeDirection(int handleId) const
54 {
55     Q_Q(const KoConnectionShape);
56     QPointF direction;
57     if (handleConnected(handleId)) {
58         KoShape *attachedShape = handleId == KoConnectionShape::StartHandle ? shape1 : shape2;
59         int connectionPointId = handleId == KoConnectionShape::StartHandle ? connectionPointId1 : connectionPointId2;
60         KoConnectionPoint::EscapeDirection ed = attachedShape->connectionPoint(connectionPointId).escapeDirection;
61         if (ed == KoConnectionPoint::AllDirections) {
62             QPointF handlePoint = q->shapeToDocument(handles[handleId]);
63             QPointF centerPoint = attachedShape->absolutePosition(KoFlake::CenteredPosition);
64 
65             /*
66              * Determine the best escape direction from the position of the handle point
67              * and the position and orientation of the attached shape.
68              * The idea is to define 4 sectors, one for each edge of the attached shape.
69              * Each sector starts at the center point of the attached shape and has it
70              * left and right edge going through the two points which define the edge.
71              * Then we check which sector contains our handle point, for which we can
72              * simply calculate the corresponding direction which is orthogonal to the
73              * corresponding bounding box edge.
74              * From that we derive the escape direction from looking at the main coordinate
75              * of the orthogonal direction.
76              */
77             // define our edge points in the right order
78             const KoFlake::Position corners[4] = {
79                 KoFlake::BottomRightCorner,
80                 KoFlake::BottomLeftCorner,
81                 KoFlake::TopLeftCorner,
82                 KoFlake::TopRightCorner
83             };
84 
85             QPointF vHandle = handlePoint-centerPoint;
86             for (int i = 0; i < 4; ++i) {
87                 // first point of bounding box edge
88                 QPointF p1 = attachedShape->absolutePosition(corners[i]);
89                 // second point of bounding box edge
90                 QPointF p2 = attachedShape->absolutePosition(corners[(i+1)%4]);
91                 // check on which side of the first sector edge our second sector edge is
92                 const qreal c0 = crossProd(p1-centerPoint, p2-centerPoint);
93                 // check on which side of the first sector edge our handle point is
94                 const qreal c1 = crossProd(p1-centerPoint, vHandle);
95                 // second egde and handle point must be on the same side of first edge
96                 if ((c0 < 0 && c1 > 0) || (c0 > 0 && c1 < 0))
97                     continue;
98                 // check on which side of the handle point our second sector edge is
99                 const qreal c2 = crossProd(vHandle, p2-centerPoint);
100                 // second edge must be on the same side of the handle point as on first edge
101                 if ((c0 < 0 && c2 > 0) || (c0 > 0 && c2 < 0))
102                     continue;
103                 // now we found the correct edge
104                 QPointF vDir = 0.5 *(p1+p2) - centerPoint;
105                 // look at coordinate with the greatest absolute value
106                 // and construct our escape direction accordingly
107                 const qreal xabs = qAbs<qreal>(vDir.x());
108                 const qreal yabs = qAbs<qreal>(vDir.y());
109                 if (xabs > yabs) {
110                     direction.rx() = vDir.x() > 0 ? 1.0 : -1.0;
111                     direction.ry() = 0.0;
112                 } else {
113                     direction.rx() = 0.0;
114                     direction.ry() = vDir.y() > 0 ? 1.0 : -1.0;
115                 }
116                 break;
117             }
118         } else if (ed == KoConnectionPoint::HorizontalDirections) {
119             QPointF handlePoint = q->shapeToDocument(handles[handleId]);
120             QPointF centerPoint = attachedShape->absolutePosition(KoFlake::CenteredPosition);
121             // use horizontal direction pointing away from center point
122             if (handlePoint.x() < centerPoint.x())
123                 direction = QPointF(-1.0, 0.0);
124             else
125                 direction = QPointF(1.0, 0.0);
126         } else if (ed == KoConnectionPoint::VerticalDirections) {
127             QPointF handlePoint = q->shapeToDocument(handles[handleId]);
128             QPointF centerPoint = attachedShape->absolutePosition(KoFlake::CenteredPosition);
129             // use vertical direction pointing away from center point
130             if (handlePoint.y() < centerPoint.y())
131                 direction = QPointF(0.0, -1.0);
132             else
133                 direction = QPointF(0.0, 1.0);
134         } else if (ed == KoConnectionPoint::LeftDirection) {
135             direction = QPointF(-1.0, 0.0);
136         } else if (ed == KoConnectionPoint::RightDirection) {
137             direction = QPointF(1.0, 0.0);
138         } else if (ed == KoConnectionPoint::UpDirection) {
139             direction = QPointF(0.0, -1.0);
140         } else if (ed == KoConnectionPoint::DownDirection) {
141             direction = QPointF(0.0, 1.0);
142         }
143 
144         // transform escape direction by using our own transformation matrix
145         QTransform invMatrix = q->absoluteTransformation(0).inverted();
146         direction = invMatrix.map(direction) - invMatrix.map(QPointF());
147         direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y());
148     }
149 
150     return direction;
151 }
152 
intersects(const QPointF & p1,const QPointF & d1,const QPointF & p2,const QPointF & d2,QPointF & isect)153 bool KoConnectionShapePrivate::intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect)
154 {
155     qreal sp1 = scalarProd(d1, p2 - p1);
156     if (sp1 < 0.0)
157         return false;
158 
159     qreal sp2 = scalarProd(d2, p1 - p2);
160     if (sp2 < 0.0)
161         return false;
162 
163     // use cross product to check if rays intersects at all
164     qreal cp = crossProd(d1, d2);
165     if (cp == 0.0) {
166         // rays are parallel or coincident
167         if (p1.x() == p2.x() && d1.x() == 0.0 && d1.y() != d2.y()) {
168             // vertical, coincident
169             isect = 0.5 * (p1 + p2);
170         } else if (p1.y() == p2.y() && d1.y() == 0.0 && d1.x() != d2.x()) {
171             // horizontal, coincident
172             isect = 0.5 * (p1 + p2);
173         } else {
174             return false;
175         }
176     } else {
177         // they are intersecting normally
178         isect = p1 + sp1 * d1;
179     }
180 
181     return true;
182 }
183 
perpendicularDirection(const QPointF & p1,const QPointF & d1,const QPointF & p2)184 QPointF KoConnectionShapePrivate::perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2)
185 {
186     QPointF perpendicular(d1.y(), -d1.x());
187     qreal sp = scalarProd(perpendicular, p2 - p1);
188     if (sp < 0.0)
189         perpendicular *= -1.0;
190 
191     return perpendicular;
192 }
193 
normalPath(const qreal MinimumEscapeLength)194 void KoConnectionShapePrivate::normalPath(const qreal MinimumEscapeLength)
195 {
196     // Clear the path to build it again.
197     path.clear();
198     path.append(handles[KoConnectionShape::StartHandle]);
199 
200     QVector<QPointF> edges1;
201     QVector<QPointF> edges2;
202 
203     QPointF direction1 = escapeDirection(KoConnectionShape::StartHandle);
204     QPointF direction2 = escapeDirection(KoConnectionShape::EndHandle);
205 
206     QPointF edgePoint1 = handles[KoConnectionShape::StartHandle] + MinimumEscapeLength * direction1;
207     QPointF edgePoint2 = handles[KoConnectionShape::EndHandle] + MinimumEscapeLength * direction2;
208 
209     edges1.append(edgePoint1);
210     edges2.prepend(edgePoint2);
211 
212     if (handleConnected(KoConnectionShape::StartHandle) && handleConnected(KoConnectionShape::EndHandle)) {
213         QPointF intersection;
214         bool connected = false;
215         do {
216             // first check if directions from current edge points intersect
217             if (intersects(edgePoint1, direction1, edgePoint2, direction2, intersection)) {
218                 // directions intersect, we have another edge point and be done
219                 edges1.append(intersection);
220                 break;
221             }
222 
223             // check if we are going toward the other handle
224             qreal sp = scalarProd(direction1, edgePoint2 - edgePoint1);
225             if (sp >= 0.0) {
226                 // if we are having the same direction, go all the way toward
227                 // the other handle, else only go half the way
228                 if (direction1 == direction2)
229                     edgePoint1 += sp * direction1;
230                 else
231                     edgePoint1 += 0.5 * sp * direction1;
232                 edges1.append(edgePoint1);
233                 // switch direction
234                 direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
235             } else {
236                 // we are not going into the same direction, so switch direction
237                 direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
238             }
239         } while (! connected);
240     }
241 
242     path.append(edges1);
243     path.append(edges2);
244 
245     path.append(handles[KoConnectionShape::EndHandle]);
246 }
247 
scalarProd(const QPointF & v1,const QPointF & v2) const248 qreal KoConnectionShapePrivate::scalarProd(const QPointF &v1, const QPointF &v2) const
249 {
250     return v1.x() * v2.x() + v1.y() * v2.y();
251 }
252 
crossProd(const QPointF & v1,const QPointF & v2) const253 qreal KoConnectionShapePrivate::crossProd(const QPointF &v1, const QPointF &v2) const
254 {
255     return v1.x() * v2.y() - v1.y() * v2.x();
256 }
257 
handleConnected(int handleId) const258 bool KoConnectionShapePrivate::handleConnected(int handleId) const
259 {
260     if (handleId == KoConnectionShape::StartHandle && shape1 && connectionPointId1 >= 0)
261         return true;
262     if (handleId == KoConnectionShape::EndHandle && shape2 && connectionPointId2 >= 0)
263         return true;
264 
265     return false;
266 }
267 
updateConnections()268 void KoConnectionShape::updateConnections()
269 {
270     Q_D(KoConnectionShape);
271     bool updateHandles = false;
272 
273     if (d->handleConnected(StartHandle)) {
274         if (d->shape1->hasConnectionPoint(d->connectionPointId1)) {
275             // map connection point into our shape coordinates
276             QPointF p = documentToShape(d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position));
277             if (d->handles[StartHandle] != p) {
278                 d->handles[StartHandle] = p;
279                 updateHandles = true;
280             }
281         }
282     }
283     if (d->handleConnected(EndHandle)) {
284         if (d->shape2->hasConnectionPoint(d->connectionPointId2)) {
285             // map connection point into our shape coordinates
286             QPointF p = documentToShape(d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position));
287             if (d->handles[EndHandle] != p) {
288                 d->handles[EndHandle] = p;
289                 updateHandles = true;
290             }
291         }
292     }
293 
294     if (updateHandles || d->forceUpdate) {
295         update(); // ugly, for repainting the connection we just changed
296         updatePath(QSizeF());
297         update(); // ugly, for repainting the connection we just changed
298         d->forceUpdate = false;
299     }
300 }
301 
KoConnectionShape()302 KoConnectionShape::KoConnectionShape()
303     : KoParameterShape(*(new KoConnectionShapePrivate(this)))
304 {
305     Q_D(KoConnectionShape);
306     d->handles.push_back(QPointF(0, 0));
307     d->handles.push_back(QPointF(140, 140));
308 
309     moveTo(d->handles[StartHandle]);
310     lineTo(d->handles[EndHandle]);
311 
312     updatePath(QSizeF(140, 140));
313 
314     clearConnectionPoints();
315 }
316 
~KoConnectionShape()317 KoConnectionShape::~KoConnectionShape()
318 {
319     Q_D(KoConnectionShape);
320     if (d->shape1)
321         d->shape1->removeDependee(this);
322     if (d->shape2)
323         d->shape2->removeDependee(this);
324 }
325 
saveOdf(KoShapeSavingContext & context) const326 void KoConnectionShape::saveOdf(KoShapeSavingContext & context) const
327 {
328     Q_D(const KoConnectionShape);
329     context.xmlWriter().startElement("draw:connector");
330     saveOdfAttributes(context, OdfMandatories | OdfAdditionalAttributes);
331 
332     switch (d->connectionType) {
333     case Lines:
334         context.xmlWriter().addAttribute("draw:type", "lines");
335         break;
336     case Straight:
337         context.xmlWriter().addAttribute("draw:type", "line");
338         break;
339     case Curve:
340         context.xmlWriter().addAttribute("draw:type", "curve");
341         break;
342     default:
343         context.xmlWriter().addAttribute("draw:type", "standard");
344         break;
345     }
346 
347     if (d->shape1) {
348         context.xmlWriter().addAttribute("draw:start-shape", context.xmlid(d->shape1, "shape", KoElementReference::Counter).toString());
349         context.xmlWriter().addAttribute("draw:start-glue-point", d->connectionPointId1);
350     } else {
351         QPointF p(shapeToDocument(d->handles[StartHandle]) * context.shapeOffset(this));
352         context.xmlWriter().addAttributePt("svg:x1", p.x());
353         context.xmlWriter().addAttributePt("svg:y1", p.y());
354     }
355     if (d->shape2) {
356         context.xmlWriter().addAttribute("draw:end-shape", context.xmlid(d->shape2, "shape", KoElementReference::Counter).toString());
357         context.xmlWriter().addAttribute("draw:end-glue-point", d->connectionPointId2);
358     } else {
359         QPointF p(shapeToDocument(d->handles[EndHandle]) * context.shapeOffset(this));
360         context.xmlWriter().addAttributePt("svg:x2", p.x());
361         context.xmlWriter().addAttributePt("svg:y2", p.y());
362     }
363 
364     // write the path data
365     context.xmlWriter().addAttribute("svg:d", toString());
366     saveOdfAttributes(context, OdfViewbox);
367 
368     saveOdfCommonChildElements(context);
369     saveText(context);
370 
371     context.xmlWriter().endElement();
372 }
373 
loadOdf(const KoXmlElement & element,KoShapeLoadingContext & context)374 bool KoConnectionShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context)
375 {
376     Q_D(KoConnectionShape);
377     loadOdfAttributes(element, context, OdfMandatories | OdfCommonChildElements | OdfAdditionalAttributes);
378 
379     QString type = element.attributeNS(KoXmlNS::draw, "type", "standard");
380     if (type == "lines")
381         d->connectionType = Lines;
382     else if (type == "line")
383         d->connectionType = Straight;
384     else if (type == "curve")
385         d->connectionType = Curve;
386     else
387         d->connectionType = Standard;
388 
389     // reset connection point indices
390     d->connectionPointId1 = -1;
391     d->connectionPointId2 = -1;
392     // reset connected shapes
393     d->shape1 = 0;
394     d->shape2 = 0;
395 
396     if (element.hasAttributeNS(KoXmlNS::draw, "start-shape")) {
397         d->connectionPointId1 = element.attributeNS(KoXmlNS::draw, "start-glue-point", QString()).toInt();
398         QString shapeId1 = element.attributeNS(KoXmlNS::draw, "start-shape", QString());
399         debugFlake << "references start-shape" << shapeId1 << "at glue-point" << d->connectionPointId1;
400         d->shape1 = context.shapeById(shapeId1);
401         if (d->shape1) {
402             debugFlake << "start-shape was already loaded";
403             d->shape1->addDependee(this);
404             if (d->shape1->hasConnectionPoint(d->connectionPointId1)) {
405                 debugFlake << "connecting to start-shape";
406                 d->handles[StartHandle] = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position);
407                 debugFlake << "start handle position =" << d->handles[StartHandle];
408             }
409         } else {
410             debugFlake << "start-shape not loaded yet, deferring connection";
411             context.updateShape(shapeId1, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::First));
412         }
413     } else {
414         d->handles[StartHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", QString())));
415         d->handles[StartHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", QString())));
416     }
417 
418     if (element.hasAttributeNS(KoXmlNS::draw, "end-shape")) {
419         d->connectionPointId2 = element.attributeNS(KoXmlNS::draw, "end-glue-point", "").toInt();
420         QString shapeId2 = element.attributeNS(KoXmlNS::draw, "end-shape", "");
421         debugFlake << "references end-shape " << shapeId2 << "at glue-point" << d->connectionPointId2;
422         d->shape2 = context.shapeById(shapeId2);
423         if (d->shape2) {
424             debugFlake << "end-shape was already loaded";
425             d->shape2->addDependee(this);
426             if (d->shape2->hasConnectionPoint(d->connectionPointId2)) {
427                 debugFlake << "connecting to end-shape";
428                 d->handles[EndHandle] = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position);
429                 debugFlake << "end handle position =" << d->handles[EndHandle];
430             }
431         } else {
432             debugFlake << "end-shape not loaded yet, deferring connection";
433             context.updateShape(shapeId2, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::Second));
434         }
435     } else {
436         d->handles[EndHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", QString())));
437         d->handles[EndHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", QString())));
438     }
439 
440     QString skew = element.attributeNS(KoXmlNS::draw, "line-skew", QString());
441     QStringList skewValues = skew.simplified().split(' ', QString::SkipEmptyParts);
442     // TODO apply skew values once we support them
443 
444     // load the path data if there is any
445     d->hasCustomPath = element.hasAttributeNS(KoXmlNS::svg, "d");
446     if (d->hasCustomPath) {
447         KoPathShapeLoader loader(this);
448         loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true);
449         if (m_subpaths.size() > 0) {
450             QRectF viewBox = loadOdfViewbox(element);
451             if (viewBox.isEmpty()) {
452                 // there should be a viewBox to transform the path data
453                 // if there is none, use the bounding rectangle of the parsed path
454                 viewBox = outline().boundingRect();
455             }
456             // convert path to viewbox coordinates to have a bounding rect of (0,0 1x1)
457             // which can later be fitted back into the target rect once we have all
458             // the required information
459             QTransform viewMatrix;
460             viewMatrix.scale(viewBox.width() ? static_cast<qreal>(1.0) / viewBox.width() : 1.0,
461                              viewBox.height() ? static_cast<qreal>(1.0) / viewBox.height() : 1.0);
462             viewMatrix.translate(-viewBox.left(), -viewBox.top());
463             d->map(viewMatrix);
464 
465             // trigger finishing the connections in case we have all data
466             // otherwise it gets called again once the shapes we are
467             // connected to are loaded
468         }
469         else {
470             d->hasCustomPath = false;
471         }
472         finishLoadingConnection();
473     } else {
474         d->forceUpdate = true;
475         updateConnections();
476     }
477 
478     loadText(element, context);
479 
480     return true;
481 }
482 
finishLoadingConnection()483 void KoConnectionShape::finishLoadingConnection()
484 {
485     Q_D(KoConnectionShape);
486 
487     if (d->hasCustomPath) {
488         const bool loadingFinished1 = d->connectionPointId1 >= 0 ? d->shape1 != 0 : true;
489         const bool loadingFinished2 = d->connectionPointId2 >= 0 ? d->shape2 != 0 : true;
490         if (loadingFinished1 && loadingFinished2) {
491             QPointF p1, p2;
492             if (d->handleConnected(StartHandle)) {
493                 if (d->shape1->hasConnectionPoint(d->connectionPointId1)) {
494                     p1 = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position);
495                 }
496             } else {
497                 p1 = d->handles[StartHandle];
498             }
499             if (d->handleConnected(EndHandle)) {
500                 if (d->shape2->hasConnectionPoint(d->connectionPointId2)) {
501                     p2 = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position);
502                 }
503             } else {
504                 p2 = d->handles[EndHandle];
505             }
506 
507             QPointF relativeBegin = m_subpaths.first()->first()->point();
508             QPointF relativeEnd = m_subpaths.last()->last()->point();
509 
510             QPointF diffRelative(relativeBegin - relativeEnd);
511             QPointF diffAbsolute(p1 - p2);
512 
513             qreal factorX = diffRelative.x() ? diffAbsolute.x() / diffRelative.x(): 1.0;
514             qreal factorY = diffRelative.y() ? diffAbsolute.y() / diffRelative.y(): 1.0;
515 
516             p1.setX(p1.x() - relativeBegin.x() * factorX);
517             p1.setY(p1.y() - relativeBegin.y() * factorY);
518             p2.setX(p2.x() + (1 - relativeEnd.x()) * factorX);
519             p2.setY(p2.y() + (1 - relativeEnd.y()) * factorY);
520 
521             QRectF targetRect = QRectF(p1, p2).normalized();
522 
523             // transform the normalized coordinates back to our target rectangle
524             QTransform viewMatrix;
525             viewMatrix.translate(targetRect.x(), targetRect.y());
526             viewMatrix.scale(targetRect.width(), targetRect.height());
527             d->map(viewMatrix);
528 
529             // pretend we are during a forced update, so normalize()
530             // will not trigger an updateConnections() call
531             d->forceUpdate = true;
532             normalize();
533             d->forceUpdate = false;
534         }
535     } else {
536         updateConnections();
537     }
538 }
539 
moveHandleAction(int handleId,const QPointF & point,Qt::KeyboardModifiers modifiers)540 void KoConnectionShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers)
541 {
542     Q_UNUSED(modifiers);
543     Q_D(KoConnectionShape);
544 
545     if (handleId >= d->handles.size())
546         return;
547 
548     d->handles[handleId] = point;
549 }
550 
updatePath(const QSizeF & size)551 void KoConnectionShape::updatePath(const QSizeF &size)
552 {
553     Q_UNUSED(size);
554     Q_D(KoConnectionShape);
555 
556     clear();
557     // Do not create a path when all handles point to the same point.
558     bool equal = true;
559     const QPointF first = d->handles.value(0);
560     for (int i = 1; equal && i < d->handles.count(); ++i) {
561         equal = d->handles[i] == first;
562     }
563     if (equal) {
564         return;
565     }
566     const qreal MinimumEscapeLength = (qreal)20.;
567     switch (d->connectionType) {
568     case Standard: {
569         d->normalPath(MinimumEscapeLength);
570         if (d->path.count() != 0){
571             moveTo(d->path[0]);
572             for (int index = 1; index < d->path.count(); ++index)
573                 lineTo(d->path[index]);
574         }
575 
576         break;
577     }
578     case Lines: {
579         QPointF direction1 = d->escapeDirection(0);
580         QPointF direction2 = d->escapeDirection(d->handles.count() - 1);
581         moveTo(d->handles[StartHandle]);
582         if (! direction1.isNull())
583             lineTo(d->handles[StartHandle] + MinimumEscapeLength * direction1);
584         if (! direction2.isNull())
585             lineTo(d->handles[EndHandle] + MinimumEscapeLength * direction2);
586         lineTo(d->handles[EndHandle]);
587         break;
588     }
589     case Straight:
590         moveTo(d->handles[StartHandle]);
591         lineTo(d->handles[EndHandle]);
592         break;
593     case Curve:
594         // TODO
595         QPointF direction1 = d->escapeDirection(0);
596         QPointF direction2 = d->escapeDirection(d->handles.count() - 1);
597         moveTo(d->handles[StartHandle]);
598         if (! direction1.isNull() && ! direction2.isNull()) {
599             QPointF curvePoint1 = d->handles[StartHandle] + 5.0 * MinimumEscapeLength * direction1;
600             QPointF curvePoint2 = d->handles[EndHandle] + 5.0 * MinimumEscapeLength * direction2;
601             curveTo(curvePoint1, curvePoint2, d->handles[EndHandle]);
602         } else {
603             lineTo(d->handles[EndHandle]);
604         }
605         break;
606     }
607     normalize();
608 }
609 
connectFirst(KoShape * shape1,int connectionPointId)610 bool KoConnectionShape::connectFirst(KoShape * shape1, int connectionPointId)
611 {
612     Q_D(KoConnectionShape);
613     // refuse to connect to a shape that depends on us (e.g. a artistic text shape)
614     if (hasDependee(shape1))
615         return false;
616 
617     if (shape1) {
618         // check if the connection point does exist
619         if (!shape1->hasConnectionPoint(connectionPointId))
620             return false;
621         // do not connect to the same connection point twice
622         if (d->shape2 == shape1 && d->connectionPointId2 == connectionPointId)
623             return false;
624     }
625 
626     if (d->shape1)
627         d->shape1->removeDependee(this);
628     d->shape1 = shape1;
629     if (d->shape1)
630         d->shape1->addDependee(this);
631 
632     d->connectionPointId1 = connectionPointId;
633 
634     return true;
635 }
636 
connectSecond(KoShape * shape2,int connectionPointId)637 bool KoConnectionShape::connectSecond(KoShape * shape2, int connectionPointId)
638 {
639     Q_D(KoConnectionShape);
640     // refuse to connect to a shape that depends on us (e.g. a artistic text shape)
641     if (hasDependee(shape2))
642         return false;
643 
644     if (shape2) {
645         // check if the connection point does exist
646         if (!shape2->hasConnectionPoint(connectionPointId))
647             return false;
648         // do not connect to the same connection point twice
649         if (d->shape1 == shape2 && d->connectionPointId1 == connectionPointId)
650             return false;
651     }
652 
653     if (d->shape2)
654         d->shape2->removeDependee(this);
655     d->shape2 = shape2;
656     if (d->shape2)
657         d->shape2->addDependee(this);
658 
659     d->connectionPointId2 = connectionPointId;
660 
661     return true;
662 }
663 
firstShape() const664 KoShape *KoConnectionShape::firstShape() const
665 {
666     Q_D(const KoConnectionShape);
667     return d->shape1;
668 }
669 
firstConnectionId() const670 int KoConnectionShape::firstConnectionId() const
671 {
672     Q_D(const KoConnectionShape);
673     return d->connectionPointId1;
674 }
675 
secondShape() const676 KoShape *KoConnectionShape::secondShape() const
677 {
678     Q_D(const KoConnectionShape);
679     return d->shape2;
680 }
681 
secondConnectionId() const682 int KoConnectionShape::secondConnectionId() const
683 {
684     Q_D(const KoConnectionShape);
685     return d->connectionPointId2;
686 }
687 
type() const688 KoConnectionShape::Type KoConnectionShape::type() const
689 {
690     Q_D(const KoConnectionShape);
691     return d->connectionType;
692 }
693 
setType(Type connectionType)694 void KoConnectionShape::setType(Type connectionType)
695 {
696     Q_D(KoConnectionShape);
697     d->connectionType = connectionType;
698     updatePath(size());
699 }
700 
shapeChanged(ChangeType type,KoShape * shape)701 void KoConnectionShape::shapeChanged(ChangeType type, KoShape *shape)
702 {
703     Q_D(KoConnectionShape);
704 
705     KoTosContainer::shapeChanged(type, shape);
706     // check if we are during a forced update
707     const bool updateIsActive = d->forceUpdate;
708 
709     switch (type) {
710     case PositionChanged:
711     case RotationChanged:
712     case ShearChanged:
713     case ScaleChanged:
714     case GenericMatrixChange:
715     case ParameterChanged:
716         if (isParametricShape() && shape == 0)
717             d->forceUpdate = true;
718         break;
719     case Deleted:
720         if (shape != d->shape1 && shape != d->shape2)
721             return;
722         if (shape == d->shape1)
723             connectFirst(0, -1);
724         if (shape == d->shape2)
725             connectSecond(0, -1);
726         break;
727     case ConnectionPointChanged:
728         if (shape == d->shape1 && !shape->hasConnectionPoint(d->connectionPointId1)) {
729             connectFirst(0, -1);
730         } else if ( shape == d->shape2 && !shape->hasConnectionPoint(d->connectionPointId2)){
731             connectSecond(0, -1);
732         } else {
733             d->forceUpdate = true;
734         }
735         break;
736     case BackgroundChanged:
737     {
738         // connection shape should not have a background
739         QSharedPointer<KoShapeBackground> fill = background();
740         if (fill) {
741             setBackground(QSharedPointer<KoShapeBackground>(0));
742         }
743         return;
744     }
745     default:
746         return;
747     }
748 
749     // the connection was moved while it is connected to some other shapes
750     const bool connectionChanged = !shape && (d->shape1 || d->shape2);
751     // one of the connected shape has moved
752     const bool connectedShapeChanged = shape && (shape == d->shape1 || shape == d->shape2);
753 
754     if (!updateIsActive && (connectionChanged || connectedShapeChanged) && isParametricShape())
755         updateConnections();
756 
757     // reset the forced update flag
758     d->forceUpdate = false;
759 }
760 
pathShapeId() const761 QString KoConnectionShape::pathShapeId() const
762 {
763     return KOCONNECTIONSHAPEID;
764 }
765