Code

Fix bug #552289 - Ungrouping objects changes position of arrow lines. This was due...
[inkscape.git] / src / sp-conn-end-pair.cpp
1 /*
2  * A class for handling connector endpoint movement and libavoid interaction.
3  *
4  * Authors:
5  *   Peter Moulder <pmoulder@mail.csse.monash.edu.au>
6  *   Michael Wybrow <mjwybrow@users.sourceforge.net>
7  *   Abhishek Sharma
8  *
9  *    * Copyright (C) 2004-2005 Monash University
10  *
11  * Released under GNU GPL, read the file 'COPYING' for more information
12  */
14 #include <cstring>
15 #include <string>
16 #include <iostream>
17 #include <glibmm/stringutils.h>
19 #include "attributes.h"
20 #include "sp-conn-end.h"
21 #include "uri.h"
22 #include "display/curve.h"
23 #include "xml/repr.h"
24 #include "sp-path.h"
25 #include "libavoid/vertices.h"
26 #include "libavoid/router.h"
27 #include "document.h"
28 #include "sp-item-group.h"
31 SPConnEndPair::SPConnEndPair(SPPath *const owner)
32     : _path(owner)
33     , _connRef(NULL)
34     , _connType(SP_CONNECTOR_NOAVOID)
35     , _connCurvature(0.0)
36     , _transformed_connection()
37 {
38     for (unsigned handle_ix = 0; handle_ix <= 1; ++handle_ix) {
39         this->_connEnd[handle_ix] = new SPConnEnd(SP_OBJECT(owner));
40         this->_connEnd[handle_ix]->_changed_connection
41             = this->_connEnd[handle_ix]->ref.changedSignal()
42             .connect(sigc::bind(sigc::ptr_fun(sp_conn_end_href_changed),
43                                 this->_connEnd[handle_ix], owner, handle_ix));
44     }
45 }
47 SPConnEndPair::~SPConnEndPair()
48 {
49     for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
50         delete this->_connEnd[handle_ix];
51         this->_connEnd[handle_ix] = NULL;
52     }
53 }
55 void
56 SPConnEndPair::release()
57 {
58     for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
59         this->_connEnd[handle_ix]->_changed_connection.disconnect();
60         this->_connEnd[handle_ix]->_delete_connection.disconnect();
61         this->_connEnd[handle_ix]->_transformed_connection.disconnect();
62         g_free(this->_connEnd[handle_ix]->href);
63         this->_connEnd[handle_ix]->href = NULL;
64         this->_connEnd[handle_ix]->ref.detach();
65     }
67     // If the document is being destroyed then the router instance
68     // and the ConnRefs will have been destroyed with it.
69     const bool routerInstanceExists = (_path->document->router != NULL);
71     if (_connRef && routerInstanceExists) {
72         _connRef->removeFromGraph();
73         delete _connRef;
74     }
75     _connRef = NULL;
77     _transformed_connection.disconnect();
78 }
80 void
81 sp_conn_end_pair_build(SPObject *object)
82 {
83     object->readAttr( "inkscape:connector-type" );
84     object->readAttr( "inkscape:connection-start" );
85     object->readAttr( "inkscape:connection-start-point" );
86     object->readAttr( "inkscape:connection-end" );
87     object->readAttr( "inkscape:connection-end-point" );
88     object->readAttr( "inkscape:connector-curvature" );
89 }
92 static void
93 avoid_conn_transformed(Geom::Matrix const */*mp*/, SPItem *moved_item)
94 {
95     SPPath *path = SP_PATH(moved_item);
96     if (path->connEndPair.isAutoRoutingConn()) {
97         path->connEndPair.tellLibavoidNewEndpoints();
98     }
99 }
102 void
103 SPConnEndPair::setAttr(unsigned const key, gchar const *const value)
105     switch (key)
106     {
107         case SP_ATTR_CONNECTOR_TYPE:
108             if (value && (strcmp(value, "polyline") == 0 || strcmp(value, "orthogonal") == 0)) {
109                 int newconnType = strcmp(value, "polyline") ? SP_CONNECTOR_ORTHOGONAL : SP_CONNECTOR_POLYLINE;
111                 if (!_connRef)
112                 {
113                     _connType = newconnType;
114                     Avoid::Router *router = _path->document->router;
115                     GQuark itemID = g_quark_from_string(_path->getId());
116                     _connRef = new Avoid::ConnRef(router, itemID);
117                     switch (newconnType)
118                     {
119                         case SP_CONNECTOR_POLYLINE:
120                             _connRef->setRoutingType(Avoid::ConnType_PolyLine);
121                             break;
122                         case SP_CONNECTOR_ORTHOGONAL:
123                             _connRef->setRoutingType(Avoid::ConnType_Orthogonal);
124                     }
125                     _transformed_connection = _path->connectTransformed(
126                             sigc::ptr_fun(&avoid_conn_transformed));
127                 }
128                 else
129                     if (newconnType != _connType)
130                     {
131                         _connType = newconnType;
132                         switch (newconnType)
133                         {
134                             case SP_CONNECTOR_POLYLINE:
135                                 _connRef->setRoutingType(Avoid::ConnType_PolyLine);
136                                 break;
137                             case SP_CONNECTOR_ORTHOGONAL:
138                                 _connRef->setRoutingType(Avoid::ConnType_Orthogonal);
139                         }
140                         sp_conn_reroute_path(_path);
141                     }
142             }
143             else {
144                 _connType = SP_CONNECTOR_NOAVOID;
146                 if (_connRef) {
147                     _connRef->removeFromGraph();
148                     delete _connRef;
149                     _connRef = NULL;
150                     _transformed_connection.disconnect();
151                 }
152             }
153             break;
154         case SP_ATTR_CONNECTOR_CURVATURE:
155             if (value) {
156                 _connCurvature = g_strtod(value, NULL);
157                 if (_connRef && _connRef->isInitialised()) {
158                     // Redraw the connector, but only if it has been initialised.
159                     sp_conn_reroute_path(_path);
160                 }
161             }
162             break;
163         case SP_ATTR_CONNECTION_START:
164         case SP_ATTR_CONNECTION_END:
165             this->_connEnd[(key == SP_ATTR_CONNECTION_START ? 0 : 1)]->setAttacherHref(value, _path);
166             break;
167         case SP_ATTR_CONNECTION_START_POINT:
168         case SP_ATTR_CONNECTION_END_POINT:
169             this->_connEnd[(key == SP_ATTR_CONNECTION_START_POINT ? 0 : 1)]->setAttacherEndpoint(value, _path);
170             break;
171     }
175 void
176 SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const
178     char const * const attr_strs[] = {"inkscape:connection-start", "inkscape:connection-start-point",
179                                       "inkscape:connection-end", "inkscape:connection-end-point"};
180     for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
181         if (this->_connEnd[handle_ix]->ref.getURI()) {
182             repr->setAttribute(attr_strs[2*handle_ix], this->_connEnd[handle_ix]->ref.getURI()->toString());
183             std::ostringstream ostr;
184             ostr<<(this->_connEnd[handle_ix]->type == ConnPointDefault ? "d":"u") <<
185                   this->_connEnd[handle_ix]->id;
186             repr->setAttribute(attr_strs[2*handle_ix+1], ostr.str().c_str());
187         }
188     }
189     repr->setAttribute("inkscape:connector-curvature", Glib::Ascii::dtostr(_connCurvature).c_str());
190     if (_connType == SP_CONNECTOR_POLYLINE || _connType == SP_CONNECTOR_ORTHOGONAL)
191         repr->setAttribute("inkscape:connector-type", _connType == SP_CONNECTOR_POLYLINE ? "polyline" : "orthogonal" );
194 void
195 SPConnEndPair::getAttachedItems(SPItem *h2attItem[2]) const {
196     for (unsigned h = 0; h < 2; ++h) {
197         h2attItem[h] = this->_connEnd[h]->ref.getObject();
199         // Deal with the case of the attached object being an empty group.
200         // A group containing no items does not have a valid bbox, so
201         // causes problems for the auto-routing code.  Also, since such a
202         // group no longer has an onscreen representation and can only be
203         // selected through the XML editor, it makes sense just to detach
204         // connectors from them.
205         if (SP_IS_GROUP(h2attItem[h])) {
206             if (SP_GROUP(h2attItem[h])->group->getItemCount() == 0) {
207                 // This group is empty, so detach.
208                 sp_conn_end_detach(_path, h);
209                 h2attItem[h] = NULL;
210             }
211         }
212     }
215 void
216 SPConnEndPair::getEndpoints(Geom::Point endPts[]) const {
217     SPCurve *curve = _path->original_curve ? _path->original_curve : _path->curve;
218     SPItem *h2attItem[2];
219     getAttachedItems(h2attItem);
220     Geom::Matrix i2d = SP_ITEM(_path)->i2doc_affine();
222     for (unsigned h = 0; h < 2; ++h) {
223         if ( h2attItem[h] ) {
224             g_assert(h2attItem[h]->avoidRef);
225             endPts[h] = h2attItem[h]->avoidRef->getConnectionPointPos(_connEnd[h]->type, _connEnd[h]->id);
226         }
227         else
228         {
229             if (h == 0) {
230                 endPts[h] = *(curve->first_point())*i2d;
231             }
232             else {
233                 endPts[h] = *(curve->last_point())*i2d;
234             }
235         }
236     }
239 gdouble
240 SPConnEndPair::getCurvature(void) const {
241     return _connCurvature;
244 SPConnEnd**
245 SPConnEndPair::getConnEnds(void)
247     return _connEnd;
250 bool
251 SPConnEndPair::isOrthogonal(void) const {
252     return _connType == SP_CONNECTOR_ORTHOGONAL;
256 static void redrawConnectorCallback(void *ptr)
258     SPPath *path = SP_PATH(ptr);
259     if (path->document == NULL) {
260         // This can happen when the document is being destroyed.
261         return;
262     }
263     sp_conn_redraw_path(path);
266 void
267 SPConnEndPair::rerouteFromManipulation(void)
269     sp_conn_reroute_path_immediate(_path);
273 // Called from sp_path_update to initialise the endpoints.
274 void
275 SPConnEndPair::update(void)
277     if (_connType != SP_CONNECTOR_NOAVOID) {
278         g_assert(_connRef != NULL);
279         if (!(_connRef->isInitialised())) {
280             Geom::Point endPt[2];
281             getEndpoints(endPt);
283             Avoid::Point src(endPt[0][Geom::X], endPt[0][Geom::Y]);
284             Avoid::Point dst(endPt[1][Geom::X], endPt[1][Geom::Y]);
286             _connRef->setEndpoints(src, dst);
287             _connRef->setCallback(&redrawConnectorCallback, _path);
288         }
289         // Store the ID of the objects attached to the connector.
290         storeIds();
291     }
295 void SPConnEndPair::storeIds(void)
297     if (_connEnd[0]->href) {
298         // href begins with a '#' which we don't want.
299         const char *startId = _connEnd[0]->href + 1;
300         GQuark itemId = g_quark_from_string(startId);
301         _connRef->setEndPointId(Avoid::VertID::src, itemId);
302     }
303     else {
304         _connRef->setEndPointId(Avoid::VertID::src, 0);
305     }
306     if (_connEnd[1]->href) {
307         // href begins with a '#' which we don't want.
308         const char *endId = _connEnd[1]->href + 1;
309         GQuark itemId = g_quark_from_string(endId);
310         _connRef->setEndPointId(Avoid::VertID::tar, itemId);
311     }
312     else {
313         _connRef->setEndPointId(Avoid::VertID::tar, 0);
314     }
318 bool
319 SPConnEndPair::isAutoRoutingConn(void)
321     if (_connType != SP_CONNECTOR_NOAVOID) {
322         return true;
323     }
324     return false;
327 void
328 SPConnEndPair::makePathInvalid(void)
330     _connRef->makePathInvalid();
334 // Redraws the curve along the recalculated route
335 // Straight or curved
336 void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, const gdouble curvature)
338     bool straight = curvature<1e-3;
340     Avoid::PolyLine route = connRef->displayRoute();
341     if (!straight)
342         route = route.curvedPolyline(curvature);
343     connRef->calcRouteDist();
345     curve->reset();
347     curve->moveto( Geom::Point(route.ps[0].x, route.ps[0].y) );
348     int pn = route.size();
349     for (int i = 1; i < pn; ++i) {
350         Geom::Point p(route.ps[i].x, route.ps[i].y);
351         if (straight) {
352             curve->lineto( p );
353         }
354         else {
355             switch (route.ts[i]) {
356                 case 'M':
357                     curve->moveto( p );
358                     break;
359                 case 'L':
360                     curve->lineto( p );
361                     break;
362                 case 'C':
363                     g_assert( i+2<pn );
364                     curve->curveto( p, Geom::Point(route.ps[i+1].x, route.ps[i+1].y),
365                             Geom::Point(route.ps[i+2].x, route.ps[i+2].y) );
366                     i+=2;
367                     break;
368             }
369         }
370     }
374 void
375 SPConnEndPair::tellLibavoidNewEndpoints(const bool processTransaction)
377     if (!isAutoRoutingConn()) {
378         // Do nothing
379         return;
380     }
381     makePathInvalid();
383     Geom::Point endPt[2];
384     getEndpoints(endPt);
386     Avoid::Point src(endPt[0][Geom::X], endPt[0][Geom::Y]);
387     Avoid::Point dst(endPt[1][Geom::X], endPt[1][Geom::Y]);
389     _connRef->setEndpoints(src, dst);
390     if (processTransaction)
391     {
392         _connRef->router()->processTransaction();
393     }
394     return;
398 bool
399 SPConnEndPair::reroutePathFromLibavoid(void)
401     if (!isAutoRoutingConn()) {
402         // Do nothing
403         return false;
404     }
406     SPCurve *curve = _path->original_curve ?_path->original_curve : _path->curve;
408     recreateCurve( curve, _connRef, _connCurvature );
410     Geom::Matrix doc2item = SP_ITEM(_path)->i2doc_affine().inverse();
411     curve->transform(doc2item);
413     return true;
417 /*
418   Local Variables:
419   mode:c++
420   c-file-style:"stroustrup"
421   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
422   indent-tabs-mode:nil
423   fill-column:99
424   End:
425 */
426 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :