Code

Fix a crash and add more safety checks to catch NULL pointers
[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  *   Nathan Hurst <njh@njhurst.com>
11  *   Carl Hetherington <inkscape@carlh.net>
12  *   Diederik van Lierop <mail@diedenrezi.nl>
13  *
14  * Copyright (C) 2006-2007 Johan Engelen <johan@shouraizou.nl>
15  * Copyrigth (C) 2004      Nathan Hurst
16  * Copyright (C) 1999-2010 Authors
17  *
18  * Released under GNU GPL, read the file 'COPYING' for more information
19  */
21 #include <utility>
23 #include "sp-namedview.h"
24 #include "snap.h"
25 #include "snapped-line.h"
26 #include "snapped-curve.h"
28 #include "display/canvas-grid.h"
29 #include "display/snap-indicator.h"
31 #include "inkscape.h"
32 #include "desktop.h"
33 #include "selection.h"
34 #include "sp-guide.h"
35 #include "preferences.h"
36 #include "event-context.h"
37 using std::vector;
39 /**
40  *  Construct a SnapManager for a SPNamedView.
41  *
42  *  \param v `Owning' SPNamedView.
43  */
45 SnapManager::SnapManager(SPNamedView const *v) :
46     guide(this, 0),
47     object(this, 0),
48     snapprefs(),
49     _named_view(v),
50     _rotation_center_source_item(NULL),
51     _guide_to_ignore(NULL),
52     _desktop(NULL),
53     _unselected_nodes(NULL)
54 {
55 }
57 /**
58  *  \brief Return a list of snappers
59  *
60  *  Inkscape snaps to objects, grids, and guides. For each of these snap targets a
61  *  separate class is used, which has been derived from the base Snapper class. The
62  *  getSnappers() method returns a list of pointers to instances of this class. This
63  *  list contains exactly one instance of the guide snapper and of the object snapper
64  *  class, but any number of grid snappers (because each grid has its own snapper
65  *  instance)
66  *
67  *  \return List of snappers that we use.
68  */
69 SnapManager::SnapperList
70 SnapManager::getSnappers() const
71 {
72     SnapManager::SnapperList s;
73     s.push_back(&guide);
74     s.push_back(&object);
76     SnapManager::SnapperList gs = getGridSnappers();
77     s.splice(s.begin(), gs);
79     return s;
80 }
82 /**
83  *  \brief Return a list of gridsnappers
84  *
85  *  Each grid has its own instance of the snapper class. This way snapping can
86  *  be enabled per grid individually. A list will be returned containing the
87  *  pointers to these instances, but only for grids that are being displayed
88  *  and for which snapping is enabled.
89  *
90  *  \return List of gridsnappers that we use.
91  */
92 SnapManager::SnapperList
93 SnapManager::getGridSnappers() const
94 {
95     SnapperList s;
97     if (_desktop && _desktop->gridsEnabled() && snapprefs.getSnapToGrids()) {
98         for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
99             Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
100             s.push_back(grid->snapper);
101         }
102     }
104     return s;
107 /**
108  * \brief Return true if any snapping might occur, whether its to grids, guides or objects
109  *
110  * Each snapper instance handles its own snapping target, e.g. grids, guides or
111  * objects. This method iterates through all these snapper instances and returns
112  * true if any of the snappers might possible snap, considering only the relevant
113  * snapping preferences.
114  *
115  * \return true if one of the snappers will try to snap to something.
116  */
118 bool SnapManager::someSnapperMightSnap() const
120     if ( !snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally() ) {
121         return false;
122     }
124     SnapperList const s = getSnappers();
125     SnapperList::const_iterator i = s.begin();
126     while (i != s.end() && (*i)->ThisSnapperMightSnap() == false) {
127         i++;
128     }
130     return (i != s.end());
133 /**
134  * \return true if one of the grids might be snapped to.
135  */
137 bool SnapManager::gridSnapperMightSnap() const
139     if ( !snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally() ) {
140         return false;
141     }
143     SnapperList const s = getGridSnappers();
144     SnapperList::const_iterator i = s.begin();
145     while (i != s.end() && (*i)->ThisSnapperMightSnap() == false) {
146         i++;
147     }
149     return (i != s.end());
152 /**
153  *  \brief Try to snap a point to grids, guides or objects.
154  *
155  *  Try to snap a point to grids, guides or objects, in two degrees-of-freedom,
156  *  i.e. snap in any direction on the two dimensional canvas to the nearest
157  *  snap target. freeSnapReturnByRef() is equal in snapping behavior to
158  *  freeSnap(), but the former returns the snapped point trough the referenced
159  *  parameter p. This parameter p initially contains the position of the snap
160  *  source and will we overwritten by the target position if snapping has occurred.
161  *  This makes snapping transparent to the calling code. If this is not desired
162  *  because either the calling code must know whether snapping has occurred, or
163  *  because the original position should not be touched, then freeSnap() should be
164  *  called instead.
165  *
166  *  PS:
167  *  1) SnapManager::setup() must have been called before calling this method,
168  *  but only once for a set of points
169  *  2) Only to be used when a single source point is to be snapped; it assumes
170  *  that source_num = 0, which is inefficient when snapping sets our source points
171  *
172  *  \param p Current position of the snap source; will be overwritten by the position of the snap target if snapping has occurred
173  *  \param source_type Detailed description of the source type, will be used by the snap indicator
174  *  \param bbox_to_snap Bounding box hulling the set of points, all from the same selection and having the same transformation
175  */
177 void SnapManager::freeSnapReturnByRef(Geom::Point &p,
178                                       Inkscape::SnapSourceType const source_type,
179                                       Geom::OptRect const &bbox_to_snap) const
181     Inkscape::SnappedPoint const s = freeSnap(Inkscape::SnapCandidatePoint(p, source_type), bbox_to_snap);
182     s.getPoint(p);
186 /**
187  *  \brief Try to snap a point to grids, guides or objects.
188  *
189  *  Try to snap a point to grids, guides or objects, in two degrees-of-freedom,
190  *  i.e. snap in any direction on the two dimensional canvas to the nearest
191  *  snap target. freeSnap() is equal in snapping behavior to
192  *  freeSnapReturnByRef(). Please read the comments of the latter for more details
193  *
194  *  PS: SnapManager::setup() must have been called before calling this method,
195  *  but only once for a set of points
196  *
197  *  \param p Source point to be snapped
198  *  \param bbox_to_snap Bounding box hulling the set of points, all from the same selection and having the same transformation
199  *  \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics
200  */
203 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::SnapCandidatePoint const &p,
204                                              Geom::OptRect const &bbox_to_snap) const
206     if (!someSnapperMightSnap()) {
207         return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, false);
208     }
210     SnappedConstraints sc;
211     SnapperList const snappers = getSnappers();
213     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
214         (*i)->freeSnap(sc, p, bbox_to_snap, &_items_to_ignore, _unselected_nodes);
215     }
217     return findBestSnap(p, sc, false);
220 void SnapManager::preSnap(Inkscape::SnapCandidatePoint const &p)
222     // setup() must have been called before calling this method!
224     if (_snapindicator) {
225         _snapindicator = false; // prevent other methods from drawing a snap indicator; we want to control this here
226         Inkscape::SnappedPoint s = freeSnap(p);
227         g_assert(_desktop != NULL);
228         if (s.getSnapped()) {
229             _desktop->snapindicator->set_new_snaptarget(s, true);
230         } else {
231             _desktop->snapindicator->remove_snaptarget(true);
232         }
233         _snapindicator = true; // restore the original value
234     }
237 /**
238  * \brief Snap to the closest multiple of a grid pitch
239  *
240  * When pasting, we would like to snap to the grid. Problem is that we don't know which
241  * nodes were aligned to the grid at the time of copying, so we don't know which nodes
242  * to snap. If we'd snap an unaligned node to the grid, previously aligned nodes would
243  * become unaligned. That's undesirable. Instead we will make sure that the offset
244  * between the source and its pasted copy is a multiple of the grid pitch. If the source
245  * was aligned, then the copy will therefore also be aligned.
246  *
247  * PS: Whether we really find a multiple also depends on the snapping range! Most users
248  * will have "always snap" enabled though, in which case a multiple will always be found.
249  * PS2: When multiple grids are present then the result will become ambiguous. There is no
250  * way to control to which grid this method will snap.
251  *
252  * \param t Vector that represents the offset of the pasted copy with respect to the original
253  * \return Offset vector after snapping to the closest multiple of a grid pitch
254  */
256 Geom::Point SnapManager::multipleOfGridPitch(Geom::Point const &t, Geom::Point const &origin)
258     if (!snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally())
259         return t;
261     if (_desktop && _desktop->gridsEnabled()) {
262         bool success = false;
263         Geom::Point nearest_multiple;
264         Geom::Coord nearest_distance = NR_HUGE;
265         Inkscape::SnappedPoint bestSnappedPoint(t);
267         // It will snap to the grid for which we find the closest snap. This might be a different
268         // grid than to which the objects were initially aligned. I don't see an easy way to fix
269         // this, so when using multiple grids one can get unexpected results
271         // Cannot use getGridSnappers() because we need both the grids AND their snappers
272         // Therefore we iterate through all grids manually
273         for (GSList const *l = _named_view->grids; l != NULL; l = l->next) {
274             Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
275             const Inkscape::Snapper* snapper = grid->snapper;
276             if (snapper && snapper->ThisSnapperMightSnap()) {
277                 // To find the nearest multiple of the grid pitch for a given translation t, we
278                 // will use the grid snapper. Simply snapping the value t to the grid will do, but
279                 // only if the origin of the grid is at (0,0). If it's not then compensate for this
280                 // in the translation t
281                 Geom::Point const t_offset = t + grid->origin;
282                 SnappedConstraints sc;
283                 // Only the first three parameters are being used for grid snappers
284                 snapper->freeSnap(sc, Inkscape::SnapCandidatePoint(t_offset, Inkscape::SNAPSOURCE_GRID_PITCH),Geom::OptRect(), NULL, NULL);
285                 // Find the best snap for this grid, including intersections of the grid-lines
286                 bool old_val = _snapindicator;
287                 _snapindicator = false;
288                 Inkscape::SnappedPoint s = findBestSnap(Inkscape::SnapCandidatePoint(t_offset, Inkscape::SNAPSOURCE_GRID_PITCH), sc, false, false, true);
289                 _snapindicator = old_val;
290                 if (s.getSnapped() && (s.getSnapDistance() < nearest_distance)) {
291                     // use getSnapDistance() instead of getWeightedDistance() here because the pointer's position
292                     // doesn't tell us anything about which node to snap
293                     success = true;
294                     nearest_multiple = s.getPoint() - to_2geom(grid->origin);
295                     nearest_distance = s.getSnapDistance();
296                     bestSnappedPoint = s;
297                 }
298             }
299         }
301         if (success) {
302             bestSnappedPoint.setPoint(origin + nearest_multiple);
303             _desktop->snapindicator->set_new_snaptarget(bestSnappedPoint);
304             return nearest_multiple;
305         }
306     }
308     return t;
311 /**
312  *  \brief Try to snap a point along a constraint line to grids, guides or objects.
313  *
314  *  Try to snap a point to grids, guides or objects, in only one degree-of-freedom,
315  *  i.e. snap in a specific direction on the two dimensional canvas to the nearest
316  *  snap target.
317  *
318  *  constrainedSnapReturnByRef() is equal in snapping behavior to
319  *  constrainedSnap(), but the former returns the snapped point trough the referenced
320  *  parameter p. This parameter p initially contains the position of the snap
321  *  source and will we overwritten by the target position if snapping has occurred.
322  *  This makes snapping transparent to the calling code. If this is not desired
323  *  because either the calling code must know whether snapping has occurred, or
324  *  because the original position should not be touched, then constrainedSnap() should
325  *  be called instead.
326  *
327  *  PS:
328  *  1) SnapManager::setup() must have been called before calling this method,
329  *  but only once for a set of points
330  *  2) Only to be used when a single source point is to be snapped; it assumes
331  *  that source_num = 0, which is inefficient when snapping sets our source points
333  *
334  *  \param p Current position of the snap source; will be overwritten by the position of the snap target if snapping has occurred
335  *  \param source_type Detailed description of the source type, will be used by the snap indicator
336  *  \param constraint The direction or line along which snapping must occur
337  *  \param bbox_to_snap Bounding box hulling the set of points, all from the same selection and having the same transformation
338  */
340 void SnapManager::constrainedSnapReturnByRef(Geom::Point &p,
341                                              Inkscape::SnapSourceType const source_type,
342                                              Inkscape::Snapper::SnapConstraint const &constraint,
343                                              Geom::OptRect const &bbox_to_snap) const
345     Inkscape::SnappedPoint const s = constrainedSnap(Inkscape::SnapCandidatePoint(p, source_type, 0), constraint, bbox_to_snap);
346     s.getPoint(p);
349 /**
350  *  \brief Try to snap a point along a constraint line to grids, guides or objects.
351  *
352  *  Try to snap a point to grids, guides or objects, in only one degree-of-freedom,
353  *  i.e. snap in a specific direction on the two dimensional canvas to the nearest
354  *  snap target. constrainedSnap is equal in snapping behavior to
355  *  constrainedSnapReturnByRef(). Please read the comments of the latter for more details.
356  *
357  *  PS: SnapManager::setup() must have been called before calling this method,
358  *  but only once for a set of points
359  *
360  *  \param p Source point to be snapped
361  *  \param constraint The direction or line along which snapping must occur
362  *  \param bbox_to_snap Bounding box hulling the set of points, all from the same selection and having the same transformation
363  */
365 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapCandidatePoint const &p,
366                                                     Inkscape::Snapper::SnapConstraint const &constraint,
367                                                     Geom::OptRect const &bbox_to_snap) const
369     // First project the mouse pointer onto the constraint
370     Geom::Point pp = constraint.projection(p.getPoint());
372     Inkscape::SnappedPoint no_snap = Inkscape::SnappedPoint(pp, p.getSourceType(), p.getSourceNum(), Inkscape::SNAPTARGET_CONSTRAINT, NR_HUGE, 0, false, true, false);
374     if (!someSnapperMightSnap()) {
375         // Always return point on constraint
376         return no_snap;
377     }
379     SnappedConstraints sc;
380     SnapperList const snappers = getSnappers();
381     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
382         (*i)->constrainedSnap(sc, p, bbox_to_snap, constraint, &_items_to_ignore, _unselected_nodes);
383     }
385     Inkscape::SnappedPoint result = findBestSnap(p, sc, true);
387     if (result.getSnapped()) {
388         // only change the snap indicator if we really snapped to something
389         if (_snapindicator && _desktop) {
390             _desktop->snapindicator->set_new_snaptarget(result);
391         }
392         return result;
393     }
394     return no_snap;
397 /* See the documentation for constrainedSnap() directly above for more details.
398  * The difference is that multipleConstrainedSnaps() will take a list of constraints instead of a single one,
399  * and will try to snap the SnapCandidatePoint to all of the provided constraints and see which one fits best
400  *  \param p Source point to be snapped
401  *  \param constraints List of directions or lines along which snapping must occur
402  *  \param bbox_to_snap Bounding box hulling the set of points, all from the same selection and having the same transformation
403  */
406 Inkscape::SnappedPoint SnapManager::multipleConstrainedSnaps(Inkscape::SnapCandidatePoint const &p,
407                                                     std::vector<Inkscape::Snapper::SnapConstraint> const &constraints,
408                                                     Geom::OptRect const &bbox_to_snap) const
411     Inkscape::SnappedPoint no_snap = Inkscape::SnappedPoint(p.getPoint(), p.getSourceType(), p.getSourceNum(), Inkscape::SNAPTARGET_CONSTRAINT, NR_HUGE, 0, false, true, false);
412     if (constraints.size() == 0) {
413         return no_snap;
414     }
416     SnappedConstraints sc;
417     SnapperList const snappers = getSnappers();
418     std::vector<Geom::Point> projections;
419     bool snapping_is_futile = !someSnapperMightSnap();
421     // Iterate over the constraints
422     for (std::vector<Inkscape::Snapper::SnapConstraint>::const_iterator c = constraints.begin(); c != constraints.end(); c++) {
423         // Project the mouse pointer onto the constraint; In case we don't snap then we will
424         // return the projection onto the constraint, such that the constraint is always enforced
425         Geom::Point pp = (*c).projection(p.getPoint());
426         projections.push_back(pp);
427         // Try to snap to the constraint
428         if (!snapping_is_futile) {
429             for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
430                 (*i)->constrainedSnap(sc, p, bbox_to_snap, *c, &_items_to_ignore,_unselected_nodes);
431             }
432         }
433     }
435     Inkscape::SnappedPoint result = findBestSnap(p, sc, true);
437     if (result.getSnapped()) {
438         // only change the snap indicator if we really snapped to something
439         if (_snapindicator && _desktop) {
440             _desktop->snapindicator->set_new_snaptarget(result);
441         }
442         return result;
443     }
445     // So we didn't snap, but we still need to return a point on one of the constraints
446     // Find out which of the constraints yielded the closest projection of point p
447     no_snap.setPoint(projections.front());
448     for (std::vector<Geom::Point>::iterator pp = projections.begin(); pp != projections.end(); pp++) {
449         if (pp != projections.begin()) {
450             if (Geom::L2(*pp - p.getPoint()) < Geom::L2(no_snap.getPoint() - p.getPoint())) {
451                 no_snap.setPoint(*pp);
452             }
453         }
454     }
456     return no_snap;
459 /**
460  *  \brief Try to snap a point of a guide to another guide or to a node
461  *
462  *  Try to snap a point of a guide to another guide or to a node in two degrees-
463  *  of-freedom, i.e. snap in any direction on the two dimensional canvas to the
464  *  nearest snap target. This method is used when dragging or rotating a guide
465  *
466  *  PS: SnapManager::setup() must have been called before calling this method,
467  *
468  *  \param p Current position of the point on the guide that is to be snapped; will be overwritten by the position of the snap target if snapping has occurred
469  *  \param guide_normal Vector normal to the guide line
470  */
471 void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal, SPGuideDragType drag_type) const
473     if (!snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally()) {
474         return;
475     }
477     if (!(object.ThisSnapperMightSnap() || snapprefs.getSnapToGuides())) {
478         return;
479     }
481     Inkscape::SnapCandidatePoint candidate(p, Inkscape::SNAPSOURCE_GUIDE_ORIGIN);
482     if (drag_type == SP_DRAG_ROTATE) {
483         candidate = Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_GUIDE);
484     }
486     // Snap to nodes
487     SnappedConstraints sc;
488     if (object.ThisSnapperMightSnap()) {
489         object.guideFreeSnap(sc, p, guide_normal);
490     }
492     // Snap to guides & grid lines
493     SnapperList snappers = getGridSnappers();
494     snappers.push_back(&guide);
495     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
496         (*i)->freeSnap(sc, candidate, Geom::OptRect(), NULL, NULL);
497     }
499     Inkscape::SnappedPoint const s = findBestSnap(candidate, sc, false, false);
501     s.getPoint(p);
504 /**
505  *  \brief Try to snap a point on a guide to the intersection with another guide or a path
506  *
507  *  Try to snap a point on a guide to the intersection of that guide with another
508  *  guide or with a path. The snapped point will lie somewhere on the guide-line,
509  *  making this is a constrained snap, i.e. in only one degree-of-freedom.
510  *  This method is used when dragging the origin of the guide along the guide itself.
511  *
512  *  PS: SnapManager::setup() must have been called before calling this method,
513  *
514  *  \param p Current position of the point on the guide that is to be snapped; will be overwritten by the position of the snap target if snapping has occurred
515  *  \param guide_normal Vector normal to the guide line
516  */
518 void SnapManager::guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline) const
520     if (!snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally()) {
521         return;
522     }
524     if (!(object.ThisSnapperMightSnap() || snapprefs.getSnapToGuides())) {
525         return;
526     }
528     Inkscape::SnapCandidatePoint candidate(p, Inkscape::SNAPSOURCE_GUIDE_ORIGIN, Inkscape::SNAPTARGET_UNDEFINED);
530     // Snap to nodes or paths
531     SnappedConstraints sc;
532     Inkscape::Snapper::SnapConstraint cl(guideline.point_on_line, Geom::rot90(guideline.normal_to_line));
533     if (object.ThisSnapperMightSnap()) {
534         object.constrainedSnap(sc, candidate, Geom::OptRect(), cl, NULL, NULL);
535     }
537     // Snap to guides & grid lines
538     SnapperList snappers = getGridSnappers();
539     snappers.push_back(&guide);
540     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
541         (*i)->constrainedSnap(sc, candidate, Geom::OptRect(), cl, NULL, NULL);
542     }
544     Inkscape::SnappedPoint const s = findBestSnap(candidate, sc, false);
545     s.getPoint(p);
548 /**
549  *  \brief Method for snapping sets of points while they are being transformed
550  *
551  *  Method for snapping sets of points while they are being transformed, when using
552  *  for example the selector tool. This method is for internal use only, and should
553  *  not have to be called directly. Use freeSnapTransalation(), constrainedSnapScale(),
554  *  etc. instead.
555  *
556  *  This is what is being done in this method: transform each point, find out whether
557  *  a free snap or constrained snap is more appropriate, do the snapping, calculate
558  *  some metrics to quantify the snap "distance", and see if it's better than the
559  *  previous snap. Finally, the best ("nearest") snap from all these points is returned.
560  *
561  *  \param points Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source.
562  *  \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed).
563  *  \param constrained true if the snap is constrained, e.g. for stretching or for purely horizontal translation.
564  *  \param constraint The direction or line along which snapping must occur, if 'constrained' is true; otherwise undefined.
565  *  \param transformation_type Type of transformation to apply to points before trying to snap them.
566  *  \param transformation Description of the transformation; details depend on the type.
567  *  \param origin Origin of the transformation, if applicable.
568  *  \param dim Dimension to which the transformation applies, if applicable.
569  *  \param uniform true if the transformation should be uniform; only applicable for stretching and scaling.
570  *  \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics.
571  */
573 Inkscape::SnappedPoint SnapManager::_snapTransformed(
574     std::vector<Inkscape::SnapCandidatePoint> const &points,
575     Geom::Point const &pointer,
576     bool constrained,
577     Inkscape::Snapper::SnapConstraint const &constraint,
578     Transformation transformation_type,
579     Geom::Point const &transformation,
580     Geom::Point const &origin,
581     Geom::Dim2 dim,
582     bool uniform) const
584     /* We have a list of points, which we are proposing to transform in some way.  We need to see
585     ** if any of these points, when transformed, snap to anything.  If they do, we return the
586     ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
587     */
589     /* Quick check to see if we have any snappers that are enabled
590     ** Also used to globally disable all snapping
591     */
592     if (someSnapperMightSnap() == false || points.size() == 0) {
593         return Inkscape::SnappedPoint(pointer);
594     }
596     std::vector<Inkscape::SnapCandidatePoint> transformed_points;
597     Geom::Rect bbox;
599     long source_num = 0;
600     for (std::vector<Inkscape::SnapCandidatePoint>::const_iterator i = points.begin(); i != points.end(); i++) {
602         /* Work out the transformed version of this point */
603         Geom::Point transformed = _transformPoint(*i, transformation_type, transformation, origin, dim, uniform);
605         // add the current transformed point to the box hulling all transformed points
606         if (i == points.begin()) {
607             bbox = Geom::Rect(transformed, transformed);
608         } else {
609             bbox.expandTo(transformed);
610         }
612         transformed_points.push_back(Inkscape::SnapCandidatePoint(transformed, (*i).getSourceType(), source_num));
613         source_num++;
614     }
616     /* The current best transformation */
617     Geom::Point best_transformation = transformation;
619     /* The current best metric for the best transformation; lower is better, NR_HUGE
620     ** means that we haven't snapped anything.
621     */
622     Geom::Point best_scale_metric(NR_HUGE, NR_HUGE);
623     Inkscape::SnappedPoint best_snapped_point;
624     g_assert(best_snapped_point.getAlwaysSnap() == false); // Check initialization of snapped point
625     g_assert(best_snapped_point.getAtIntersection() == false);
627     // Warnings for the devs
628     if (constrained && transformation_type == SCALE && !uniform) {
629         g_warning("Non-uniform constrained scaling is not supported!");
630     }
632     if (!constrained && transformation_type == ROTATE) {
633         // We do not yet allow for simultaneous rotation and scaling
634         g_warning("Unconstrained rotation is not supported!");
635     }
637     std::vector<Inkscape::SnapCandidatePoint>::iterator j = transformed_points.begin();
639     // std::cout << std::endl;
640     bool first_free_snap = true;
641     for (std::vector<Inkscape::SnapCandidatePoint>::const_iterator i = points.begin(); i != points.end(); i++) {
643         /* Snap it */
644         Inkscape::SnappedPoint snapped_point;
645         Inkscape::Snapper::SnapConstraint dedicated_constraint = constraint;
646         Geom::Point const b = ((*i).getPoint() - origin); // vector to original point (not the transformed point! required for rotations!)
648         if (constrained) {
649             if (((transformation_type == SCALE || transformation_type == STRETCH) && uniform)) {
650                 // When uniformly scaling, each point will have its own unique constraint line,
651                 // running from the scaling origin to the original untransformed point. We will
652                 // calculate that line here
653                 dedicated_constraint = Inkscape::Snapper::SnapConstraint(origin, b);
654             } else if (transformation_type == ROTATE) {
655                 Geom::Coord r = Geom::L2(b); // the radius of the circular constraint
656                 if (r < 1e-9) { // points too close to the rotation center will not move. Don't try to snap these
657                     // as they will always yield a perfect snap result if they're already snapped beforehand (e.g.
658                     // when the transformation center has been snapped to a grid intersection in the selector tool)
659                     continue; // skip this SnapCandidate and continue with the next one
660                     // PS1: Apparently we don't have to do this for skewing, but why?
661                     // PS2: We cannot easily filter these points upstream, e.g. in the grab() method (seltrans.cpp)
662                     // because the rotation center will change when pressing shift, and grab() won't be recalled.
663                     // Filtering could be done in handleRequest() (again in seltrans.cpp), by iterating through
664                     // the snap candidates. But hey, we're iterating here anyway.
665                 }
666                 dedicated_constraint = Inkscape::Snapper::SnapConstraint(origin, b, r);
667             } else if (transformation_type == STRETCH) { // when non-uniform stretching {
668                 dedicated_constraint = Inkscape::Snapper::SnapConstraint((*i).getPoint(), component_vectors[dim]);
669             } else if (transformation_type == TRANSLATE) {
670                 // When doing a constrained translation, all points will move in the same direction, i.e.
671                 // either horizontally or vertically. The lines along which they move are therefore all
672                 // parallel, but might not be colinear. Therefore we will have to specify the point through
673                 // which the constraint-line runs here, for each point individually. (we could also have done this
674                 // earlier on, e.g. in seltrans.cpp but we're being lazy there and don't want to add an iteration loop)
675                 dedicated_constraint = Inkscape::Snapper::SnapConstraint((*i).getPoint(), constraint.getDirection());
676             } // else: leave the original constraint, e.g. for skewing
677             snapped_point = constrainedSnap(*j, dedicated_constraint, bbox);
678         } else {
679             bool const c1 = fabs(b[Geom::X]) < 1e-6;
680             bool const c2 = fabs(b[Geom::Y]) < 1e-6;
681             if (transformation_type == SCALE && (c1 || c2) && !(c1 && c2)) {
682                 // When scaling, a point aligned either horizontally or vertically with the origin can only
683                 // move in that specific direction; therefore it should only snap in that direction, otherwise
684                 // we will get snapped points with an invalid transformation
685                 dedicated_constraint = Inkscape::Snapper::SnapConstraint(origin, component_vectors[c1]);
686                 snapped_point = constrainedSnap(*j, dedicated_constraint, bbox);
687             } else {
688                 // If we have a collection of SnapCandidatePoints, with mixed constrained snapping and free snapping
689                 // requirements, then freeSnap might never see the SnapCandidatePoint with source_num == 0. The freeSnap()
690                 // method in the object snapper depends on this, because only for source-num == 0 the target nodes will
691                 // be collected. Therefore we enforce that the first SnapCandidatePoint that is to be freeSnapped always
692                 // has source_num == 0;
693                 // TODO: This is a bit ugly so fix this; do we need sourcenum for anything else? if we don't then get rid
694                 // of it and explicitely communicate to the object snapper that this is a first point
695                 if (first_free_snap) {
696                     (*j).setSourceNum(0);
697                     first_free_snap = false;
698                 }
699                 snapped_point = freeSnap(*j, bbox);
700             }
701         }
702         // std::cout << "dist = " << snapped_point.getSnapDistance() << std::endl;
703         snapped_point.setPointerDistance(Geom::L2(pointer - (*i).getPoint()));
705         Geom::Point result;
707         if (snapped_point.getSnapped()) {
708             /* We snapped.  Find the transformation that describes where the snapped point has
709             ** ended up, and also the metric for this transformation.
710             */
711             Geom::Point const a = snapped_point.getPoint() - origin; // vector to snapped point
712             //Geom::Point const b = (*i - origin); // vector to original point
714             switch (transformation_type) {
715                 case TRANSLATE:
716                     result = snapped_point.getPoint() - (*i).getPoint();
717                     /* Consider the case in which a box is almost aligned with a grid in both
718                      * horizontal and vertical directions. The distance to the intersection of
719                      * the grid lines will always be larger then the distance to a single grid
720                      * line. If we prefer snapping to an intersection instead of to a single
721                      * grid line, then we cannot use "metric = Geom::L2(result)". Therefore the
722                      * snapped distance will be used as a metric. Please note that the snapped
723                      * distance is defined as the distance to the nearest line of the intersection,
724                      * and not to the intersection itself!
725                      */
726                     // Only for translations, the relevant metric will be the real snapped distance,
727                     // so we don't have to do anything special here
728                     break;
729                 case SCALE:
730                 {
731                     result = Geom::Point(NR_HUGE, NR_HUGE);
732                     // If this point *i is horizontally or vertically aligned with
733                     // the origin of the scaling, then it will scale purely in X or Y
734                     // We can therefore only calculate the scaling in this direction
735                     // and the scaling factor for the other direction should remain
736                     // untouched (unless scaling is uniform of course)
737                     for (int index = 0; index < 2; index++) {
738                         if (fabs(b[index]) > 1e-6) { // if SCALING CAN occur in this direction
739                             if (fabs(fabs(a[index]/b[index]) - fabs(transformation[index])) > 1e-12) { // if SNAPPING DID occur in this direction
740                                 result[index] = a[index] / b[index]; // then calculate it!
741                             }
742                             // we might leave result[1-index] = NR_HUGE
743                             // if scaling didn't occur in the other direction
744                         }
745                     }
746                     if (uniform) {
747                         if (fabs(result[0]) < fabs(result[1])) {
748                             result[1] = result[0];
749                         } else {
750                             result[0] = result[1];
751                         }
752                     }
753                     // Compare the resulting scaling with the desired scaling
754                     Geom::Point scale_metric = Geom::abs(result - transformation); // One or both of its components might be NR_HUGE
755                     snapped_point.setSnapDistance(std::min(scale_metric[0], scale_metric[1]));
756                     snapped_point.setSecondSnapDistance(std::max(scale_metric[0], scale_metric[1]));
757                     break;
758                 }
759                 case STRETCH:
760                     result = Geom::Point(NR_HUGE, NR_HUGE);
761                     if (fabs(b[dim]) > 1e-6) { // if STRETCHING will occur for this point
762                         result[dim] = a[dim] / b[dim];
763                         result[1-dim] = uniform ? result[dim] : 1;
764                     } else { // STRETCHING might occur for this point, but only when the stretching is uniform
765                         if (uniform && fabs(b[1-dim]) > 1e-6) {
766                            result[1-dim] = a[1-dim] / b[1-dim];
767                            result[dim] = result[1-dim];
768                         }
769                     }
770                     // Store the metric for this transformation as a virtual distance
771                     snapped_point.setSnapDistance(std::abs(result[dim] - transformation[dim]));
772                     snapped_point.setSecondSnapDistance(NR_HUGE);
773                     break;
774                 case SKEW:
775                     result[0] = (snapped_point.getPoint()[dim] - ((*i).getPoint())[dim]) / b[1 - dim]; // skew factor
776                     result[1] = transformation[1]; // scale factor
777                     // Store the metric for this transformation as a virtual distance
778                     snapped_point.setSnapDistance(std::abs(result[0] - transformation[0]));
779                     snapped_point.setSecondSnapDistance(NR_HUGE);
780                     break;
781                 case ROTATE:
782                     // a is vector to snapped point; b is vector to original point; now lets calculate angle between a and b
783                     result[0] = atan2(Geom::dot(Geom::rot90(b), a), Geom::dot(b, a));
784                     result[1] = result[1]; // how else should we store an angle in a point ;-)
785                     // Store the metric for this transformation as a virtual distance (we're storing an angle)
786                     snapped_point.setSnapDistance(std::abs(result[0] - transformation[0]));
787                     snapped_point.setSecondSnapDistance(NR_HUGE);
788                     break;
789                 default:
790                     g_assert_not_reached();
791             }
793             if (best_snapped_point.isOtherSnapBetter(snapped_point, true)) {
794                 best_transformation = result;
795                 best_snapped_point = snapped_point;
796             }
797         }
799         j++;
800     }
802     Geom::Coord best_metric;
803     if (transformation_type == SCALE) {
804         // When scaling, don't ever exit with one of scaling components set to NR_HUGE
805         for (int index = 0; index < 2; index++) {
806             if (best_transformation[index] == NR_HUGE) {
807                 if (uniform && best_transformation[1-index] < NR_HUGE) {
808                     best_transformation[index] = best_transformation[1-index];
809                 } else {
810                     best_transformation[index] = transformation[index];
811                 }
812             }
813         }
814     }
816     best_metric = best_snapped_point.getSnapDistance();
817     best_snapped_point.setTransformation(best_transformation);
818     // Using " < 1e6" instead of " < NR_HUGE" for catching some rounding errors
819     // These rounding errors might be caused by NRRects, see bug #1584301
820     best_snapped_point.setSnapDistance(best_metric < 1e6 ? best_metric : NR_HUGE);
821     return best_snapped_point;
825 /**
826  *  \brief Apply a translation to a set of points and try to snap freely in 2 degrees-of-freedom
827  *
828  *  \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source.
829  *  \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed).
830  *  \param tr Proposed translation; the final translation can only be calculated after snapping has occurred
831  *  \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics.
832  */
834 Inkscape::SnappedPoint SnapManager::freeSnapTranslate(std::vector<Inkscape::SnapCandidatePoint> const &p,
835                                                         Geom::Point const &pointer,
836                                                         Geom::Point const &tr) const
838     if (p.size() == 1) {
839         Geom::Point pt = _transformPoint(p.at(0), TRANSLATE, tr, Geom::Point(0,0), Geom::X, false);
840         _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType()));
841     }
845     return _snapTransformed(p, pointer, false, Geom::Point(0,0), TRANSLATE, tr, Geom::Point(0,0), Geom::X, false);
848 /**
849  *  \brief Apply a translation to a set of points and try to snap along a constraint
850  *
851  *  \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source.
852  *  \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed).
853  *  \param constraint The direction or line along which snapping must occur.
854  *  \param tr Proposed translation; the final translation can only be calculated after snapping has occurred.
855  *  \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics.
856  */
858 Inkscape::SnappedPoint SnapManager::constrainedSnapTranslate(std::vector<Inkscape::SnapCandidatePoint> const &p,
859                                                                Geom::Point const &pointer,
860                                                                Inkscape::Snapper::SnapConstraint const &constraint,
861                                                                Geom::Point const &tr) const
863     if (p.size() == 1) {
864         Geom::Point pt = _transformPoint(p.at(0), TRANSLATE, tr, Geom::Point(0,0), Geom::X, false);
865         _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType()));
866     }
868     return _snapTransformed(p, pointer, true, constraint, TRANSLATE, tr, Geom::Point(0,0), Geom::X, false);
872 /**
873  *  \brief Apply a scaling to a set of points and try to snap freely in 2 degrees-of-freedom
874  *
875  *  \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source.
876  *  \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed).
877  *  \param s Proposed scaling; the final scaling can only be calculated after snapping has occurred
878  *  \param o Origin of the scaling
879  *  \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics.
880  */
882 Inkscape::SnappedPoint SnapManager::freeSnapScale(std::vector<Inkscape::SnapCandidatePoint> const &p,
883                                                   Geom::Point const &pointer,
884                                                   Geom::Scale const &s,
885                                                   Geom::Point const &o) const
887     if (p.size() == 1) {
888         Geom::Point pt = _transformPoint(p.at(0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, false);
889         _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType()));
890     }
892     return _snapTransformed(p, pointer, false, Geom::Point(0,0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, false);
896 /**
897  *  \brief Apply a scaling to a set of points and snap such that the aspect ratio of the selection is preserved
898  *
899  *  \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source.
900  *  \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed).
901  *  \param s Proposed scaling; the final scaling can only be calculated after snapping has occurred
902  *  \param o Origin of the scaling
903  *  \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics.
904  */
906 Inkscape::SnappedPoint SnapManager::constrainedSnapScale(std::vector<Inkscape::SnapCandidatePoint> const &p,
907                                                          Geom::Point const &pointer,
908                                                          Geom::Scale const &s,
909                                                          Geom::Point const &o) const
911     // When constrained scaling, only uniform scaling is supported.
912     if (p.size() == 1) {
913         Geom::Point pt = _transformPoint(p.at(0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, true);
914         _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType()));
915     }
917     return _snapTransformed(p, pointer, true, Geom::Point(0,0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, true);
920 /**
921  *  \brief Apply a stretch to a set of points and snap such that the direction of the stretch is preserved
922  *
923  *  \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source.
924  *  \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed).
925  *  \param s Proposed stretch; the final stretch can only be calculated after snapping has occurred
926  *  \param o Origin of the stretching
927  *  \param d Dimension in which to apply proposed stretch.
928  *  \param u true if the stretch should be uniform (i.e. to be applied equally in both dimensions)
929  *  \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics.
930  */
932 Inkscape::SnappedPoint SnapManager::constrainedSnapStretch(std::vector<Inkscape::SnapCandidatePoint> const &p,
933                                                             Geom::Point const &pointer,
934                                                             Geom::Coord const &s,
935                                                             Geom::Point const &o,
936                                                             Geom::Dim2 d,
937                                                             bool u) const
939     if (p.size() == 1) {
940         Geom::Point pt = _transformPoint(p.at(0), STRETCH, Geom::Point(s, s), o, d, u);
941         _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType()));
942     }
944     return _snapTransformed(p, pointer, true, Geom::Point(0,0), STRETCH, Geom::Point(s, s), o, d, u);
947 /**
948  *  \brief Apply a skew to a set of points and snap such that the direction of the skew is preserved
949  *
950  *  \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source.
951  *  \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed).
952  *  \param constraint The direction or line along which snapping must occur.
953  *  \param s Proposed skew; the final skew can only be calculated after snapping has occurred
954  *  \param o Origin of the proposed skew
955  *  \param d Dimension in which to apply proposed skew.
956  *  \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics.
957  */
959 Inkscape::SnappedPoint SnapManager::constrainedSnapSkew(std::vector<Inkscape::SnapCandidatePoint> const &p,
960                                                  Geom::Point const &pointer,
961                                                  Inkscape::Snapper::SnapConstraint const &constraint,
962                                                  Geom::Point const &s,
963                                                  Geom::Point const &o,
964                                                  Geom::Dim2 d) const
966     // "s" contains skew factor in s[0], and scale factor in s[1]
968     // Snapping the nodes of the bounding box of a selection that is being transformed, will only work if
969     // the transformation of the bounding box is equal to the transformation of the individual nodes. This is
970     // NOT the case for example when rotating or skewing. The bounding box itself cannot possibly rotate or skew,
971     // so it's corners have a different transformation. The snappers cannot handle this, therefore snapping
972     // of bounding boxes is not allowed here.
973     if (p.size() > 0) {
974         g_assert(!(p.at(0).getSourceType() & Inkscape::SNAPSOURCE_BBOX_CATEGORY));
975     }
977     if (p.size() == 1) {
978         Geom::Point pt = _transformPoint(p.at(0), SKEW, s, o, d, false);
979         _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType()));
980     }
982     return _snapTransformed(p, pointer, true, constraint, SKEW, s, o, d, false);
985 /**
986  *  \brief Apply a rotation to a set of points and snap, without scaling
987  *
988  *  \param p Collection of points to snap (snap sources), at their untransformed position, all points undergoing the same transformation. Paired with an identifier of the type of the snap source.
989  *  \param pointer Location of the mouse pointer at the time dragging started (i.e. when the selection was still untransformed).
990  *  \param angle Proposed rotation (in radians); the final rotation can only be calculated after snapping has occurred
991  *  \param o Origin of the rotation
992  *  \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics.
993  */
995 Inkscape::SnappedPoint SnapManager::constrainedSnapRotate(std::vector<Inkscape::SnapCandidatePoint> const &p,
996                                                     Geom::Point const &pointer,
997                                                     Geom::Coord const &angle,
998                                                     Geom::Point const &o) const
1000     // Snapping the nodes of the bounding box of a selection that is being transformed, will only work if
1001     // the transformation of the bounding box is equal to the transformation of the individual nodes. This is
1002     // NOT the case for example when rotating or skewing. The bounding box itself cannot possibly rotate or skew,
1003     // so it's corners have a different transformation. The snappers cannot handle this, therefore snapping
1004     // of bounding boxes is not allowed here.
1006     if (p.size() == 1) {
1007         Geom::Point pt = _transformPoint(p.at(0), ROTATE, Geom::Point(angle, angle), o, Geom::X, false);
1008         _displaySnapsource(Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType()));
1009     }
1011     return _snapTransformed(p, pointer, true, Geom::Point(0,0), ROTATE, Geom::Point(angle, angle), o, Geom::X, false);
1015 /**
1016  * \brief Given a set of possible snap targets, find the best target (which is not necessarily
1017  * also the nearest target), and show the snap indicator if requested
1018  *
1019  * \param p Source point to be snapped
1020  * \param sc A structure holding all snap targets that have been found so far
1021  * \param constrained True if the snap is constrained, e.g. for stretching or for purely horizontal translation.
1022  * \param noCurves If true, then do consider snapping to intersections of curves, but not to the curves themselves
1023  * \param allowOffScreen If true, then snapping to points which are off the screen is allowed (needed for example when pasting to the grid)
1024  * \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics
1025  */
1027 Inkscape::SnappedPoint SnapManager::findBestSnap(Inkscape::SnapCandidatePoint const &p,
1028                                                  SnappedConstraints const &sc,
1029                                                  bool constrained,
1030                                                  bool noCurves,
1031                                                  bool allowOffScreen) const
1033     g_assert(_desktop != NULL);
1035     /*
1036     std::cout << "Type and number of snapped constraints: " << std::endl;
1037     std::cout << "  Points      : " << sc.points.size() << std::endl;
1038     std::cout << "  Lines       : " << sc.lines.size() << std::endl;
1039     std::cout << "  Grid lines  : " << sc.grid_lines.size()<< std::endl;
1040     std::cout << "  Guide lines : " << sc.guide_lines.size()<< std::endl;
1041     std::cout << "  Curves      : " << sc.curves.size()<< std::endl;
1042     */
1044     // Store all snappoints
1045     std::list<Inkscape::SnappedPoint> sp_list;
1047     // search for the closest snapped point
1048     Inkscape::SnappedPoint closestPoint;
1049     if (getClosestSP(sc.points, closestPoint)) {
1050         sp_list.push_back(closestPoint);
1051     }
1053     // search for the closest snapped curve
1054     if (!noCurves) {
1055         Inkscape::SnappedCurve closestCurve;
1056         if (getClosestCurve(sc.curves, closestCurve)) {
1057             sp_list.push_back(Inkscape::SnappedPoint(closestCurve));
1058         }
1059     }
1061     if (snapprefs.getSnapIntersectionCS()) {
1062         // search for the closest snapped intersection of curves
1063         Inkscape::SnappedPoint closestCurvesIntersection;
1064         if (getClosestIntersectionCS(sc.curves, p.getPoint(), closestCurvesIntersection, _desktop->dt2doc())) {
1065             closestCurvesIntersection.setSource(p.getSourceType());
1066             sp_list.push_back(closestCurvesIntersection);
1067         }
1068     }
1070     // search for the closest snapped grid line
1071     Inkscape::SnappedLine closestGridLine;
1072     if (getClosestSL(sc.grid_lines, closestGridLine)) {
1073         sp_list.push_back(Inkscape::SnappedPoint(closestGridLine));
1074     }
1076     // search for the closest snapped guide line
1077     Inkscape::SnappedLine closestGuideLine;
1078     if (getClosestSL(sc.guide_lines, closestGuideLine)) {
1079         sp_list.push_back(Inkscape::SnappedPoint(closestGuideLine));
1080     }
1082     // When freely snapping to a grid/guide/path, only one degree of freedom is eliminated
1083     // Therefore we will try get fully constrained by finding an intersection with another grid/guide/path
1085     // When doing a constrained snap however, we're already at an intersection of the constrained line and
1086     // the grid/guide/path we're snapping to. This snappoint is therefore fully constrained, so there's
1087     // no need to look for additional intersections
1088     if (!constrained) {
1089         // search for the closest snapped intersection of grid lines
1090         Inkscape::SnappedPoint closestGridPoint;
1091         if (getClosestIntersectionSL(sc.grid_lines, closestGridPoint)) {
1092             closestGridPoint.setSource(p.getSourceType());
1093             closestGridPoint.setTarget(Inkscape::SNAPTARGET_GRID_INTERSECTION);
1094             sp_list.push_back(closestGridPoint);
1095         }
1097         // search for the closest snapped intersection of guide lines
1098         Inkscape::SnappedPoint closestGuidePoint;
1099         if (getClosestIntersectionSL(sc.guide_lines, closestGuidePoint)) {
1100             closestGuidePoint.setSource(p.getSourceType());
1101             closestGuidePoint.setTarget(Inkscape::SNAPTARGET_GUIDE_INTERSECTION);
1102             sp_list.push_back(closestGuidePoint);
1103         }
1105         // search for the closest snapped intersection of grid with guide lines
1106         if (snapprefs.getSnapIntersectionGG()) {
1107             Inkscape::SnappedPoint closestGridGuidePoint;
1108             if (getClosestIntersectionSL(sc.grid_lines, sc.guide_lines, closestGridGuidePoint)) {
1109                 closestGridGuidePoint.setSource(p.getSourceType());
1110                 closestGridGuidePoint.setTarget(Inkscape::SNAPTARGET_GRID_GUIDE_INTERSECTION);
1111                 sp_list.push_back(closestGridGuidePoint);
1112             }
1113         }
1114     }
1116     // now let's see which snapped point gets a thumbs up
1117     Inkscape::SnappedPoint bestSnappedPoint(p.getPoint());
1118     // std::cout << "Finding the best snap..." << std::endl;
1119     for (std::list<Inkscape::SnappedPoint>::const_iterator i = sp_list.begin(); i != sp_list.end(); i++) {
1120         // std::cout << "sp = " << (*i).getPoint() << " | source = " << (*i).getSource() << " | target = " << (*i).getTarget();
1121         bool onScreen = _desktop->get_display_area().contains((*i).getPoint());
1122         if (onScreen || allowOffScreen) { // Only snap to points which are not off the screen
1123             if ((*i).getSnapDistance() <= (*i).getTolerance()) { // Only snap to points within snapping range
1124                 // if it's the first point, or if it is closer than the best snapped point so far
1125                 if (i == sp_list.begin() || bestSnappedPoint.isOtherSnapBetter(*i, false)) {
1126                     // then prefer this point over the previous one
1127                     bestSnappedPoint = *i;
1128                 }
1129             }
1130         }
1131         // std::cout << std::endl;
1132     }
1134     // Update the snap indicator, if requested
1135     if (_snapindicator) {
1136         if (bestSnappedPoint.getSnapped()) {
1137             _desktop->snapindicator->set_new_snaptarget(bestSnappedPoint);
1138         } else {
1139             _desktop->snapindicator->remove_snaptarget();
1140         }
1141     }
1143     // std::cout << "findBestSnap = " << bestSnappedPoint.getPoint() << " | dist = " << bestSnappedPoint.getSnapDistance() << std::endl;
1144     return bestSnappedPoint;
1147 /// Convenience shortcut when there is only one item to ignore
1148 void SnapManager::setup(SPDesktop const *desktop,
1149                         bool snapindicator,
1150                         SPItem const *item_to_ignore,
1151                         std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes,
1152                         SPGuide *guide_to_ignore)
1154     g_assert(desktop != NULL);
1155     if (_desktop != NULL) {
1156         g_warning("The snapmanager has been set up before, but unSetup() hasn't been called afterwards. It possibly held invalid pointers");
1157     }
1158     _items_to_ignore.clear();
1159     _items_to_ignore.push_back(item_to_ignore);
1160     _desktop = desktop;
1161     _snapindicator = snapindicator;
1162     _unselected_nodes = unselected_nodes;
1163     _guide_to_ignore = guide_to_ignore;
1164     _rotation_center_source_item = NULL;
1167 /**
1168  * \brief Prepare the snap manager for the actual snapping, which includes building a list of snap targets
1169  * to ignore and toggling the snap indicator
1170  *
1171  * There are two overloaded setup() methods, of which the other one only allows for a single item to be ignored
1172  * whereas this one will take a list of items to ignore
1173  *
1174  * \param desktop Reference to the desktop to which this snap manager is attached
1175  * \param snapindicator If true then a snap indicator will be displayed automatically (when enabled in the preferences)
1176  * \param items_to_ignore These items will not be snapped to, e.g. the items that are currently being dragged. This avoids "self-snapping"
1177  * \param unselected_nodes Stationary nodes of the path that is currently being edited in the node tool and
1178  * that can be snapped too. Nodes not in this list will not be snapped to, to avoid "self-snapping". Of each
1179  * unselected node both the position (Geom::Point) and the type (Inkscape::SnapTargetType) will be stored
1180  * \param guide_to_ignore Guide that is currently being dragged and should not be snapped to
1181  */
1183 void SnapManager::setup(SPDesktop const *desktop,
1184                         bool snapindicator,
1185                         std::vector<SPItem const *> &items_to_ignore,
1186                         std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes,
1187                         SPGuide *guide_to_ignore)
1189     g_assert(desktop != NULL);
1190     if (_desktop != NULL) {
1191         g_warning("The snapmanager has been set up before, but unSetup() hasn't been called afterwards. It possibly held invalid pointers");
1192     }
1193     _items_to_ignore = items_to_ignore;
1194     _desktop = desktop;
1195     _snapindicator = snapindicator;
1196     _unselected_nodes = unselected_nodes;
1197     _guide_to_ignore = guide_to_ignore;
1198     _rotation_center_source_item = NULL;
1201 /// Setup, taking the list of items to ignore from the desktop's selection.
1202 void SnapManager::setupIgnoreSelection(SPDesktop const *desktop,
1203                                       bool snapindicator,
1204                                       std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes,
1205                                       SPGuide *guide_to_ignore)
1207     g_assert(desktop != NULL);
1208     if (_desktop != NULL) {
1209         // Someone has been naughty here! This is dangerous
1210         g_warning("The snapmanager has been set up before, but unSetup() hasn't been called afterwards. It possibly held invalid pointers");
1211     }
1212     _desktop = desktop;
1213     _snapindicator = snapindicator;
1214     _unselected_nodes = unselected_nodes;
1215     _guide_to_ignore = guide_to_ignore;
1216     _rotation_center_source_item = NULL;
1217     _items_to_ignore.clear();
1219     Inkscape::Selection *sel = _desktop->selection;
1220     GSList const *items = sel->itemList();
1221     for (GSList *i = const_cast<GSList*>(items); i; i = i->next) {
1222         _items_to_ignore.push_back(static_cast<SPItem const *>(i->data));
1223     }
1226 SPDocument *SnapManager::getDocument() const
1228     return _named_view->document;
1231 /**
1232  * \brief Takes an untransformed point, applies the given transformation, and returns the transformed point. Eliminates lots of duplicated code
1233  *
1234  * \param p The untransformed position of the point, paired with an identifier of the type of the snap source.
1235  * \param transformation_type Type of transformation to apply.
1236  * \param transformation Mathematical description of the transformation; details depend on the type.
1237  * \param origin Origin of the transformation, if applicable.
1238  * \param dim Dimension to which the transformation applies, if applicable.
1239  * \param uniform true if the transformation should be uniform; only applicable for stretching and scaling.
1240  * \return The position of the point after transformation
1241  */
1243 Geom::Point SnapManager::_transformPoint(Inkscape::SnapCandidatePoint const &p,
1244                                         Transformation const transformation_type,
1245                                         Geom::Point const &transformation,
1246                                         Geom::Point const &origin,
1247                                         Geom::Dim2 const dim,
1248                                         bool const uniform) const
1250     /* Work out the transformed version of this point */
1251     Geom::Point transformed;
1252     switch (transformation_type) {
1253         case TRANSLATE:
1254             transformed = p.getPoint() + transformation;
1255             break;
1256         case SCALE:
1257             transformed = (p.getPoint() - origin) * Geom::Scale(transformation[Geom::X], transformation[Geom::Y]) + origin;
1258             break;
1259         case STRETCH:
1260         {
1261             Geom::Scale s(1, 1);
1262             if (uniform)
1263                 s[Geom::X] = s[Geom::Y] = transformation[dim];
1264             else {
1265                 s[dim] = transformation[dim];
1266                 s[1 - dim] = 1;
1267             }
1268             transformed = ((p.getPoint() - origin) * s) + origin;
1269             break;
1270         }
1271         case SKEW:
1272             // Apply the skew factor
1273             transformed[dim] = (p.getPoint())[dim] + transformation[0] * ((p.getPoint())[1 - dim] - origin[1 - dim]);
1274             // While skewing, mirroring and scaling (by integer multiples) in the opposite direction is also allowed.
1275             // Apply that scale factor here
1276             transformed[1-dim] = (p.getPoint() - origin)[1 - dim] * transformation[1] + origin[1 - dim];
1277             break;
1278         case ROTATE:
1279             // for rotations: transformation[0] stores the angle in radians
1280             transformed = (p.getPoint() - origin) * Geom::Rotate(transformation[0]) + origin;
1281             break;
1282         default:
1283             g_assert_not_reached();
1284     }
1286     return transformed;
1289 /**
1290  * \brief Mark the location of the snap source (not the snap target!) on the canvas by drawing a symbol
1291  *
1292  * \param point_type Category of points to which the source point belongs: node, guide or bounding box
1293  * \param p The transformed position of the source point, paired with an identifier of the type of the snap source.
1294  */
1296 void SnapManager::_displaySnapsource(Inkscape::SnapCandidatePoint const &p) const {
1298     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1299     if (prefs->getBool("/options/snapclosestonly/value")) {
1300         bool p_is_a_node = p.getSourceType() & Inkscape::SNAPSOURCE_NODE_CATEGORY;
1301         bool p_is_a_bbox = p.getSourceType() & Inkscape::SNAPSOURCE_BBOX_CATEGORY;
1302         bool p_is_other = p.getSourceType() & Inkscape::SNAPSOURCE_OTHER_CATEGORY;
1304         g_assert(_desktop != NULL);
1305         if (snapprefs.getSnapEnabledGlobally() && (p_is_other || (p_is_a_node && snapprefs.getSnapModeNode()) || (p_is_a_bbox && snapprefs.getSnapModeBBox()))) {
1306             _desktop->snapindicator->set_new_snapsource(p);
1307         } else {
1308             _desktop->snapindicator->remove_snapsource();
1309         }
1310     }
1313 /*
1314   Local Variables:
1315   mode:c++
1316   c-file-style:"stroustrup"
1317   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1318   indent-tabs-mode:nil
1319   fill-column:99
1320   End:
1321 */
1322 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :