1 #define __SP_DESKTOP_SNAP_C__
3 /**
4 * \file snap.cpp
5 * \brief SnapManager class.
6 *
7 * Authors:
8 * Lauris Kaplinski <lauris@kaplinski.com>
9 * Frank Felfe <innerspace@iname.com>
10 * Carl Hetherington <inkscape@carlh.net>
11 *
12 * Copyright (C) 2006-2007 Johan Engelen <johan@shouraizou.nl>
13 * Copyright (C) 1999-2002 Authors
14 *
15 * Released under GNU GPL, read the file 'COPYING' for more information
16 */
18 #include "sp-namedview.h"
19 #include "snap.h"
20 #include <libnr/nr-point-fns.h>
21 #include <libnr/nr-scale-ops.h>
22 #include <libnr/nr-values.h>
24 #include "display/canvas-grid.h"
26 /**
27 * Construct a SnapManager for a SPNamedView.
28 *
29 * \param v `Owning' SPNamedView.
30 */
32 SnapManager::SnapManager(SPNamedView const *v) :
33 grid(v, 0),
34 guide(v, 0),
35 object(v, 0),
36 _named_view(v)
37 {
39 }
42 /**
43 * \return List of snappers that we use.
44 */
46 SnapManager::SnapperList SnapManager::getSnappers() const
47 {
48 SnapManager::SnapperList s;
49 s.push_back(&grid);
50 s.push_back(&guide);
51 s.push_back(&object);
53 //add new grid snappers that are active for this desktop
54 // SPDesktop* desktop = SP_ACTIVE_DESKTOP;
55 // if (desktop) {
57 for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
58 Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
59 s.push_back(grid->snapper);
60 }
62 // }
64 return s;
65 }
67 /**
68 * \return true if one of the snappers will try to snap something.
69 */
71 bool SnapManager::willSnapSomething() const
72 {
73 SnapperList const s = getSnappers();
74 SnapperList::const_iterator i = s.begin();
75 while (i != s.end() && (*i)->willSnapSomething() == false) {
76 i++;
77 }
79 return (i != s.end());
80 }
83 /**
84 * Try to snap a point to any interested snappers.
85 *
86 * \param t Type of point.
87 * \param p Point.
88 * \param it Item to ignore when snapping.
89 * \return Snapped point.
90 */
92 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
93 NR::Point const &p,
94 SPItem const *it) const
96 {
97 std::list<SPItem const *> lit;
98 lit.push_back(it);
99 return freeSnap(t, p, lit);
100 }
103 /**
104 * Try to snap a point to any interested snappers.
105 *
106 * \param t Type of point.
107 * \param p Point.
108 * \param it List of items to ignore when snapping.
109 * \return Snapped point.
110 */
112 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
113 NR::Point const &p,
114 std::list<SPItem const *> const &it) const
115 {
116 Inkscape::SnappedPoint r(p, NR_HUGE);
118 SnapperList const snappers = getSnappers();
119 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
120 Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
121 if (s.getDistance() < r.getDistance()) {
122 r = s;
123 }
124 }
126 return r;
127 }
130 /**
131 * Try to snap a point to any interested snappers. A snap will only occur along
132 * a line described by a Inkscape::Snapper::ConstraintLine.
133 *
134 * \param t Type of point.
135 * \param p Point.
136 * \param c Constraint line.
137 * \param it Item to ignore when snapping.
138 * \return Snapped point.
139 */
141 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
142 NR::Point const &p,
143 Inkscape::Snapper::ConstraintLine const &c,
144 SPItem const *it) const
145 {
146 std::list<SPItem const *> lit;
147 lit.push_back(it);
148 return constrainedSnap(t, p, c, lit);
149 }
153 /**
154 * Try to snap a point to any interested snappers. A snap will only occur along
155 * a line described by a Inkscape::Snapper::ConstraintLine.
156 *
157 * \param t Type of point.
158 * \param p Point.
159 * \param c Constraint line.
160 * \param it List of items to ignore when snapping.
161 * \return Snapped point.
162 */
164 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
165 NR::Point const &p,
166 Inkscape::Snapper::ConstraintLine const &c,
167 std::list<SPItem const *> const &it) const
168 {
169 Inkscape::SnappedPoint r(p, NR_HUGE);
171 SnapperList const snappers = getSnappers();
172 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
173 Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, p, c, it);
174 if (s.getDistance() < r.getDistance()) {
175 r = s;
176 }
177 }
179 return r;
180 }
184 /**
185 * Main internal snapping method, which is called by the other, friendlier, public
186 * methods. It's a bit hairy as it has lots of parameters, but it saves on a lot
187 * of duplicated code.
188 *
189 * \param type Type of points being snapped.
190 * \param points List of points to snap.
191 * \param ignore List of items to ignore while snapping.
192 * \param constrained true if the snap is constrained.
193 * \param constraint Constraint line to use, if `constrained' is true, otherwise undefined.
194 * \param transformation_type Type of transformation to apply to points before trying to snap them.
195 * \param transformation Description of the transformation; details depend on the type.
196 * \param origin Origin of the transformation, if applicable.
197 * \param dim Dimension of the transformation, if applicable.
198 * \param uniform true if the transformation should be uniform, if applicable.
199 */
201 std::pair<NR::Point, bool> SnapManager::_snapTransformed(
202 Inkscape::Snapper::PointType type,
203 std::vector<NR::Point> const &points,
204 std::list<SPItem const *> const &ignore,
205 bool constrained,
206 Inkscape::Snapper::ConstraintLine const &constraint,
207 Transformation transformation_type,
208 NR::Point const &transformation,
209 NR::Point const &origin,
210 NR::Dim2 dim,
211 bool uniform) const
212 {
213 /* We have a list of points, which we are proposing to transform in some way. We need to see
214 ** if any of these points, when transformed, snap to anything. If they do, we return the
215 ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
216 */
218 /* Quick check to see if we have any snappers that are enabled */
219 if (willSnapSomething() == false) {
220 return std::make_pair(transformation, false);
221 }
223 /* The current best transformation */
224 NR::Point best_transformation = transformation;
226 /* The current best metric for the best transformation; lower is better, NR_HUGE
227 ** means that we haven't snapped anything.
228 */
229 double best_metric = NR_HUGE;
231 for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
233 /* Work out the transformed version of this point */
234 NR::Point transformed;
235 switch (transformation_type) {
236 case TRANSLATION:
237 transformed = *i + transformation;
238 break;
239 case SCALE:
240 transformed = ((*i - origin) * NR::scale(transformation[NR::X], transformation[NR::Y])) + origin;
241 break;
242 case STRETCH:
243 {
244 NR::scale s(1, 1);
245 if (uniform)
246 s[NR::X] = s[NR::Y] = transformation[dim];
247 else {
248 s[dim] = transformation[dim];
249 s[1 - dim] = 1;
250 }
251 transformed = ((*i - origin) * s) + origin;
252 break;
253 }
254 case SKEW:
255 transformed = *i;
256 transformed[dim] += transformation[dim] * ((*i)[1 - dim] - origin[1 - dim]);
257 break;
258 default:
259 g_assert_not_reached();
260 }
262 /* Snap it */
263 Inkscape::SnappedPoint const snapped = constrained ?
264 constrainedSnap(type, transformed, constraint, ignore) : freeSnap(type, transformed, ignore);
266 if (snapped.getDistance() < NR_HUGE) {
267 /* We snapped. Find the transformation that describes where the snapped point has
268 ** ended up, and also the metric for this transformation.
269 */
270 NR::Point result;
271 NR::Coord metric;
272 switch (transformation_type) {
273 case TRANSLATION:
274 result = snapped.getPoint() - *i;
275 metric = NR::L2(result);
276 break;
277 case SCALE:
278 {
279 NR::Point const a = (snapped.getPoint() - origin);
280 NR::Point const b = (*i - origin);
281 result = NR::Point(a[NR::X] / b[NR::X], a[NR::Y] / b[NR::Y]);
282 metric = std::abs(NR::L2(result) - NR::L2(transformation));
283 break;
284 }
285 case STRETCH:
286 {
287 for (int j = 0; j < 2; j++) {
288 if (uniform || j == dim) {
289 result[j] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]);
290 } else {
291 result[j] = 1;
292 }
293 }
294 metric = std::abs(result[dim] - transformation[dim]);
295 break;
296 }
297 case SKEW:
298 result[dim] = (snapped.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]);
299 metric = std::abs(result[dim] - transformation[dim]);
300 break;
301 default:
302 g_assert_not_reached();
303 }
305 /* Note it if it's the best so far */
306 if (metric < best_metric && metric != 0) {
307 best_transformation = result;
308 best_metric = metric;
309 }
310 }
311 }
313 // Using " < 1e6" instead of " < NR::HUGE" for catching some rounding errors
314 // These rounding errors might be caused by NRRects, see bug #1584301
315 return std::make_pair(best_transformation, best_metric < 1e6);
316 }
319 /**
320 * Try to snap a list of points to any interested snappers after they have undergone
321 * a translation.
322 *
323 * \param t Type of points.
324 * \param p Points.
325 * \param it List of items to ignore when snapping.
326 * \param tr Proposed translation.
327 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
328 */
330 std::pair<NR::Point, bool> SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t,
331 std::vector<NR::Point> const &p,
332 std::list<SPItem const *> const &it,
333 NR::Point const &tr) const
334 {
335 return _snapTransformed(
336 t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false
337 );
338 }
341 /**
342 * Try to snap a list of points to any interested snappers after they have undergone a
343 * translation. A snap will only occur along a line described by a
344 * Inkscape::Snapper::ConstraintLine.
345 *
346 * \param t Type of points.
347 * \param p Points.
348 * \param it List of items to ignore when snapping.
349 * \param c Constraint line.
350 * \param tr Proposed translation.
351 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
352 */
354 std::pair<NR::Point, bool> SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t,
355 std::vector<NR::Point> const &p,
356 std::list<SPItem const *> const &it,
357 Inkscape::Snapper::ConstraintLine const &c,
358 NR::Point const &tr) const
359 {
360 return _snapTransformed(
361 t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false
362 );
363 }
366 /**
367 * Try to snap a list of points to any interested snappers after they have undergone
368 * a scale.
369 *
370 * \param t Type of points.
371 * \param p Points.
372 * \param it List of items to ignore when snapping.
373 * \param s Proposed scale.
374 * \param o Origin of proposed scale.
375 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
376 */
378 std::pair<NR::scale, bool> SnapManager::freeSnapScale(Inkscape::Snapper::PointType t,
379 std::vector<NR::Point> const &p,
380 std::list<SPItem const *> const &it,
381 NR::scale const &s,
382 NR::Point const &o) const
383 {
384 return _snapTransformed(
385 t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
386 );
387 }
390 /**
391 * Try to snap a list of points to any interested snappers after they have undergone
392 * a scale. A snap will only occur along a line described by a
393 * Inkscape::Snapper::ConstraintLine.
394 *
395 * \param t Type of points.
396 * \param p Points.
397 * \param it List of items to ignore when snapping.
398 * \param s Proposed scale.
399 * \param o Origin of proposed scale.
400 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
401 */
403 std::pair<NR::scale, bool> SnapManager::constrainedSnapScale(Inkscape::Snapper::PointType t,
404 std::vector<NR::Point> const &p,
405 std::list<SPItem const *> const &it,
406 Inkscape::Snapper::ConstraintLine const &c,
407 NR::scale const &s,
408 NR::Point const &o) const
409 {
410 return _snapTransformed(
411 t, p, it, true, c, SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
412 );
413 }
416 /**
417 * Try to snap a list of points to any interested snappers after they have undergone
418 * a stretch.
419 *
420 * \param t Type of points.
421 * \param p Points.
422 * \param it List of items to ignore when snapping.
423 * \param s Proposed stretch.
424 * \param o Origin of proposed stretch.
425 * \param d Dimension in which to apply proposed stretch.
426 * \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
427 * \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
428 */
430 std::pair<NR::Coord, bool> SnapManager::freeSnapStretch(Inkscape::Snapper::PointType t,
431 std::vector<NR::Point> const &p,
432 std::list<SPItem const *> const &it,
433 NR::Coord const &s,
434 NR::Point const &o,
435 NR::Dim2 d,
436 bool u) const
437 {
438 std::pair<NR::Point, bool> const r = _snapTransformed(
439 t, p, it, false, NR::Point(), STRETCH, NR::Point(s, s), o, d, u
440 );
442 return std::make_pair(r.first[d], r.second);
443 }
446 /**
447 * Try to snap a list of points to any interested snappers after they have undergone
448 * a skew.
449 *
450 * \param t Type of points.
451 * \param p Points.
452 * \param it List of items to ignore when snapping.
453 * \param s Proposed skew.
454 * \param o Origin of proposed skew.
455 * \param d Dimension in which to apply proposed skew.
456 * \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
457 */
459 std::pair<NR::Coord, bool> SnapManager::freeSnapSkew(Inkscape::Snapper::PointType t,
460 std::vector<NR::Point> const &p,
461 std::list<SPItem const *> const &it,
462 NR::Coord const &s,
463 NR::Point const &o,
464 NR::Dim2 d) const
465 {
466 std::pair<NR::Point, bool> const r = _snapTransformed(
467 t, p, it, false, NR::Point(), SKEW, NR::Point(s, s), o, d, false
468 );
470 return std::make_pair(r.first[d], r.second);
471 }
473 /*
474 Local Variables:
475 mode:c++
476 c-file-style:"stroustrup"
477 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
478 indent-tabs-mode:nil
479 fill-column:99
480 End:
481 */
482 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :