Code

work a bit on snapindicator: added switch statement for different types
[inkscape.git] / src / sp-conn-end.cpp
2 #include <cstring>
3 #include <string>
5 #include "display/curve.h"
6 #include "libnr/nr-matrix-fns.h"
7 #include "xml/repr.h"
8 #include "sp-conn-end.h"
9 #include "sp-path.h"
10 #include "uri.h"
11 #include "document.h"
14 static void change_endpts(SPCurve *const curve, Geom::Point const h2endPt[2]);
15 static Geom::Point calc_bbox_conn_pt(Geom::Rect const &bbox, Geom::Point const &p);
16 static double signed_one(double const x);
18 SPConnEnd::SPConnEnd(SPObject *const owner) :
19     ref(owner),
20     href(NULL),
21     _changed_connection(),
22     _delete_connection(),
23     _transformed_connection()
24 {
25 }
27 static SPObject const *
28 get_nearest_common_ancestor(SPObject const *const obj, SPItem const *const objs[2]) {
29     SPObject const *anc_sofar = obj;
30     for (unsigned i = 0; i < 2; ++i) {
31         if ( objs[i] != NULL ) {
32             anc_sofar = anc_sofar->nearestCommonAncestor(objs[i]);
33         }
34     }
35     return anc_sofar;
36 }
38 static void
39 sp_conn_end_move_compensate(Geom::Matrix const */*mp*/, SPItem */*moved_item*/,
40                             SPPath *const path,
41                             bool const updatePathRepr = true)
42 {
43     // TODO: SPItem::getBounds gives the wrong result for some objects
44     //       that have internal representations that are updated later
45     //       by the sp_*_update functions, e.g., text.
46     sp_document_ensure_up_to_date(path->document);
48     // Get the new route around obstacles.
49     path->connEndPair.reroutePath();
51     SPItem *h2attItem[2];
52     path->connEndPair.getAttachedItems(h2attItem);
53     if ( !h2attItem[0] && !h2attItem[1] ) {
54         if (updatePathRepr) {
55             path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
56             path->updateRepr();
57         }
58         return;
59     }
61     SPItem const *const path_item = SP_ITEM(path);
62     SPObject const *const ancestor = get_nearest_common_ancestor(path_item, h2attItem);
63     Geom::Matrix const path2anc(i2anc_affine(path_item, ancestor));
65     if (h2attItem[0] != NULL && h2attItem[1] != NULL) {
66         /* Initial end-points: centre of attached object. */
67         Geom::Point h2endPt_icoordsys[2];
68         Geom::Matrix h2i2anc[2];
69         Geom::Rect h2bbox_icoordsys[2];
70         Geom::Point last_seg_endPt[2] = {
71             *(path->curve->second_point()),
72             *(path->curve->penultimate_point())
73         };
74         for (unsigned h = 0; h < 2; ++h) {
75             Geom::OptRect bbox = h2attItem[h]->getBounds(Geom::identity());
76             if (!bbox) {
77                 if (updatePathRepr) {
78                     path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
79                     path->updateRepr();
80                 }
81                 return;
82             }
83             h2bbox_icoordsys[h] = *bbox;
84             h2i2anc[h] = i2anc_affine(h2attItem[h], ancestor);
85             h2endPt_icoordsys[h] = h2bbox_icoordsys[h].midpoint();
86         }
88         // For each attached object, change the corresponding point to be
89         // on the edge of the bbox.
90         Geom::Point h2endPt_pcoordsys[2];
91         for (unsigned h = 0; h < 2; ++h) {
92             h2endPt_icoordsys[h] = calc_bbox_conn_pt(h2bbox_icoordsys[h],
93                                          ( last_seg_endPt[h] * h2i2anc[h].inverse() ));
94             h2endPt_pcoordsys[h] = h2endPt_icoordsys[h] * h2i2anc[h] * path2anc.inverse();
95         }
96         change_endpts(path->curve, h2endPt_pcoordsys);
97     } else {
98         // We leave the unattached endpoint where it is, and adjust the
99         // position of the attached endpoint to be on the edge of the bbox.
100         unsigned ind;
101         Geom::Point other_endpt;
102         Geom::Point last_seg_pt;
103         if (h2attItem[0] != NULL) {
104             other_endpt = *(path->curve->last_point());
105             last_seg_pt = *(path->curve->second_point());
106             ind = 0;
107         }
108         else {
109             other_endpt = *(path->curve->first_point());
110             last_seg_pt = *(path->curve->penultimate_point());
111             ind = 1;
112         }
113         Geom::Point h2endPt_icoordsys[2];
114         Geom::Matrix h2i2anc;
116         Geom::Rect otherpt_rect = Geom::Rect(other_endpt, other_endpt);
117         Geom::Rect h2bbox_icoordsys[2] = { otherpt_rect, otherpt_rect };
118         Geom::OptRect bbox = h2attItem[ind]->getBounds(Geom::identity());
119         if (!bbox) {
120             if (updatePathRepr) {
121                 path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
122                 path->updateRepr();
123             }
124             return;
125         }
127         h2bbox_icoordsys[ind] = *bbox;
128         h2i2anc = i2anc_affine(h2attItem[ind], ancestor);
129         h2endPt_icoordsys[ind] = h2bbox_icoordsys[ind].midpoint();
131         h2endPt_icoordsys[!ind] = other_endpt;
133         // For the attached object, change the corresponding point to be
134         // on the edge of the bbox.
135         Geom::Point h2endPt_pcoordsys[2];
136         h2endPt_icoordsys[ind] = calc_bbox_conn_pt(h2bbox_icoordsys[ind],
137                                                  ( last_seg_pt * h2i2anc.inverse() ));
138         h2endPt_pcoordsys[ind] = h2endPt_icoordsys[ind] * h2i2anc * path2anc.inverse();
140         // Leave the other where it is.
141         h2endPt_pcoordsys[!ind] = other_endpt;
143         change_endpts(path->curve, h2endPt_pcoordsys);
144     }
145     if (updatePathRepr) {
146         path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
147         path->updateRepr();
148     }
151 // TODO: This triggering of makeInvalidPath could be cleaned up to be
152 //       another option passed to move_compensate.
153 static void
154 sp_conn_end_shape_move_compensate(Geom::Matrix const *mp, SPItem *moved_item,
155                             SPPath *const path)
157     if (path->connEndPair.isAutoRoutingConn()) {
158         path->connEndPair.makePathInvalid();
159     }
160     sp_conn_end_move_compensate(mp, moved_item, path);
164 void
165 sp_conn_adjust_invalid_path(SPPath *const path)
167     sp_conn_end_move_compensate(NULL, NULL, path);
170 void
171 sp_conn_adjust_path(SPPath *const path)
173     if (path->connEndPair.isAutoRoutingConn()) {
174         path->connEndPair.makePathInvalid();
175     }
176     // Don't update the path repr or else connector dragging is slowed by
177     // constant update of values to the xml editor, and each step is also
178     // needlessly remembered by undo/redo.
179     bool const updatePathRepr = false;
180     sp_conn_end_move_compensate(NULL, NULL, path, updatePathRepr);
183 static Geom::Point
184 calc_bbox_conn_pt(Geom::Rect const &bbox, Geom::Point const &p)
186     using Geom::X;
187     using Geom::Y;
188     Geom::Point const ctr(bbox.midpoint());
189     Geom::Point const lengths(bbox.dimensions());
190     if ( ctr == p ) {
191         /* Arbitrarily choose centre of right edge. */
192         return Geom::Point(ctr[X] + .5 * lengths[X],
193                          ctr[Y]);
194     }
195     Geom::Point const cp( p - ctr );
196     Geom::Dim2 const edgeDim = ( ( fabs(lengths[Y] * cp[X]) <
197                                  fabs(lengths[X] * cp[Y])  )
198                                ? Y
199                                : X );
200     Geom::Dim2 const otherDim = (Geom::Dim2) !edgeDim;
201     Geom::Point offset;
202     offset[edgeDim] = (signed_one(cp[edgeDim])
203                        * lengths[edgeDim]);
204     offset[otherDim] = (lengths[edgeDim]
205                         * cp[otherDim]
206                         / fabs(cp[edgeDim]));
207     g_assert((offset[otherDim] >= 0) == (cp[otherDim] >= 0));
208 #ifndef NDEBUG
209     for (unsigned d = 0; d < 2; ++d) {
210         g_assert(fabs(offset[d]) <= lengths[d] + .125);
211     }
212 #endif
213     return ctr + .5 * offset;
216 static double signed_one(double const x)
218     return (x < 0
219             ? -1.
220             : 1.);
223 static void
224 change_endpts(SPCurve *const curve, Geom::Point const h2endPt[2])
226 #if 0
227     curve->reset();
228     curve->moveto(h2endPt[0]);
229     curve->lineto(h2endPt[1]);
230 #else
231     curve->move_endpoints(h2endPt[0], h2endPt[1]);
232 #endif
235 static void
236 sp_conn_end_deleted(SPObject *, SPObject *const owner, unsigned const handle_ix)
238     // todo: The first argument is the deleted object, or just NULL if
239     //       called by sp_conn_end_detach.
240     g_return_if_fail(handle_ix < 2);
241     char const *const attr_str[] = {"inkscape:connection-start",
242                                     "inkscape:connection-end"};
243     SP_OBJECT_REPR(owner)->setAttribute(attr_str[handle_ix], NULL);
244     /* I believe this will trigger sp_conn_end_href_changed. */
247 void
248 sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix)
250     sp_conn_end_deleted(NULL, owner, handle_ix);
253 void
254 SPConnEnd::setAttacherHref(gchar const *value)
256     if ( value && href && ( strcmp(value, href) == 0 ) ) {
257         /* No change, do nothing. */
258     } else {
259         g_free(href);
260         href = NULL;
261         if (value) {
262             // First, set the href field, because sp_conn_end_href_changed will need it.
263             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                 ref.detach();
274             }
275         } else {
276             ref.detach();
277         }
278     }
281 void
282 sp_conn_end_href_changed(SPObject */*old_ref*/, SPObject */*ref*/,
283                          SPConnEnd *connEndPtr, SPPath *const path, unsigned const handle_ix)
285     g_return_if_fail(connEndPtr != NULL);
286     SPConnEnd &connEnd = *connEndPtr;
287     connEnd._delete_connection.disconnect();
288     connEnd._transformed_connection.disconnect();
290     if (connEnd.href) {
291         SPObject *refobj = connEnd.ref.getObject();
292         if (refobj) {
293             connEnd._delete_connection
294                 = SP_OBJECT(refobj)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_conn_end_deleted),
295                                                               SP_OBJECT(path), handle_ix));
296             connEnd._transformed_connection
297                 = SP_ITEM(refobj)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_conn_end_shape_move_compensate),
298                                                                  path));
299         }
300     }
305 /*
306   Local Variables:
307   mode:c++
308   c-file-style:"stroustrup"
309   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
310   indent-tabs-mode:nil
311   fill-column:99
312   End:
313 */
314 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :