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)
104 {
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 }
173 }
175 void
176 SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const
177 {
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" );
192 }
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 }
213 }
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);
221 for (unsigned h = 0; h < 2; ++h) {
222 if ( h2attItem[h] ) {
223 g_assert(h2attItem[h]->avoidRef);
224 endPts[h] = h2attItem[h]->avoidRef->getConnectionPointPos(_connEnd[h]->type, _connEnd[h]->id);
225 }
226 else
227 {
228 if (h == 0) {
229 endPts[h] = *(curve->first_point());
230 }
231 else {
232 endPts[h] = *(curve->last_point());
233 }
234 }
235 }
236 }
238 gdouble
239 SPConnEndPair::getCurvature(void) const {
240 return _connCurvature;
241 }
243 SPConnEnd**
244 SPConnEndPair::getConnEnds(void)
245 {
246 return _connEnd;
247 }
249 bool
250 SPConnEndPair::isOrthogonal(void) const {
251 return _connType == SP_CONNECTOR_ORTHOGONAL;
252 }
255 static void redrawConnectorCallback(void *ptr)
256 {
257 SPPath *path = SP_PATH(ptr);
258 if (path->document == NULL) {
259 // This can happen when the document is being destroyed.
260 return;
261 }
262 sp_conn_redraw_path(path);
263 }
265 void
266 SPConnEndPair::rerouteFromManipulation(void)
267 {
268 sp_conn_reroute_path_immediate(_path);
269 }
272 // Called from sp_path_update to initialise the endpoints.
273 void
274 SPConnEndPair::update(void)
275 {
276 if (_connType != SP_CONNECTOR_NOAVOID) {
277 g_assert(_connRef != NULL);
278 if (!(_connRef->isInitialised())) {
279 Geom::Point endPt[2];
280 getEndpoints(endPt);
282 Avoid::Point src(endPt[0][Geom::X], endPt[0][Geom::Y]);
283 Avoid::Point dst(endPt[1][Geom::X], endPt[1][Geom::Y]);
285 _connRef->setEndpoints(src, dst);
286 _connRef->setCallback(&redrawConnectorCallback, _path);
287 }
288 // Store the ID of the objects attached to the connector.
289 storeIds();
290 }
291 }
294 void SPConnEndPair::storeIds(void)
295 {
296 if (_connEnd[0]->href) {
297 // href begins with a '#' which we don't want.
298 const char *startId = _connEnd[0]->href + 1;
299 GQuark itemId = g_quark_from_string(startId);
300 _connRef->setEndPointId(Avoid::VertID::src, itemId);
301 }
302 else {
303 _connRef->setEndPointId(Avoid::VertID::src, 0);
304 }
305 if (_connEnd[1]->href) {
306 // href begins with a '#' which we don't want.
307 const char *endId = _connEnd[1]->href + 1;
308 GQuark itemId = g_quark_from_string(endId);
309 _connRef->setEndPointId(Avoid::VertID::tar, itemId);
310 }
311 else {
312 _connRef->setEndPointId(Avoid::VertID::tar, 0);
313 }
314 }
317 bool
318 SPConnEndPair::isAutoRoutingConn(void)
319 {
320 if (_connType != SP_CONNECTOR_NOAVOID) {
321 return true;
322 }
323 return false;
324 }
326 void
327 SPConnEndPair::makePathInvalid(void)
328 {
329 _connRef->makePathInvalid();
330 }
333 // Redraws the curve along the recalculated route
334 // Straight or curved
335 void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, const gdouble curvature)
336 {
337 bool straight = curvature<1e-3;
339 Avoid::PolyLine route = connRef->displayRoute();
340 if (!straight)
341 route = route.curvedPolyline(curvature);
342 connRef->calcRouteDist();
344 curve->reset();
346 curve->moveto( Geom::Point(route.ps[0].x, route.ps[0].y) );
347 int pn = route.size();
348 for (int i = 1; i < pn; ++i) {
349 Geom::Point p(route.ps[i].x, route.ps[i].y);
350 if (straight) {
351 curve->lineto( p );
352 }
353 else {
354 switch (route.ts[i]) {
355 case 'M':
356 curve->moveto( p );
357 break;
358 case 'L':
359 curve->lineto( p );
360 break;
361 case 'C':
362 g_assert( i+2<pn );
363 curve->curveto( p, Geom::Point(route.ps[i+1].x, route.ps[i+1].y),
364 Geom::Point(route.ps[i+2].x, route.ps[i+2].y) );
365 i+=2;
366 break;
367 }
368 }
369 }
370 }
373 void
374 SPConnEndPair::tellLibavoidNewEndpoints(const bool processTransaction)
375 {
376 if (!isAutoRoutingConn()) {
377 // Do nothing
378 return;
379 }
380 makePathInvalid();
382 Geom::Point endPt[2];
383 getEndpoints(endPt);
385 Avoid::Point src(endPt[0][Geom::X], endPt[0][Geom::Y]);
386 Avoid::Point dst(endPt[1][Geom::X], endPt[1][Geom::Y]);
388 _connRef->setEndpoints(src, dst);
389 if (processTransaction)
390 {
391 _connRef->router()->processTransaction();
392 }
393 return;
394 }
397 bool
398 SPConnEndPair::reroutePathFromLibavoid(void)
399 {
400 if (!isAutoRoutingConn()) {
401 // Do nothing
402 return false;
403 }
405 SPCurve *curve = _path->original_curve ?_path->original_curve : _path->curve;
407 recreateCurve( curve, _connRef, _connCurvature );
409 Geom::Matrix doc2item = SP_ITEM(_path)->i2doc_affine().inverse();
410 curve->transform(doc2item);
412 return true;
413 }
416 /*
417 Local Variables:
418 mode:c++
419 c-file-style:"stroustrup"
420 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
421 indent-tabs-mode:nil
422 fill-column:99
423 End:
424 */
425 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :