~ubuntu-branches/ubuntu/precise/koffice/precise

« back to all changes in this revision

Viewing changes to libs/flake/KoConnectionShape.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Jonathan Riddell
  • Date: 2010-09-21 15:36:35 UTC
  • mfrom: (1.4.1 upstream) (60.2.11 maverick)
  • Revision ID: james.westby@ubuntu.com-20100921153635-6tejqkiro2u21ydi
Tags: 1:2.2.2-0ubuntu3
Add kubuntu_03_fix-crash-on-closing-sqlite-connection-2.2.2.diff and
kubuntu_04_support-large-memo-values-for-msaccess-2.2.2.diff as
recommended by upstream http://kexi-
project.org/wiki/wikiview/index.php@Kexi2.2_Patches.html#sqlite_stab
ility

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
/* This file is part of the KDE project
2
2
 * Copyright (C) 2007 Boudewijn Rempt <boud@kde.org>
3
3
 * Copyright (C) 2007,2009 Thorsten Zachmann <zachmann@kde.org>
4
 
 * Copyright (C) 2007,2009  Jan Hambrecht <jaham@gmx.net>
 
4
 * Copyright (C) 2007,2009,2010  Jan Hambrecht <jaham@gmx.net>
5
5
 *
6
6
 * This library is free software; you can redistribute it and/or
7
7
 * modify it under the terms of the GNU Library General Public
20
20
 */
21
21
 
22
22
#include "KoConnectionShape.h"
 
23
#include "KoConnectionShape_p.h"
23
24
 
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"
33
 
 
34
35
#include <QPainter>
35
36
 
36
37
#include <KDebug>
37
 
// XXX: Add editable text in path shapes so we can get a label here
38
38
 
39
 
struct KoConnectionShape::Private {
40
 
    Private()
41
 
            : shape1(0), shape2(0), connectionPointIndex1(-1), connectionPointIndex2(-1)
42
 
            , connectionType(Standard), forceUpdate(false) {}
43
 
    KoSubpath points;
44
 
    KoShape * shape1;
45
 
    KoShape * shape2;
46
 
    int connectionPointIndex1;
47
 
    int connectionPointIndex2;
48
 
    Type connectionType;
49
 
    bool forceUpdate;
 
39
// IDs of the connecting handles
 
40
enum HandleId {
 
41
    StartHandle,
 
42
    EndHandle
50
43
};
51
44
 
 
45
KoConnectionShapePrivate::KoConnectionShapePrivate(KoConnectionShape *q)
 
46
    : KoParameterShapePrivate(q),
 
47
    shape1(0),
 
48
    shape2(0),
 
49
    connectionPointIndex1(-1),
 
50
    connectionPointIndex2(-1),
 
51
    connectionType(KoConnectionShape::Standard),
 
52
    forceUpdate(false),
 
53
    hasCustomPath(false)
 
54
{
 
55
}
 
56
 
 
57
QPointF KoConnectionShapePrivate::escapeDirection(int handleId) const
 
58
{
 
59
    Q_Q(const KoConnectionShape);
 
60
    QPointF direction;
 
61
    if (handleConnected(handleId)) {
 
62
        QMatrix absoluteMatrix = q->absoluteTransformation(0);
 
63
        QPointF handlePoint = absoluteMatrix.map(handles[handleId]);
 
64
        QPointF centerPoint;
 
65
        if (handleId == StartHandle)
 
66
            centerPoint = shape1->absolutePosition(KoFlake::CenteredPosition);
 
67
        else
 
68
            centerPoint = shape2->absolutePosition(KoFlake::CenteredPosition);
 
69
 
 
70
        qreal angle = atan2(handlePoint.y() - centerPoint.y(), handlePoint.x() - centerPoint.x());
 
71
        if (angle < 0.0)
 
72
            angle += 2.0 * M_PI;
 
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);
 
80
        else
 
81
            direction = QPointF(1.0, 0.0);
 
82
 
 
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());
 
87
    }
 
88
 
 
89
    return direction;
 
90
}
 
91
 
 
92
bool KoConnectionShapePrivate::intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect)
 
93
{
 
94
    qreal sp1 = scalarProd(d1, p2 - p1);
 
95
    if (sp1 < 0.0)
 
96
        return false;
 
97
 
 
98
    qreal sp2 = scalarProd(d2, p1 - p2);
 
99
    if (sp2 < 0.0)
 
100
        return false;
 
101
 
 
102
    // use cross product to check if rays intersects at all
 
103
    qreal cp = crossProd(d1, d2);
 
104
    if (cp == 0.0) {
 
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);
 
112
        } else {
 
113
            return false;
 
114
        }
 
115
    } else {
 
116
        // they are intersecting normally
 
117
        isect = p1 + sp1 * d1;
 
118
    }
 
119
 
 
120
    return true;
 
121
}
 
122
 
 
123
QPointF KoConnectionShapePrivate::perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2)
 
124
{
 
125
    QPointF perpendicular(d1.y(), -d1.x());
 
126
    qreal sp = scalarProd(perpendicular, p2 - p1);
 
127
    if (sp < 0.0)
 
128
        perpendicular *= -1.0;
 
129
 
 
130
    return perpendicular;
 
131
}
 
132
 
 
133
void KoConnectionShapePrivate::normalPath( const qreal MinimumEscapeLength )
 
134
{
 
135
    // Clear the path to build it again.
 
136
    path.clear();
 
137
    path.append(handles[StartHandle]);
 
138
 
 
139
    QList<QPointF> edges1;
 
140
    QList<QPointF> edges2;
 
141
 
 
142
    QPointF direction1 = escapeDirection(StartHandle);
 
143
    QPointF direction2 = escapeDirection(EndHandle);
 
144
 
 
145
    QPointF edgePoint1 = handles[StartHandle] + MinimumEscapeLength * direction1;
 
146
    QPointF edgePoint2 = handles[EndHandle] + MinimumEscapeLength * direction2;
 
147
 
 
148
    edges1.append(edgePoint1);
 
149
    edges2.prepend(edgePoint2);
 
150
 
 
151
    if (handleConnected(StartHandle) && handleConnected(EndHandle)) {
 
152
        QPointF intersection;
 
153
        bool connected = false;
 
154
        do {
 
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);
 
159
                break;
 
160
            }
 
161
 
 
162
            // check if we are going toward the other handle
 
163
            qreal sp = scalarProd(direction1, edgePoint2 - edgePoint1);
 
164
            if (sp >= 0.0) {
 
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;
 
169
                else
 
170
                    edgePoint1 += 0.5 * sp * direction1;
 
171
                edges1.append(edgePoint1);
 
172
                // switch direction
 
173
                direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
 
174
            } else {
 
175
                // we are not going into the same direction, so switch direction
 
176
                direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
 
177
            }
 
178
        } while (! connected);
 
179
    }
 
180
 
 
181
    path.append(edges1);
 
182
    path.append(edges2);
 
183
 
 
184
    path.append(handles[EndHandle]);
 
185
}
 
186
 
 
187
qreal KoConnectionShapePrivate::scalarProd(const QPointF &v1, const QPointF &v2)
 
188
{
 
189
    return v1.x() * v2.x() + v1.y() * v2.y();
 
190
}
 
191
 
 
192
qreal KoConnectionShapePrivate::crossProd(const QPointF &v1, const QPointF &v2)
 
193
{
 
194
    return v1.x() * v2.y() - v1.y() * v2.x();
 
195
}
 
196
 
 
197
bool KoConnectionShapePrivate::handleConnected(int handleId) const
 
198
{
 
199
    if (handleId == StartHandle && shape1 && connectionPointIndex1 >= 0)
 
200
        return true;
 
201
    if (handleId == EndHandle && shape2 && connectionPointIndex2 >= 0)
 
202
        return true;
 
203
 
 
204
    return false;
 
205
}
 
206
 
 
207
void KoConnectionShape::updateConnections()
 
208
{
 
209
    Q_D(KoConnectionShape);
 
210
    bool updateHandles = false;
 
211
 
 
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;
 
220
            }
 
221
        }
 
222
    }
 
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;
 
231
            }
 
232
        }
 
233
    }
 
234
 
 
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;
 
240
    }
 
241
}
52
242
 
53
243
KoConnectionShape::KoConnectionShape()
54
 
        : d(new Private)
 
244
    : KoParameterShape(*(new KoConnectionShapePrivate(this)))
55
245
{
56
 
    m_handles.push_back(QPointF(0, 0));
57
 
    m_handles.push_back(QPointF(140, 140));
58
 
 
59
 
    moveTo(m_handles[0]);
60
 
    lineTo(m_handles[1]);
61
 
 
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));
 
249
 
 
250
    moveTo(d->handles[StartHandle]);
 
251
    lineTo(d->handles[EndHandle]);
 
252
 
63
253
    updatePath(QSizeF(140, 140));
64
254
 
65
255
    int connectionPointCount = connectionPoints().size();
66
256
    for (int i = 0; i < connectionPointCount; ++i)
67
257
        removeConnectionPoint(0);
68
 
 
69
 
    m_hasMoved = true;
70
258
}
71
259
 
72
260
KoConnectionShape::~KoConnectionShape()
73
261
{
 
262
    Q_D(KoConnectionShape);
74
263
    if (d->shape1)
75
264
        d->shape1->removeDependee(this);
76
265
    if (d->shape2)
77
266
        d->shape2->removeDependee(this);
78
 
 
79
 
    delete d;
80
 
}
81
 
 
82
 
void KoConnectionShape::paint(QPainter&, const KoViewConverter&)
83
 
{
84
267
}
85
268
 
86
269
void KoConnectionShape::saveOdf(KoShapeSavingContext & context) const
87
270
{
 
271
    Q_D(const KoConnectionShape);
88
272
    context.xmlWriter().startElement("draw:connector");
89
273
    saveOdfAttributes( context, OdfMandatories | OdfAdditionalAttributes );
90
274
 
91
 
    switch( d->connectionType )
92
 
    {
93
 
        case Lines:
94
 
            context.xmlWriter().addAttribute( "draw:type", "lines" );
95
 
            break;
96
 
        case Straight:
97
 
            context.xmlWriter().addAttribute( "draw:type", "line" );
98
 
            break;
99
 
        case Curve:
100
 
            context.xmlWriter().addAttribute( "draw:type", "curve" );
101
 
            break;
102
 
        default:
103
 
            context.xmlWriter().addAttribute( "draw:type", "standard" );
104
 
            break;
 
275
    switch (d->connectionType) {
 
276
    case Lines:
 
277
        context.xmlWriter().addAttribute("draw:type", "lines");
 
278
        break;
 
279
    case Straight:
 
280
        context.xmlWriter().addAttribute("draw:type", "line");
 
281
        break;
 
282
    case Curve:
 
283
        context.xmlWriter().addAttribute("draw:type", "curve");
 
284
        break;
 
285
    default:
 
286
        context.xmlWriter().addAttribute("draw:type", "standard");
 
287
        break;
105
288
    }
106
289
 
107
290
    if (d->shape1) {
108
 
        context.xmlWriter().addAttribute( "draw:start-shape", context.drawId(d->shape1) );
109
 
        context.xmlWriter().addAttribute( "draw:start-glue-point", d->connectionPointIndex1 );
110
 
    }
111
 
    else {
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 );
 
293
    } else {
 
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());
115
297
    }
116
298
    if (d->shape2) {
117
 
        context.xmlWriter().addAttribute( "draw:end-shape", context.drawId(d->shape2) );
118
 
        context.xmlWriter().addAttribute( "draw:end-glue-point", d->connectionPointIndex2 );
119
 
    }
120
 
    else {
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() );
124
 
    }
 
299
        context.xmlWriter().addAttribute("draw:end-shape", context.drawId(d->shape2));
 
300
        context.xmlWriter().addAttribute("draw:end-glue-point", d->connectionPointIndex2 );
 
301
    } else {
 
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());
 
305
    }
 
306
 
 
307
    // write the path data
 
308
    context.xmlWriter().addAttribute("svg:d", toString());
 
309
    saveOdfAttributes(context, OdfViewbox);
125
310
 
126
311
    saveOdfCommonChildElements(context);
127
312
 
130
315
 
131
316
bool KoConnectionShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context)
132
317
{
 
318
    Q_D(KoConnectionShape);
133
319
    loadOdfAttributes(element, context, OdfMandatories | OdfCommonChildElements | OdfAdditionalAttributes);
134
320
 
135
321
    QString type = element.attributeNS(KoXmlNS::draw, "type", "standard");
142
328
    else
143
329
        d->connectionType = Standard;
144
330
 
 
331
    // reset connection point indices
 
332
    d->connectionPointIndex1 = -1;
 
333
    d->connectionPointIndex2 = -1;
 
334
    // reset connected shapes
 
335
    d->shape1 = 0;
 
336
    d->shape2 = 0;
 
337
 
145
338
    if (element.hasAttributeNS(KoXmlNS::draw, "start-shape")) {
146
 
        d->connectionPointIndex1 = element.attributeNS(KoXmlNS::draw, "start-glue-point", "").toInt();
147
 
        QString shapeId1 = element.attributeNS(KoXmlNS::draw, "start-shape", "");
 
339
        d->connectionPointIndex1 = element.attributeNS(KoXmlNS::draw, "start-glue-point", QString()).toInt();
 
340
        QString shapeId1 = element.attributeNS(KoXmlNS::draw, "start-shape", QString());
148
341
        d->shape1 = context.shapeById(shapeId1);
149
342
        if (d->shape1) {
150
343
            d->shape1->addDependee(this);
151
 
        }
152
 
        else {
 
344
            QList<QPointF> connectionPoints = d->shape1->connectionPoints();
 
345
            if (d->connectionPointIndex1 < connectionPoints.count()) {
 
346
                d->handles[StartHandle] = d->shape1->absoluteTransformation(0).map(connectionPoints[d->connectionPointIndex1]);
 
347
            }
 
348
        } else {
153
349
            context.updateShape(shapeId1, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::First));
154
350
        }
155
351
    } else {
156
 
        m_handles[0].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", QString())));
157
 
        m_handles[0].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", QString())));
 
352
        d->handles[StartHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", QString())));
 
353
        d->handles[StartHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", QString())));
158
354
    }
159
355
 
160
356
    if (element.hasAttributeNS(KoXmlNS::draw, "end-shape")) {
163
359
        d->shape2 = context.shapeById(shapeId2);
164
360
        if (d->shape2) {
165
361
            d->shape2->addDependee(this);
166
 
        }
167
 
        else {
 
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]);
 
365
            }
 
366
        } else {
168
367
            context.updateShape(shapeId2, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::Second));
169
368
        }
170
369
    } else {
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())));
173
372
    }
174
373
 
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
178
377
 
179
 
    updateConnections();
 
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();
 
388
        }
 
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
 
392
        QMatrix viewMatrix;
 
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());
 
396
        d->map(viewMatrix);
 
397
 
 
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();
 
402
    } else {
 
403
        d->forceUpdate = true;
 
404
        updateConnections();
 
405
    }
180
406
 
181
407
    return true;
182
408
}
183
409
 
184
 
bool KoConnectionShape::hitTest(const QPointF &position) const
 
410
void KoConnectionShape::finishLoadingConnection()
185
411
{
186
 
    return KoParameterShape::hitTest(position);
 
412
    Q_D(KoConnectionShape);
 
413
 
 
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) {
 
418
            QPointF p1, p2;
 
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]);
 
423
                }
 
424
            } else {
 
425
                p1 = d->handles[StartHandle];
 
426
            }
 
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]);
 
431
                }
 
432
            } else {
 
433
                p2 = d->handles[EndHandle];
 
434
            }
 
435
            QRectF targetRect = QRectF(p1, p2).normalized();
 
436
            // transform the normalized coordinates back to our target rectangle
 
437
            QMatrix viewMatrix;
 
438
            viewMatrix.translate(targetRect.x(), targetRect.y());
 
439
            viewMatrix.scale(targetRect.width(), targetRect.height());
 
440
            d->map(viewMatrix);
 
441
 
 
442
            // pretend we are during a forced update, so normalize()
 
443
            // will not trigger an updateConnections() call
 
444
            d->forceUpdate = true;
 
445
            normalize();
 
446
            d->forceUpdate = false;
 
447
        }
 
448
    } else {
 
449
        updateConnections();
 
450
    }
187
451
}
188
452
 
189
 
void KoConnectionShape::moveHandleAction(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers)
 
453
void KoConnectionShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers)
190
454
{
191
455
    Q_UNUSED(modifiers);
 
456
    Q_D(KoConnectionShape);
192
457
 
193
 
    if (handleId >= m_handles.size())
 
458
    if (handleId >= d->handles.size())
194
459
        return;
195
460
 
196
 
    m_handles[handleId] = point;
 
461
    d->handles[handleId] = point;
197
462
}
198
463
 
199
464
void KoConnectionShape::updatePath(const QSizeF &size)
200
465
{
201
466
    Q_UNUSED(size);
 
467
    Q_D(KoConnectionShape);
202
468
 
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.;
205
471
    clear();
206
472
    switch (d->connectionType) {
207
473
    case Standard: {
208
 
        normalPath(MinimumEscapeLength);
209
 
        if( m_path.count() != 0 ){
210
 
            moveTo( m_path[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){
 
476
            moveTo(d->path[0]);
 
477
            for (int index = 1; index < d->path.count(); ++index )
 
478
                lineTo(d->path[index]);
213
479
        }
214
480
 
215
481
        break;
216
482
    }
217
483
    case Lines: {
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]);
226
492
        break;
227
493
    }
228
494
    case Straight:
229
 
        moveTo(m_handles[0]);
230
 
        lineTo(m_handles[m_handles.count() - 1]);
 
495
        moveTo(d->handles[StartHandle]);
 
496
        lineTo(d->handles[EndHandle]);
231
497
        break;
232
498
    case Curve:
233
499
        // TODO
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]);
241
507
        } else {
242
 
            lineTo(m_handles[m_handles.count() - 1]);
 
508
            lineTo(d->handles[EndHandle]);
243
509
        }
244
510
        break;
245
511
    }
246
512
    normalize();
247
513
}
248
514
 
249
 
void KoConnectionShape::normalPath( const qreal MinimumEscapeLength )
250
 
{
251
 
    if(m_hasMoved) {
252
 
 
253
 
        m_hasMoved = false;
254
 
        QPointF firstHandle;
255
 
        QPointF lastHandle;
256
 
 
257
 
        // Clear handles keeping the first and last one.
258
 
        firstHandle = m_handles[0];
259
 
        lastHandle = m_handles[m_handles.count() - 1];
260
 
 
261
 
        m_handles.clear();
262
 
        m_handles.append(firstHandle);
263
 
        m_handles.append(lastHandle);
264
 
 
265
 
        // Clear the path to build it again.
266
 
        m_path.clear();
267
 
        m_path.append( m_handles[0] );
268
 
 
269
 
        QList<QPointF> edges1;
270
 
        QList<QPointF> edges2;
271
 
 
272
 
        QPointF direction1 = escapeDirection(0);
273
 
        QPointF direction2 = escapeDirection(m_handles.count() - 1);
274
 
        
275
 
        QPointF edgePoint1 = m_handles[0] + MinimumEscapeLength * direction1;
276
 
        QPointF edgePoint2 = m_handles[m_handles.count() - 1] + MinimumEscapeLength * direction2;
277
 
 
278
 
        edges1.append(edgePoint1);
279
 
        edges2.prepend(edgePoint2);
280
 
 
281
 
        if (handleConnected(0) && handleConnected(1)) {
282
 
            QPointF intersection;
283
 
            bool connected = false;
284
 
            do {
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);
289
 
                    break;
290
 
                }
291
 
 
292
 
                // check if we are going toward the other handle
293
 
                qreal sp = scalarProd(direction1, edgePoint2 - edgePoint1);
294
 
                if (sp >= 0.0) {
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;
299
 
                    else
300
 
                        edgePoint1 += 0.5 * sp * direction1;
301
 
                    edges1.append(edgePoint1);
302
 
                    // switch direction
303
 
                    direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
304
 
                } else {
305
 
                    // we are not going into the same direction, so switch direction
306
 
                    direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
307
 
                }
308
 
            } while (! connected);
309
 
        }
310
 
 
311
 
        m_path.append(edges1);
312
 
        m_path.append(edges2);
313
 
 
314
 
        m_path.append( m_handles[m_handles.count() - 1] );
315
 
    }
316
 
}
317
 
 
318
 
bool KoConnectionShape::setConnection1(KoShape * shape1, int connectionPointIndex1)
319
 
{
 
515
bool KoConnectionShape::connectFirst(KoShape * shape1, int connectionPointIndex1)
 
516
{
 
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))
322
520
        return false;
336
534
    return true;
337
535
}
338
536
 
339
 
bool KoConnectionShape::setConnection2(KoShape * shape2, int connectionPointIndex2)
 
537
bool KoConnectionShape::connectSecond(KoShape * shape2, int connectionPointIndex2)
340
538
{
 
539
    Q_D(KoConnectionShape);
341
540
    // refuse to connect to a shape that depends on us (e.g. a artistic text shape)
342
541
    if (hasDependee(shape2))
343
542
        return false;
357
556
    return true;
358
557
}
359
558
 
360
 
KoConnection KoConnectionShape::connection1() const
361
 
{
362
 
    return KoConnection(d->shape1, d->connectionPointIndex1);
363
 
}
364
 
 
365
 
KoConnection KoConnectionShape::connection2() const
366
 
{
367
 
    return KoConnection(d->shape2, d->connectionPointIndex2);
368
 
}
369
 
 
370
 
void KoConnectionShape::updateConnections()
371
 
{
372
 
    bool updateHandles = false;
373
 
 
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) {
380
 
                m_handles[0] = p;
381
 
                updateHandles = true;
382
 
            }
383
 
        }
384
 
    }
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;
393
 
            }
394
 
        }
395
 
    }
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;
401
 
    }
402
 
}
403
 
 
404
 
KoConnectionShape::Type KoConnectionShape::connectionType() const
405
 
{
 
559
KoShape *KoConnectionShape::firstShape() const
 
560
{
 
561
    Q_D(const KoConnectionShape);
 
562
    return d->shape1;
 
563
}
 
564
 
 
565
int KoConnectionShape::firstConnectionIndex() const
 
566
{
 
567
    Q_D(const KoConnectionShape);
 
568
    return d->connectionPointIndex1;
 
569
}
 
570
 
 
571
KoShape *KoConnectionShape::secondShape() const
 
572
{
 
573
    Q_D(const KoConnectionShape);
 
574
    return d->shape2;
 
575
}
 
576
 
 
577
int KoConnectionShape::secondConnectionIndex() const
 
578
{
 
579
    Q_D(const KoConnectionShape);
 
580
    return d->connectionPointIndex2;
 
581
}
 
582
 
 
583
KoConnectionShape::Type KoConnectionShape::type() const
 
584
{
 
585
    Q_D(const KoConnectionShape);
406
586
    return d->connectionType;
407
587
}
408
588
 
409
 
void KoConnectionShape::setConnectionType(Type connectionType)
 
589
void KoConnectionShape::setType(Type connectionType)
410
590
{
 
591
    Q_D(KoConnectionShape);
411
592
    d->connectionType = connectionType;
412
593
    updatePath(size());
413
594
}
414
595
 
415
 
bool KoConnectionShape::handleConnected(int handleId) const
416
 
{
417
 
    if (handleId == 0 && d->shape1 && d->connectionPointIndex1 >= 0)
418
 
        return true;
419
 
    if (handleId == 1 && d->shape2 && d->connectionPointIndex2 >= 0)
420
 
        return true;
421
 
 
422
 
    return false;
423
 
}
424
 
 
425
 
QPointF KoConnectionShape::escapeDirection(int handleId) const
426
 
{
427
 
    QPointF direction;
428
 
    if (handleConnected(handleId)) {
429
 
        QMatrix absoluteMatrix = absoluteTransformation(0);
430
 
        QPointF handlePoint = absoluteMatrix.map(m_handles[handleId]);
431
 
        QPointF centerPoint;
432
 
        if (handleId == 0)
433
 
            centerPoint = d->shape1->absolutePosition(KoFlake::CenteredPosition);
434
 
        else
435
 
            centerPoint = d->shape2->absolutePosition(KoFlake::CenteredPosition);
436
 
 
437
 
        qreal angle = atan2(handlePoint.y() - centerPoint.y(), handlePoint.x() - centerPoint.x());
438
 
        if (angle < 0.0)
439
 
            angle += 2.0 * M_PI;
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);
447
 
        else
448
 
            direction = QPointF(1.0, 0.0);
449
 
 
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());
454
 
    }
455
 
 
456
 
    return direction;
457
 
}
458
 
 
459
 
bool KoConnectionShape::intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect)
460
 
{
461
 
    qreal sp1 = scalarProd(d1, p2 - p1);
462
 
    if (sp1 < 0.0)
463
 
        return false;
464
 
 
465
 
    qreal sp2 = scalarProd(d2, p1 - p2);
466
 
    if (sp2 < 0.0)
467
 
        return false;
468
 
 
469
 
    // use cross product to check if rays intersects at all
470
 
    qreal cp = crossProd(d1, d2);
471
 
    if (cp == 0.0) {
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);
479
 
        } else
480
 
            return false;
481
 
    } else {
482
 
        // they are intersecting normally
483
 
        isect = p1 + sp1 * d1;
484
 
    }
485
 
 
486
 
    return true;
487
 
}
488
 
 
489
 
qreal KoConnectionShape::scalarProd(const QPointF &v1, const QPointF &v2)
490
 
{
491
 
    return v1.x()*v2.x() + v1.y()*v2.y();
492
 
}
493
 
 
494
 
qreal KoConnectionShape::crossProd(const QPointF &v1, const QPointF &v2)
495
 
{
496
 
    return (v1.x()*v2.y() - v1.y()*v2.x());
497
 
}
498
 
 
499
 
QPointF KoConnectionShape::perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2)
500
 
{
501
 
    QPointF perpendicular(d1.y(), -d1.x());
502
 
    qreal sp = scalarProd(perpendicular, p2 - p1);
503
 
    if (sp < 0.0)
504
 
        perpendicular *= -1.0;
505
 
 
506
 
    return perpendicular;
507
 
}
508
 
 
509
 
void KoConnectionShape::shapeChanged(ChangeType type, KoShape * shape)
510
 
{
 
596
void KoConnectionShape::shapeChanged(ChangeType type, KoShape *shape)
 
597
{
 
598
    Q_D(KoConnectionShape);
511
599
    // check if we are during a forced update
512
600
    const bool updateIsActive = d->forceUpdate;
513
601
 
514
 
    m_hasMoved = true;
515
602
    switch (type) {
516
 
        case PositionChanged:
517
 
        case RotationChanged:
518
 
        case ShearChanged:
519
 
        case ScaleChanged:
520
 
        case GenericMatrixChange:
521
 
        case ParameterChanged:
522
 
            if (isParametricShape() && shape == 0)
523
 
                d->forceUpdate = true;
524
 
            break;
525
 
        case Deleted:
526
 
            if (shape != d->shape1 && shape != d->shape2)
527
 
                return;
528
 
            if (shape == d->shape1)
529
 
                setConnection1(0, -1);
530
 
            if (shape == d->shape2)
531
 
                setConnection2(0, -1);
532
 
            break;
533
 
        default:
 
603
    case PositionChanged:
 
604
    case RotationChanged:
 
605
    case ShearChanged:
 
606
    case ScaleChanged:
 
607
    case GenericMatrixChange:
 
608
    case ParameterChanged:
 
609
        if (isParametricShape() && shape == 0)
 
610
            d->forceUpdate = true;
 
611
        break;
 
612
    case Deleted:
 
613
        if (shape != d->shape1 && shape != d->shape2)
534
614
            return;
 
615
        if (shape == d->shape1)
 
616
            connectFirst(0, -1);
 
617
        if (shape == d->shape2)
 
618
            connectSecond(0, -1);
 
619
        break;
 
620
    default:
 
621
        return;
535
622
    }
536
623
 
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);
541
 
    
 
628
 
542
629
    if (!updateIsActive && (connectionChanged || connectedShapeChanged) && isParametricShape())
543
630
        updateConnections();
544
 
    
 
631
 
545
632
    // reset the forced update flag
546
633
    d->forceUpdate = false;
547
634
}
548
635
 
 
636
QString KoConnectionShape::pathShapeId() const
 
637
{
 
638
    return KOCONNECTIONSHAPEID;
 
639
}