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"
22 #include "sp-item-group.h"
25 SPConnEndPair::SPConnEndPair(SPPath *const owner)
26 : _invalid_path_connection()
27 , _path(owner)
28 , _connRef(NULL)
29 , _connType(SP_CONNECTOR_NOAVOID)
30 , _transformed_connection()
31 {
32 for (unsigned handle_ix = 0; handle_ix <= 1; ++handle_ix) {
33 this->_connEnd[handle_ix] = new SPConnEnd(SP_OBJECT(owner));
34 this->_connEnd[handle_ix]->_changed_connection
35 = this->_connEnd[handle_ix]->ref.changedSignal()
36 .connect(sigc::bind(sigc::ptr_fun(sp_conn_end_href_changed),
37 this->_connEnd[handle_ix], owner, handle_ix));
38 }
39 }
41 SPConnEndPair::~SPConnEndPair()
42 {
43 for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
44 delete this->_connEnd[handle_ix];
45 this->_connEnd[handle_ix] = NULL;
46 }
47 if (_connRef) {
48 _connRef->removeFromGraph();
49 delete _connRef;
50 _connRef = NULL;
51 }
53 _invalid_path_connection.disconnect();
54 _transformed_connection.disconnect();
55 }
57 void
58 SPConnEndPair::release()
59 {
60 for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
61 this->_connEnd[handle_ix]->_changed_connection.disconnect();
62 this->_connEnd[handle_ix]->_delete_connection.disconnect();
63 this->_connEnd[handle_ix]->_transformed_connection.disconnect();
64 g_free(this->_connEnd[handle_ix]->href);
65 this->_connEnd[handle_ix]->href = NULL;
66 this->_connEnd[handle_ix]->ref.detach();
67 }
68 }
70 void
71 sp_conn_end_pair_build(SPObject *object)
72 {
73 sp_object_read_attr(object, "inkscape:connector-type");
74 sp_object_read_attr(object, "inkscape:connection-start");
75 sp_object_read_attr(object, "inkscape:connection-end");
76 }
79 static void
80 avoid_conn_move(NR::Matrix const *mp, SPItem *moved_item)
81 {
82 // Reroute connector
83 SPPath *path = SP_PATH(moved_item);
84 path->connEndPair.makePathInvalid();
85 sp_conn_adjust_invalid_path(path);
86 }
89 void
90 SPConnEndPair::setAttr(unsigned const key, gchar const *const value)
91 {
92 if (key == SP_ATTR_CONNECTOR_TYPE) {
93 if (value && (strcmp(value, "polyline") == 0)) {
94 _connType = SP_CONNECTOR_POLYLINE;
96 Avoid::Router *router = _path->document->router;
97 GQuark itemID = g_quark_from_string(SP_OBJECT(_path)->id);
98 _connRef = new Avoid::ConnRef(router, itemID);
99 _invalid_path_connection = connectInvalidPath(
100 sigc::ptr_fun(&sp_conn_adjust_invalid_path));
101 _transformed_connection = _path->connectTransformed(
102 sigc::ptr_fun(&avoid_conn_move));
103 }
104 else {
105 _connType = SP_CONNECTOR_NOAVOID;
107 if (_connRef) {
108 _connRef->removeFromGraph();
109 delete _connRef;
110 _connRef = NULL;
111 _invalid_path_connection.disconnect();
112 _transformed_connection.disconnect();
113 }
114 }
115 return;
117 }
119 unsigned const handle_ix = key - SP_ATTR_CONNECTION_START;
120 g_assert( handle_ix <= 1 );
121 this->_connEnd[handle_ix]->setAttacherHref(value);
122 }
124 void
125 SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const
126 {
127 for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
128 if (this->_connEnd[handle_ix]->ref.getURI()) {
129 char const * const attr_strs[] = {"inkscape:connection-start",
130 "inkscape:connection-end"};
131 gchar *uri_string = this->_connEnd[handle_ix]->ref.getURI()->toString();
132 repr->setAttribute(attr_strs[handle_ix], uri_string);
133 g_free(uri_string);
134 }
135 }
136 }
138 void
139 SPConnEndPair::getAttachedItems(SPItem *h2attItem[2]) const {
140 for (unsigned h = 0; h < 2; ++h) {
141 h2attItem[h] = this->_connEnd[h]->ref.getObject();
143 // Deal with the case of the attached object being an empty group.
144 // A group containing no items does not have a valid bbox, so
145 // causes problems for the auto-routing code. Also, since such a
146 // group no longer has an onscreen representation and can only be
147 // selected through the XML editor, it makes sense just to detach
148 // connectors from them.
149 if (SP_IS_GROUP(h2attItem[h])) {
150 if (SP_GROUP(h2attItem[h])->group->getItemCount() == 0) {
151 // This group is empty, so detach.
152 sp_conn_end_detach(_path, h);
153 h2attItem[h] = NULL;
154 }
155 }
156 }
157 }
159 void
160 SPConnEndPair::getEndpoints(NR::Point endPts[]) const {
161 SPCurve *curve = _path->curve;
162 SPItem *h2attItem[2];
163 getAttachedItems(h2attItem);
165 for (unsigned h = 0; h < 2; ++h) {
166 if ( h2attItem[h] ) {
167 NR::Maybe<NR::Rect> bbox = h2attItem[h]->getBounds(sp_item_i2doc_affine(h2attItem[h]));
168 if (bbox) {
169 endPts[h] = bbox->midpoint();
170 } else {
171 // FIXME
172 endPts[h] = NR::Point(0, 0);
173 }
174 }
175 else
176 {
177 if (h == 0) {
178 endPts[h] = sp_curve_first_point(curve);
179 }
180 else {
181 endPts[h] = sp_curve_last_point(curve);
182 }
183 }
184 }
185 }
187 sigc::connection
188 SPConnEndPair::connectInvalidPath(sigc::slot<void, SPPath *> slot)
189 {
190 return _invalid_path_signal.connect(slot);
191 }
193 static void emitPathInvalidationNotification(void *ptr)
194 {
195 // We emit a signal here rather than just calling the reroute function
196 // since this allows all the movement action computation to happen,
197 // then all connectors (that require it) will be rerouted. Otherwise,
198 // one connector could get rerouted several times as a result of
199 // dragging a couple of shapes.
201 SPPath *path = SP_PATH(ptr);
202 path->connEndPair._invalid_path_signal.emit(path);
203 }
205 void
206 SPConnEndPair::rerouteFromManipulation(void)
207 {
208 _connRef->makePathInvalid();
209 sp_conn_adjust_path(_path);
210 }
212 void
213 SPConnEndPair::reroute(void)
214 {
215 sp_conn_adjust_path(_path);
216 }
218 // Called from sp_path_update to initialise the endpoints.
219 void
220 SPConnEndPair::update(void)
221 {
222 if (_connType != SP_CONNECTOR_NOAVOID) {
223 g_assert(_connRef != NULL);
224 if (!(_connRef->isInitialised())) {
225 NR::Point endPt[2];
226 getEndpoints(endPt);
228 Avoid::Point src(endPt[0][NR::X], endPt[0][NR::Y]);
229 Avoid::Point dst(endPt[1][NR::X], endPt[1][NR::Y]);
231 _connRef->lateSetup(src, dst);
232 _connRef->setCallback(&emitPathInvalidationNotification, _path);
233 }
234 // Store the ID of the objects attached to the connector.
235 storeIds();
236 }
237 }
240 void SPConnEndPair::storeIds(void)
241 {
242 if (_connEnd[0]->href) {
243 // href begins with a '#' which we don't want.
244 const char *startId = _connEnd[0]->href + 1;
245 GQuark itemId = g_quark_from_string(startId);
246 _connRef->setEndPointId(Avoid::VertID::src, itemId);
247 }
248 else {
249 _connRef->setEndPointId(Avoid::VertID::src, 0);
250 }
251 if (_connEnd[1]->href) {
252 // href begins with a '#' which we don't want.
253 const char *endId = _connEnd[1]->href + 1;
254 GQuark itemId = g_quark_from_string(endId);
255 _connRef->setEndPointId(Avoid::VertID::tar, itemId);
256 }
257 else {
258 _connRef->setEndPointId(Avoid::VertID::tar, 0);
259 }
260 }
263 bool
264 SPConnEndPair::isAutoRoutingConn(void)
265 {
266 if (_connType != SP_CONNECTOR_NOAVOID) {
267 return true;
268 }
269 return false;
270 }
272 void
273 SPConnEndPair::makePathInvalid(void)
274 {
275 _connRef->makePathInvalid();
276 }
278 void
279 SPConnEndPair::reroutePath(void)
280 {
281 if (!isAutoRoutingConn()) {
282 // Do nothing
283 return;
284 }
286 SPCurve *curve = _path->curve;
288 NR::Point endPt[2];
289 getEndpoints(endPt);
291 Avoid::Point src(endPt[0][NR::X], endPt[0][NR::Y]);
292 Avoid::Point dst(endPt[1][NR::X], endPt[1][NR::Y]);
294 _connRef->updateEndPoint(Avoid::VertID::src, src);
295 _connRef->updateEndPoint(Avoid::VertID::tar, dst);
297 _connRef->generatePath(src, dst);
299 Avoid::PolyLine route = _connRef->route();
300 _connRef->calcRouteDist();
302 sp_curve_reset(curve);
303 sp_curve_moveto(curve, endPt[0]);
305 for (int i = 1; i < route.pn; ++i) {
306 NR::Point p(route.ps[i].x, route.ps[i].y);
307 sp_curve_lineto(curve, p);
308 }
309 }
311 /*
312 Local Variables:
313 mode:c++
314 c-file-style:"stroustrup"
315 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
316 indent-tabs-mode:nil
317 fill-column:99
318 End:
319 */
320 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :