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 #include "inkscape.h"
27 #include "desktop.h"
29 /**
30 * Construct a SnapManager for a SPNamedView.
31 *
32 * \param v `Owning' SPNamedView.
33 */
35 SnapManager::SnapManager(SPNamedView const *v) :
36 guide(v, 0),
37 object(v, 0),
38 _named_view(v)
39 {
41 }
44 /**
45 * \return List of snappers that we use.
46 */
47 SnapManager::SnapperList
48 SnapManager::getSnappers() const
49 {
50 SnapManager::SnapperList s;
51 s.push_back(&guide);
52 s.push_back(&object);
54 SnapManager::SnapperList gs = getGridSnappers();
55 s.splice(s.begin(), gs);
57 return s;
58 }
60 /**
61 * \return List of gridsnappers that we use.
62 */
63 SnapManager::SnapperList
64 SnapManager::getGridSnappers() const
65 {
66 SnapperList s;
68 //FIXME: this code should actually do this: add new grid snappers that are active for this desktop. now it just adds all gridsnappers
69 SPDesktop* desktop = SP_ACTIVE_DESKTOP;
70 if (desktop && desktop->gridsEnabled()) {
71 for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
72 Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
73 s.push_back(grid->snapper);
74 }
75 }
77 return s;
78 }
80 /**
81 * \return true if one of the snappers will try to snap something.
82 */
84 bool SnapManager::willSnapSomething() const
85 {
86 SnapperList const s = getSnappers();
87 SnapperList::const_iterator i = s.begin();
88 while (i != s.end() && (*i)->willSnapSomething() == false) {
89 i++;
90 }
92 return (i != s.end());
93 }
96 /**
97 * Try to snap a point to any interested snappers.
98 *
99 * \param t Type of point.
100 * \param p Point.
101 * \param it Item to ignore when snapping.
102 * \return Snapped point.
103 */
105 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
106 NR::Point const &p,
107 SPItem const *it) const
109 {
110 std::list<SPItem const *> lit;
111 lit.push_back(it);
112 return freeSnap(t, p, lit);
113 }
116 /**
117 * Try to snap a point to any interested snappers.
118 *
119 * \param t Type of point.
120 * \param p Point.
121 * \param it List of items to ignore when snapping.
122 * \return Snapped point.
123 */
125 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
126 NR::Point const &p,
127 std::list<SPItem const *> const &it) const
128 {
129 SnapperList const snappers = getSnappers();
131 return freeSnap(t, p, it, snappers);
132 }
134 /**
135 * Try to snap a point to any of the specified snappers.
136 *
137 * \param t Type of point.
138 * \param p Point.
139 * \param it List of items to ignore when snapping.
140 * \param snappers List of snappers to try to snap to
141 * \return Snapped point.
142 */
144 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
145 NR::Point const &p,
146 std::list<SPItem const *> const &it,
147 SnapperList const &snappers) const
148 {
149 Inkscape::SnappedPoint r(p, NR_HUGE);
151 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
152 Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
153 if (s.getDistance() < r.getDistance()) {
154 r = s;
155 }
156 }
158 return r;
159 }
161 /**
162 * Try to snap a point to any of the specified snappers. Snap always, ignoring the snap-distance
163 *
164 * \param t Type of point.
165 * \param p Point.
166 * \param it Item to ignore when snapping.
167 * \param snappers List of snappers to try to snap to
168 * \return Snapped point.
169 */
171 Inkscape::SnappedPoint
172 SnapManager::freeSnapAlways( Inkscape::Snapper::PointType t,
173 NR::Point const &p,
174 SPItem const *it,
175 SnapperList &snappers )
176 {
177 std::list<SPItem const *> lit;
178 lit.push_back(it);
179 return freeSnapAlways(t, p, lit, snappers);
180 }
182 /**
183 * Try to snap a point to any of the specified snappers. Snap always, ignoring the snap-distance
184 *
185 * \param t Type of point.
186 * \param p Point.
187 * \param it List of items to ignore when snapping.
188 * \param snappers List of snappers to try to snap to
189 * \return Snapped point.
190 */
192 Inkscape::SnappedPoint
193 SnapManager::freeSnapAlways( Inkscape::Snapper::PointType t,
194 NR::Point const &p,
195 std::list<SPItem const *> const &it,
196 SnapperList &snappers )
197 {
198 Inkscape::SnappedPoint r(p, NR_HUGE);
200 for (SnapperList::iterator i = snappers.begin(); i != snappers.end(); i++) {
201 gdouble const curr_gridsnap = (*i)->getDistance();
202 const_cast<Inkscape::Snapper*> (*i)->setDistance(NR_HUGE);
203 Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
204 const_cast<Inkscape::Snapper*> (*i)->setDistance(curr_gridsnap);
206 if (s.getDistance() < r.getDistance()) {
207 r = s;
208 }
209 }
211 return r;
212 }
216 /**
217 * Try to snap a point to any interested snappers. A snap will only occur along
218 * a line described by a Inkscape::Snapper::ConstraintLine.
219 *
220 * \param t Type of point.
221 * \param p Point.
222 * \param c Constraint line.
223 * \param it Item to ignore when snapping.
224 * \return Snapped point.
225 */
227 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
228 NR::Point const &p,
229 Inkscape::Snapper::ConstraintLine const &c,
230 SPItem const *it) const
231 {
232 std::list<SPItem const *> lit;
233 lit.push_back(it);
234 return constrainedSnap(t, p, c, lit);
235 }
239 /**
240 * Try to snap a point to any interested snappers. A snap will only occur along
241 * a line described by a Inkscape::Snapper::ConstraintLine.
242 *
243 * \param t Type of point.
244 * \param p Point.
245 * \param c Constraint line.
246 * \param it List of items to ignore when snapping.
247 * \return Snapped point.
248 */
250 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
251 NR::Point const &p,
252 Inkscape::Snapper::ConstraintLine const &c,
253 std::list<SPItem const *> const &it) const
254 {
255 Inkscape::SnappedPoint r(p, NR_HUGE);
257 SnapperList const snappers = getSnappers();
258 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
259 Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, p, c, it);
260 if (s.getDistance() < r.getDistance()) {
261 r = s;
262 }
263 }
265 return r;
266 }
270 /**
271 * Main internal snapping method, which is called by the other, friendlier, public
272 * methods. It's a bit hairy as it has lots of parameters, but it saves on a lot
273 * of duplicated code.
274 *
275 * \param type Type of points being snapped.
276 * \param points List of points to snap.
277 * \param ignore List of items to ignore while snapping.
278 * \param constrained true if the snap is constrained.
279 * \param constraint Constraint line to use, if `constrained' is true, otherwise undefined.
280 * \param transformation_type Type of transformation to apply to points before trying to snap them.
281 * \param transformation Description of the transformation; details depend on the type.
282 * \param origin Origin of the transformation, if applicable.
283 * \param dim Dimension of the transformation, if applicable.
284 * \param uniform true if the transformation should be uniform, if applicable.
285 */
287 std::pair<NR::Point, bool> SnapManager::_snapTransformed(
288 Inkscape::Snapper::PointType type,
289 std::vector<NR::Point> const &points,
290 std::list<SPItem const *> const &ignore,
291 bool constrained,
292 Inkscape::Snapper::ConstraintLine const &constraint,
293 Transformation transformation_type,
294 NR::Point const &transformation,
295 NR::Point const &origin,
296 NR::Dim2 dim,
297 bool uniform) const
298 {
299 /* We have a list of points, which we are proposing to transform in some way. We need to see
300 ** if any of these points, when transformed, snap to anything. If they do, we return the
301 ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
302 */
304 /* Quick check to see if we have any snappers that are enabled */
305 if (willSnapSomething() == false) {
306 return std::make_pair(transformation, false);
307 }
309 /* The current best transformation */
310 NR::Point best_transformation = transformation;
312 /* The current best metric for the best transformation; lower is better, NR_HUGE
313 ** means that we haven't snapped anything.
314 */
315 double best_metric = NR_HUGE;
317 for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
319 /* Work out the transformed version of this point */
320 NR::Point transformed;
321 switch (transformation_type) {
322 case TRANSLATION:
323 transformed = *i + transformation;
324 break;
325 case SCALE:
326 transformed = ((*i - origin) * NR::scale(transformation[NR::X], transformation[NR::Y])) + origin;
327 break;
328 case STRETCH:
329 {
330 NR::scale s(1, 1);
331 if (uniform)
332 s[NR::X] = s[NR::Y] = transformation[dim];
333 else {
334 s[dim] = transformation[dim];
335 s[1 - dim] = 1;
336 }
337 transformed = ((*i - origin) * s) + origin;
338 break;
339 }
340 case SKEW:
341 transformed = *i;
342 transformed[dim] += transformation[dim] * ((*i)[1 - dim] - origin[1 - dim]);
343 break;
344 default:
345 g_assert_not_reached();
346 }
348 /* Snap it */
349 Inkscape::SnappedPoint const snapped = constrained ?
350 constrainedSnap(type, transformed, constraint, ignore) : freeSnap(type, transformed, ignore);
352 if (snapped.getDistance() < NR_HUGE) {
353 /* We snapped. Find the transformation that describes where the snapped point has
354 ** ended up, and also the metric for this transformation.
355 */
356 NR::Point result;
357 NR::Coord metric;
358 switch (transformation_type) {
359 case TRANSLATION:
360 result = snapped.getPoint() - *i;
361 metric = NR::L2(result);
362 break;
363 case SCALE:
364 {
365 NR::Point const a = (snapped.getPoint() - origin);
366 NR::Point const b = (*i - origin);
367 result = NR::Point(a[NR::X] / b[NR::X], a[NR::Y] / b[NR::Y]);
368 metric = std::abs(NR::L2(result) - NR::L2(transformation));
369 break;
370 }
371 case STRETCH:
372 {
373 for (int j = 0; j < 2; j++) {
374 if (uniform || j == dim) {
375 result[j] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]);
376 } else {
377 result[j] = 1;
378 }
379 }
380 metric = std::abs(result[dim] - transformation[dim]);
381 break;
382 }
383 case SKEW:
384 result[dim] = (snapped.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]);
385 metric = std::abs(result[dim] - transformation[dim]);
386 break;
387 default:
388 g_assert_not_reached();
389 }
391 /* Note it if it's the best so far */
392 if (metric < best_metric && metric != 0) {
393 best_transformation = result;
394 best_metric = metric;
395 }
396 }
397 }
399 // Using " < 1e6" instead of " < NR::HUGE" for catching some rounding errors
400 // These rounding errors might be caused by NRRects, see bug #1584301
401 return std::make_pair(best_transformation, best_metric < 1e6);
402 }
405 /**
406 * Try to snap a list of points to any interested snappers after they have undergone
407 * a translation.
408 *
409 * \param t Type of points.
410 * \param p Points.
411 * \param it List of items to ignore when snapping.
412 * \param tr Proposed translation.
413 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
414 */
416 std::pair<NR::Point, bool> SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t,
417 std::vector<NR::Point> const &p,
418 std::list<SPItem const *> const &it,
419 NR::Point const &tr) const
420 {
421 return _snapTransformed(
422 t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false
423 );
424 }
427 /**
428 * Try to snap a list of points to any interested snappers after they have undergone a
429 * translation. A snap will only occur along a line described by a
430 * Inkscape::Snapper::ConstraintLine.
431 *
432 * \param t Type of points.
433 * \param p Points.
434 * \param it List of items to ignore when snapping.
435 * \param c Constraint line.
436 * \param tr Proposed translation.
437 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
438 */
440 std::pair<NR::Point, bool> SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t,
441 std::vector<NR::Point> const &p,
442 std::list<SPItem const *> const &it,
443 Inkscape::Snapper::ConstraintLine const &c,
444 NR::Point const &tr) const
445 {
446 return _snapTransformed(
447 t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false
448 );
449 }
452 /**
453 * Try to snap a list of points to any interested snappers after they have undergone
454 * a scale.
455 *
456 * \param t Type of points.
457 * \param p Points.
458 * \param it List of items to ignore when snapping.
459 * \param s Proposed scale.
460 * \param o Origin of proposed scale.
461 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
462 */
464 std::pair<NR::scale, bool> SnapManager::freeSnapScale(Inkscape::Snapper::PointType t,
465 std::vector<NR::Point> const &p,
466 std::list<SPItem const *> const &it,
467 NR::scale const &s,
468 NR::Point const &o) const
469 {
470 return _snapTransformed(
471 t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
472 );
473 }
476 /**
477 * Try to snap a list of points to any interested snappers after they have undergone
478 * a scale. A snap will only occur along a line described by a
479 * Inkscape::Snapper::ConstraintLine.
480 *
481 * \param t Type of points.
482 * \param p Points.
483 * \param it List of items to ignore when snapping.
484 * \param s Proposed scale.
485 * \param o Origin of proposed scale.
486 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
487 */
489 std::pair<NR::scale, bool> SnapManager::constrainedSnapScale(Inkscape::Snapper::PointType t,
490 std::vector<NR::Point> const &p,
491 std::list<SPItem const *> const &it,
492 Inkscape::Snapper::ConstraintLine const &c,
493 NR::scale const &s,
494 NR::Point const &o) const
495 {
496 return _snapTransformed(
497 t, p, it, true, c, SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
498 );
499 }
502 /**
503 * Try to snap a list of points to any interested snappers after they have undergone
504 * a stretch.
505 *
506 * \param t Type of points.
507 * \param p Points.
508 * \param it List of items to ignore when snapping.
509 * \param s Proposed stretch.
510 * \param o Origin of proposed stretch.
511 * \param d Dimension in which to apply proposed stretch.
512 * \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
513 * \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
514 */
516 std::pair<NR::Coord, bool> SnapManager::freeSnapStretch(Inkscape::Snapper::PointType t,
517 std::vector<NR::Point> const &p,
518 std::list<SPItem const *> const &it,
519 NR::Coord const &s,
520 NR::Point const &o,
521 NR::Dim2 d,
522 bool u) const
523 {
524 std::pair<NR::Point, bool> const r = _snapTransformed(
525 t, p, it, false, NR::Point(), STRETCH, NR::Point(s, s), o, d, u
526 );
528 return std::make_pair(r.first[d], r.second);
529 }
532 /**
533 * Try to snap a list of points to any interested snappers after they have undergone
534 * a skew.
535 *
536 * \param t Type of points.
537 * \param p Points.
538 * \param it List of items to ignore when snapping.
539 * \param s Proposed skew.
540 * \param o Origin of proposed skew.
541 * \param d Dimension in which to apply proposed skew.
542 * \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
543 */
545 std::pair<NR::Coord, bool> SnapManager::freeSnapSkew(Inkscape::Snapper::PointType t,
546 std::vector<NR::Point> const &p,
547 std::list<SPItem const *> const &it,
548 NR::Coord const &s,
549 NR::Point const &o,
550 NR::Dim2 d) const
551 {
552 std::pair<NR::Point, bool> const r = _snapTransformed(
553 t, p, it, false, NR::Point(), SKEW, NR::Point(s, s), o, d, false
554 );
556 return std::make_pair(r.first[d], r.second);
557 }
559 /*
560 Local Variables:
561 mode:c++
562 c-file-style:"stroustrup"
563 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
564 indent-tabs-mode:nil
565 fill-column:99
566 End:
567 */
568 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :