Code

A simple layout document as to what, why and how is cppification.
[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  *
8  *    * Copyright (C) 2004-2005 Monash University
9  *
10  * Released under GNU GPL, read the file 'COPYING' for more information
11  */
13 #include <cstring>
14 #include <string>
15 #include <iostream>
16 #include <glibmm/stringutils.h>
18 #include "attributes.h"
19 #include "sp-conn-end.h"
20 #include "uri.h"
21 #include "display/curve.h"
22 #include "xml/repr.h"
23 #include "sp-path.h"
24 #include "libavoid/vertices.h"
25 #include "libavoid/router.h"
26 #include "document.h"
27 #include "sp-item-group.h"
30 SPConnEndPair::SPConnEndPair(SPPath *const owner)
31     : _path(owner)
32     , _connRef(NULL)
33     , _connType(SP_CONNECTOR_NOAVOID)
34     , _connCurvature(0.0)
35     , _transformed_connection()
36 {
37     for (unsigned handle_ix = 0; handle_ix <= 1; ++handle_ix) {
38         this->_connEnd[handle_ix] = new SPConnEnd(SP_OBJECT(owner));
39         this->_connEnd[handle_ix]->_changed_connection
40             = this->_connEnd[handle_ix]->ref.changedSignal()
41             .connect(sigc::bind(sigc::ptr_fun(sp_conn_end_href_changed),
42                                 this->_connEnd[handle_ix], owner, handle_ix));
43     }
44 }
46 SPConnEndPair::~SPConnEndPair()
47 {
48     for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
49         delete this->_connEnd[handle_ix];
50         this->_connEnd[handle_ix] = NULL;
51     }
52 }
54 void
55 SPConnEndPair::release()
56 {
57     for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
58         this->_connEnd[handle_ix]->_changed_connection.disconnect();
59         this->_connEnd[handle_ix]->_delete_connection.disconnect();
60         this->_connEnd[handle_ix]->_transformed_connection.disconnect();
61         g_free(this->_connEnd[handle_ix]->href);
62         this->_connEnd[handle_ix]->href = NULL;
63         this->_connEnd[handle_ix]->ref.detach();
64     }
66     // If the document is being destroyed then the router instance
67     // and the ConnRefs will have been destroyed with it.
68     const bool routerInstanceExists = (_path->document->router != NULL);
70     if (_connRef && routerInstanceExists) {
71         _connRef->removeFromGraph();
72         delete _connRef;
73     }
74     _connRef = NULL;
76     _transformed_connection.disconnect();
77 }
79 void
80 sp_conn_end_pair_build(SPObject *object)
81 {
82     object->readAttr( "inkscape:connector-type");
83     object->readAttr( "inkscape:connection-start");
84     object->readAttr( "inkscape:connection-start-point");
85     object->readAttr( "inkscape:connection-end");
86     object->readAttr( "inkscape:connection-end-point");
87     object->readAttr( "inkscape:connector-curvature");
88 }
91 static void
92 avoid_conn_transformed(Geom::Matrix const */*mp*/, SPItem *moved_item)
93 {
94     SPPath *path = SP_PATH(moved_item);
95     if (path->connEndPair.isAutoRoutingConn()) {
96         path->connEndPair.tellLibavoidNewEndpoints();
97     }
98 }
101 void
102 SPConnEndPair::setAttr(unsigned const key, gchar const *const value)
104     switch (key)
105     {
106         case SP_ATTR_CONNECTOR_TYPE:
107             if (value && (strcmp(value, "polyline") == 0 || strcmp(value, "orthogonal") == 0)) {
108                 int newconnType = strcmp(value, "polyline") ? SP_CONNECTOR_ORTHOGONAL : SP_CONNECTOR_POLYLINE;
110                 if (!_connRef)
111                 {
112                     _connType = newconnType;
113                     Avoid::Router *router = _path->document->router;
114                     GQuark itemID = g_quark_from_string(_path->getId());
115                     _connRef = new Avoid::ConnRef(router, itemID);
116                     switch (newconnType)
117                     {
118                         case SP_CONNECTOR_POLYLINE:
119                             _connRef->setRoutingType(Avoid::ConnType_PolyLine);
120                             break;
121                         case SP_CONNECTOR_ORTHOGONAL:
122                             _connRef->setRoutingType(Avoid::ConnType_Orthogonal);
123                     }
124                     _transformed_connection = _path->connectTransformed(
125                             sigc::ptr_fun(&avoid_conn_transformed));
126                 }
127                 else
128                     if (newconnType != _connType)
129                     {
130                         _connType = newconnType;
131                         switch (newconnType)
132                         {
133                             case SP_CONNECTOR_POLYLINE:
134                                 _connRef->setRoutingType(Avoid::ConnType_PolyLine);
135                                 break;
136                             case SP_CONNECTOR_ORTHOGONAL:
137                                 _connRef->setRoutingType(Avoid::ConnType_Orthogonal);
138                         }
139                         sp_conn_reroute_path(_path);
140                     }
141             }
142             else {
143                 _connType = SP_CONNECTOR_NOAVOID;
145                 if (_connRef) {
146                     _connRef->removeFromGraph();
147                     delete _connRef;
148                     _connRef = NULL;
149                     _transformed_connection.disconnect();
150                 }
151             }
152             break;
153         case SP_ATTR_CONNECTOR_CURVATURE:
154             if (value) {
155                 _connCurvature = g_strtod(value, NULL);
156                 if (_connRef && _connRef->isInitialised()) {
157                     // Redraw the connector, but only if it has been initialised.
158                     sp_conn_reroute_path(_path);
159                 }
160             }
161             break;
162         case SP_ATTR_CONNECTION_START:
163         case SP_ATTR_CONNECTION_END:
164             this->_connEnd[(key == SP_ATTR_CONNECTION_START ? 0 : 1)]->setAttacherHref(value, _path);
165             break;
166         case SP_ATTR_CONNECTION_START_POINT:
167         case SP_ATTR_CONNECTION_END_POINT:
168             this->_connEnd[(key == SP_ATTR_CONNECTION_START_POINT ? 0 : 1)]->setAttacherEndpoint(value, _path);
169             break;
170     }
174 void
175 SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const
177     char const * const attr_strs[] = {"inkscape:connection-start", "inkscape:connection-start-point",
178                                       "inkscape:connection-end", "inkscape:connection-end-point"};
179     for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
180         if (this->_connEnd[handle_ix]->ref.getURI()) {
181             repr->setAttribute(attr_strs[2*handle_ix], this->_connEnd[handle_ix]->ref.getURI()->toString());
182             std::ostringstream ostr;
183             ostr<<(this->_connEnd[handle_ix]->type == ConnPointDefault ? "d":"u") <<
184                   this->_connEnd[handle_ix]->id;
185             repr->setAttribute(attr_strs[2*handle_ix+1], ostr.str().c_str());
186         }
187     }
188     repr->setAttribute("inkscape:connector-curvature", Glib::Ascii::dtostr(_connCurvature).c_str());
189     if (_connType == SP_CONNECTOR_POLYLINE || _connType == SP_CONNECTOR_ORTHOGONAL)
190         repr->setAttribute("inkscape:connector-type", _connType == SP_CONNECTOR_POLYLINE ? "polyline" : "orthogonal" );
193 void
194 SPConnEndPair::getAttachedItems(SPItem *h2attItem[2]) const {
195     for (unsigned h = 0; h < 2; ++h) {
196         h2attItem[h] = this->_connEnd[h]->ref.getObject();
198         // Deal with the case of the attached object being an empty group.
199         // A group containing no items does not have a valid bbox, so
200         // causes problems for the auto-routing code.  Also, since such a
201         // group no longer has an onscreen representation and can only be
202         // selected through the XML editor, it makes sense just to detach
203         // connectors from them.
204         if (SP_IS_GROUP(h2attItem[h])) {
205             if (SP_GROUP(h2attItem[h])->group->getItemCount() == 0) {
206                 // This group is empty, so detach.
207                 sp_conn_end_detach(_path, h);
208                 h2attItem[h] = NULL;
209             }
210         }
211     }
214 void
215 SPConnEndPair::getEndpoints(Geom::Point endPts[]) const {
216     SPCurve *curve = _path->original_curve ? _path->original_curve : _path->curve;
217     SPItem *h2attItem[2];
218     getAttachedItems(h2attItem);
220     for (unsigned h = 0; h < 2; ++h) {
221         if ( h2attItem[h] ) {
222             g_assert(h2attItem[h]->avoidRef);
223             endPts[h] = h2attItem[h]->avoidRef->getConnectionPointPos(_connEnd[h]->type, _connEnd[h]->id);
224         }
225         else
226         {
227             if (h == 0) {
228                 endPts[h] = *(curve->first_point());
229             }
230             else {
231                 endPts[h] = *(curve->last_point());
232             }
233         }
234     }
237 gdouble
238 SPConnEndPair::getCurvature(void) const {
239     return _connCurvature;
242 SPConnEnd**
243 SPConnEndPair::getConnEnds(void)
245     return _connEnd;
248 bool
249 SPConnEndPair::isOrthogonal(void) const {
250     return _connType == SP_CONNECTOR_ORTHOGONAL;
254 static void redrawConnectorCallback(void *ptr)
256     SPPath *path = SP_PATH(ptr);
257     if (path->document == NULL) {
258         // This can happen when the document is being destroyed.
259         return;
260     }
261     sp_conn_redraw_path(path);
264 void
265 SPConnEndPair::rerouteFromManipulation(void)
267     sp_conn_reroute_path_immediate(_path);
271 // Called from sp_path_update to initialise the endpoints.
272 void
273 SPConnEndPair::update(void)
275     if (_connType != SP_CONNECTOR_NOAVOID) {
276         g_assert(_connRef != NULL);
277         if (!(_connRef->isInitialised())) {
278             Geom::Point endPt[2];
279             getEndpoints(endPt);
281             Avoid::Point src(endPt[0][Geom::X], endPt[0][Geom::Y]);
282             Avoid::Point dst(endPt[1][Geom::X], endPt[1][Geom::Y]);
284             _connRef->setEndpoints(src, dst);
285             _connRef->setCallback(&redrawConnectorCallback, _path);
286         }
287         // Store the ID of the objects attached to the connector.
288         storeIds();
289     }
293 void SPConnEndPair::storeIds(void)
295     if (_connEnd[0]->href) {
296         // href begins with a '#' which we don't want.
297         const char *startId = _connEnd[0]->href + 1;
298         GQuark itemId = g_quark_from_string(startId);
299         _connRef->setEndPointId(Avoid::VertID::src, itemId);
300     }
301     else {
302         _connRef->setEndPointId(Avoid::VertID::src, 0);
303     }
304     if (_connEnd[1]->href) {
305         // href begins with a '#' which we don't want.
306         const char *endId = _connEnd[1]->href + 1;
307         GQuark itemId = g_quark_from_string(endId);
308         _connRef->setEndPointId(Avoid::VertID::tar, itemId);
309     }
310     else {
311         _connRef->setEndPointId(Avoid::VertID::tar, 0);
312     }
316 bool
317 SPConnEndPair::isAutoRoutingConn(void)
319     if (_connType != SP_CONNECTOR_NOAVOID) {
320         return true;
321     }
322     return false;
325 void
326 SPConnEndPair::makePathInvalid(void)
328     _connRef->makePathInvalid();
332 // Redraws the curve along the recalculated route
333 // Straight or curved
334 void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, const gdouble curvature)
336     bool straight = curvature<1e-3;
338     Avoid::PolyLine route = connRef->displayRoute();
339     if (!straight)
340         route = route.curvedPolyline(curvature);
341     connRef->calcRouteDist();
343     curve->reset();
345     curve->moveto( Geom::Point(route.ps[0].x, route.ps[0].y) );
346     int pn = route.size();
347     for (int i = 1; i < pn; ++i) {
348         Geom::Point p(route.ps[i].x, route.ps[i].y);
349         if (straight) {
350             curve->lineto( p );
351         }
352         else {
353             switch (route.ts[i]) {
354                 case 'M':
355                     curve->moveto( p );
356                     break;
357                 case 'L':
358                     curve->lineto( p );
359                     break;
360                 case 'C':
361                     g_assert( i+2<pn );
362                     curve->curveto( p, Geom::Point(route.ps[i+1].x, route.ps[i+1].y),
363                             Geom::Point(route.ps[i+2].x, route.ps[i+2].y) );
364                     i+=2;
365                     break;
366             }
367         }
368     }
372 void
373 SPConnEndPair::tellLibavoidNewEndpoints(const bool processTransaction)
375     if (!isAutoRoutingConn()) {
376         // Do nothing
377         return;
378     }
379     makePathInvalid();
381     Geom::Point endPt[2];
382     getEndpoints(endPt);
384     Avoid::Point src(endPt[0][Geom::X], endPt[0][Geom::Y]);
385     Avoid::Point dst(endPt[1][Geom::X], endPt[1][Geom::Y]);
387     _connRef->setEndpoints(src, dst);
388     if (processTransaction)
389     {
390         _connRef->router()->processTransaction();
391     }
392     return;
396 bool
397 SPConnEndPair::reroutePathFromLibavoid(void)
399     if (!isAutoRoutingConn()) {
400         // Do nothing
401         return false;
402     }
404     SPCurve *curve = _path->original_curve ?_path->original_curve : _path->curve;
406     recreateCurve( curve, _connRef, _connCurvature );
408     Geom::Matrix doc2item = SP_ITEM(_path)->i2doc_affine().inverse();
409     curve->transform(doc2item);
411     return true;
415 /*
416   Local Variables:
417   mode:c++
418   c-file-style:"stroustrup"
419   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
420   indent-tabs-mode:nil
421   fill-column:99
422   End:
423 */
424 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :