Code

Fix bug #552289 - Ungrouping objects changes position of arrow lines. This was due...
[inkscape.git] / src / sp-conn-end-pair.cpp
index a1c4697e55007eecbd85d90faa8ee94354303e5a..5bce1a4f80d7a4378848e0e0c66229b73c239c08 100644 (file)
@@ -4,12 +4,18 @@
  * Authors:
  *   Peter Moulder <pmoulder@mail.csse.monash.edu.au>
  *   Michael Wybrow <mjwybrow@users.sourceforge.net>
+ *   Abhishek Sharma
  *
  *    * Copyright (C) 2004-2005 Monash University
  *
  * Released under GNU GPL, read the file 'COPYING' for more information
  */
 
+#include <cstring>
+#include <string>
+#include <iostream>
+#include <glibmm/stringutils.h>
+
 #include "attributes.h"
 #include "sp-conn-end.h"
 #include "uri.h"
 #include "libavoid/vertices.h"
 #include "libavoid/router.h"
 #include "document.h"
+#include "sp-item-group.h"
 
 
 SPConnEndPair::SPConnEndPair(SPPath *const owner)
-    : _invalid_path_connection()
-    , _path(owner)
+    : _path(owner)
     , _connRef(NULL)
     , _connType(SP_CONNECTOR_NOAVOID)
+    , _connCurvature(0.0)
     , _transformed_connection()
 {
     for (unsigned handle_ix = 0; handle_ix <= 1; ++handle_ix) {
@@ -43,14 +50,6 @@ SPConnEndPair::~SPConnEndPair()
         delete this->_connEnd[handle_ix];
         this->_connEnd[handle_ix] = NULL;
     }
-    if (_connRef) {
-        _connRef->removeFromGraph();
-        delete _connRef;
-        _connRef = NULL;
-    }
-
-    _invalid_path_connection.disconnect();
-    _transformed_connection.disconnect();
 }
 
 void
@@ -64,137 +63,213 @@ SPConnEndPair::release()
         this->_connEnd[handle_ix]->href = NULL;
         this->_connEnd[handle_ix]->ref.detach();
     }
+
+    // If the document is being destroyed then the router instance
+    // and the ConnRefs will have been destroyed with it.
+    const bool routerInstanceExists = (_path->document->router != NULL);
+
+    if (_connRef && routerInstanceExists) {
+        _connRef->removeFromGraph();
+        delete _connRef;
+    }
+    _connRef = NULL;
+
+    _transformed_connection.disconnect();
 }
 
 void
 sp_conn_end_pair_build(SPObject *object)
 {
-    sp_object_read_attr(object, "inkscape:connector-type");
-    sp_object_read_attr(object, "inkscape:connection-start");
-    sp_object_read_attr(object, "inkscape:connection-end");
+    object->readAttr( "inkscape:connector-type" );
+    object->readAttr( "inkscape:connection-start" );
+    object->readAttr( "inkscape:connection-start-point" );
+    object->readAttr( "inkscape:connection-end" );
+    object->readAttr( "inkscape:connection-end-point" );
+    object->readAttr( "inkscape:connector-curvature" );
 }
 
 
 static void
-avoid_conn_move(NR::Matrix const *mp, SPItem *moved_item)
+avoid_conn_transformed(Geom::Matrix const */*mp*/, SPItem *moved_item)
 {
-    // Reroute connector
     SPPath *path = SP_PATH(moved_item);
-    path->connEndPair.makePathInvalid();
-    sp_conn_adjust_invalid_path(path);
+    if (path->connEndPair.isAutoRoutingConn()) {
+        path->connEndPair.tellLibavoidNewEndpoints();
+    }
 }
 
 
 void
 SPConnEndPair::setAttr(unsigned const key, gchar const *const value)
 {
-    if (key == SP_ATTR_CONNECTOR_TYPE) {
-        if (value && (strcmp(value, "polyline") == 0)) {
-            _connType = SP_CONNECTOR_POLYLINE;
-
-            Avoid::Router *router = _path->document->router;
-            GQuark itemID = g_quark_from_string(SP_OBJECT(_path)->id);
-            _connRef = new Avoid::ConnRef(router, itemID);
-            _invalid_path_connection = connectInvalidPath(
-                    sigc::ptr_fun(&sp_conn_adjust_invalid_path));
-            _transformed_connection = _path->connectTransformed(
-                    sigc::ptr_fun(&avoid_conn_move));
-        }
-        else {
-            _connType = SP_CONNECTOR_NOAVOID;
-
-            if (_connRef) {
-                _connRef->removeFromGraph();
-                delete _connRef;
-                _connRef = NULL;
-                _invalid_path_connection.disconnect();
-                _transformed_connection.disconnect();
+    switch (key)
+    {
+        case SP_ATTR_CONNECTOR_TYPE:
+            if (value && (strcmp(value, "polyline") == 0 || strcmp(value, "orthogonal") == 0)) {
+                int newconnType = strcmp(value, "polyline") ? SP_CONNECTOR_ORTHOGONAL : SP_CONNECTOR_POLYLINE;
+
+                if (!_connRef)
+                {
+                    _connType = newconnType;
+                    Avoid::Router *router = _path->document->router;
+                    GQuark itemID = g_quark_from_string(_path->getId());
+                    _connRef = new Avoid::ConnRef(router, itemID);
+                    switch (newconnType)
+                    {
+                        case SP_CONNECTOR_POLYLINE:
+                            _connRef->setRoutingType(Avoid::ConnType_PolyLine);
+                            break;
+                        case SP_CONNECTOR_ORTHOGONAL:
+                            _connRef->setRoutingType(Avoid::ConnType_Orthogonal);
+                    }
+                    _transformed_connection = _path->connectTransformed(
+                            sigc::ptr_fun(&avoid_conn_transformed));
+                }
+                else
+                    if (newconnType != _connType)
+                    {
+                        _connType = newconnType;
+                        switch (newconnType)
+                        {
+                            case SP_CONNECTOR_POLYLINE:
+                                _connRef->setRoutingType(Avoid::ConnType_PolyLine);
+                                break;
+                            case SP_CONNECTOR_ORTHOGONAL:
+                                _connRef->setRoutingType(Avoid::ConnType_Orthogonal);
+                        }
+                        sp_conn_reroute_path(_path);
+                    }
             }
-        }
-        return;
-
+            else {
+                _connType = SP_CONNECTOR_NOAVOID;
+
+                if (_connRef) {
+                    _connRef->removeFromGraph();
+                    delete _connRef;
+                    _connRef = NULL;
+                    _transformed_connection.disconnect();
+                }
+            }
+            break;
+        case SP_ATTR_CONNECTOR_CURVATURE:
+            if (value) {
+                _connCurvature = g_strtod(value, NULL);
+                if (_connRef && _connRef->isInitialised()) {
+                    // Redraw the connector, but only if it has been initialised.
+                    sp_conn_reroute_path(_path);
+                }
+            }
+            break;
+        case SP_ATTR_CONNECTION_START:
+        case SP_ATTR_CONNECTION_END:
+            this->_connEnd[(key == SP_ATTR_CONNECTION_START ? 0 : 1)]->setAttacherHref(value, _path);
+            break;
+        case SP_ATTR_CONNECTION_START_POINT:
+        case SP_ATTR_CONNECTION_END_POINT:
+            this->_connEnd[(key == SP_ATTR_CONNECTION_START_POINT ? 0 : 1)]->setAttacherEndpoint(value, _path);
+            break;
     }
 
-    unsigned const handle_ix = key - SP_ATTR_CONNECTION_START;
-    g_assert( handle_ix <= 1 );
-    this->_connEnd[handle_ix]->setAttacherHref(value);
 }
 
 void
 SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const
 {
+    char const * const attr_strs[] = {"inkscape:connection-start", "inkscape:connection-start-point",
+                                      "inkscape:connection-end", "inkscape:connection-end-point"};
     for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
         if (this->_connEnd[handle_ix]->ref.getURI()) {
-            char const * const attr_strs[] = {"inkscape:connection-start",
-                                              "inkscape:connection-end"};
-            gchar *uri_string = this->_connEnd[handle_ix]->ref.getURI()->toString();
-            repr->setAttribute(attr_strs[handle_ix], uri_string);
-            g_free(uri_string);
+            repr->setAttribute(attr_strs[2*handle_ix], this->_connEnd[handle_ix]->ref.getURI()->toString());
+            std::ostringstream ostr;
+            ostr<<(this->_connEnd[handle_ix]->type == ConnPointDefault ? "d":"u") <<
+                  this->_connEnd[handle_ix]->id;
+            repr->setAttribute(attr_strs[2*handle_ix+1], ostr.str().c_str());
         }
     }
+    repr->setAttribute("inkscape:connector-curvature", Glib::Ascii::dtostr(_connCurvature).c_str());
+    if (_connType == SP_CONNECTOR_POLYLINE || _connType == SP_CONNECTOR_ORTHOGONAL)
+        repr->setAttribute("inkscape:connector-type", _connType == SP_CONNECTOR_POLYLINE ? "polyline" : "orthogonal" );
 }
 
 void
 SPConnEndPair::getAttachedItems(SPItem *h2attItem[2]) const {
     for (unsigned h = 0; h < 2; ++h) {
         h2attItem[h] = this->_connEnd[h]->ref.getObject();
+
+        // Deal with the case of the attached object being an empty group.
+        // A group containing no items does not have a valid bbox, so
+        // causes problems for the auto-routing code.  Also, since such a
+        // group no longer has an onscreen representation and can only be
+        // selected through the XML editor, it makes sense just to detach
+        // connectors from them.
+        if (SP_IS_GROUP(h2attItem[h])) {
+            if (SP_GROUP(h2attItem[h])->group->getItemCount() == 0) {
+                // This group is empty, so detach.
+                sp_conn_end_detach(_path, h);
+                h2attItem[h] = NULL;
+            }
+        }
     }
 }
 
 void
-SPConnEndPair::getEndpoints(NR::Point endPts[]) const {
-    SPCurve *curve = _path->curve;
+SPConnEndPair::getEndpoints(Geom::Point endPts[]) const {
+    SPCurve *curve = _path->original_curve ? _path->original_curve : _path->curve;
     SPItem *h2attItem[2];
     getAttachedItems(h2attItem);
+    Geom::Matrix i2d = SP_ITEM(_path)->i2doc_affine();
 
     for (unsigned h = 0; h < 2; ++h) {
         if ( h2attItem[h] ) {
-            NR::Rect const bbox = h2attItem[h]->invokeBbox(sp_item_i2doc_affine(h2attItem[h]));
-            endPts[h] = bbox.midpoint();
+            g_assert(h2attItem[h]->avoidRef);
+            endPts[h] = h2attItem[h]->avoidRef->getConnectionPointPos(_connEnd[h]->type, _connEnd[h]->id);
         }
         else
         {
             if (h == 0) {
-                endPts[h] = sp_curve_first_point(curve);
+                endPts[h] = *(curve->first_point())*i2d;
             }
             else {
-                endPts[h] = sp_curve_last_point(curve);
+                endPts[h] = *(curve->last_point())*i2d;
             }
         }
     }
 }
 
-sigc::connection
-SPConnEndPair::connectInvalidPath(sigc::slot<void, SPPath *> slot)
-{
-    return _invalid_path_signal.connect(slot);
+gdouble
+SPConnEndPair::getCurvature(void) const {
+    return _connCurvature;
 }
 
-static void emitPathInvalidationNotification(void *ptr)
+SPConnEnd**
+SPConnEndPair::getConnEnds(void)
 {
-    // We emit a signal here rather than just calling the reroute function
-    // since this allows all the movement action computation to happen,
-    // then all connectors (that require it) will be rerouted.  Otherwise,
-    // one connector could get rerouted several times as a result of
-    // dragging a couple of shapes.
+    return _connEnd;
+}
 
-    SPPath *path = SP_PATH(ptr);
-    path->connEndPair._invalid_path_signal.emit(path);
+bool
+SPConnEndPair::isOrthogonal(void) const {
+    return _connType == SP_CONNECTOR_ORTHOGONAL;
 }
 
-void
-SPConnEndPair::rerouteFromManipulation(void)
+
+static void redrawConnectorCallback(void *ptr)
 {
-    _connRef->makePathInvalid();
-    sp_conn_adjust_path(_path);
+    SPPath *path = SP_PATH(ptr);
+    if (path->document == NULL) {
+        // This can happen when the document is being destroyed.
+        return;
+    }
+    sp_conn_redraw_path(path);
 }
 
 void
-SPConnEndPair::reroute(void)
+SPConnEndPair::rerouteFromManipulation(void)
 {
-    sp_conn_adjust_path(_path);
+    sp_conn_reroute_path_immediate(_path);
 }
 
+
 // Called from sp_path_update to initialise the endpoints.
 void
 SPConnEndPair::update(void)
@@ -202,14 +277,14 @@ SPConnEndPair::update(void)
     if (_connType != SP_CONNECTOR_NOAVOID) {
         g_assert(_connRef != NULL);
         if (!(_connRef->isInitialised())) {
-            NR::Point endPt[2];
+            Geom::Point endPt[2];
             getEndpoints(endPt);
 
-            Avoid::Point src = { endPt[0][NR::X], endPt[0][NR::Y] };
-            Avoid::Point dst = { endPt[1][NR::X], endPt[1][NR::Y] };
+            Avoid::Point src(endPt[0][Geom::X], endPt[0][Geom::Y]);
+            Avoid::Point dst(endPt[1][Geom::X], endPt[1][Geom::Y]);
 
-            _connRef->lateSetup(src, dst);
-            _connRef->setCallback(&emitPathInvalidationNotification, _path);
+            _connRef->setEndpoints(src, dst);
+            _connRef->setCallback(&redrawConnectorCallback, _path);
         }
         // Store the ID of the objects attached to the connector.
         storeIds();
@@ -255,39 +330,90 @@ SPConnEndPair::makePathInvalid(void)
     _connRef->makePathInvalid();
 }
 
+
+// Redraws the curve along the recalculated route
+// Straight or curved
+void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, const gdouble curvature)
+{
+    bool straight = curvature<1e-3;
+
+    Avoid::PolyLine route = connRef->displayRoute();
+    if (!straight)
+        route = route.curvedPolyline(curvature);
+    connRef->calcRouteDist();
+
+    curve->reset();
+
+    curve->moveto( Geom::Point(route.ps[0].x, route.ps[0].y) );
+    int pn = route.size();
+    for (int i = 1; i < pn; ++i) {
+        Geom::Point p(route.ps[i].x, route.ps[i].y);
+        if (straight) {
+            curve->lineto( p );
+        }
+        else {
+            switch (route.ts[i]) {
+                case 'M':
+                    curve->moveto( p );
+                    break;
+                case 'L':
+                    curve->lineto( p );
+                    break;
+                case 'C':
+                    g_assert( i+2<pn );
+                    curve->curveto( p, Geom::Point(route.ps[i+1].x, route.ps[i+1].y),
+                            Geom::Point(route.ps[i+2].x, route.ps[i+2].y) );
+                    i+=2;
+                    break;
+            }
+        }
+    }
+}
+
+
 void
-SPConnEndPair::reroutePath(void)
+SPConnEndPair::tellLibavoidNewEndpoints(const bool processTransaction)
 {
     if (!isAutoRoutingConn()) {
         // Do nothing
         return;
     }
+    makePathInvalid();
 
-    SPCurve *curve = _path->curve;
-
-    NR::Point endPt[2];
+    Geom::Point endPt[2];
     getEndpoints(endPt);
 
-    Avoid::Point src = { endPt[0][NR::X], endPt[0][NR::Y] };
-    Avoid::Point dst = { endPt[1][NR::X], endPt[1][NR::Y] };
+    Avoid::Point src(endPt[0][Geom::X], endPt[0][Geom::Y]);
+    Avoid::Point dst(endPt[1][Geom::X], endPt[1][Geom::Y]);
 
-    _connRef->updateEndPoint(Avoid::VertID::src, src);
-    _connRef->updateEndPoint(Avoid::VertID::tar, dst);
+    _connRef->setEndpoints(src, dst);
+    if (processTransaction)
+    {
+        _connRef->router()->processTransaction();
+    }
+    return;
+}
 
-    _connRef->generatePath(src, dst);
 
-    Avoid::PolyLine route = _connRef->route();
-    _connRef->calcRouteDist();
+bool
+SPConnEndPair::reroutePathFromLibavoid(void)
+{
+    if (!isAutoRoutingConn()) {
+        // Do nothing
+        return false;
+    }
 
-    sp_curve_reset(curve);
-    sp_curve_moveto(curve, endPt[0]);
+    SPCurve *curve = _path->original_curve ?_path->original_curve : _path->curve;
 
-    for (int i = 1; i < route.pn; ++i) {
-        NR::Point p(route.ps[i].x, route.ps[i].y);
-        sp_curve_lineto(curve, p);
-    }
+    recreateCurve( curve, _connRef, _connCurvature );
+
+    Geom::Matrix doc2item = SP_ITEM(_path)->i2doc_affine().inverse();
+    curve->transform(doc2item);
+
+    return true;
 }
 
+
 /*
   Local Variables:
   mode:c++