52914ee9e0bab7b7f770a40820526ef07c9c53b4
2 #include <cstring>
3 #include <string>
4 #include <limits>
6 #include "display/curve.h"
7 #include "libnr/nr-matrix-fns.h"
8 #include "xml/repr.h"
9 #include "sp-conn-end.h"
10 #include "sp-path.h"
11 #include "uri.h"
12 #include "document.h"
13 #include "sp-item-group.h"
14 #include "2geom/path.h"
15 #include "2geom/pathvector.h"
16 #include "2geom/path-intersection.h"
19 static void change_endpts(SPCurve *const curve, double const endPos[2]);
21 SPConnEnd::SPConnEnd(SPObject *const owner) :
22 ref(owner),
23 href(NULL),
24 _changed_connection(),
25 _delete_connection(),
26 _transformed_connection()
27 {
28 }
30 static SPObject const *
31 get_nearest_common_ancestor(SPObject const *const obj, SPItem const *const objs[2]) {
32 SPObject const *anc_sofar = obj;
33 for (unsigned i = 0; i < 2; ++i) {
34 if ( objs[i] != NULL ) {
35 anc_sofar = anc_sofar->nearestCommonAncestor(objs[i]);
36 }
37 }
38 return anc_sofar;
39 }
42 static bool try_get_intersect_point_with_item_recursive(Geom::PathVector& conn_pv, SPItem* item,
43 const Geom::Matrix& item_transform, double& intersect_pos) {
45 double initial_pos = intersect_pos;
46 // if this is a group...
47 if (SP_IS_GROUP(item)) {
48 SPGroup* group = SP_GROUP(item);
50 // consider all first-order children
51 double child_pos = std::numeric_limits<double>::max();
52 for (GSList const* i = sp_item_group_item_list(group); i != NULL; i = i->next) {
53 SPItem* child_item = SP_ITEM(i->data);
54 try_get_intersect_point_with_item_recursive(conn_pv, child_item,
55 item_transform * child_item->transform, child_pos);
56 if (intersect_pos > child_pos)
57 intersect_pos = child_pos;
58 }
59 return intersect_pos != initial_pos;
60 }
62 // if this is not a shape, nothing to be done
63 if (!SP_IS_SHAPE(item)) return false;
65 // make sure it has an associated curve
66 SPCurve* item_curve = sp_shape_get_curve(SP_SHAPE(item));
67 if (!item_curve) return false;
69 // apply transformations (up to common ancestor)
70 item_curve->transform(item_transform);
72 const Geom::PathVector& curve_pv = item_curve->get_pathvector();
73 Geom::CrossingSet cross = crossings(conn_pv, curve_pv);
74 // iterate over all Crossings
75 for (Geom::CrossingSet::const_iterator i = cross.begin(); i != cross.end(); i++) {
76 const Geom::Crossings& cr = *i;
78 for (Geom::Crossings::const_iterator i = cr.begin(); i != cr.end(); i++) {
79 const Geom::Crossing& cr_pt = *i;
80 if ( intersect_pos > cr_pt.ta)
81 intersect_pos = cr_pt.ta;
82 }
83 }
85 item_curve->unref();
87 return intersect_pos != initial_pos;
88 }
91 // This function returns the outermost intersection point between the path (a connector)
92 // and the item given. If the item is a group, then the component items are considered.
93 // The transforms given should be to a common ancestor of both the path and item.
94 //
95 static bool try_get_intersect_point_with_item(SPPath* conn, SPItem* item,
96 const Geom::Matrix& item_transform, const Geom::Matrix& conn_transform,
97 const bool at_start, double& intersect_pos) {
99 // Copy the curve and apply transformations up to common ancestor.
100 SPCurve* conn_curve = conn->curve->copy();
101 conn_curve->transform(conn_transform);
103 Geom::PathVector conn_pv = conn_curve->get_pathvector();
105 // If this is not the starting point, use Geom::Path::reverse() to reverse the path
106 if (!at_start)
107 {
108 // connectors are actually a single path, so consider the first element from a Geom::PathVector
109 conn_pv[0] = conn_pv[0].reverse();
110 }
112 // We start with the intersection point at the end of the path
113 intersect_pos = conn_pv[0].size();
115 // Find the intersection.
116 bool result = try_get_intersect_point_with_item_recursive(conn_pv, item, item_transform, intersect_pos);
118 if (!result)
119 // No intersection point has been found (why?)
120 // just default to connector end
121 intersect_pos = 0;
122 // If not at the starting point, recompute position with respect to original path
123 if (!at_start)
124 intersect_pos = conn_pv[0].size() - intersect_pos;
125 // Free the curve copy.
126 conn_curve->unref();
128 return result;
129 }
132 static void
133 sp_conn_get_route_and_redraw(SPPath *const path,
134 const bool updatePathRepr = true)
135 {
136 // Get the new route around obstacles.
137 bool rerouted = path->connEndPair.reroutePathFromLibavoid();
138 if (!rerouted) {
139 return;
140 }
142 SPItem *h2attItem[2];
143 path->connEndPair.getAttachedItems(h2attItem);
145 SPItem const *const path_item = SP_ITEM(path);
146 SPObject const *const ancestor = get_nearest_common_ancestor(path_item, h2attItem);
147 Geom::Matrix const path2anc(i2anc_affine(path_item, ancestor));
149 // Set sensible values incase there the connector ends are not
150 // attached to any shapes.
151 Geom::PathVector conn_pv = path->curve->get_pathvector();
152 double endPos[2] = { 0, conn_pv[0].size() };
154 SPConnEnd** _connEnd = path->connEndPair.getConnEnds();
155 for (unsigned h = 0; h < 2; ++h) {
156 if (h2attItem[h] && _connEnd[h]->type == ConnPointDefault && _connEnd[h]->id == ConnPointPosCC) {
157 Geom::Matrix h2i2anc = i2anc_affine(h2attItem[h], ancestor);
158 try_get_intersect_point_with_item(path, h2attItem[h], h2i2anc, path2anc,
159 (h == 0), endPos[h]);
160 }
161 }
162 change_endpts(path->curve, endPos);
163 if (updatePathRepr) {
164 path->updateRepr();
165 path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
166 }
167 }
170 static void
171 sp_conn_end_shape_move(Geom::Matrix const */*mp*/, SPItem */*moved_item*/,
172 SPPath *const path)
173 {
174 if (path->connEndPair.isAutoRoutingConn()) {
175 path->connEndPair.tellLibavoidNewEndpoints();
176 }
177 }
180 void
181 sp_conn_reroute_path(SPPath *const path)
182 {
183 if (path->connEndPair.isAutoRoutingConn()) {
184 path->connEndPair.tellLibavoidNewEndpoints();
185 }
186 }
189 void
190 sp_conn_reroute_path_immediate(SPPath *const path)
191 {
192 if (path->connEndPair.isAutoRoutingConn()) {
193 bool processTransaction = true;
194 path->connEndPair.tellLibavoidNewEndpoints(processTransaction);
195 }
196 // Don't update the path repr or else connector dragging is slowed by
197 // constant update of values to the xml editor, and each step is also
198 // needlessly remembered by undo/redo.
199 bool const updatePathRepr = false;
200 sp_conn_get_route_and_redraw(path, updatePathRepr);
201 }
203 void sp_conn_redraw_path(SPPath *const path)
204 {
205 sp_conn_get_route_and_redraw(path);
206 }
209 static void
210 change_endpts(SPCurve *const curve, double const endPos[2])
211 {
212 // Use Geom::Path::portion to cut the curve at the end positions
213 if (endPos[0] > endPos[1])
214 {
215 // Path is "negative", reset the curve and return
216 curve->reset();
217 return;
218 }
219 const Geom::Path& old_path = curve->get_pathvector()[0];
220 Geom::PathVector new_path_vector;
221 new_path_vector.push_back(old_path.portion(endPos[0], endPos[1]));
222 curve->set_pathvector(new_path_vector);
223 }
225 static void
226 sp_conn_end_deleted(SPObject *, SPObject *const owner, unsigned const handle_ix)
227 {
228 // todo: The first argument is the deleted object, or just NULL if
229 // called by sp_conn_end_detach.
230 g_return_if_fail(handle_ix < 2);
231 char const *const attr_str[] = {"inkscape:connection-start",
232 "inkscape:connection-end"};
233 SP_OBJECT_REPR(owner)->setAttribute(attr_str[handle_ix], NULL);
234 /* I believe this will trigger sp_conn_end_href_changed. */
235 }
237 void
238 sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix)
239 {
240 sp_conn_end_deleted(NULL, owner, handle_ix);
241 }
243 void
244 SPConnEnd::setAttacherHref(gchar const *value, SPPath* path)
245 {
246 if ( value && href && ( strcmp(value, href) == 0 ) ) {
247 /* No change, do nothing. */
248 } else {
249 if (!value)
250 {
251 ref.detach();
252 g_free(href);
253 href = NULL;
254 }
255 else
256 {
258 /* References to the connection points have the following format
259 #svguri_t_id, where #svguri is the id of the item the
260 connector is attached to, t is the type of the point, which
261 can be either "d" for default or "u" for user-defined, and
262 id is the local (inside the item) id of the connection point.
263 In the case of default points id represents the position on the
264 item (i.e. Top-Left, Centre-Centre, etc.).
265 */
267 gchar ** href_strarray = NULL;
268 if (href)
269 href_strarray = g_strsplit(href, "_", 0);
270 gchar ** value_strarray = g_strsplit(value, "_", 0);
272 g_free(href);
273 href = NULL;
275 bool changed = false;
276 bool validRef = true;
278 if ( !href_strarray || g_strcmp0(href_strarray[0], value_strarray[0]) != 0 )
279 {
280 // The href has changed, so update it.
281 changed = true;
282 // Set the href field, because sp_conn_end_href_changed will need it.
283 href = g_strdup(value);
284 // Now do the attaching, which emits the changed signal.
285 try {
286 ref.attach(Inkscape::URI(value_strarray[0]));
287 } catch (Inkscape::BadURIException &e) {
288 /* TODO: Proper error handling as per
289 * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. (Also needed for
290 * sp-use.) */
291 g_warning("%s", e.what());
292 validRef = false;
293 }
294 }
295 // Check to see if the connection point changed and update it.
296 //
298 if ( !value_strarray[1] )
299 {
300 /* Treat the old references to connection points
301 as default points that connect to the centre
302 of the item.
303 */
304 if ( type != ConnPointDefault )
305 {
306 type = ConnPointDefault;
307 changed = true;
308 }
309 if ( id != ConnPointPosCC )
310 {
311 id = ConnPointPosCC;
312 changed = true;
313 }
314 }
315 else
316 {
317 switch (value_strarray[1][0])
318 {
319 case 'd':
320 if ( type != ConnPointDefault )
321 {
322 type = ConnPointDefault;
323 changed = true;
324 }
325 break;
326 case 'u':
327 if ( type != ConnPointUserDefined)
328 {
329 type = ConnPointUserDefined;
330 changed = true;
331 }
332 break;
333 default:
334 g_warning("Bad reference to a connection point.");
335 validRef = false;
336 }
337 if ( value_strarray[2] )
338 {
339 int newId = (int) g_ascii_strtod( value_strarray[2], 0 );
340 if ( id != newId )
341 {
342 id = newId;
343 changed = true;
344 }
346 }
347 else
348 {
349 // We have a malformed reference to a connection point,
350 // emit a warning, clear href and detach ref.
351 changed = true;
352 g_warning("Bad reference to a connection point.");\
353 validRef = false;
354 }
355 }
357 if ( changed )
358 {
359 // We still have to verify that the reference to the
360 // connection point is a valid one.
362 // Get the item the connector is attached to
363 SPItem* item = ref.getObject();
364 if ( item && !item->avoidRef->isValidConnPointId( type, id ) )
365 {
366 g_warning("Bad reference to a connection point.");
367 validRef = false;
368 }
369 /* else
370 // Update the connector
371 if (path->connEndPair.isAutoRoutingConn()) {
372 path->connEndPair.tellLibavoidNewEndpoints();
373 }*/
374 }
376 if ( !validRef )
377 {
378 ref.detach();
379 g_free(href);
380 href = NULL;
381 }
382 else
383 if (!href)
384 href = g_strdup(value);
386 g_strfreev(href_strarray);
387 g_strfreev(value_strarray);
388 }
389 }
390 }
392 void
393 sp_conn_end_href_changed(SPObject */*old_ref*/, SPObject */*ref*/,
394 SPConnEnd *connEndPtr, SPPath *const path, unsigned const handle_ix)
395 {
396 g_return_if_fail(connEndPtr != NULL);
397 SPConnEnd &connEnd = *connEndPtr;
398 connEnd._delete_connection.disconnect();
399 connEnd._transformed_connection.disconnect();
401 if (connEnd.href) {
402 SPObject *refobj = connEnd.ref.getObject();
403 if (refobj) {
404 connEnd._delete_connection
405 = SP_OBJECT(refobj)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_conn_end_deleted),
406 SP_OBJECT(path), handle_ix));
407 connEnd._transformed_connection
408 = SP_ITEM(refobj)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_conn_end_shape_move),
409 path));
410 }
411 }
412 }
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 :