Code

Connector tool updates
[inkscape.git] / src / sp-conn-end.cpp
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 = 0.0;
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 beginning of the path
113     intersect_pos = 0.0;
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;
132 static void
133 sp_conn_get_route_and_redraw(SPPath *const path,
134         const bool updatePathRepr = true)
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     }
170 static void
171 sp_conn_end_shape_move(Geom::Matrix const */*mp*/, SPItem */*moved_item*/,
172                             SPPath *const path)
174     if (path->connEndPair.isAutoRoutingConn()) {
175         path->connEndPair.tellLibavoidNewEndpoints();
176     }
180 void
181 sp_conn_reroute_path(SPPath *const path)
183     if (path->connEndPair.isAutoRoutingConn()) {
184         path->connEndPair.tellLibavoidNewEndpoints();
185     }
189 void
190 sp_conn_reroute_path_immediate(SPPath *const path)
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);
203 void sp_conn_redraw_path(SPPath *const path)
205     sp_conn_get_route_and_redraw(path);
209 static void
210 change_endpts(SPCurve *const curve, double const endPos[2])
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);
225 static void
226 sp_conn_end_deleted(SPObject *, SPObject *const owner, unsigned const handle_ix)
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. */
237 void
238 sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix)
240     sp_conn_end_deleted(NULL, owner, handle_ix);
243 void
244 SPConnEnd::setAttacherHref(gchar const *value, SPPath* /*path*/)
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 */
375             }
377             if ( !validRef )
378             {
379                 ref.detach();
380                 g_free(href);
381                 href = NULL;
382             }
383             else
384                 if (!href)
385                     href = g_strdup(value);
387             g_strfreev(href_strarray);
388             g_strfreev(value_strarray);
389         }
390     }
393 void
394 sp_conn_end_href_changed(SPObject */*old_ref*/, SPObject */*ref*/,
395                          SPConnEnd *connEndPtr, SPPath *const path, unsigned const handle_ix)
397     g_return_if_fail(connEndPtr != NULL);
398     SPConnEnd &connEnd = *connEndPtr;
399     connEnd._delete_connection.disconnect();
400     connEnd._transformed_connection.disconnect();
402     if (connEnd.href) {
403         SPObject *refobj = connEnd.ref.getObject();
404         if (refobj) {
405             connEnd._delete_connection
406                 = SP_OBJECT(refobj)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_conn_end_deleted),
407                                                               SP_OBJECT(path), handle_ix));
408             connEnd._transformed_connection
409                 = SP_ITEM(refobj)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_conn_end_shape_move),
410                                                                  path));
411         }
412     }
417 /*
418   Local Variables:
419   mode:c++
420   c-file-style:"stroustrup"
421   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
422   indent-tabs-mode:nil
423   fill-column:99
424   End:
425 */
426 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :