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 // Default to center connection endpoint
25 type(ConnPointDefault),
26 id(4),
27 _changed_connection(),
28 _delete_connection(),
29 _transformed_connection()
30 {
31 }
33 static SPObject const *
34 get_nearest_common_ancestor(SPObject const *const obj, SPItem const *const objs[2]) {
35 SPObject const *anc_sofar = obj;
36 for (unsigned i = 0; i < 2; ++i) {
37 if ( objs[i] != NULL ) {
38 anc_sofar = anc_sofar->nearestCommonAncestor(objs[i]);
39 }
40 }
41 return anc_sofar;
42 }
45 static bool try_get_intersect_point_with_item_recursive(Geom::PathVector& conn_pv, SPItem* item,
46 const Geom::Matrix& item_transform, double& intersect_pos) {
48 double initial_pos = intersect_pos;
49 // if this is a group...
50 if (SP_IS_GROUP(item)) {
51 SPGroup* group = SP_GROUP(item);
53 // consider all first-order children
54 double child_pos = 0.0;
55 for (GSList const* i = sp_item_group_item_list(group); i != NULL; i = i->next) {
56 SPItem* child_item = SP_ITEM(i->data);
57 try_get_intersect_point_with_item_recursive(conn_pv, child_item,
58 item_transform * child_item->transform, child_pos);
59 if (intersect_pos < child_pos)
60 intersect_pos = child_pos;
61 }
62 return intersect_pos != initial_pos;
63 }
65 // if this is not a shape, nothing to be done
66 if (!SP_IS_SHAPE(item)) return false;
68 // make sure it has an associated curve
69 SPCurve* item_curve = SP_SHAPE(item)->getCurve();
70 if (!item_curve) return false;
72 // apply transformations (up to common ancestor)
73 item_curve->transform(item_transform);
75 const Geom::PathVector& curve_pv = item_curve->get_pathvector();
76 Geom::CrossingSet cross = crossings(conn_pv, curve_pv);
77 // iterate over all Crossings
78 for (Geom::CrossingSet::const_iterator i = cross.begin(); i != cross.end(); i++) {
79 const Geom::Crossings& cr = *i;
81 for (Geom::Crossings::const_iterator i = cr.begin(); i != cr.end(); i++) {
82 const Geom::Crossing& cr_pt = *i;
83 if ( intersect_pos < cr_pt.ta)
84 intersect_pos = cr_pt.ta;
85 }
86 }
88 item_curve->unref();
90 return intersect_pos != initial_pos;
91 }
94 // This function returns the outermost intersection point between the path (a connector)
95 // and the item given. If the item is a group, then the component items are considered.
96 // The transforms given should be to a common ancestor of both the path and item.
97 //
98 static bool try_get_intersect_point_with_item(SPPath* conn, SPItem* item,
99 const Geom::Matrix& item_transform, const Geom::Matrix& conn_transform,
100 const bool at_start, double& intersect_pos) {
102 // Copy the curve and apply transformations up to common ancestor.
103 SPCurve* conn_curve = conn->curve->copy();
104 conn_curve->transform(conn_transform);
106 Geom::PathVector conn_pv = conn_curve->get_pathvector();
108 // If this is not the starting point, use Geom::Path::reverse() to reverse the path
109 if (!at_start)
110 {
111 // connectors are actually a single path, so consider the first element from a Geom::PathVector
112 conn_pv[0] = conn_pv[0].reverse();
113 }
115 // We start with the intersection point at the beginning of the path
116 intersect_pos = 0.0;
118 // Find the intersection.
119 bool result = try_get_intersect_point_with_item_recursive(conn_pv, item, item_transform, intersect_pos);
121 if (!result)
122 // No intersection point has been found (why?)
123 // just default to connector end
124 intersect_pos = 0;
125 // If not at the starting point, recompute position with respect to original path
126 if (!at_start)
127 intersect_pos = conn_pv[0].size() - intersect_pos;
128 // Free the curve copy.
129 conn_curve->unref();
131 return result;
132 }
135 static void
136 sp_conn_get_route_and_redraw(SPPath *const path,
137 const bool updatePathRepr = true)
138 {
139 // Get the new route around obstacles.
140 bool rerouted = path->connEndPair.reroutePathFromLibavoid();
141 if (!rerouted) {
142 return;
143 }
145 SPItem *h2attItem[2];
146 path->connEndPair.getAttachedItems(h2attItem);
148 SPItem const *const path_item = SP_ITEM(path);
149 SPObject const *const ancestor = get_nearest_common_ancestor(path_item, h2attItem);
150 Geom::Matrix const path2anc(i2anc_affine(path_item, ancestor));
152 // Set sensible values incase there the connector ends are not
153 // attached to any shapes.
154 Geom::PathVector conn_pv = path->curve->get_pathvector();
155 double endPos[2] = { 0, conn_pv[0].size() };
157 SPConnEnd** _connEnd = path->connEndPair.getConnEnds();
158 for (unsigned h = 0; h < 2; ++h) {
159 if (h2attItem[h] && _connEnd[h]->type == ConnPointDefault && _connEnd[h]->id == ConnPointPosCC) {
160 Geom::Matrix h2i2anc = i2anc_affine(h2attItem[h], ancestor);
161 try_get_intersect_point_with_item(path, h2attItem[h], h2i2anc, path2anc,
162 (h == 0), endPos[h]);
163 }
164 }
165 change_endpts(path->curve, endPos);
166 if (updatePathRepr) {
167 path->updateRepr();
168 path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
169 }
170 }
173 static void
174 sp_conn_end_shape_move(Geom::Matrix const */*mp*/, SPItem */*moved_item*/,
175 SPPath *const path)
176 {
177 if (path->connEndPair.isAutoRoutingConn()) {
178 path->connEndPair.tellLibavoidNewEndpoints();
179 }
180 }
183 void
184 sp_conn_reroute_path(SPPath *const path)
185 {
186 if (path->connEndPair.isAutoRoutingConn()) {
187 path->connEndPair.tellLibavoidNewEndpoints();
188 }
189 }
192 void
193 sp_conn_reroute_path_immediate(SPPath *const path)
194 {
195 if (path->connEndPair.isAutoRoutingConn()) {
196 bool processTransaction = true;
197 path->connEndPair.tellLibavoidNewEndpoints(processTransaction);
198 }
199 // Don't update the path repr or else connector dragging is slowed by
200 // constant update of values to the xml editor, and each step is also
201 // needlessly remembered by undo/redo.
202 bool const updatePathRepr = false;
203 sp_conn_get_route_and_redraw(path, updatePathRepr);
204 }
206 void sp_conn_redraw_path(SPPath *const path)
207 {
208 sp_conn_get_route_and_redraw(path);
209 }
212 static void
213 change_endpts(SPCurve *const curve, double const endPos[2])
214 {
215 // Use Geom::Path::portion to cut the curve at the end positions
216 if (endPos[0] > endPos[1])
217 {
218 // Path is "negative", reset the curve and return
219 curve->reset();
220 return;
221 }
222 const Geom::Path& old_path = curve->get_pathvector()[0];
223 Geom::PathVector new_path_vector;
224 new_path_vector.push_back(old_path.portion(endPos[0], endPos[1]));
225 curve->set_pathvector(new_path_vector);
226 }
228 static void
229 sp_conn_end_deleted(SPObject *, SPObject *const owner, unsigned const handle_ix)
230 {
231 // todo: The first argument is the deleted object, or just NULL if
232 // called by sp_conn_end_detach.
233 g_return_if_fail(handle_ix < 2);
234 char const * const attr_strs[] = {"inkscape:connection-start", "inkscape:connection-start-point",
235 "inkscape:connection-end", "inkscape:connection-end-point"};
236 SP_OBJECT_REPR(owner)->setAttribute(attr_strs[2*handle_ix], NULL);
237 SP_OBJECT_REPR(owner)->setAttribute(attr_strs[2*handle_ix+1], NULL);
238 /* I believe this will trigger sp_conn_end_href_changed. */
239 }
241 void
242 sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix)
243 {
244 sp_conn_end_deleted(NULL, owner, handle_ix);
245 }
247 void
248 SPConnEnd::setAttacherHref(gchar const *value, SPPath* /*path*/)
249 {
250 if ( value && href && ( strcmp(value, href) == 0 ) ) {
251 /* No change, do nothing. */
252 }
253 else
254 {
255 if (!value)
256 {
257 ref.detach();
258 g_free(href);
259 href = NULL;
260 }
261 else
262 {
263 bool validRef = true;
264 href = g_strdup(value);
265 // Now do the attaching, which emits the changed signal.
266 try {
267 ref.attach(Inkscape::URI(value));
268 } catch (Inkscape::BadURIException &e) {
269 /* TODO: Proper error handling as per
270 * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. (Also needed for
271 * sp-use.) */
272 g_warning("%s", e.what());
273 validRef = false;
274 }
276 if ( !validRef )
277 {
278 ref.detach();
279 g_free(href);
280 href = NULL;
281 }
282 }
283 }
284 }
286 void
287 SPConnEnd::setAttacherEndpoint(gchar const *value, SPPath* /*path*/)
288 {
290 /* References to the connection points have the following format
291 <t><id>, where t is the type of the point, which
292 can be either "d" for default or "u" for user-defined, and
293 id is the local (inside the item) id of the connection point.
294 In the case of default points id represents the position on the
295 item (i.e. Top-Left, Center-Center, etc.).
296 */
298 bool changed = false;
299 ConnPointType newtype = type;
301 if (!value)
302 {
303 // Default to center endpoint
304 type = ConnPointDefault;
305 id = 4;
306 }
307 else
308 {
309 switch (value[0])
310 {
311 case 'd':
312 if ( newtype != ConnPointDefault )
313 {
314 newtype = ConnPointDefault;
315 changed = true;
316 }
317 break;
318 case 'u':
319 if ( newtype != ConnPointUserDefined)
320 {
321 newtype = ConnPointUserDefined;
322 changed = true;
323 }
324 break;
325 default:
326 g_warning("Bad reference to a connection point.");
327 }
329 int newid = (int) g_ascii_strtod( value+1, 0 );
330 if ( id != newid )
331 {
332 id = newid;
333 changed = true;
334 }
336 // We have to verify that the reference to the
337 // connection point is a valid one.
339 if ( changed )
340 {
342 // Get the item the connector is attached to
343 SPItem* item = ref.getObject();
344 if ( item )
345 {
346 if (!item->avoidRef->isValidConnPointId( newtype, newid ) )
347 {
348 g_warning("Bad reference to a connection point.");
349 }
350 else
351 {
352 type = newtype;
353 id = newid;
354 }
355 /* // Update the connector
356 if (path->connEndPair.isAutoRoutingConn()) {
357 path->connEndPair.tellLibavoidNewEndpoints();
358 }
359 */
360 }
361 }
362 }
363 }
365 void
366 sp_conn_end_href_changed(SPObject */*old_ref*/, SPObject */*ref*/,
367 SPConnEnd *connEndPtr, SPPath *const path, unsigned const handle_ix)
368 {
369 g_return_if_fail(connEndPtr != NULL);
370 SPConnEnd &connEnd = *connEndPtr;
371 connEnd._delete_connection.disconnect();
372 connEnd._transformed_connection.disconnect();
374 if (connEnd.href) {
375 SPObject *refobj = connEnd.ref.getObject();
376 if (refobj) {
377 connEnd._delete_connection
378 = SP_OBJECT(refobj)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_conn_end_deleted),
379 SP_OBJECT(path), handle_ix));
380 connEnd._transformed_connection
381 = SP_ITEM(refobj)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_conn_end_shape_move),
382 path));
383 }
384 }
385 }
389 /*
390 Local Variables:
391 mode:c++
392 c-file-style:"stroustrup"
393 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
394 indent-tabs-mode:nil
395 fill-column:99
396 End:
397 */
398 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :