00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "KoConnectionShape.h"
00023
00024 #include <KoViewConverter.h>
00025 #include <KoShapeLoadingContext.h>
00026 #include <KoShapeSavingContext.h>
00027 #include <KoXmlReader.h>
00028 #include <KoXmlWriter.h>
00029 #include <KoXmlNS.h>
00030 #include <KoStoreDevice.h>
00031 #include <KoUnit.h>
00032 #include "KoConnectionShapeLoadingUpdater.h"
00033
00034 #include <QPainter>
00035
00036 #include <KDebug>
00037
00038
00039 struct KoConnectionShape::Private {
00040 Private()
00041 : shape1(0), shape2(0), connectionPointIndex1(-1), connectionPointIndex2(-1)
00042 , connectionType(Standard), forceUpdate(false) {}
00043 KoSubpath points;
00044 KoShape * shape1;
00045 KoShape * shape2;
00046 int connectionPointIndex1;
00047 int connectionPointIndex2;
00048 Type connectionType;
00049 bool forceUpdate;
00050 };
00051
00052
00053 KoConnectionShape::KoConnectionShape()
00054 : d(new Private)
00055 {
00056 m_handles.push_back(QPointF(0, 0));
00057 m_handles.push_back(QPointF(140, 140));
00058
00059 moveTo(m_handles[0]);
00060 lineTo(m_handles[1]);
00061
00062 d->points = *m_subpaths[0];
00063 updatePath(QSizeF(140, 140));
00064
00065 int connectionPointCount = connectionPoints().size();
00066 for (int i = 0; i < connectionPointCount; ++i)
00067 removeConnectionPoint(0);
00068
00069 m_hasMoved = true;
00070 }
00071
00072 KoConnectionShape::~KoConnectionShape()
00073 {
00074 if (d->shape1)
00075 d->shape1->removeDependee(this);
00076 if (d->shape2)
00077 d->shape2->removeDependee(this);
00078
00079 delete d;
00080 }
00081
00082 void KoConnectionShape::paint(QPainter&, const KoViewConverter&)
00083 {
00084 }
00085
00086 void KoConnectionShape::saveOdf(KoShapeSavingContext & context) const
00087 {
00088 context.xmlWriter().startElement("draw:connector");
00089 saveOdfAttributes( context, OdfMandatories | OdfAdditionalAttributes );
00090
00091 switch( d->connectionType )
00092 {
00093 case Lines:
00094 context.xmlWriter().addAttribute( "draw:type", "lines" );
00095 break;
00096 case Straight:
00097 context.xmlWriter().addAttribute( "draw:type", "line" );
00098 break;
00099 case Curve:
00100 context.xmlWriter().addAttribute( "draw:type", "curve" );
00101 break;
00102 default:
00103 context.xmlWriter().addAttribute( "draw:type", "standard" );
00104 break;
00105 }
00106
00107 if (d->shape1) {
00108 context.xmlWriter().addAttribute( "draw:start-shape", context.drawId(d->shape1) );
00109 context.xmlWriter().addAttribute( "draw:start-glue-point", d->connectionPointIndex1 );
00110 }
00111 else {
00112 QPointF p((m_handles[0] + position()) * context.shapeOffset(this));
00113 context.xmlWriter().addAttributePt( "svg:x1", p.x() );
00114 context.xmlWriter().addAttributePt( "svg:y1", p.y() );
00115 }
00116 if (d->shape2) {
00117 context.xmlWriter().addAttribute( "draw:end-shape", context.drawId(d->shape2) );
00118 context.xmlWriter().addAttribute( "draw:end-glue-point", d->connectionPointIndex2 );
00119 }
00120 else {
00121 QPointF p((m_handles[m_handles.count()-1] + position()) * context.shapeOffset(this));
00122 context.xmlWriter().addAttributePt( "svg:x2", p.x() );
00123 context.xmlWriter().addAttributePt( "svg:y2", p.y() );
00124 }
00125
00126 saveOdfCommonChildElements(context);
00127
00128 context.xmlWriter().endElement();
00129 }
00130
00131 bool KoConnectionShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context)
00132 {
00133 loadOdfAttributes(element, context, OdfMandatories | OdfCommonChildElements | OdfAdditionalAttributes);
00134
00135 QString type = element.attributeNS(KoXmlNS::draw, "type", "standard");
00136 if (type == "lines")
00137 d->connectionType = Lines;
00138 else if (type == "line")
00139 d->connectionType = Straight;
00140 else if (type == "curve")
00141 d->connectionType = Curve;
00142 else
00143 d->connectionType = Standard;
00144
00145 if (element.hasAttributeNS(KoXmlNS::draw, "start-shape")) {
00146 d->connectionPointIndex1 = element.attributeNS(KoXmlNS::draw, "start-glue-point", "").toInt();
00147 QString shapeId1 = element.attributeNS(KoXmlNS::draw, "start-shape", "");
00148 d->shape1 = context.shapeById(shapeId1);
00149 if (d->shape1) {
00150 d->shape1->addDependee(this);
00151 }
00152 else {
00153 context.updateShape(shapeId1, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::First));
00154 }
00155 } else {
00156 m_handles[0].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", QString())));
00157 m_handles[0].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", QString())));
00158 }
00159
00160 if (element.hasAttributeNS(KoXmlNS::draw, "end-shape")) {
00161 d->connectionPointIndex2 = element.attributeNS(KoXmlNS::draw, "end-glue-point", "").toInt();
00162 QString shapeId2 = element.attributeNS(KoXmlNS::draw, "end-shape", "");
00163 d->shape2 = context.shapeById(shapeId2);
00164 if (d->shape2) {
00165 d->shape2->addDependee(this);
00166 }
00167 else {
00168 context.updateShape(shapeId2, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::Second));
00169 }
00170 } else {
00171 m_handles[m_handles.count() - 1].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", QString())));
00172 m_handles[m_handles.count() - 1].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", QString())));
00173 }
00174
00175 QString skew = element.attributeNS(KoXmlNS::draw, "line-skew", "");
00176 QStringList skewValues = skew.simplified().split(' ', QString::SkipEmptyParts);
00177
00178
00179 updateConnections();
00180
00181 return true;
00182 }
00183
00184 bool KoConnectionShape::hitTest(const QPointF &position) const
00185 {
00186 return KoParameterShape::hitTest(position);
00187 }
00188
00189 void KoConnectionShape::moveHandleAction(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers)
00190 {
00191 Q_UNUSED(modifiers);
00192
00193 if (handleId >= m_handles.size())
00194 return;
00195
00196 m_handles[handleId] = point;
00197 }
00198
00199 void KoConnectionShape::updatePath(const QSizeF &size)
00200 {
00201 Q_UNUSED(size);
00202
00203 QPointF dst = 0.3 * ( m_handles[0] - m_handles[m_handles.count() - 1]);
00204 const qreal MinimumEscapeLength = (qreal)20.;
00205 clear();
00206 switch (d->connectionType) {
00207 case Standard: {
00208 normalPath(MinimumEscapeLength);
00209 if( m_path.count() != 0 ){
00210 moveTo( m_path[0] );
00211 for(int index = 1; index < m_path.count(); ++index )
00212 lineTo( m_path[index] );
00213 }
00214
00215 break;
00216 }
00217 case Lines: {
00218 QPointF direction1 = escapeDirection(0);
00219 QPointF direction2 = escapeDirection(m_handles.count() - 1);
00220 moveTo(m_handles[0]);
00221 if (! direction1.isNull())
00222 lineTo(m_handles[0] + MinimumEscapeLength * direction1);
00223 if (! direction2.isNull())
00224 lineTo(m_handles[m_handles.count() - 1] + MinimumEscapeLength * direction2);
00225 lineTo(m_handles[m_handles.count() - 1]);
00226 break;
00227 }
00228 case Straight:
00229 moveTo(m_handles[0]);
00230 lineTo(m_handles[m_handles.count() - 1]);
00231 break;
00232 case Curve:
00233
00234 QPointF direction1 = escapeDirection(0);
00235 QPointF direction2 = escapeDirection(m_handles.count() - 1);
00236 moveTo(m_handles[0]);
00237 if (! direction1.isNull() && ! direction2.isNull()) {
00238 QPointF curvePoint1 = m_handles[0] + 5.0 * MinimumEscapeLength * direction1;
00239 QPointF curvePoint2 = m_handles[m_handles.count() - 1] + 5.0 * MinimumEscapeLength * direction2;
00240 curveTo(curvePoint1, curvePoint2, m_handles[m_handles.count() - 1]);
00241 } else {
00242 lineTo(m_handles[m_handles.count() - 1]);
00243 }
00244 break;
00245 }
00246 normalize();
00247 }
00248
00249 void KoConnectionShape::normalPath( const qreal MinimumEscapeLength )
00250 {
00251 if(m_hasMoved) {
00252
00253 m_hasMoved = false;
00254 QPointF firstHandle;
00255 QPointF lastHandle;
00256
00257
00258 firstHandle = m_handles[0];
00259 lastHandle = m_handles[m_handles.count() - 1];
00260
00261 m_handles.clear();
00262 m_handles.append(firstHandle);
00263 m_handles.append(lastHandle);
00264
00265
00266 m_path.clear();
00267 m_path.append( m_handles[0] );
00268
00269 QList<QPointF> edges1;
00270 QList<QPointF> edges2;
00271
00272 QPointF direction1 = escapeDirection(0);
00273 QPointF direction2 = escapeDirection(m_handles.count() - 1);
00274
00275 QPointF edgePoint1 = m_handles[0] + MinimumEscapeLength * direction1;
00276 QPointF edgePoint2 = m_handles[m_handles.count() - 1] + MinimumEscapeLength * direction2;
00277
00278 edges1.append(edgePoint1);
00279 edges2.prepend(edgePoint2);
00280
00281 if (handleConnected(0) && handleConnected(1)) {
00282 QPointF intersection;
00283 bool connected = false;
00284 do {
00285
00286 if (intersects(edgePoint1, direction1, edgePoint2, direction2, intersection)) {
00287
00288 edges1.append(intersection);
00289 break;
00290 }
00291
00292
00293 qreal sp = scalarProd(direction1, edgePoint2 - edgePoint1);
00294 if (sp >= 0.0) {
00295
00296
00297 if (direction1 == direction2)
00298 edgePoint1 += sp * direction1;
00299 else
00300 edgePoint1 += 0.5 * sp * direction1;
00301 edges1.append(edgePoint1);
00302
00303 direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
00304 } else {
00305
00306 direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
00307 }
00308 } while (! connected);
00309 }
00310
00311 m_path.append(edges1);
00312 m_path.append(edges2);
00313
00314 m_path.append( m_handles[m_handles.count() - 1] );
00315 }
00316 }
00317
00318 bool KoConnectionShape::setConnection1(KoShape * shape1, int connectionPointIndex1)
00319 {
00320
00321 if (hasDependee(shape1))
00322 return false;
00323
00324
00325 if (shape1 && connectionPointIndex1 >= shape1->connectionPoints().count())
00326 return false;
00327
00328 if (d->shape1)
00329 d->shape1->removeDependee(this);
00330 d->shape1 = shape1;
00331 if (d->shape1)
00332 d->shape1->addDependee(this);
00333
00334 d->connectionPointIndex1 = connectionPointIndex1;
00335
00336 return true;
00337 }
00338
00339 bool KoConnectionShape::setConnection2(KoShape * shape2, int connectionPointIndex2)
00340 {
00341
00342 if (hasDependee(shape2))
00343 return false;
00344
00345
00346 if (shape2 && connectionPointIndex2 >= shape2->connectionPoints().count())
00347 return false;
00348
00349 if (d->shape2)
00350 d->shape2->removeDependee(this);
00351 d->shape2 = shape2;
00352 if (d->shape2)
00353 d->shape2->addDependee(this);
00354
00355 d->connectionPointIndex2 = connectionPointIndex2;
00356
00357 return true;
00358 }
00359
00360 KoConnection KoConnectionShape::connection1() const
00361 {
00362 return KoConnection(d->shape1, d->connectionPointIndex1);
00363 }
00364
00365 KoConnection KoConnectionShape::connection2() const
00366 {
00367 return KoConnection(d->shape2, d->connectionPointIndex2);
00368 }
00369
00370 void KoConnectionShape::updateConnections()
00371 {
00372 bool updateHandles = false;
00373
00374 if (handleConnected(0)) {
00375 QList<QPointF> connectionPoints = d->shape1->connectionPoints();
00376 if (d->connectionPointIndex1 < connectionPoints.count()) {
00377
00378 QPointF p = documentToShape(d->shape1->absoluteTransformation(0).map(connectionPoints[d->connectionPointIndex1]));
00379 if (m_handles[0] != p) {
00380 m_handles[0] = p;
00381 updateHandles = true;
00382 }
00383 }
00384 }
00385 if (handleConnected(1)) {
00386 QList<QPointF> connectionPoints = d->shape2->connectionPoints();
00387 if (d->connectionPointIndex2 < connectionPoints.count()) {
00388
00389 QPointF p = documentToShape(d->shape2->absoluteTransformation(0).map(connectionPoints[d->connectionPointIndex2]));
00390 if (m_handles[m_handles.count() - 1] != p) {
00391 m_handles[m_handles.count() - 1] = p;
00392 updateHandles = true;
00393 }
00394 }
00395 }
00396 if (updateHandles || d->forceUpdate) {
00397 update();
00398 updatePath(QSizeF());
00399 update();
00400 d->forceUpdate = false;
00401 }
00402 }
00403
00404 KoConnectionShape::Type KoConnectionShape::connectionType() const
00405 {
00406 return d->connectionType;
00407 }
00408
00409 void KoConnectionShape::setConnectionType(Type connectionType)
00410 {
00411 d->connectionType = connectionType;
00412 updatePath(size());
00413 }
00414
00415 bool KoConnectionShape::handleConnected(int handleId) const
00416 {
00417 if (handleId == 0 && d->shape1 && d->connectionPointIndex1 >= 0)
00418 return true;
00419 if (handleId == 1 && d->shape2 && d->connectionPointIndex2 >= 0)
00420 return true;
00421
00422 return false;
00423 }
00424
00425 QPointF KoConnectionShape::escapeDirection(int handleId) const
00426 {
00427 QPointF direction;
00428 if (handleConnected(handleId)) {
00429 QMatrix absoluteMatrix = absoluteTransformation(0);
00430 QPointF handlePoint = absoluteMatrix.map(m_handles[handleId]);
00431 QPointF centerPoint;
00432 if (handleId == 0)
00433 centerPoint = d->shape1->absolutePosition(KoFlake::CenteredPosition);
00434 else
00435 centerPoint = d->shape2->absolutePosition(KoFlake::CenteredPosition);
00436
00437 qreal angle = atan2(handlePoint.y() - centerPoint.y(), handlePoint.x() - centerPoint.x());
00438 if (angle < 0.0)
00439 angle += 2.0 * M_PI;
00440 angle *= 180.0 / M_PI;
00441 if (angle >= 45.0 && angle < 135.0)
00442 direction = QPointF(0.0, 1.0);
00443 else if (angle >= 135.0 && angle < 225.0)
00444 direction = QPointF(-1.0, 0.0);
00445 else if (angle >= 225.0 && angle < 315.0)
00446 direction = QPointF(0.0, -1.0);
00447 else
00448 direction = QPointF(1.0, 0.0);
00449
00450
00451 QMatrix invMatrix = absoluteMatrix.inverted();
00452 direction = invMatrix.map(direction) - invMatrix.map(QPointF());
00453 direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y());
00454 }
00455
00456 return direction;
00457 }
00458
00459 bool KoConnectionShape::intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect)
00460 {
00461 qreal sp1 = scalarProd(d1, p2 - p1);
00462 if (sp1 < 0.0)
00463 return false;
00464
00465 qreal sp2 = scalarProd(d2, p1 - p2);
00466 if (sp2 < 0.0)
00467 return false;
00468
00469
00470 qreal cp = crossProd(d1, d2);
00471 if (cp == 0.0) {
00472
00473 if (p1.x() == p2.x() && d1.x() == 0.0 && d1.y() != d2.y()) {
00474
00475 isect = 0.5 * (p1 + p2);
00476 } else if (p1.y() == p2.y() && d1.y() == 0.0 && d1.x() != d2.x()) {
00477
00478 isect = 0.5 * (p1 + p2);
00479 } else
00480 return false;
00481 } else {
00482
00483 isect = p1 + sp1 * d1;
00484 }
00485
00486 return true;
00487 }
00488
00489 qreal KoConnectionShape::scalarProd(const QPointF &v1, const QPointF &v2)
00490 {
00491 return v1.x()*v2.x() + v1.y()*v2.y();
00492 }
00493
00494 qreal KoConnectionShape::crossProd(const QPointF &v1, const QPointF &v2)
00495 {
00496 return (v1.x()*v2.y() - v1.y()*v2.x());
00497 }
00498
00499 QPointF KoConnectionShape::perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2)
00500 {
00501 QPointF perpendicular(d1.y(), -d1.x());
00502 qreal sp = scalarProd(perpendicular, p2 - p1);
00503 if (sp < 0.0)
00504 perpendicular *= -1.0;
00505
00506 return perpendicular;
00507 }
00508
00509 void KoConnectionShape::shapeChanged(ChangeType type, KoShape * shape)
00510 {
00511
00512 const bool updateIsActive = d->forceUpdate;
00513
00514 m_hasMoved = true;
00515 switch (type) {
00516 case PositionChanged:
00517 case RotationChanged:
00518 case ShearChanged:
00519 case ScaleChanged:
00520 case GenericMatrixChange:
00521 case ParameterChanged:
00522 if (isParametricShape() && shape == 0)
00523 d->forceUpdate = true;
00524 break;
00525 case Deleted:
00526 if (shape != d->shape1 && shape != d->shape2)
00527 return;
00528 if (shape == d->shape1)
00529 setConnection1(0, -1);
00530 if (shape == d->shape2)
00531 setConnection2(0, -1);
00532 break;
00533 default:
00534 return;
00535 }
00536
00537
00538 const bool connectionChanged = !shape && (d->shape1 || d->shape2);
00539
00540 const bool connectedShapeChanged = shape && (shape == d->shape1 || shape == d->shape2);
00541
00542 if (!updateIsActive && (connectionChanged || connectedShapeChanged) && isParametricShape())
00543 updateConnections();
00544
00545
00546 d->forceUpdate = false;
00547 }
00548