22
22
#include "KoConnectionShape.h"
23
#include "KoConnectionShape_p.h"
24
#include <KoViewConverter.h>
25
#include <KoShapeLoadingContext.h>
26
#include <KoShapeSavingContext.h>
25
#include "KoViewConverter.h"
26
#include "KoShapeLoadingContext.h"
27
#include "KoShapeSavingContext.h"
28
#include "KoConnectionShapeLoadingUpdater.h"
29
#include "KoPathShapeLoader.h"
27
30
#include <KoXmlReader.h>
28
31
#include <KoXmlWriter.h>
29
32
#include <KoXmlNS.h>
30
33
#include <KoStoreDevice.h>
31
34
#include <KoUnit.h>
32
#include "KoConnectionShapeLoadingUpdater.h"
34
35
#include <QPainter>
37
// XXX: Add editable text in path shapes so we can get a label here
39
struct KoConnectionShape::Private {
41
: shape1(0), shape2(0), connectionPointIndex1(-1), connectionPointIndex2(-1)
42
, connectionType(Standard), forceUpdate(false) {}
46
int connectionPointIndex1;
47
int connectionPointIndex2;
39
// IDs of the connecting handles
45
KoConnectionShapePrivate::KoConnectionShapePrivate(KoConnectionShape *q)
46
: KoParameterShapePrivate(q),
49
connectionPointIndex1(-1),
50
connectionPointIndex2(-1),
51
connectionType(KoConnectionShape::Standard),
57
QPointF KoConnectionShapePrivate::escapeDirection(int handleId) const
59
Q_Q(const KoConnectionShape);
61
if (handleConnected(handleId)) {
62
QMatrix absoluteMatrix = q->absoluteTransformation(0);
63
QPointF handlePoint = absoluteMatrix.map(handles[handleId]);
65
if (handleId == StartHandle)
66
centerPoint = shape1->absolutePosition(KoFlake::CenteredPosition);
68
centerPoint = shape2->absolutePosition(KoFlake::CenteredPosition);
70
qreal angle = atan2(handlePoint.y() - centerPoint.y(), handlePoint.x() - centerPoint.x());
73
angle *= 180.0 / M_PI;
74
if (angle >= 45.0 && angle < 135.0)
75
direction = QPointF(0.0, 1.0);
76
else if (angle >= 135.0 && angle < 225.0)
77
direction = QPointF(-1.0, 0.0);
78
else if (angle >= 225.0 && angle < 315.0)
79
direction = QPointF(0.0, -1.0);
81
direction = QPointF(1.0, 0.0);
83
// transform escape direction by using our own transformation matrix
84
QMatrix invMatrix = absoluteMatrix.inverted();
85
direction = invMatrix.map(direction) - invMatrix.map(QPointF());
86
direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y());
92
bool KoConnectionShapePrivate::intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect)
94
qreal sp1 = scalarProd(d1, p2 - p1);
98
qreal sp2 = scalarProd(d2, p1 - p2);
102
// use cross product to check if rays intersects at all
103
qreal cp = crossProd(d1, d2);
105
// rays are parallel or coincidient
106
if (p1.x() == p2.x() && d1.x() == 0.0 && d1.y() != d2.y()) {
107
// vertical, coincident
108
isect = 0.5 * (p1 + p2);
109
} else if (p1.y() == p2.y() && d1.y() == 0.0 && d1.x() != d2.x()) {
110
// horizontal, coincident
111
isect = 0.5 * (p1 + p2);
116
// they are intersecting normally
117
isect = p1 + sp1 * d1;
123
QPointF KoConnectionShapePrivate::perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2)
125
QPointF perpendicular(d1.y(), -d1.x());
126
qreal sp = scalarProd(perpendicular, p2 - p1);
128
perpendicular *= -1.0;
130
return perpendicular;
133
void KoConnectionShapePrivate::normalPath( const qreal MinimumEscapeLength )
135
// Clear the path to build it again.
137
path.append(handles[StartHandle]);
139
QList<QPointF> edges1;
140
QList<QPointF> edges2;
142
QPointF direction1 = escapeDirection(StartHandle);
143
QPointF direction2 = escapeDirection(EndHandle);
145
QPointF edgePoint1 = handles[StartHandle] + MinimumEscapeLength * direction1;
146
QPointF edgePoint2 = handles[EndHandle] + MinimumEscapeLength * direction2;
148
edges1.append(edgePoint1);
149
edges2.prepend(edgePoint2);
151
if (handleConnected(StartHandle) && handleConnected(EndHandle)) {
152
QPointF intersection;
153
bool connected = false;
155
// first check if directions from current edge points intersect
156
if (intersects(edgePoint1, direction1, edgePoint2, direction2, intersection)) {
157
// directions intersect, we have another edge point and be done
158
edges1.append(intersection);
162
// check if we are going toward the other handle
163
qreal sp = scalarProd(direction1, edgePoint2 - edgePoint1);
165
// if we are having the same direction, go all the way toward
166
// the other handle, else only go half the way
167
if (direction1 == direction2)
168
edgePoint1 += sp * direction1;
170
edgePoint1 += 0.5 * sp * direction1;
171
edges1.append(edgePoint1);
173
direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
175
// we are not going into the same direction, so switch direction
176
direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
178
} while (! connected);
184
path.append(handles[EndHandle]);
187
qreal KoConnectionShapePrivate::scalarProd(const QPointF &v1, const QPointF &v2)
189
return v1.x() * v2.x() + v1.y() * v2.y();
192
qreal KoConnectionShapePrivate::crossProd(const QPointF &v1, const QPointF &v2)
194
return v1.x() * v2.y() - v1.y() * v2.x();
197
bool KoConnectionShapePrivate::handleConnected(int handleId) const
199
if (handleId == StartHandle && shape1 && connectionPointIndex1 >= 0)
201
if (handleId == EndHandle && shape2 && connectionPointIndex2 >= 0)
207
void KoConnectionShape::updateConnections()
209
Q_D(KoConnectionShape);
210
bool updateHandles = false;
212
if (d->handleConnected(StartHandle)) {
213
QList<QPointF> connectionPoints = d->shape1->connectionPoints();
214
if (d->connectionPointIndex1 < connectionPoints.count()) {
215
// map connection point into our shape coordinates
216
QPointF p = documentToShape(d->shape1->absoluteTransformation(0).map(connectionPoints[d->connectionPointIndex1]));
217
if (d->handles[StartHandle] != p) {
218
d->handles[StartHandle] = p;
219
updateHandles = true;
223
if (d->handleConnected(EndHandle)) {
224
QList<QPointF> connectionPoints = d->shape2->connectionPoints();
225
if (d->connectionPointIndex2 < connectionPoints.count()) {
226
// map connection point into our shape coordinates
227
QPointF p = documentToShape(d->shape2->absoluteTransformation(0).map(connectionPoints[d->connectionPointIndex2]));
228
if (d->handles[EndHandle] != p) {
229
d->handles[EndHandle] = p;
230
updateHandles = true;
235
if (updateHandles || d->forceUpdate) {
236
update(); // ugly, for repainting the connection we just changed
237
updatePath(QSizeF());
238
update(); // ugly, for repainting the connection we just changed
239
d->forceUpdate = false;
53
243
KoConnectionShape::KoConnectionShape()
244
: KoParameterShape(*(new KoConnectionShapePrivate(this)))
56
m_handles.push_back(QPointF(0, 0));
57
m_handles.push_back(QPointF(140, 140));
62
d->points = *m_subpaths[0];
246
Q_D(KoConnectionShape);
247
d->handles.push_back(QPointF(0, 0));
248
d->handles.push_back(QPointF(140, 140));
250
moveTo(d->handles[StartHandle]);
251
lineTo(d->handles[EndHandle]);
63
253
updatePath(QSizeF(140, 140));
65
255
int connectionPointCount = connectionPoints().size();
66
256
for (int i = 0; i < connectionPointCount; ++i)
67
257
removeConnectionPoint(0);
72
260
KoConnectionShape::~KoConnectionShape()
262
Q_D(KoConnectionShape);
75
264
d->shape1->removeDependee(this);
77
266
d->shape2->removeDependee(this);
82
void KoConnectionShape::paint(QPainter&, const KoViewConverter&)
86
269
void KoConnectionShape::saveOdf(KoShapeSavingContext & context) const
271
Q_D(const KoConnectionShape);
88
272
context.xmlWriter().startElement("draw:connector");
89
273
saveOdfAttributes( context, OdfMandatories | OdfAdditionalAttributes );
91
switch( d->connectionType )
94
context.xmlWriter().addAttribute( "draw:type", "lines" );
97
context.xmlWriter().addAttribute( "draw:type", "line" );
100
context.xmlWriter().addAttribute( "draw:type", "curve" );
103
context.xmlWriter().addAttribute( "draw:type", "standard" );
275
switch (d->connectionType) {
277
context.xmlWriter().addAttribute("draw:type", "lines");
280
context.xmlWriter().addAttribute("draw:type", "line");
283
context.xmlWriter().addAttribute("draw:type", "curve");
286
context.xmlWriter().addAttribute("draw:type", "standard");
108
context.xmlWriter().addAttribute( "draw:start-shape", context.drawId(d->shape1) );
109
context.xmlWriter().addAttribute( "draw:start-glue-point", d->connectionPointIndex1 );
112
QPointF p((m_handles[0] + position()) * context.shapeOffset(this));
113
context.xmlWriter().addAttributePt( "svg:x1", p.x() );
114
context.xmlWriter().addAttributePt( "svg:y1", p.y() );
291
context.xmlWriter().addAttribute("draw:start-shape", context.drawId(d->shape1));
292
context.xmlWriter().addAttribute("draw:start-glue-point", d->connectionPointIndex1 );
294
QPointF p((d->handles[StartHandle] + position()) * context.shapeOffset(this));
295
context.xmlWriter().addAttributePt("svg:x1", p.x());
296
context.xmlWriter().addAttributePt("svg:y1", p.y());
117
context.xmlWriter().addAttribute( "draw:end-shape", context.drawId(d->shape2) );
118
context.xmlWriter().addAttribute( "draw:end-glue-point", d->connectionPointIndex2 );
121
QPointF p((m_handles[m_handles.count()-1] + position()) * context.shapeOffset(this));
122
context.xmlWriter().addAttributePt( "svg:x2", p.x() );
123
context.xmlWriter().addAttributePt( "svg:y2", p.y() );
299
context.xmlWriter().addAttribute("draw:end-shape", context.drawId(d->shape2));
300
context.xmlWriter().addAttribute("draw:end-glue-point", d->connectionPointIndex2 );
302
QPointF p((d->handles[EndHandle] + position()) * context.shapeOffset(this));
303
context.xmlWriter().addAttributePt("svg:x2", p.x());
304
context.xmlWriter().addAttributePt("svg:y2", p.y());
307
// write the path data
308
context.xmlWriter().addAttribute("svg:d", toString());
309
saveOdfAttributes(context, OdfViewbox);
126
311
saveOdfCommonChildElements(context);
163
359
d->shape2 = context.shapeById(shapeId2);
165
361
d->shape2->addDependee(this);
362
QList<QPointF> connectionPoints = d->shape2->connectionPoints();
363
if (d->connectionPointIndex2 < connectionPoints.count()) {
364
d->handles[EndHandle] = d->shape2->absoluteTransformation(0).map(connectionPoints[d->connectionPointIndex2]);
168
367
context.updateShape(shapeId2, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::Second));
171
m_handles[m_handles.count() - 1].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", QString())));
172
m_handles[m_handles.count() - 1].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", QString())));
370
d->handles[EndHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", QString())));
371
d->handles[EndHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", QString())));
175
QString skew = element.attributeNS(KoXmlNS::draw, "line-skew", "");
374
QString skew = element.attributeNS(KoXmlNS::draw, "line-skew", QString());
176
375
QStringList skewValues = skew.simplified().split(' ', QString::SkipEmptyParts);
177
376
// TODO apply skew values once we support them
378
// load the path data if there is any
379
d->hasCustomPath = element.hasAttributeNS(KoXmlNS::svg, "d");
380
if (d->hasCustomPath) {
381
KoPathShapeLoader loader(this);
382
loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true);
383
QRectF viewBox = loadOdfViewbox(element);
384
if (viewBox.isEmpty()) {
385
// there should be a viewBox to transform the path data
386
// if there is none, use the bounding rectangle of the parsed path
387
viewBox = outline().boundingRect();
389
// convert path to viewbox coordinates to have a bounding rect of (0,0 1x1)
390
// which can later be fitted back into the target rect once we have all
391
// the required information
393
viewMatrix.scale(viewBox.width() ? static_cast<qreal>(1.0) / viewBox.width() : 1.0,
394
viewBox.height() ? static_cast<qreal>(1.0) / viewBox.height() : 1.0);
395
viewMatrix.translate(-viewBox.left(), -viewBox.top());
398
// trigger finishing the connections in case we have all data
399
// otherwise it gets called again once the shapes we are
400
// connected to are loaded
401
finishLoadingConnection();
403
d->forceUpdate = true;
184
bool KoConnectionShape::hitTest(const QPointF &position) const
410
void KoConnectionShape::finishLoadingConnection()
186
return KoParameterShape::hitTest(position);
412
Q_D(KoConnectionShape);
414
if (d->hasCustomPath) {
415
const bool loadingFinished1 = d->connectionPointIndex1 >= 0 ? d->shape1 != 0 : true;
416
const bool loadingFinished2 = d->connectionPointIndex2 >= 0 ? d->shape2 != 0 : true;
417
if (loadingFinished1 && loadingFinished2) {
419
if (d->handleConnected(StartHandle)) {
420
QList<QPointF> connectionPoints = d->shape1->connectionPoints();
421
if (d->connectionPointIndex1 < connectionPoints.count()) {
422
p1 = d->shape1->absoluteTransformation(0).map(connectionPoints[d->connectionPointIndex1]);
425
p1 = d->handles[StartHandle];
427
if (d->handleConnected(EndHandle)) {
428
QList<QPointF> connectionPoints = d->shape2->connectionPoints();
429
if (d->connectionPointIndex2 < connectionPoints.count()) {
430
p2 = d->shape2->absoluteTransformation(0).map(connectionPoints[d->connectionPointIndex2]);
433
p2 = d->handles[EndHandle];
435
QRectF targetRect = QRectF(p1, p2).normalized();
436
// transform the normalized coordinates back to our target rectangle
438
viewMatrix.translate(targetRect.x(), targetRect.y());
439
viewMatrix.scale(targetRect.width(), targetRect.height());
442
// pretend we are during a forced update, so normalize()
443
// will not trigger an updateConnections() call
444
d->forceUpdate = true;
446
d->forceUpdate = false;
189
void KoConnectionShape::moveHandleAction(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers)
453
void KoConnectionShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers)
191
455
Q_UNUSED(modifiers);
456
Q_D(KoConnectionShape);
193
if (handleId >= m_handles.size())
458
if (handleId >= d->handles.size())
196
m_handles[handleId] = point;
461
d->handles[handleId] = point;
199
464
void KoConnectionShape::updatePath(const QSizeF &size)
467
Q_D(KoConnectionShape);
203
QPointF dst = 0.3 * ( m_handles[0] - m_handles[m_handles.count() - 1]);
469
QPointF dst = 0.3 * ( d->handles[StartHandle] - d->handles[EndHandle]);
204
470
const qreal MinimumEscapeLength = (qreal)20.;
206
472
switch (d->connectionType) {
208
normalPath(MinimumEscapeLength);
209
if( m_path.count() != 0 ){
211
for(int index = 1; index < m_path.count(); ++index )
212
lineTo( m_path[index] );
474
d->normalPath(MinimumEscapeLength);
475
if (d->path.count() != 0){
477
for (int index = 1; index < d->path.count(); ++index )
478
lineTo(d->path[index]);
218
QPointF direction1 = escapeDirection(0);
219
QPointF direction2 = escapeDirection(m_handles.count() - 1);
220
moveTo(m_handles[0]);
484
QPointF direction1 = d->escapeDirection(0);
485
QPointF direction2 = d->escapeDirection(d->handles.count() - 1);
486
moveTo(d->handles[StartHandle]);
221
487
if (! direction1.isNull())
222
lineTo(m_handles[0] + MinimumEscapeLength * direction1);
488
lineTo(d->handles[StartHandle] + MinimumEscapeLength * direction1);
223
489
if (! direction2.isNull())
224
lineTo(m_handles[m_handles.count() - 1] + MinimumEscapeLength * direction2);
225
lineTo(m_handles[m_handles.count() - 1]);
490
lineTo(d->handles[EndHandle] + MinimumEscapeLength * direction2);
491
lineTo(d->handles[EndHandle]);
229
moveTo(m_handles[0]);
230
lineTo(m_handles[m_handles.count() - 1]);
495
moveTo(d->handles[StartHandle]);
496
lineTo(d->handles[EndHandle]);
234
QPointF direction1 = escapeDirection(0);
235
QPointF direction2 = escapeDirection(m_handles.count() - 1);
236
moveTo(m_handles[0]);
500
QPointF direction1 = d->escapeDirection(0);
501
QPointF direction2 = d->escapeDirection(d->handles.count() - 1);
502
moveTo(d->handles[StartHandle]);
237
503
if (! direction1.isNull() && ! direction2.isNull()) {
238
QPointF curvePoint1 = m_handles[0] + 5.0 * MinimumEscapeLength * direction1;
239
QPointF curvePoint2 = m_handles[m_handles.count() - 1] + 5.0 * MinimumEscapeLength * direction2;
240
curveTo(curvePoint1, curvePoint2, m_handles[m_handles.count() - 1]);
504
QPointF curvePoint1 = d->handles[StartHandle] + 5.0 * MinimumEscapeLength * direction1;
505
QPointF curvePoint2 = d->handles[EndHandle] + 5.0 * MinimumEscapeLength * direction2;
506
curveTo(curvePoint1, curvePoint2, d->handles[EndHandle]);
242
lineTo(m_handles[m_handles.count() - 1]);
508
lineTo(d->handles[EndHandle]);
249
void KoConnectionShape::normalPath( const qreal MinimumEscapeLength )
257
// Clear handles keeping the first and last one.
258
firstHandle = m_handles[0];
259
lastHandle = m_handles[m_handles.count() - 1];
262
m_handles.append(firstHandle);
263
m_handles.append(lastHandle);
265
// Clear the path to build it again.
267
m_path.append( m_handles[0] );
269
QList<QPointF> edges1;
270
QList<QPointF> edges2;
272
QPointF direction1 = escapeDirection(0);
273
QPointF direction2 = escapeDirection(m_handles.count() - 1);
275
QPointF edgePoint1 = m_handles[0] + MinimumEscapeLength * direction1;
276
QPointF edgePoint2 = m_handles[m_handles.count() - 1] + MinimumEscapeLength * direction2;
278
edges1.append(edgePoint1);
279
edges2.prepend(edgePoint2);
281
if (handleConnected(0) && handleConnected(1)) {
282
QPointF intersection;
283
bool connected = false;
285
// first check if directions from current edge points intersect
286
if (intersects(edgePoint1, direction1, edgePoint2, direction2, intersection)) {
287
// directions intersect, we have another edge point and be done
288
edges1.append(intersection);
292
// check if we are going toward the other handle
293
qreal sp = scalarProd(direction1, edgePoint2 - edgePoint1);
295
// if we are having the same direction, go all the way toward
296
// the other handle, else only go half the way
297
if (direction1 == direction2)
298
edgePoint1 += sp * direction1;
300
edgePoint1 += 0.5 * sp * direction1;
301
edges1.append(edgePoint1);
303
direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
305
// we are not going into the same direction, so switch direction
306
direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
308
} while (! connected);
311
m_path.append(edges1);
312
m_path.append(edges2);
314
m_path.append( m_handles[m_handles.count() - 1] );
318
bool KoConnectionShape::setConnection1(KoShape * shape1, int connectionPointIndex1)
515
bool KoConnectionShape::connectFirst(KoShape * shape1, int connectionPointIndex1)
517
Q_D(KoConnectionShape);
320
518
// refuse to connect to a shape that depends on us (e.g. a artistic text shape)
321
519
if (hasDependee(shape1))
360
KoConnection KoConnectionShape::connection1() const
362
return KoConnection(d->shape1, d->connectionPointIndex1);
365
KoConnection KoConnectionShape::connection2() const
367
return KoConnection(d->shape2, d->connectionPointIndex2);
370
void KoConnectionShape::updateConnections()
372
bool updateHandles = false;
374
if (handleConnected(0)) {
375
QList<QPointF> connectionPoints = d->shape1->connectionPoints();
376
if (d->connectionPointIndex1 < connectionPoints.count()) {
377
// map connection point into our shape coordinates
378
QPointF p = documentToShape(d->shape1->absoluteTransformation(0).map(connectionPoints[d->connectionPointIndex1]));
379
if (m_handles[0] != p) {
381
updateHandles = true;
385
if (handleConnected(1)) {
386
QList<QPointF> connectionPoints = d->shape2->connectionPoints();
387
if (d->connectionPointIndex2 < connectionPoints.count()) {
388
// map connection point into our shape coordinates
389
QPointF p = documentToShape(d->shape2->absoluteTransformation(0).map(connectionPoints[d->connectionPointIndex2]));
390
if (m_handles[m_handles.count() - 1] != p) {
391
m_handles[m_handles.count() - 1] = p;
392
updateHandles = true;
396
if (updateHandles || d->forceUpdate) {
397
update(); // ugly, for repainting the connection we just changed
398
updatePath(QSizeF());
399
update(); // ugly, for repainting the connection we just changed
400
d->forceUpdate = false;
404
KoConnectionShape::Type KoConnectionShape::connectionType() const
559
KoShape *KoConnectionShape::firstShape() const
561
Q_D(const KoConnectionShape);
565
int KoConnectionShape::firstConnectionIndex() const
567
Q_D(const KoConnectionShape);
568
return d->connectionPointIndex1;
571
KoShape *KoConnectionShape::secondShape() const
573
Q_D(const KoConnectionShape);
577
int KoConnectionShape::secondConnectionIndex() const
579
Q_D(const KoConnectionShape);
580
return d->connectionPointIndex2;
583
KoConnectionShape::Type KoConnectionShape::type() const
585
Q_D(const KoConnectionShape);
406
586
return d->connectionType;
409
void KoConnectionShape::setConnectionType(Type connectionType)
589
void KoConnectionShape::setType(Type connectionType)
591
Q_D(KoConnectionShape);
411
592
d->connectionType = connectionType;
412
593
updatePath(size());
415
bool KoConnectionShape::handleConnected(int handleId) const
417
if (handleId == 0 && d->shape1 && d->connectionPointIndex1 >= 0)
419
if (handleId == 1 && d->shape2 && d->connectionPointIndex2 >= 0)
425
QPointF KoConnectionShape::escapeDirection(int handleId) const
428
if (handleConnected(handleId)) {
429
QMatrix absoluteMatrix = absoluteTransformation(0);
430
QPointF handlePoint = absoluteMatrix.map(m_handles[handleId]);
433
centerPoint = d->shape1->absolutePosition(KoFlake::CenteredPosition);
435
centerPoint = d->shape2->absolutePosition(KoFlake::CenteredPosition);
437
qreal angle = atan2(handlePoint.y() - centerPoint.y(), handlePoint.x() - centerPoint.x());
440
angle *= 180.0 / M_PI;
441
if (angle >= 45.0 && angle < 135.0)
442
direction = QPointF(0.0, 1.0);
443
else if (angle >= 135.0 && angle < 225.0)
444
direction = QPointF(-1.0, 0.0);
445
else if (angle >= 225.0 && angle < 315.0)
446
direction = QPointF(0.0, -1.0);
448
direction = QPointF(1.0, 0.0);
450
// transform escape direction by using our own transformation matrix
451
QMatrix invMatrix = absoluteMatrix.inverted();
452
direction = invMatrix.map(direction) - invMatrix.map(QPointF());
453
direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y());
459
bool KoConnectionShape::intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect)
461
qreal sp1 = scalarProd(d1, p2 - p1);
465
qreal sp2 = scalarProd(d2, p1 - p2);
469
// use cross product to check if rays intersects at all
470
qreal cp = crossProd(d1, d2);
472
// rays are parallel or coincidient
473
if (p1.x() == p2.x() && d1.x() == 0.0 && d1.y() != d2.y()) {
474
// vertical, coincident
475
isect = 0.5 * (p1 + p2);
476
} else if (p1.y() == p2.y() && d1.y() == 0.0 && d1.x() != d2.x()) {
477
// horizontal, coincident
478
isect = 0.5 * (p1 + p2);
482
// they are intersecting normally
483
isect = p1 + sp1 * d1;
489
qreal KoConnectionShape::scalarProd(const QPointF &v1, const QPointF &v2)
491
return v1.x()*v2.x() + v1.y()*v2.y();
494
qreal KoConnectionShape::crossProd(const QPointF &v1, const QPointF &v2)
496
return (v1.x()*v2.y() - v1.y()*v2.x());
499
QPointF KoConnectionShape::perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2)
501
QPointF perpendicular(d1.y(), -d1.x());
502
qreal sp = scalarProd(perpendicular, p2 - p1);
504
perpendicular *= -1.0;
506
return perpendicular;
509
void KoConnectionShape::shapeChanged(ChangeType type, KoShape * shape)
596
void KoConnectionShape::shapeChanged(ChangeType type, KoShape *shape)
598
Q_D(KoConnectionShape);
511
599
// check if we are during a forced update
512
600
const bool updateIsActive = d->forceUpdate;
516
case PositionChanged:
517
case RotationChanged:
520
case GenericMatrixChange:
521
case ParameterChanged:
522
if (isParametricShape() && shape == 0)
523
d->forceUpdate = true;
526
if (shape != d->shape1 && shape != d->shape2)
528
if (shape == d->shape1)
529
setConnection1(0, -1);
530
if (shape == d->shape2)
531
setConnection2(0, -1);
603
case PositionChanged:
604
case RotationChanged:
607
case GenericMatrixChange:
608
case ParameterChanged:
609
if (isParametricShape() && shape == 0)
610
d->forceUpdate = true;
613
if (shape != d->shape1 && shape != d->shape2)
615
if (shape == d->shape1)
617
if (shape == d->shape2)
618
connectSecond(0, -1);
537
624
// the connection was moved while it is connected to some other shapes
538
625
const bool connectionChanged = !shape && (d->shape1 || d->shape2);
539
626
// one of the connected shape has moved
540
627
const bool connectedShapeChanged = shape && (shape == d->shape1 || shape == d->shape2);
542
629
if (!updateIsActive && (connectionChanged || connectedShapeChanged) && isParametricShape())
543
630
updateConnections();
545
632
// reset the forced update flag
546
633
d->forceUpdate = false;
636
QString KoConnectionShape::pathShapeId() const
638
return KOCONNECTIONSHAPEID;