1 /**
2 * \file object-snapper.cpp
3 * \brief Snapping things to objects.
4 *
5 * Authors:
6 * Carl Hetherington <inkscape@carlh.net>
7 *
8 * Copyright (C) 2005 Authors
9 *
10 * Released under GNU GPL, read the file 'COPYING' for more information
11 */
13 #include "libnr/n-art-bpath.h"
14 #include "libnr/nr-rect-ops.h"
15 #include "document.h"
16 #include "sp-namedview.h"
17 #include "sp-path.h"
18 #include "sp-image.h"
19 #include "sp-item-group.h"
20 #include "sp-item.h"
21 #include "sp-use.h"
22 #include "display/curve.h"
23 #include "desktop.h"
24 #include "inkscape.h"
25 #include "splivarot.h"
26 #include "prefs-utils.h"
29 Inkscape::ObjectSnapper::ObjectSnapper(SPNamedView const *nv, NR::Coord const d)
30 : Snapper(nv, d), _snap_to_itemnode(true), _snap_to_itempath(true),
31 _snap_to_bboxnode(true), _snap_to_bboxpath(true), _strict_snapping(true)
32 {
34 }
37 /**
38 * \param p Point we are trying to snap (desktop coordinates)
39 */
41 void Inkscape::ObjectSnapper::_findCandidates(std::list<SPItem*>& c,
42 SPObject* r,
43 std::list<SPItem const *> const &it,
44 NR::Point const &p) const
45 {
46 if (ThisSnapperMightSnap()) {
47 SPDesktop const *desktop = SP_ACTIVE_DESKTOP;
48 for (SPObject* o = sp_object_first_child(r); o != NULL; o = SP_OBJECT_NEXT(o)) {
49 if (SP_IS_ITEM(o) && !SP_ITEM(o)->isLocked() && !desktop->itemIsHidden(SP_ITEM(o))) {
51 /* See if this item is on the ignore list */
52 std::list<SPItem const *>::const_iterator i = it.begin();
53 while (i != it.end() && *i != o) {
54 i++;
55 }
57 if (i == it.end()) {
58 /* See if the item is within range */
59 if (SP_IS_GROUP(o)) {
60 _findCandidates(c, o, it, p);
61 } else {
62 NR::Maybe<NR::Rect> b = sp_item_bbox_desktop(SP_ITEM(o));
63 if ( b && NR::expand(*b, -getDistance()).contains(p) ) {
64 c.push_back(SP_ITEM(o));
65 }
66 }
67 }
69 }
70 }
71 }
72 }
75 void Inkscape::ObjectSnapper::_snapNodes(Inkscape::Snapper::PointType const &t,
76 Inkscape::SnappedPoint &s,
77 NR::Point const &p,
78 std::list<SPItem*> const &cand) const
79 {
80 /* FIXME: this seems like a hack. Perhaps Snappers should be
81 ** in SPDesktop rather than SPNamedView?
82 */
83 SPDesktop const *desktop = SP_ACTIVE_DESKTOP;
85 // Determine the type of bounding box we should snap to
86 SPItem::BBoxType bbox_type = SPItem::GEOMETRIC_BBOX;
87 if (_snap_to_bboxnode) {
88 gchar const *prefs_bbox = prefs_get_string_attribute("tools.select", "bounding_box");
89 bbox_type = (prefs_bbox != NULL && strcmp(prefs_bbox, "geometric")==0)? SPItem::GEOMETRIC_BBOX : SPItem::APPROXIMATE_BBOX;
90 }
92 bool p_is_a_node = t & Inkscape::Snapper::SNAPPOINT_NODE;
94 for (std::list<SPItem*>::const_iterator i = cand.begin(); i != cand.end(); i++) {
96 NR::Matrix i2doc(NR::identity());
97 SPItem *root_item = NULL;
98 if (SP_IS_USE(*i)) {
99 i2doc = sp_use_get_root_transform(SP_USE(*i));
100 root_item = sp_use_root(SP_USE(*i));
101 } else {
102 i2doc = sp_item_i2doc_affine(*i);
103 root_item = *i;
104 }
106 SPCurve *curve = NULL;
108 if (SP_IS_SHAPE(root_item)) {
109 SPShape const *sh = SP_SHAPE(root_item);
110 curve = sh->curve;
111 } else if (SP_IS_IMAGE(root_item)) {
112 SPImage const *im = SP_IMAGE(root_item);
113 curve = im->curve;
114 }
116 std::list<NR::Point> points_to_snap_to;
118 //Collect all nodes so we can snap to them
119 if (_snap_to_itemnode) {
120 if (!(_strict_snapping && !p_is_a_node)) {
121 if (curve) {
122 int j = 0;
123 while (SP_CURVE_BPATH(curve)[j].code != NR_END) {
124 /* Get this node in desktop coordinates */
125 NArtBpath const &bp = SP_CURVE_BPATH(curve)[j];
126 points_to_snap_to.push_back(desktop->doc2dt(bp.c(3) * i2doc));
127 j++;
128 }
129 }
130 }
131 }
133 //Collect the bounding box's corners so we can snap to them
134 if (_snap_to_bboxnode) {
135 if (!(_strict_snapping && p_is_a_node)) {
136 NR::Maybe<NR::Rect> b = sp_item_bbox_desktop(root_item, bbox_type);
137 if (b) {
138 for ( unsigned k = 0 ; k < 4 ; k++ ) {
139 points_to_snap_to.push_back(b->corner(k));
140 }
141 }
142 }
143 }
145 //Do the snapping, using all the nodes and corners collected above
146 for (std::list<NR::Point>::const_iterator k = points_to_snap_to.begin(); k != points_to_snap_to.end(); k++) {
147 /* Try to snap to this node of the path */
148 NR::Coord const dist = NR::L2(*k - p);
149 if (dist < getDistance() && dist < s.getDistance()) {
150 s = SnappedPoint(*k, dist);
151 }
152 }
153 }
154 }
157 void Inkscape::ObjectSnapper::_snapPaths(Inkscape::Snapper::PointType const &t,
158 Inkscape::SnappedPoint &s,
159 NR::Point const &p,
160 std::list<SPItem*> const &cand) const
161 {
162 /* FIXME: this seems like a hack. Perhaps Snappers should be
163 ** in SPDesktop rather than SPNamedView?
164 */
165 SPDesktop const *desktop = SP_ACTIVE_DESKTOP;
167 NR::Point const p_doc = desktop->dt2doc(p);
169 // Determine the type of bounding box we should snap to
170 SPItem::BBoxType bbox_type = SPItem::GEOMETRIC_BBOX;
171 if (_snap_to_bboxpath) {
172 gchar const *prefs_bbox = prefs_get_string_attribute("tools.select", "bounding_box");
173 bbox_type = (prefs_bbox != NULL && strcmp(prefs_bbox, "geometric")==0)? SPItem::GEOMETRIC_BBOX : SPItem::APPROXIMATE_BBOX;
174 }
176 bool p_is_a_node = t & Inkscape::Snapper::SNAPPOINT_NODE;
178 for (std::list<SPItem*>::const_iterator i = cand.begin(); i != cand.end(); i++) {
180 /* Transform the requested snap point to this item's coordinates */
181 NR::Matrix i2doc(NR::identity());
182 SPItem *root_item = NULL;
183 /* We might have a clone at hand, so make sure we get the root item */
184 if (SP_IS_USE(*i)) {
185 i2doc = sp_use_get_root_transform(SP_USE(*i));
186 root_item = sp_use_root(SP_USE(*i));
187 } else {
188 i2doc = sp_item_i2doc_affine(*i);
189 root_item = *i;
190 }
192 //Build a list of all paths considered for snapping to
193 std::list<Path*> paths_to_snap_to;
195 //Add the item's path to snap to
196 if (_snap_to_itempath) {
197 if (!(_strict_snapping && !p_is_a_node)) {
198 paths_to_snap_to.push_back(Path_for_item(root_item, true, true));
199 }
200 }
202 //Add the item's bounding box to snap to
203 if (_snap_to_bboxpath) {
204 if (!(_strict_snapping && p_is_a_node)) {
205 //This will get ugly... rect -> curve -> bpath
206 NRRect rect;
207 sp_item_invoke_bbox(root_item, &rect, i2doc, TRUE, bbox_type);
208 NR::Maybe<NR::Rect> bbox = rect.upgrade();
209 SPCurve *curve = sp_curve_new_from_rect(bbox);
210 NArtBpath *bpath = SP_CURVE_BPATH(curve);
211 Path *path = bpath_to_Path(bpath);
212 paths_to_snap_to.push_back(path);
213 delete curve;
214 delete bpath;
215 }
216 }
218 //Now we can finally do the real snapping, using the paths collected above
219 for (std::list<Path*>::const_iterator k = paths_to_snap_to.begin(); k != paths_to_snap_to.end(); k++) {
220 if (*k) {
221 (*k)->ConvertWithBackData(0.01);
223 /* Look for the nearest position on this SPItem to our snap point */
224 NR::Maybe<Path::cut_position> const o = get_nearest_position_on_Path(*k, p_doc);
225 if (o && o->t >= 0 && o->t <= 1) {
227 /* Convert the nearest point back to desktop coordinates */
228 NR::Point const o_it = get_point_on_Path(*k, o->piece, o->t);
229 NR::Point const o_dt = desktop->doc2dt(o_it);
231 NR::Coord const dist = NR::L2(o_dt - p);
232 if (dist < getDistance() && dist < s.getDistance()) {
233 s = SnappedPoint(o_dt, dist);
234 }
235 }
237 delete *k;
238 }
239 }
240 }
241 }
244 Inkscape::SnappedPoint Inkscape::ObjectSnapper::_doFreeSnap(Inkscape::Snapper::PointType const &t,
245 NR::Point const &p,
246 std::list<SPItem const *> const &it) const
247 {
248 if ( NULL == _named_view ) {
249 return SnappedPoint(p, NR_HUGE);
250 }
252 /* Get a list of all the SPItems that we will try to snap to */
253 std::list<SPItem*> cand;
254 _findCandidates(cand, sp_document_root(_named_view->document), it, p);
256 SnappedPoint s(p, NR_HUGE);
258 if (_snap_to_itemnode || _snap_to_bboxnode) {
259 _snapNodes(t, s, p, cand);
260 }
261 if (_snap_to_itempath || _snap_to_bboxpath) {
262 _snapPaths(t, s, p, cand);
263 }
265 return s;
266 }
270 Inkscape::SnappedPoint Inkscape::ObjectSnapper::_doConstrainedSnap(Inkscape::Snapper::PointType const &t,
271 NR::Point const &p,
272 ConstraintLine const &c,
273 std::list<SPItem const *> const &it) const
274 {
275 /* FIXME: this needs implementing properly; I think we have to do the
276 ** intersection of c with the objects.
277 */
278 return _doFreeSnap(t, p, it);
279 }
281 /**
282 * \return true if this Snapper will snap at least one kind of point.
283 */
284 bool Inkscape::ObjectSnapper::ThisSnapperMightSnap() const
285 {
286 bool snap_to_something = _snap_to_itempath || _snap_to_itemnode || _snap_to_bboxpath || _snap_to_bboxnode;
287 return (_enabled && _snap_from != 0 && snap_to_something);
288 }
291 /*
292 Local Variables:
293 mode:c++
294 c-file-style:"stroustrup"
295 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
296 indent-tabs-mode:nil
297 fill-column:99
298 End:
299 */
300 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :