Code

1) Changes to snapping preferences dialog
[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 {
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     }
93     
94     return (i != s.end());
95 }
97 void SnapManager::setSnapModeBBox(bool enabled)
98 {
99         guide.setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
100         object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
101         object.setSnapToBBoxNodes(enabled);
102         object.setSnapToBBoxPaths(enabled);
103         object.setStrictSnapping(true);
106 bool SnapManager::getSnapModeBBox() const
108         return guide.getSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX);
112 void SnapManager::setSnapModeNodes(bool enabled)
114         guide.setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
115         object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
116         object.setSnapToItemNodes(enabled);
117         object.setSnapToItemPaths(enabled);     
118         object.setStrictSnapping(true);
121 bool SnapManager::getSnapModeNodes() const
123         return guide.getSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE);
126 /**
127  *  Try to snap a point to any interested snappers.
128  *
129  *  \param t Type of point.
130  *  \param p Point.
131  *  \param it Item to ignore when snapping.
132  *  \return Snapped point.
133  */
135 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
136                                              NR::Point const &p,
137                                              SPItem const *it) const
140     std::list<SPItem const *> lit;
141     lit.push_back(it);
142     return freeSnap(t, p, lit);
146 /**
147  *  Try to snap a point to any interested snappers.
148  *
149  *  \param t Type of point.
150  *  \param p Point.
151  *  \param it List of items to ignore when snapping.
152  *  \return Snapped point.
153  */
155 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
156                                              NR::Point const &p,
157                                              std::list<SPItem const *> const &it) const
159     SnapperList const snappers = getSnappers();
161     return freeSnap(t, p, it, snappers);
164 /**
165  *  Try to snap a point to any of the specified snappers.
166  *
167  *  \param t Type of point.
168  *  \param p Point.
169  *  \param it List of items to ignore when snapping.
170  * \param snappers  List of snappers to try to snap to
171  *  \return Snapped point.
172  */
174 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
175                                              NR::Point const &p,
176                                              std::list<SPItem const *> const &it,
177                                              SnapperList const &snappers) const
179     Inkscape::SnappedPoint r(p, NR_HUGE);
181     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
182         Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
183         if (s.getDistance() < r.getDistance()) {
184             r = s;
185         }
186     }
188     return r;
191 /**
192  *  Try to snap a point to any of the specified snappers. Snap always, ignoring the snap-distance
193  *
194  *  \param t Type of point.
195  *  \param p Point.
196  *  \param it Item to ignore when snapping.
197  * \param snappers  List of snappers to try to snap to
198  *  \return Snapped point.
199  */
201 Inkscape::SnappedPoint
202 SnapManager::freeSnapAlways( Inkscape::Snapper::PointType t,
203                              NR::Point const &p,
204                              SPItem const *it,
205                              SnapperList &snappers )
207     std::list<SPItem const *> lit;
208     lit.push_back(it);
209     return freeSnapAlways(t, p, lit, snappers);
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 List of items 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                              std::list<SPItem const *> const &it,
226                              SnapperList &snappers )
228     Inkscape::SnappedPoint r(p, NR_HUGE);
230     for (SnapperList::iterator i = snappers.begin(); i != snappers.end(); i++) {
231         gdouble const curr_gridsnap = (*i)->getDistance();
232         const_cast<Inkscape::Snapper*> (*i)->setDistance(NR_HUGE);
233         Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
234         const_cast<Inkscape::Snapper*> (*i)->setDistance(curr_gridsnap);
236         if (s.getDistance() < r.getDistance()) {
237             r = s;
238         }
239     }
241     return r;
246 /**
247  *  Try to snap a point to any interested snappers.  A snap will only occur along
248  *  a line described by a Inkscape::Snapper::ConstraintLine.
249  *
250  *  \param t Type of point.
251  *  \param p Point.
252  *  \param c Constraint line.
253  *  \param it Item to ignore when snapping.
254  *  \return Snapped point.
255  */
257 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
258                                                     NR::Point const &p,
259                                                     Inkscape::Snapper::ConstraintLine const &c,
260                                                     SPItem const *it) const
262     std::list<SPItem const *> lit;
263     lit.push_back(it);
264     return constrainedSnap(t, p, c, lit);
269 /**
270  *  Try to snap a point to any interested snappers.  A snap will only occur along
271  *  a line described by a Inkscape::Snapper::ConstraintLine.
272  *
273  *  \param t Type of point.
274  *  \param p Point.
275  *  \param c Constraint line.
276  *  \param it List of items to ignore when snapping.
277  *  \return Snapped point.
278  */
280 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
281                                                     NR::Point const &p,
282                                                     Inkscape::Snapper::ConstraintLine const &c,
283                                                     std::list<SPItem const *> const &it) const
285     Inkscape::SnappedPoint r(p, NR_HUGE);
287     SnapperList const snappers = getSnappers();
288     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
289         Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, p, c, it);
290         if (s.getDistance() < r.getDistance()) {
291             r = s;
292         }
293     }
295     return r;
300 /**
301  *  Main internal snapping method, which is called by the other, friendlier, public
302  *  methods.  It's a bit hairy as it has lots of parameters, but it saves on a lot
303  *  of duplicated code.
304  *
305  *  \param type Type of points being snapped.
306  *  \param points List of points to snap.
307  *  \param ignore List of items to ignore while snapping.
308  *  \param constrained true if the snap is constrained.
309  *  \param constraint Constraint line to use, if `constrained' is true, otherwise undefined.
310  *  \param transformation_type Type of transformation to apply to points before trying to snap them.
311  *  \param transformation Description of the transformation; details depend on the type.
312  *  \param origin Origin of the transformation, if applicable.
313  *  \param dim Dimension of the transformation, if applicable.
314  *  \param uniform true if the transformation should be uniform, if applicable.
315  */
317 std::pair<NR::Point, bool> SnapManager::_snapTransformed(
318     Inkscape::Snapper::PointType type,
319     std::vector<NR::Point> const &points,
320     std::list<SPItem const *> const &ignore,
321     bool constrained,
322     Inkscape::Snapper::ConstraintLine const &constraint,
323     Transformation transformation_type,
324     NR::Point const &transformation,
325     NR::Point const &origin,
326     NR::Dim2 dim,
327     bool uniform) const
329     /* We have a list of points, which we are proposing to transform in some way.  We need to see
330     ** if any of these points, when transformed, snap to anything.  If they do, we return the
331     ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
332     */
334     /* Quick check to see if we have any snappers that are enabled */
335     if (SomeSnapperMightSnap() == false) {
336         return std::make_pair(transformation, false);
337     }
339     /* The current best transformation */
340     NR::Point best_transformation = transformation;
342     /* The current best metric for the best transformation; lower is better, NR_HUGE
343     ** means that we haven't snapped anything.
344     */
345     double best_metric = NR_HUGE;
347     for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
349         /* Work out the transformed version of this point */
350         NR::Point transformed;
351         switch (transformation_type) {
352             case TRANSLATION:
353                 transformed = *i + transformation;
354                 break;
355             case SCALE:
356                 transformed = ((*i - origin) * NR::scale(transformation[NR::X], transformation[NR::Y])) + origin;
357                 break;
358             case STRETCH:
359             {
360                 NR::scale s(1, 1);
361                 if (uniform)
362                     s[NR::X] = s[NR::Y] = transformation[dim];
363                 else {
364                     s[dim] = transformation[dim];
365                     s[1 - dim] = 1;
366                 }
367                 transformed = ((*i - origin) * s) + origin;
368                 break;
369             }
370             case SKEW:
371                 transformed = *i;
372                 transformed[dim] += transformation[dim] * ((*i)[1 - dim] - origin[1 - dim]);
373                 break;
374             default:
375                 g_assert_not_reached();
376         }
378         /* Snap it */
379         Inkscape::SnappedPoint const snapped = constrained ?
380             constrainedSnap(type, transformed, constraint, ignore) : freeSnap(type, transformed, ignore);
382         if (snapped.getDistance() < NR_HUGE) {
383             /* We snapped.  Find the transformation that describes where the snapped point has
384             ** ended up, and also the metric for this transformation.
385             */
386             NR::Point result;
387             NR::Coord metric;
388             switch (transformation_type) {
389                 case TRANSLATION:
390                     result = snapped.getPoint() - *i;
391                     metric = NR::L2(result);
392                     break;
393                 case SCALE:
394                 {
395                     NR::Point const a = (snapped.getPoint() - origin);
396                     NR::Point const b = (*i - origin);
397                     result = NR::Point(a[NR::X] / b[NR::X], a[NR::Y] / b[NR::Y]);
398                     metric = std::abs(NR::L2(result) - NR::L2(transformation));
399                     break;
400                 }
401                 case STRETCH:
402                 {
403                     for (int j = 0; j < 2; j++) {
404                         if (uniform || j == dim) {
405                             result[j] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]);
406                         } else {
407                             result[j] = 1;
408                         }
409                     }
410                     metric = std::abs(result[dim] - transformation[dim]);
411                     break;
412                 }
413                 case SKEW:
414                     result[dim] = (snapped.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]);
415                     metric = std::abs(result[dim] - transformation[dim]);
416                     break;
417                 default:
418                     g_assert_not_reached();
419             }
421             /* Note it if it's the best so far */
422             if (metric < best_metric && metric != 0) {
423                 best_transformation = result;
424                 best_metric = metric;
425             }
426         }
427     }
429     // Using " < 1e6" instead of " < NR::HUGE" for catching some rounding errors
430     // These rounding errors might be caused by NRRects, see bug #1584301
431     return std::make_pair(best_transformation, best_metric < 1e6);
435 /**
436  *  Try to snap a list of points to any interested snappers after they have undergone
437  *  a translation.
438  *
439  *  \param t Type of points.
440  *  \param p Points.
441  *  \param it List of items to ignore when snapping.
442  *  \param tr Proposed translation.
443  *  \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
444  */
446 std::pair<NR::Point, bool> SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t,
447                                                             std::vector<NR::Point> const &p,
448                                                             std::list<SPItem const *> const &it,
449                                                             NR::Point const &tr) const
451     return _snapTransformed(
452         t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false
453         );
457 /**
458  *  Try to snap a list of points to any interested snappers after they have undergone a
459  *  translation.  A snap will only occur along a line described by a
460  *  Inkscape::Snapper::ConstraintLine.
461  *
462  *  \param t Type of points.
463  *  \param p Points.
464  *  \param it List of items to ignore when snapping.
465  *  \param c Constraint line.
466  *  \param tr Proposed translation.
467  *  \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
468  */
470 std::pair<NR::Point, bool> SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t,
471                                                                    std::vector<NR::Point> const &p,
472                                                                    std::list<SPItem const *> const &it,
473                                                                    Inkscape::Snapper::ConstraintLine const &c,
474                                                                    NR::Point const &tr) const
476     return _snapTransformed(
477         t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false
478         );
482 /**
483  *  Try to snap a list of points to any interested snappers after they have undergone
484  *  a scale.
485  *
486  *  \param t Type of points.
487  *  \param p Points.
488  *  \param it List of items to ignore when snapping.
489  *  \param s Proposed scale.
490  *  \param o Origin of proposed scale.
491  *  \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
492  */
494 std::pair<NR::scale, bool> SnapManager::freeSnapScale(Inkscape::Snapper::PointType t,
495                                                       std::vector<NR::Point> const &p,
496                                                       std::list<SPItem const *> const &it,
497                                                       NR::scale const &s,
498                                                       NR::Point const &o) const
500     return _snapTransformed(
501         t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
502         );
506 /**
507  *  Try to snap a list of points to any interested snappers after they have undergone
508  *  a scale.  A snap will only occur along a line described by a
509  *  Inkscape::Snapper::ConstraintLine.
510  *
511  *  \param t Type of points.
512  *  \param p Points.
513  *  \param it List of items to ignore when snapping.
514  *  \param s Proposed scale.
515  *  \param o Origin of proposed scale.
516  *  \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
517  */
519 std::pair<NR::scale, bool> SnapManager::constrainedSnapScale(Inkscape::Snapper::PointType t,
520                                                              std::vector<NR::Point> const &p,
521                                                              std::list<SPItem const *> const &it,
522                                                              Inkscape::Snapper::ConstraintLine const &c,
523                                                              NR::scale const &s,
524                                                              NR::Point const &o) const
526     return _snapTransformed(
527         t, p, it, true, c, SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
528         );
532 /**
533  *  Try to snap a list of points to any interested snappers after they have undergone
534  *  a stretch.
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 stretch.
540  *  \param o Origin of proposed stretch.
541  *  \param d Dimension in which to apply proposed stretch.
542  *  \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
543  *  \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
544  */
546 std::pair<NR::Coord, bool> SnapManager::freeSnapStretch(Inkscape::Snapper::PointType t,
547                                                         std::vector<NR::Point> const &p,
548                                                         std::list<SPItem const *> const &it,
549                                                         NR::Coord const &s,
550                                                         NR::Point const &o,
551                                                         NR::Dim2 d,
552                                                         bool u) const
554    std::pair<NR::Point, bool> const r = _snapTransformed(
555         t, p, it, false, NR::Point(), STRETCH, NR::Point(s, s), o, d, u
556         );
558    return std::make_pair(r.first[d], r.second);
562 /**
563  *  Try to snap a list of points to any interested snappers after they have undergone
564  *  a skew.
565  *
566  *  \param t Type of points.
567  *  \param p Points.
568  *  \param it List of items to ignore when snapping.
569  *  \param s Proposed skew.
570  *  \param o Origin of proposed skew.
571  *  \param d Dimension in which to apply proposed skew.
572  *  \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
573  */
575 std::pair<NR::Coord, bool> SnapManager::freeSnapSkew(Inkscape::Snapper::PointType t,
576                                                      std::vector<NR::Point> const &p,
577                                                      std::list<SPItem const *> const &it,
578                                                      NR::Coord const &s,
579                                                      NR::Point const &o,
580                                                      NR::Dim2 d) const
582    std::pair<NR::Point, bool> const r = _snapTransformed(
583         t, p, it, false, NR::Point(), SKEW, NR::Point(s, s), o, d, false
584         );
586    return std::make_pair(r.first[d], r.second);
589 /*
590   Local Variables:
591   mode:c++
592   c-file-style:"stroustrup"
593   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
594   indent-tabs-mode:nil
595   fill-column:99
596   End:
597 */
598 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :