libs/flake

KoConnectionShape.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE project
00002  * Copyright (C) 2007 Boudewijn Rempt <boud@kde.org>
00003  * Copyright (C) 2007,2009 Thorsten Zachmann <zachmann@kde.org>
00004  * Copyright (C) 2007,2009  Jan Hambrecht <jaham@gmx.net>
00005  *
00006  * This library is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU Library General Public
00008  * License as published by the Free Software Foundation; either
00009  * version 2 of the License, or (at your option) any later version.
00010  *
00011  * This library is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  * Library General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU Library General Public License
00017  * along with this library; see the file COPYING.LIB.  If not, write to
00018  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019  * Boston, MA 02110-1301, USA.
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 // XXX: Add editable text in path shapes so we can get a label here
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     // TODO apply skew values once we support them
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         // TODO
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         // Clear handles keeping the first and last one.
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         // Clear the path to build it again.
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                 // first check if directions from current edge points intersect
00286                 if (intersects(edgePoint1, direction1, edgePoint2, direction2, intersection)) {
00287                     // directions intersect, we have another edge point and be done
00288                     edges1.append(intersection);
00289                     break;
00290                 }
00291 
00292                 // check if we are going toward the other handle
00293                 qreal sp = scalarProd(direction1, edgePoint2 - edgePoint1);
00294                 if (sp >= 0.0) {
00295                     // if we are having the same direction, go all the way toward
00296                     // the other handle, else only go half the way
00297                     if (direction1 == direction2)
00298                         edgePoint1 += sp * direction1;
00299                     else
00300                         edgePoint1 += 0.5 * sp * direction1;
00301                     edges1.append(edgePoint1);
00302                     // switch direction
00303                     direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
00304                 } else {
00305                     // we are not going into the same direction, so switch direction
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     // refuse to connect to a shape that depends on us (e.g. a artistic text shape)
00321     if (hasDependee(shape1))
00322         return false;
00323 
00324     // check if the connection point does exist
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     // refuse to connect to a shape that depends on us (e.g. a artistic text shape)
00342     if (hasDependee(shape2))
00343         return false;
00344 
00345     // check if the connection point does exist
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             // map connection point into our shape coordinates
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             // map connection point into our shape coordinates
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(); // ugly, for repainting the connection we just changed
00398         updatePath(QSizeF());
00399         update(); // ugly, for repainting the connection we just changed
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         // transform escape direction by using our own transformation matrix
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     // use cross product to check if rays intersects at all
00470     qreal cp = crossProd(d1, d2);
00471     if (cp == 0.0) {
00472         // rays are parallel or coincidient
00473         if (p1.x() == p2.x() && d1.x() == 0.0 && d1.y() != d2.y()) {
00474             // vertical, coincident
00475             isect = 0.5 * (p1 + p2);
00476         } else if (p1.y() == p2.y() && d1.y() == 0.0 && d1.x() != d2.x()) {
00477             // horizontal, coincident
00478             isect = 0.5 * (p1 + p2);
00479         } else
00480             return false;
00481     } else {
00482         // they are intersecting normally
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     // check if we are during a forced update
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     // the connection was moved while it is connected to some other shapes
00538     const bool connectionChanged = !shape && (d->shape1 || d->shape2);
00539     // one of the connected shape has moved
00540     const bool connectedShapeChanged = shape && (shape == d->shape1 || shape == d->shape2);
00541     
00542     if (!updateIsActive && (connectionChanged || connectedShapeChanged) && isParametricShape())
00543         updateConnections();
00544     
00545     // reset the forced update flag
00546     d->forceUpdate = false;
00547 }
00548