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