Code

3ef246447f3a317b4d5e9b5c45cbd10b85537dbe
[inkscape.git] / src / snap.cpp
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"
21 #include <libnr/nr-point-fns.h>
22 #include <libnr/nr-scale-ops.h>
23 #include <libnr/nr-values.h>
25 #include "display/canvas-grid.h"
27 #include "inkscape.h"
28 #include "desktop.h"
30 /**
31  *  Construct a SnapManager for a SPNamedView.
32  *
33  *  \param v `Owning' SPNamedView.
34  */
36 SnapManager::SnapManager(SPNamedView const *v) :
37     guide(v, 0),
38     object(v, 0),
39     _named_view(v)
40 {
41         
42 }
45 /**
46  *  \return List of snappers that we use.
47  */
48 SnapManager::SnapperList 
49 SnapManager::getSnappers() const
50 {
51     SnapManager::SnapperList s;
52     s.push_back(&guide);
53     s.push_back(&object);
55     SnapManager::SnapperList gs = getGridSnappers();
56     s.splice(s.begin(), gs);
58     return s;
59 }
61 /**
62  *  \return List of gridsnappers that we use.
63  */
64 SnapManager::SnapperList 
65 SnapManager::getGridSnappers() const
66 {
67     SnapperList s;
69     //FIXME: this code should actually do this: add new grid snappers that are active for this desktop. now it just adds all gridsnappers
70     SPDesktop* desktop = SP_ACTIVE_DESKTOP;
71     if (desktop && desktop->gridsEnabled()) {
72         for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
73             Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
74             s.push_back(grid->snapper);
75         }
76     }
78     return s;
79 }
81 /**
82  * \return true if one of the snappers will try to snap something.
83  */
85 bool SnapManager::SomeSnapperMightSnap() const
86 {
87     SnapperList const s = getSnappers();
88     SnapperList::const_iterator i = s.begin();
89     while (i != s.end() && (*i)->ThisSnapperMightSnap() == false) {
90         i++;
91     }
92     
93     return (i != s.end());
94 }
96 /*
97  *  The snappers have too many parameters to adjust individually. Therefore only
98  *  two snapping modes are presented to the user: snapping bounding box corners (to 
99  *      other bounding boxes, grids or guides), and/or snapping nodes (to other nodes,
100  *  paths, grids or guides). To select either of these modes (or both), use the 
101  *  methods defined below: setSnapModeBBox() and setSnapModeNode().
102  * 
103  * */
106 void SnapManager::setSnapModeBBox(bool enabled)
108         //The default values are being set in sp_namedview_set() (in sp-namedview.cpp)
109         guide.setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
110         
111         for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
112         Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
113         grid->snapper->setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
114     }
115         
116         object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
117         object.setSnapToBBoxNode(enabled);
118         object.setSnapToBBoxPath(enabled);
119         object.setStrictSnapping(true); //don't snap bboxes to nodes/paths and vice versa       
122 bool SnapManager::getSnapModeBBox() const
124         return guide.getSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX);
127 void SnapManager::setSnapModeNode(bool enabled)
129         guide.setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
130         
131         for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
132         Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
133         grid->snapper->setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
134     }
135         
136         object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
137         //object.setSnapToItemNode(enabled); // On second thought, these should be controlled
138         //object.setSnapToItemPath(enabled); // separately by the snapping prefs dialog 
139         object.setStrictSnapping(true); 
142 bool SnapManager::getSnapModeNode() const
144         return guide.getSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE);
147 /**
148  *  Try to snap a point to any interested snappers.
149  *
150  *  \param t Type of point.
151  *  \param p Point.
152  *  \param it Item to ignore when snapping.
153  *  \return Snapped point.
154  */
156 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
157                                              NR::Point const &p,
158                                              SPItem const *it) const
161     std::list<SPItem const *> lit;
162     lit.push_back(it);
163     return freeSnap(t, p, lit);
167 /**
168  *  Try to snap a point to any interested snappers.
169  *
170  *  \param t Type of point.
171  *  \param p Point.
172  *  \param it List of items to ignore when snapping.
173  *  \return Snapped point.
174  */
176 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
177                                              NR::Point const &p,
178                                              std::list<SPItem const *> const &it) const
180     SnapperList const snappers = getSnappers();
182     return freeSnap(t, p, it, snappers);
185 /**
186  *  Try to snap a point to any of the specified snappers.
187  *
188  *  \param t Type of point.
189  *  \param p Point.
190  *  \param it List of items to ignore when snapping.
191  * \param snappers  List of snappers to try to snap to
192  *  \return Snapped point.
193  */
195 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
196                                              NR::Point const &p,
197                                              std::list<SPItem const *> const &it,
198                                              SnapperList const &snappers) const
200     Inkscape::SnappedPoint r(p, NR_HUGE);
202     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
203         Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
204         if (s.getDistance() < r.getDistance()) {
205             r = s;
206         }
207     }
209     return r;
212 /**
213  *  Try to snap a point to any of the specified snappers. Snap always, ignoring the snap-distance
214  *
215  *  \param t Type of point.
216  *  \param p Point.
217  *  \param it Item to ignore when snapping.
218  * \param snappers  List of snappers to try to snap to
219  *  \return Snapped point.
220  */
222 Inkscape::SnappedPoint
223 SnapManager::freeSnapAlways( Inkscape::Snapper::PointType t,
224                              NR::Point const &p,
225                              SPItem const *it,
226                              SnapperList &snappers )
228     std::list<SPItem const *> lit;
229     lit.push_back(it);
230     return freeSnapAlways(t, p, lit, snappers);
233 /**
234  *  Try to snap a point to any of the specified snappers. Snap always, ignoring the snap-distance
235  *
236  *  \param t Type of point.
237  *  \param p Point.
238  *  \param it List of items to ignore when snapping.
239  * \param snappers  List of snappers to try to snap to
240  *  \return Snapped point.
241  */
243 Inkscape::SnappedPoint
244 SnapManager::freeSnapAlways( Inkscape::Snapper::PointType t,
245                              NR::Point const &p,
246                              std::list<SPItem const *> const &it,
247                              SnapperList &snappers )
249     Inkscape::SnappedPoint r(p, NR_HUGE);
251     for (SnapperList::iterator i = snappers.begin(); i != snappers.end(); i++) {
252         gdouble const curr_gridsnap = (*i)->getDistance();
253         const_cast<Inkscape::Snapper*> (*i)->setDistance(NR_HUGE);
254         Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
255         const_cast<Inkscape::Snapper*> (*i)->setDistance(curr_gridsnap);
257         if (s.getDistance() < r.getDistance()) {
258             r = s;
259         }
260     }
262     return r;
267 /**
268  *  Try to snap a point to any interested snappers.  A snap will only occur along
269  *  a line described by a Inkscape::Snapper::ConstraintLine.
270  *
271  *  \param t Type of point.
272  *  \param p Point.
273  *  \param c Constraint line.
274  *  \param it Item to ignore when snapping.
275  *  \return Snapped point.
276  */
278 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
279                                                     NR::Point const &p,
280                                                     Inkscape::Snapper::ConstraintLine const &c,
281                                                     SPItem const *it) const
283     std::list<SPItem const *> lit;
284     lit.push_back(it);
285     return constrainedSnap(t, p, c, lit);
290 /**
291  *  Try to snap a point to any interested snappers.  A snap will only occur along
292  *  a line described by a Inkscape::Snapper::ConstraintLine.
293  *
294  *  \param t Type of point.
295  *  \param p Point.
296  *  \param c Constraint line.
297  *  \param it List of items to ignore when snapping.
298  *  \return Snapped point.
299  */
301 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
302                                                     NR::Point const &p,
303                                                     Inkscape::Snapper::ConstraintLine const &c,
304                                                     std::list<SPItem const *> const &it) const
306     Inkscape::SnappedPoint r(p, NR_HUGE);
308     SnapperList const snappers = getSnappers();
309     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
310         Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, p, c, it);
311         if (s.getDistance() < r.getDistance()) {
312             r = s;
313         }
314     }
316     return r;
321 /**
322  *  Main internal snapping method, which is called by the other, friendlier, public
323  *  methods.  It's a bit hairy as it has lots of parameters, but it saves on a lot
324  *  of duplicated code.
325  *
326  *  \param type Type of points being snapped.
327  *  \param points List of points to snap.
328  *  \param ignore List of items to ignore while snapping.
329  *  \param constrained true if the snap is constrained.
330  *  \param constraint Constraint line to use, if `constrained' is true, otherwise undefined.
331  *  \param transformation_type Type of transformation to apply to points before trying to snap them.
332  *  \param transformation Description of the transformation; details depend on the type.
333  *  \param origin Origin of the transformation, if applicable.
334  *  \param dim Dimension of the transformation, if applicable.
335  *  \param uniform true if the transformation should be uniform, if applicable.
336  */
338 std::pair<NR::Point, bool> SnapManager::_snapTransformed(
339     Inkscape::Snapper::PointType type,
340     std::vector<NR::Point> const &points,
341     std::list<SPItem const *> const &ignore,
342     bool constrained,
343     Inkscape::Snapper::ConstraintLine const &constraint,
344     Transformation transformation_type,
345     NR::Point const &transformation,
346     NR::Point const &origin,
347     NR::Dim2 dim,
348     bool uniform) const
350     /* We have a list of points, which we are proposing to transform in some way.  We need to see
351     ** if any of these points, when transformed, snap to anything.  If they do, we return the
352     ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
353     */
355     /* Quick check to see if we have any snappers that are enabled */
356     if (SomeSnapperMightSnap() == false) {
357         return std::make_pair(transformation, false);
358     }
360     /* The current best transformation */
361     NR::Point best_transformation = transformation;
363     /* The current best metric for the best transformation; lower is better, NR_HUGE
364     ** means that we haven't snapped anything.
365     */
366     double best_metric = NR_HUGE;
368     for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
370         /* Work out the transformed version of this point */
371         NR::Point transformed;
372         switch (transformation_type) {
373             case TRANSLATION:
374                 transformed = *i + transformation;
375                 break;
376             case SCALE:
377                 transformed = ((*i - origin) * NR::scale(transformation[NR::X], transformation[NR::Y])) + origin;
378                 break;
379             case STRETCH:
380             {
381                 NR::scale s(1, 1);
382                 if (uniform)
383                     s[NR::X] = s[NR::Y] = transformation[dim];
384                 else {
385                     s[dim] = transformation[dim];
386                     s[1 - dim] = 1;
387                 }
388                 transformed = ((*i - origin) * s) + origin;
389                 break;
390             }
391             case SKEW:
392                 transformed = *i;
393                 transformed[dim] += transformation[dim] * ((*i)[1 - dim] - origin[1 - dim]);
394                 break;
395             default:
396                 g_assert_not_reached();
397         }
399         /* Snap it */
400         Inkscape::SnappedPoint const snapped = constrained ?
401             constrainedSnap(type, transformed, constraint, ignore) : freeSnap(type, transformed, ignore);
403         if (snapped.getDistance() < NR_HUGE) {
404             /* We snapped.  Find the transformation that describes where the snapped point has
405             ** ended up, and also the metric for this transformation.
406             */
407             NR::Point result;
408             NR::Coord metric;
409             switch (transformation_type) {
410                 case TRANSLATION:
411                     result = snapped.getPoint() - *i;
412                     metric = NR::L2(result);
413                     break;
414                 case SCALE:
415                 {
416                     NR::Point const a = (snapped.getPoint() - origin);
417                     NR::Point const b = (*i - origin);
418                     result = NR::Point(a[NR::X] / b[NR::X], a[NR::Y] / b[NR::Y]);
419                     metric = std::abs(NR::L2(result) - NR::L2(transformation));
420                     break;
421                 }
422                 case STRETCH:
423                 {
424                     for (int j = 0; j < 2; j++) {
425                         if (uniform || j == dim) {
426                             result[j] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]);
427                         } else {
428                             result[j] = 1;
429                         }
430                     }
431                     metric = std::abs(result[dim] - transformation[dim]);
432                     break;
433                 }
434                 case SKEW:
435                     result[dim] = (snapped.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]);
436                     metric = std::abs(result[dim] - transformation[dim]);
437                     break;
438                 default:
439                     g_assert_not_reached();
440             }
442             /* Note it if it's the best so far */
443             if (metric < best_metric && metric != 0) {
444                 best_transformation = result;
445                 best_metric = metric;
446             }
447         }
448     }
450     // Using " < 1e6" instead of " < NR::HUGE" for catching some rounding errors
451     // These rounding errors might be caused by NRRects, see bug #1584301
452     return std::make_pair(best_transformation, best_metric < 1e6);
456 /**
457  *  Try to snap a list of points to any interested snappers after they have undergone
458  *  a translation.
459  *
460  *  \param t Type of points.
461  *  \param p Points.
462  *  \param it List of items to ignore when snapping.
463  *  \param tr Proposed translation.
464  *  \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
465  */
467 std::pair<NR::Point, bool> SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t,
468                                                             std::vector<NR::Point> const &p,
469                                                             std::list<SPItem const *> const &it,
470                                                             NR::Point const &tr) const
472     return _snapTransformed(
473         t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false
474         );
478 /**
479  *  Try to snap a list of points to any interested snappers after they have undergone a
480  *  translation.  A snap will only occur along a line described by a
481  *  Inkscape::Snapper::ConstraintLine.
482  *
483  *  \param t Type of points.
484  *  \param p Points.
485  *  \param it List of items to ignore when snapping.
486  *  \param c Constraint line.
487  *  \param tr Proposed translation.
488  *  \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
489  */
491 std::pair<NR::Point, bool> SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t,
492                                                                    std::vector<NR::Point> const &p,
493                                                                    std::list<SPItem const *> const &it,
494                                                                    Inkscape::Snapper::ConstraintLine const &c,
495                                                                    NR::Point const &tr) const
497     return _snapTransformed(
498         t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false
499         );
503 /**
504  *  Try to snap a list of points to any interested snappers after they have undergone
505  *  a scale.
506  *
507  *  \param t Type of points.
508  *  \param p Points.
509  *  \param it List of items to ignore when snapping.
510  *  \param s Proposed scale.
511  *  \param o Origin of proposed scale.
512  *  \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
513  */
515 std::pair<NR::scale, bool> SnapManager::freeSnapScale(Inkscape::Snapper::PointType t,
516                                                       std::vector<NR::Point> const &p,
517                                                       std::list<SPItem const *> const &it,
518                                                       NR::scale const &s,
519                                                       NR::Point const &o) const
521     return _snapTransformed(
522         t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
523         );
527 /**
528  *  Try to snap a list of points to any interested snappers after they have undergone
529  *  a scale.  A snap will only occur along a line described by a
530  *  Inkscape::Snapper::ConstraintLine.
531  *
532  *  \param t Type of points.
533  *  \param p Points.
534  *  \param it List of items to ignore when snapping.
535  *  \param s Proposed scale.
536  *  \param o Origin of proposed scale.
537  *  \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
538  */
540 std::pair<NR::scale, bool> SnapManager::constrainedSnapScale(Inkscape::Snapper::PointType t,
541                                                              std::vector<NR::Point> const &p,
542                                                              std::list<SPItem const *> const &it,
543                                                              Inkscape::Snapper::ConstraintLine const &c,
544                                                              NR::scale const &s,
545                                                              NR::Point const &o) const
547     return _snapTransformed(
548         t, p, it, true, c, SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
549         );
553 /**
554  *  Try to snap a list of points to any interested snappers after they have undergone
555  *  a stretch.
556  *
557  *  \param t Type of points.
558  *  \param p Points.
559  *  \param it List of items to ignore when snapping.
560  *  \param s Proposed stretch.
561  *  \param o Origin of proposed stretch.
562  *  \param d Dimension in which to apply proposed stretch.
563  *  \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
564  *  \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
565  */
567 std::pair<NR::Coord, bool> SnapManager::freeSnapStretch(Inkscape::Snapper::PointType t,
568                                                         std::vector<NR::Point> const &p,
569                                                         std::list<SPItem const *> const &it,
570                                                         NR::Coord const &s,
571                                                         NR::Point const &o,
572                                                         NR::Dim2 d,
573                                                         bool u) const
575    std::pair<NR::Point, bool> const r = _snapTransformed(
576         t, p, it, false, NR::Point(), STRETCH, NR::Point(s, s), o, d, u
577         );
579    return std::make_pair(r.first[d], r.second);
583 /**
584  *  Try to snap a list of points to any interested snappers after they have undergone
585  *  a skew.
586  *
587  *  \param t Type of points.
588  *  \param p Points.
589  *  \param it List of items to ignore when snapping.
590  *  \param s Proposed skew.
591  *  \param o Origin of proposed skew.
592  *  \param d Dimension in which to apply proposed skew.
593  *  \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
594  */
596 std::pair<NR::Coord, bool> SnapManager::freeSnapSkew(Inkscape::Snapper::PointType t,
597                                                      std::vector<NR::Point> const &p,
598                                                      std::list<SPItem const *> const &it,
599                                                      NR::Coord const &s,
600                                                      NR::Point const &o,
601                                                      NR::Dim2 d) const
603    std::pair<NR::Point, bool> const r = _snapTransformed(
604         t, p, it, false, NR::Point(), SKEW, NR::Point(s, s), o, d, false
605         );
607    return std::make_pair(r.first[d], r.second);
610 /*
611   Local Variables:
612   mode:c++
613   c-file-style:"stroustrup"
614   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
615   indent-tabs-mode:nil
616   fill-column:99
617   End:
618 */
619 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :