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 sp_object_read_attr(object, "inkscape:connector-type");
83 sp_object_read_attr(object, "inkscape:connection-start");
84 sp_object_read_attr(object, "inkscape:connection-start-point");
85 sp_object_read_attr(object, "inkscape:connection-end");
86 sp_object_read_attr(object, "inkscape:connection-end-point");
87 sp_object_read_attr(object, "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)
103 {
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 }
172 }
174 void
175 SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const
176 {
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" );
191 }
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 }
212 }
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 }
235 }
237 gdouble
238 SPConnEndPair::getCurvature(void) const {
239 return _connCurvature;
240 }
242 SPConnEnd**
243 SPConnEndPair::getConnEnds(void)
244 {
245 return _connEnd;
246 }
248 bool
249 SPConnEndPair::isOrthogonal(void) const {
250 return _connType == SP_CONNECTOR_ORTHOGONAL;
251 }
254 static void redrawConnectorCallback(void *ptr)
255 {
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);
262 }
264 void
265 SPConnEndPair::rerouteFromManipulation(void)
266 {
267 sp_conn_reroute_path_immediate(_path);
268 }
271 // Called from sp_path_update to initialise the endpoints.
272 void
273 SPConnEndPair::update(void)
274 {
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 }
290 }
293 void SPConnEndPair::storeIds(void)
294 {
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 }
313 }
316 bool
317 SPConnEndPair::isAutoRoutingConn(void)
318 {
319 if (_connType != SP_CONNECTOR_NOAVOID) {
320 return true;
321 }
322 return false;
323 }
325 void
326 SPConnEndPair::makePathInvalid(void)
327 {
328 _connRef->makePathInvalid();
329 }
332 // Redraws the curve along the recalculated route
333 // Straight or curved
334 void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, const gdouble curvature)
335 {
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 }
369 }
372 void
373 SPConnEndPair::tellLibavoidNewEndpoints(const bool processTransaction)
374 {
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;
393 }
396 bool
397 SPConnEndPair::reroutePathFromLibavoid(void)
398 {
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_i2doc_affine(SP_ITEM(_path)).inverse();
409 curve->transform(doc2item);
411 return true;
412 }
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 :