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