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 }
149 }
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)
156 {
157 if (path->connEndPair.isAutoRoutingConn()) {
158 path->connEndPair.makePathInvalid();
159 }
160 sp_conn_end_move_compensate(mp, moved_item, path);
161 }
164 void
165 sp_conn_adjust_invalid_path(SPPath *const path)
166 {
167 sp_conn_end_move_compensate(NULL, NULL, path);
168 }
170 void
171 sp_conn_adjust_path(SPPath *const path)
172 {
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);
181 }
183 static Geom::Point
184 calc_bbox_conn_pt(Geom::Rect const &bbox, Geom::Point const &p)
185 {
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;
214 }
216 static double signed_one(double const x)
217 {
218 return (x < 0
219 ? -1.
220 : 1.);
221 }
223 static void
224 change_endpts(SPCurve *const curve, Geom::Point const h2endPt[2])
225 {
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
233 }
235 static void
236 sp_conn_end_deleted(SPObject *, SPObject *const owner, unsigned const handle_ix)
237 {
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. */
245 }
247 void
248 sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix)
249 {
250 sp_conn_end_deleted(NULL, owner, handle_ix);
251 }
253 void
254 SPConnEnd::setAttacherHref(gchar const *value)
255 {
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 }
279 }
281 void
282 sp_conn_end_href_changed(SPObject */*old_ref*/, SPObject */*ref*/,
283 SPConnEnd *connEndPtr, SPPath *const path, unsigned const handle_ix)
284 {
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 }
301 }
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 :