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 "attributes.h"
14 #include "sp-conn-end.h"
15 #include "uri.h"
16 #include "display/curve.h"
17 #include "xml/repr.h"
18 #include "sp-path.h"
19 #include "libavoid/vertices.h"
20 #include "libavoid/router.h"
21 #include "document.h"
24 SPConnEndPair::SPConnEndPair(SPPath *const owner)
25 : _invalid_path_connection()
26 , _path(owner)
27 , _connRef(NULL)
28 , _connType(SP_CONNECTOR_NOAVOID)
29 , _transformed_connection()
30 {
31 for (unsigned handle_ix = 0; handle_ix <= 1; ++handle_ix) {
32 this->_connEnd[handle_ix] = new SPConnEnd(SP_OBJECT(owner));
33 this->_connEnd[handle_ix]->_changed_connection
34 = this->_connEnd[handle_ix]->ref.changedSignal()
35 .connect(sigc::bind(sigc::ptr_fun(sp_conn_end_href_changed),
36 this->_connEnd[handle_ix], owner, handle_ix));
37 }
38 }
40 SPConnEndPair::~SPConnEndPair()
41 {
42 for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
43 delete this->_connEnd[handle_ix];
44 this->_connEnd[handle_ix] = NULL;
45 }
46 if (_connRef) {
47 _connRef->removeFromGraph();
48 delete _connRef;
49 _connRef = NULL;
50 }
52 _invalid_path_connection.disconnect();
53 _transformed_connection.disconnect();
54 }
56 void
57 SPConnEndPair::release()
58 {
59 for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
60 this->_connEnd[handle_ix]->_changed_connection.disconnect();
61 this->_connEnd[handle_ix]->_delete_connection.disconnect();
62 this->_connEnd[handle_ix]->_transformed_connection.disconnect();
63 g_free(this->_connEnd[handle_ix]->href);
64 this->_connEnd[handle_ix]->href = NULL;
65 this->_connEnd[handle_ix]->ref.detach();
66 }
67 }
69 void
70 sp_conn_end_pair_build(SPObject *object)
71 {
72 sp_object_read_attr(object, "inkscape:connector-type");
73 sp_object_read_attr(object, "inkscape:connection-start");
74 sp_object_read_attr(object, "inkscape:connection-end");
75 }
78 static void
79 avoid_conn_move(NR::Matrix const *mp, SPItem *moved_item)
80 {
81 // Detach from objects if attached.
82 sp_conn_end_detach(moved_item, 0);
83 sp_conn_end_detach(moved_item, 1);
84 // Reroute connector
85 SPPath *path = SP_PATH(moved_item);
86 path->connEndPair.makePathInvalid();
87 sp_conn_adjust_invalid_path(path);
88 }
91 void
92 SPConnEndPair::setAttr(unsigned const key, gchar const *const value)
93 {
94 if (key == SP_ATTR_CONNECTOR_TYPE) {
95 if (value && (strcmp(value, "polyline") == 0)) {
96 _connType = SP_CONNECTOR_POLYLINE;
98 Avoid::Router *router = _path->document->router;
99 GQuark itemID = g_quark_from_string(SP_OBJECT(_path)->id);
100 _connRef = new Avoid::ConnRef(router, itemID);
101 _invalid_path_connection = connectInvalidPath(
102 sigc::ptr_fun(&sp_conn_adjust_invalid_path));
103 _transformed_connection = _path->connectTransformed(
104 sigc::ptr_fun(&avoid_conn_move));
105 }
106 else {
107 _connType = SP_CONNECTOR_NOAVOID;
109 if (_connRef) {
110 _connRef->removeFromGraph();
111 delete _connRef;
112 _connRef = NULL;
113 _invalid_path_connection.disconnect();
114 _transformed_connection.disconnect();
115 }
116 }
117 return;
119 }
121 unsigned const handle_ix = key - SP_ATTR_CONNECTION_START;
122 g_assert( handle_ix <= 1 );
123 this->_connEnd[handle_ix]->setAttacherHref(value);
124 }
126 void
127 SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const
128 {
129 for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
130 if (this->_connEnd[handle_ix]->ref.getURI()) {
131 char const * const attr_strs[] = {"inkscape:connection-start",
132 "inkscape:connection-end"};
133 gchar *uri_string = this->_connEnd[handle_ix]->ref.getURI()->toString();
134 repr->setAttribute(attr_strs[handle_ix], uri_string);
135 g_free(uri_string);
136 }
137 }
138 }
140 void
141 SPConnEndPair::getAttachedItems(SPItem *h2attItem[2]) const {
142 for (unsigned h = 0; h < 2; ++h) {
143 h2attItem[h] = this->_connEnd[h]->ref.getObject();
144 }
145 }
147 void
148 SPConnEndPair::getEndpoints(NR::Point endPts[]) const {
149 SPCurve *curve = _path->curve;
150 SPItem *h2attItem[2];
151 getAttachedItems(h2attItem);
153 for (unsigned h = 0; h < 2; ++h) {
154 if ( h2attItem[h] ) {
155 NR::Rect const bbox = h2attItem[h]->invokeBbox(sp_item_i2doc_affine(h2attItem[h]));
156 endPts[h] = bbox.midpoint();
157 }
158 else
159 {
160 if (h == 0) {
161 endPts[h] = sp_curve_first_point(curve);
162 }
163 else {
164 endPts[h] = sp_curve_last_point(curve);
165 }
166 }
167 }
168 }
170 sigc::connection
171 SPConnEndPair::connectInvalidPath(sigc::slot<void, SPPath *> slot)
172 {
173 return _invalid_path_signal.connect(slot);
174 }
176 static void emitPathInvalidationNotification(void *ptr)
177 {
178 // We emit a signal here rather than just calling the reroute function
179 // since this allows all the movement action computation to happen,
180 // then all connectors (that require it) will be rerouted. Otherwise,
181 // one connector could get rerouted several times as a result of
182 // dragging a couple of shapes.
184 SPPath *path = SP_PATH(ptr);
185 path->connEndPair._invalid_path_signal.emit(path);
186 }
188 void
189 SPConnEndPair::rerouteFromManipulation(void)
190 {
191 _connRef->makePathInvalid();
192 sp_conn_adjust_path(_path);
193 }
195 void
196 SPConnEndPair::reroute(void)
197 {
198 sp_conn_adjust_path(_path);
199 }
201 // Called from sp_path_update to initialise the endpoints.
202 void
203 SPConnEndPair::update(void)
204 {
205 if (_connType != SP_CONNECTOR_NOAVOID) {
206 g_assert(_connRef != NULL);
207 if (!(_connRef->isInitialised())) {
208 NR::Point endPt[2];
209 getEndpoints(endPt);
211 Avoid::Point src = { endPt[0][NR::X], endPt[0][NR::Y] };
212 Avoid::Point dst = { endPt[1][NR::X], endPt[1][NR::Y] };
214 _connRef->lateSetup(src, dst);
215 _connRef->setCallback(&emitPathInvalidationNotification, _path);
216 }
217 // Store the ID of the objects attached to the connector.
218 storeIds();
219 }
220 }
223 void SPConnEndPair::storeIds(void)
224 {
225 if (_connEnd[0]->href) {
226 // href begins with a '#' which we don't want.
227 const char *startId = _connEnd[0]->href + 1;
228 GQuark itemId = g_quark_from_string(startId);
229 _connRef->setEndPointId(Avoid::VertID::src, itemId);
230 }
231 else {
232 _connRef->setEndPointId(Avoid::VertID::src, 0);
233 }
234 if (_connEnd[1]->href) {
235 // href begins with a '#' which we don't want.
236 const char *endId = _connEnd[1]->href + 1;
237 GQuark itemId = g_quark_from_string(endId);
238 _connRef->setEndPointId(Avoid::VertID::tar, itemId);
239 }
240 else {
241 _connRef->setEndPointId(Avoid::VertID::tar, 0);
242 }
243 }
246 bool
247 SPConnEndPair::isAutoRoutingConn(void)
248 {
249 if (_connType != SP_CONNECTOR_NOAVOID) {
250 return true;
251 }
252 return false;
253 }
255 void
256 SPConnEndPair::makePathInvalid(void)
257 {
258 _connRef->makePathInvalid();
259 }
261 void
262 SPConnEndPair::reroutePath(void)
263 {
264 if (!isAutoRoutingConn()) {
265 // Do nothing
266 return;
267 }
269 SPCurve *curve = _path->curve;
271 NR::Point endPt[2];
272 getEndpoints(endPt);
274 Avoid::Point src = { endPt[0][NR::X], endPt[0][NR::Y] };
275 Avoid::Point dst = { endPt[1][NR::X], endPt[1][NR::Y] };
277 _connRef->updateEndPoint(Avoid::VertID::src, src);
278 _connRef->updateEndPoint(Avoid::VertID::tar, dst);
280 _connRef->generatePath(src, dst);
282 Avoid::PolyLine route = _connRef->route();
283 _connRef->calcRouteDist();
285 sp_curve_reset(curve);
286 sp_curve_moveto(curve, endPt[0]);
288 for (int i = 1; i < route.pn; ++i) {
289 NR::Point p(route.ps[i].x, route.ps[i].y);
290 sp_curve_lineto(curve, p);
291 }
292 }
294 /*
295 Local Variables:
296 mode:c++
297 c-file-style:"stroustrup"
298 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
299 indent-tabs-mode:nil
300 fill-column:99
301 End:
302 */
303 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :