Code

Refactor snapping mechanisms: in seltrans.cpp, a GSList was converted to a std::list...
[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-2008 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"
27 #include <libnr/nr-point-fns.h>
28 #include <libnr/nr-scale-ops.h>
29 #include <libnr/nr-values.h>
31 #include "display/canvas-grid.h"
33 #include "inkscape.h"
34 #include "desktop.h"
35 #include "sp-guide.h"
36 using std::vector;
38 /**
39  *  Construct a SnapManager for a SPNamedView.
40  *
41  *  \param v `Owning' SPNamedView.
42  */
44 SnapManager::SnapManager(SPNamedView const *v) :
45     guide(v, 0),
46     object(v, 0),
47     _named_view(v),
48     _include_item_center(false),
49     _snap_enabled_globally(true)
50 {    
51 }
54 /**
55  *  \return List of snappers that we use.
56  */
57 SnapManager::SnapperList 
58 SnapManager::getSnappers() const
59 {
60     SnapManager::SnapperList s;
61     s.push_back(&guide);
62     s.push_back(&object);
64     SnapManager::SnapperList gs = getGridSnappers();
65     s.splice(s.begin(), gs);
67     return s;
68 }
70 /**
71  *  \return List of gridsnappers that we use.
72  */
73 SnapManager::SnapperList 
74 SnapManager::getGridSnappers() const
75 {
76     SnapperList s;
78     //FIXME: this code should actually do this: add new grid snappers that are active for this desktop. now it just adds all gridsnappers
79     SPDesktop* desktop = SP_ACTIVE_DESKTOP;
80     if (desktop && desktop->gridsEnabled()) {
81         for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
82             Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
83             s.push_back(grid->snapper);
84         }
85     }
87     return s;
88 }
90 /**
91  * \return true if one of the snappers will try to snap something.
92  */
94 bool SnapManager::SomeSnapperMightSnap() const
95 {
96     if (!_snap_enabled_globally) {
97         return false;
98     }
99     
100     SnapperList const s = getSnappers();
101     SnapperList::const_iterator i = s.begin();
102     while (i != s.end() && (*i)->ThisSnapperMightSnap() == false) {
103         i++;
104     }
105     
106     return (i != s.end());
109 /*
110  *  The snappers have too many parameters to adjust individually. Therefore only
111  *  two snapping modes are presented to the user: snapping bounding box corners (to 
112  *  other bounding boxes, grids or guides), and/or snapping nodes (to other nodes,
113  *  paths, grids or guides). To select either of these modes (or both), use the 
114  *  methods defined below: setSnapModeBBox() and setSnapModeNode().
115  * 
116  * */
119 void SnapManager::setSnapModeBBox(bool enabled)
121     //The default values are being set in sp_namedview_set() (in sp-namedview.cpp)
122     guide.setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
123     
124     for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
125         Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
126         grid->snapper->setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
127     }
128     
129     object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
130     //object.setSnapToBBoxNode(enabled); // On second thought, these should be controlled
131     //object.setSnapToBBoxPath(enabled); // separately by the snapping prefs dialog
132     object.setStrictSnapping(true); //don't snap bboxes to nodes/paths and vice versa    
135 bool SnapManager::getSnapModeBBox() const
137     return guide.getSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX);
140 void SnapManager::setSnapModeNode(bool enabled)
142     guide.setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
143     
144     for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
145         Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
146         grid->snapper->setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
147     }
148         
149     object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
150     //object.setSnapToItemNode(enabled); // On second thought, these should be controlled
151     //object.setSnapToItemPath(enabled); // separately by the snapping prefs dialog 
152     object.setStrictSnapping(true);
155 bool SnapManager::getSnapModeNode() const
157     return guide.getSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE);
160 void SnapManager::setSnapModeGuide(bool enabled)
162     object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_GUIDE, enabled);
165 bool SnapManager::getSnapModeGuide() const
167     return object.getSnapFrom(Inkscape::Snapper::SNAPPOINT_GUIDE);
170 /**
171  *  Try to snap a point to any interested snappers.
172  *
173  *  \param t Type of point.
174  *  \param p Point.
175  *  \param it Item to ignore when snapping.
176  *  \return Snapped point.
177  */
179 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
180                                              NR::Point const &p,
181                                              SPItem const *it,
182                                              NR::Maybe<NR::Point> point_not_to_snap_to) const
185     std::vector<SPItem const *> lit;
186     if (it) {
187         lit.push_back(it);
188     }
189     
190     std::vector<NR::Point> points_to_snap;
191     points_to_snap.push_back(p);
192     
193     return freeSnap(t, p, true, points_to_snap, lit, NULL);
196 /**
197  *  Try to snap a point to any interested snappers.
198  *
199  *  \param t Type of point.
200  *  \param p Point.
201  *  \param it Item to ignore when snapping.
202  *  \return Snapped point.
203  */
205 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
206                                              NR::Point const &p,
207                                              SPItem const *it,
208                                              std::vector<NR::Point> *unselected_nodes) const
211     std::vector<SPItem const *> lit;
212     if (it) {
213         lit.push_back(it);
214     }
215     
216     std::vector<NR::Point> points_to_snap;
217     points_to_snap.push_back(p);
218     
219     return freeSnap(t, p, true, points_to_snap, lit, unselected_nodes);
223 /**
224  *  Try to snap a point to any of the specified snappers.
225  *
226  *  \param t Type of point.
227  *  \param p Point.
228  *  \param first_point If true then this point is the first one from a whole bunch of points 
229  *  \param points_to_snap The whole bunch of points, all from the same selection and having the same transformation 
230  *  \param it List of items to ignore when snapping.
231  * \param snappers  List of snappers to try to snap to
232  *  \return Snapped point.
233  */
235 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
236                                              NR::Point const &p,
237                                              bool const &first_point,
238                                              std::vector<NR::Point> &points_to_snap,
239                                              std::vector<SPItem const *> const &it,
240                                              std::vector<NR::Point> *unselected_nodes) const
242     if (!SomeSnapperMightSnap()) {
243         return Inkscape::SnappedPoint(p, NR_HUGE, 0, false);
244     }
245     
246     SnappedConstraints sc;        
247     
248     SnapperList const snappers = getSnappers();
250     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
251         (*i)->freeSnap(sc, t, p, first_point, points_to_snap, it, unselected_nodes);
252     }
254     return findBestSnap(p, sc, false);
257 /**
258  *  Try to snap a point to any interested snappers.  A snap will only occur along
259  *  a line described by a Inkscape::Snapper::ConstraintLine.
260  *
261  *  \param t Type of point.
262  *  \param p Point.
263  *  \param c Constraint line.
264  *  \param it Item to ignore when snapping.
265  *  \return Snapped point.
266  */
268 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
269                                                     NR::Point const &p,
270                                                     Inkscape::Snapper::ConstraintLine const &c,
271                                                     SPItem const *it) const
273     std::vector<SPItem const *> lit;
274     if (it) {
275         lit.push_back(it);
276     }
277     
278     std::vector<NR::Point> points_to_snap;
279     points_to_snap.push_back(p);
280     
281     return constrainedSnap(t, p, true, points_to_snap, c, lit);
286 /**
287  *  Try to snap a point to any interested snappers.  A snap will only occur along
288  *  a line described by a Inkscape::Snapper::ConstraintLine.
289  *
290  *  \param t Type of point.
291  *  \param p Point.
292  *  \param first_point If true then this point is the first one from a whole bunch of points 
293  *  \param points_to_snap The whole bunch of points, all from the same selection and having the same transformation 
294  *  \param c Constraint line.
295  *  \param it List of items to ignore when snapping.
296  *  \return Snapped point.
297  */
299 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
300                                                     NR::Point const &p,
301                                                     bool const &first_point,
302                                                     std::vector<NR::Point> &points_to_snap,
303                                                     Inkscape::Snapper::ConstraintLine const &c,
304                                                     std::vector<SPItem const *> const &it) const
306     if (!SomeSnapperMightSnap()) {
307         return Inkscape::SnappedPoint(p, NR_HUGE, 0, false);
308     }
309     
310     SnappedConstraints sc;
311         
312     SnapperList const snappers = getSnappers();
313     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
314         (*i)->constrainedSnap(sc, t, p, first_point, points_to_snap, c, it);
315     }
317     return findBestSnap(p, sc, true);
320 Inkscape::SnappedPoint SnapManager::guideSnap(NR::Point const &p,
321                                               NR::Point const &guide_normal) const
323     // This method is used to snap a guide to nodes, while dragging the guide around
324     
325     if (!(object.GuidesMightSnap() && _snap_enabled_globally)) {
326         return Inkscape::SnappedPoint(p, NR_HUGE, 0, false);
327     }
328     
329     SnappedConstraints sc;
330     object.guideSnap(sc, p, guide_normal);
331     
332     return findBestSnap(p, sc, false);    
336 /**
337  *  Main internal snapping method, which is called by the other, friendlier, public
338  *  methods.  It's a bit hairy as it has lots of parameters, but it saves on a lot
339  *  of duplicated code.
340  *
341  *  \param type Type of points being snapped.
342  *  \param points List of points to snap.
343  *  \param ignore List of items to ignore while snapping.
344  *  \param constrained true if the snap is constrained.
345  *  \param constraint Constraint line to use, if `constrained' is true, otherwise undefined.
346  *  \param transformation_type Type of transformation to apply to points before trying to snap them.
347  *  \param transformation Description of the transformation; details depend on the type.
348  *  \param origin Origin of the transformation, if applicable.
349  *  \param dim Dimension of the transformation, if applicable.
350  *  \param uniform true if the transformation should be uniform; only applicable for stretching and scaling.
351  */
353 Inkscape::SnappedPoint SnapManager::_snapTransformed(
354     Inkscape::Snapper::PointType type,
355     std::vector<NR::Point> const &points,
356     std::vector<SPItem const *> const &ignore,
357     bool constrained,
358     Inkscape::Snapper::ConstraintLine const &constraint,
359     Transformation transformation_type,
360     NR::Point const &transformation,
361     NR::Point const &origin,
362     NR::Dim2 dim,
363     bool uniform) const
365     /* We have a list of points, which we are proposing to transform in some way.  We need to see
366     ** if any of these points, when transformed, snap to anything.  If they do, we return the
367     ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
368     */
370     /* Quick check to see if we have any snappers that are enabled
371     ** Also used to globally disable all snapping 
372     */
373     if (SomeSnapperMightSnap() == false) {
374         return Inkscape::SnappedPoint();
375     }
376     
377     std::vector<NR::Point> transformed_points;
378     
379     for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
381         /* Work out the transformed version of this point */
382         NR::Point transformed;
383         switch (transformation_type) {
384             case TRANSLATION:
385                 transformed = *i + transformation;
386                 break;
387             case SCALE:
388                 transformed = ((*i - origin) * NR::scale(transformation[NR::X], transformation[NR::Y])) + origin;
389                 break;
390             case STRETCH:
391             {
392                 NR::scale s(1, 1);
393                 if (uniform)
394                     s[NR::X] = s[NR::Y] = transformation[dim];
395                 else {
396                     s[dim] = transformation[dim];
397                     s[1 - dim] = 1;
398                 }
399                 transformed = ((*i - origin) * s) + origin;
400                 break;
401             }
402             case SKEW:
403                 transformed = *i;
404                 transformed[dim] += transformation[dim] * ((*i)[1 - dim] - origin[1 - dim]);
405                 break;
406             default:
407                 g_assert_not_reached();
408         }
409         
410         // add the current transformed point to the box hulling all transformed points
411         transformed_points.push_back(transformed);
412     }    
413     
414     /* The current best transformation */
415     NR::Point best_transformation = transformation;
417     /* The current best metric for the best transformation; lower is better, NR_HUGE
418     ** means that we haven't snapped anything.
419     */
420     NR::Coord best_metric = NR_HUGE;
421     NR::Coord best_second_metric = NR_HUGE;
422     NR::Point best_scale_metric(NR_HUGE, NR_HUGE);
423     Inkscape::SnappedPoint best_snapped_point;
424     g_assert(best_snapped_point.getAlwaysSnap() == false); // Check initialization of snapped point
425     g_assert(best_snapped_point.getAtIntersection() == false);
427     std::vector<NR::Point>::const_iterator j = transformed_points.begin();
429     // std::cout << std::endl;
430     for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
431         
432         /* Snap it */        
433         Inkscape::SnappedPoint snapped_point;
434                 
435         if (constrained) {    
436             Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
437             if ((transformation_type == SCALE || transformation_type == STRETCH) && uniform) {
438                 // When uniformly scaling, each point will have its own unique constraint line,
439                 // running from the scaling origin to the original untransformed point. We will
440                 // calculate that line here 
441                 dedicated_constraint = Inkscape::Snapper::ConstraintLine(origin, (*i) - origin);
442             } else if (transformation_type == STRETCH || transformation_type == SKEW) { // when skewing or non-uniform stretching {
443                 dedicated_constraint = Inkscape::Snapper::ConstraintLine((*i), component_vectors[dim]);
444             } // else: leave the original constraint, e.g. for constrained translation 
445             if (transformation_type == SCALE && !uniform) {
446                 g_warning("Non-uniform constrained scaling is not supported!");   
447             }
448             snapped_point = constrainedSnap(type, *j, i == points.begin(), transformed_points, dedicated_constraint, ignore);
449         } else {
450             snapped_point = freeSnap(type, *j, i == points.begin(), transformed_points, ignore, NULL);
451         }
453         NR::Point result;
454         NR::Coord metric = NR_HUGE;
455         NR::Coord second_metric = NR_HUGE;
456         NR::Point scale_metric(NR_HUGE, NR_HUGE);
457         
458         if (snapped_point.getSnapped()) {
459             /* We snapped.  Find the transformation that describes where the snapped point has
460             ** ended up, and also the metric for this transformation.
461             */
462             NR::Point const a = (snapped_point.getPoint() - origin); // vector to snapped point
463             NR::Point const b = (*i - origin); // vector to original point
464             
465             switch (transformation_type) {
466                 case TRANSLATION:
467                     result = snapped_point.getPoint() - *i;
468                     /* Consider the case in which a box is almost aligned with a grid in both 
469                      * horizontal and vertical directions. The distance to the intersection of
470                      * the grid lines will always be larger then the distance to a single grid
471                      * line. If we prefer snapping to an intersection instead of to a single 
472                      * grid line, then we cannot use "metric = NR::L2(result)". Therefore the
473                      * snapped distance will be used as a metric. Please note that the snapped
474                      * distance is defined as the distance to the nearest line of the intersection,
475                      * and not to the intersection itself! 
476                      */
477                     metric = snapped_point.getDistance(); //used to be: metric = NR::L2(result);
478                     second_metric = snapped_point.getSecondDistance();
479                     break;
480                 case SCALE:
481                 {
482                     result = NR::Point(NR_HUGE, NR_HUGE);
483                     // If this point *i is horizontally or vertically aligned with
484                     // the origin of the scaling, then it will scale purely in X or Y 
485                     // We can therefore only calculate the scaling in this direction
486                     // and the scaling factor for the other direction should remain
487                     // untouched (unless scaling is uniform ofcourse)
488                     for (int index = 0; index < 2; index++) {
489                         if (fabs(b[index]) > 1e-6) { // if SCALING CAN occur in this direction
490                             if (fabs(fabs(a[index]/b[index]) - fabs(transformation[index])) > 1e-12) { // if SNAPPING DID occur in this direction
491                                 result[index] = a[index] / b[index]; // then calculate it!
492                             }
493                             // we might leave result[1-index] = NR_HUGE
494                             // if scaling didn't occur in the other direction
495                         }
496                     }
497                     // Compare the resulting scaling with the desired scaling
498                     scale_metric = result - transformation; // One or both of its components might be NR_HUGE
499                     break;
500                 }
501                 case STRETCH:
502                     result = NR::Point(NR_HUGE, NR_HUGE);
503                     if (fabs(b[dim]) > 1e-6) { // if STRETCHING will occur for this point
504                         result[dim] = a[dim] / b[dim];
505                         result[1-dim] = uniform ? result[dim] : 1;
506                     } else { // STRETCHING might occur for this point, but only when the stretching is uniform
507                         if (uniform && fabs(b[1-dim]) > 1e-6) {
508                            result[1-dim] = a[1-dim] / b[1-dim];
509                            result[dim] = result[1-dim];
510                         }
511                     }
512                     metric = std::abs(result[dim] - transformation[dim]);
513                     break;
514                 case SKEW:
515                     result[dim] = (snapped_point.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]);
516                     metric = std::abs(result[dim] - transformation[dim]);
517                     break;
518                 default:
519                     g_assert_not_reached();
520             }
521             
522             /* Note it if it's the best so far */
523             if (transformation_type == SCALE) {
524                 for (int index = 0; index < 2; index++) {
525                     if (fabs(scale_metric[index]) < fabs(best_scale_metric[index])) {
526                         best_transformation[index] = result[index];
527                         best_scale_metric[index] = fabs(scale_metric[index]);
528                         // When scaling, we're considering the best transformation in each direction separately
529                         // Therefore two different snapped points might together make a single best transformation
530                         // We will however return only a single snapped point (e.g. to display the snapping indicator)   
531                         best_snapped_point = snapped_point;
532                         // std::cout << "SEL ";
533                     } // else { std::cout << "    ";}
534                 }
535                 if (uniform) {
536                     if (best_scale_metric[0] < best_scale_metric[1]) {
537                         best_transformation[1] = best_transformation[0];
538                         best_scale_metric[1] = best_scale_metric[0]; 
539                     } else {
540                         best_transformation[0] = best_transformation[1];
541                         best_scale_metric[0] = best_scale_metric[1];
542                     }
543                 }
544                 best_metric = std::min(best_scale_metric[0], best_scale_metric[1]);
545                 // std::cout << "P_orig = " << (*i) << " | scale_metric = " << scale_metric << " | distance = " << snapped_point.getDistance() << " | P_snap = " << snapped_point.getPoint() << std::endl;
546             } else {
547                 bool const c1 = metric < best_metric;
548                 bool const c2 = metric == best_metric && snapped_point.getAtIntersection() == true && best_snapped_point.getAtIntersection() == false;
549                         bool const c3a = metric == best_metric && snapped_point.getAtIntersection() == true && best_snapped_point.getAtIntersection() == true;
550                 bool const c3b = second_metric < best_second_metric;
551                 bool const c4 = snapped_point.getAlwaysSnap() == true && best_snapped_point.getAlwaysSnap() == false;
552                 bool const c4n = snapped_point.getAlwaysSnap() == false && best_snapped_point.getAlwaysSnap() == true;
553                 
554                 if ((c1 || c2 || (c3a && c3b) || c4) && !c4n) {
555                     best_transformation = result;
556                     best_metric = metric;
557                     best_second_metric = second_metric;
558                     best_snapped_point = snapped_point; 
559                     // std::cout << "SEL ";
560                 } // else { std::cout << "    ";}
561                 // std::cout << "P_orig = " << (*i) << " | metric = " << metric << " | distance = " << snapped_point.getDistance() << " | second metric = " << second_metric << " | P_snap = " << snapped_point.getPoint() << std::endl;
562             }
563         }
564         
565         j++;
566     }
567     
568     if (transformation_type == SCALE) {
569         // When scaling, don't ever exit with one of scaling components set to NR_HUGE
570         for (int index = 0; index < 2; index++) {
571             if (best_transformation[index] == NR_HUGE) {
572                 if (uniform && best_transformation[1-index] < NR_HUGE) {
573                         best_transformation[index] = best_transformation[1-index];
574                 } else {
575                         best_transformation[index] = transformation[index];     
576                 }
577             }
578         }
579     }
580     
581     best_snapped_point.setTransformation(best_transformation);
582     // Using " < 1e6" instead of " < NR_HUGE" for catching some rounding errors
583     // These rounding errors might be caused by NRRects, see bug #1584301    
584     best_snapped_point.setDistance(best_metric < 1e6 ? best_metric : NR_HUGE);
585     return best_snapped_point;
589 /**
590  *  Try to snap a list of points to any interested snappers after they have undergone
591  *  a translation.
592  *
593  *  \param t Type of points.
594  *  \param p Points.
595  *  \param it List of items to ignore when snapping.
596  *  \param tr Proposed translation.
597  *  \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
598  */
600 Inkscape::SnappedPoint SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t,
601                                                         std::vector<NR::Point> const &p,
602                                                         std::vector<SPItem const *> const &it,
603                                                         NR::Point const &tr) const
605     return _snapTransformed(t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false);
609 /**
610  *  Try to snap a list of points to any interested snappers after they have undergone a
611  *  translation.  A snap will only occur along a line described by a
612  *  Inkscape::Snapper::ConstraintLine.
613  *
614  *  \param t Type of points.
615  *  \param p Points.
616  *  \param it List of items to ignore when snapping.
617  *  \param c Constraint line.
618  *  \param tr Proposed translation.
619  *  \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
620  */
622 Inkscape::SnappedPoint SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t,
623                                                                std::vector<NR::Point> const &p,
624                                                                std::vector<SPItem const *> const &it,
625                                                                Inkscape::Snapper::ConstraintLine const &c,
626                                                                NR::Point const &tr) const
628     return _snapTransformed(t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false);
632 /**
633  *  Try to snap a list of points to any interested snappers after they have undergone
634  *  a scale.
635  *
636  *  \param t Type of points.
637  *  \param p Points.
638  *  \param it List of items to ignore when snapping.
639  *  \param s Proposed scale.
640  *  \param o Origin of proposed scale.
641  *  \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
642  */
644 Inkscape::SnappedPoint SnapManager::freeSnapScale(Inkscape::Snapper::PointType t,
645                                                   std::vector<NR::Point> const &p,
646                                                   std::vector<SPItem const *> const &it,
647                                                   NR::scale const &s,
648                                                   NR::Point const &o) const
650     return _snapTransformed(t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false);
654 /**
655  *  Try to snap a list of points to any interested snappers after they have undergone
656  *  a scale.  A snap will only occur along a line described by a
657  *  Inkscape::Snapper::ConstraintLine.
658  *
659  *  \param t Type of points.
660  *  \param p Points.
661  *  \param it List of items to ignore when snapping.
662  *  \param s Proposed scale.
663  *  \param o Origin of proposed scale.
664  *  \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
665  */
667 Inkscape::SnappedPoint SnapManager::constrainedSnapScale(Inkscape::Snapper::PointType t,
668                                                          std::vector<NR::Point> const &p,
669                                                          std::vector<SPItem const *> const &it,
670                                                          NR::scale const &s,
671                                                          NR::Point const &o) const
673     // When constrained scaling, only uniform scaling is supported.
674     return _snapTransformed(t, p, it, true, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, true);
678 /**
679  *  Try to snap a list of points to any interested snappers after they have undergone
680  *  a stretch.
681  *
682  *  \param t Type of points.
683  *  \param p Points.
684  *  \param it List of items to ignore when snapping.
685  *  \param s Proposed stretch.
686  *  \param o Origin of proposed stretch.
687  *  \param d Dimension in which to apply proposed stretch.
688  *  \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
689  *  \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
690  */
692 Inkscape::SnappedPoint SnapManager::constrainedSnapStretch(Inkscape::Snapper::PointType t,
693                                                             std::vector<NR::Point> const &p,
694                                                             std::vector<SPItem const *> const &it,
695                                                             NR::Coord const &s,
696                                                             NR::Point const &o,
697                                                             NR::Dim2 d,
698                                                             bool u) const
700    return _snapTransformed(t, p, it, true, NR::Point(), STRETCH, NR::Point(s, s), o, d, u);
704 /**
705  *  Try to snap a list of points to any interested snappers after they have undergone
706  *  a skew.
707  *
708  *  \param t Type of points.
709  *  \param p Points.
710  *  \param it List of items to ignore when snapping.
711  *  \param s Proposed skew.
712  *  \param o Origin of proposed skew.
713  *  \param d Dimension in which to apply proposed skew.
714  *  \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
715  */
717 Inkscape::SnappedPoint SnapManager::freeSnapSkew(Inkscape::Snapper::PointType t,
718                                                  std::vector<NR::Point> const &p,
719                                                  std::vector<SPItem const *> const &it,
720                                                  NR::Coord const &s,
721                                                  NR::Point const &o,
722                                                  NR::Dim2 d) const
724    return _snapTransformed(t, p, it, false, NR::Point(), SKEW, NR::Point(s, s), o, d, false);
727 Inkscape::SnappedPoint SnapManager::findBestSnap(NR::Point const &p, SnappedConstraints &sc, bool constrained) const
729     /*
730     std::cout << "Type and number of snapped constraints: " << std::endl;
731     std::cout << "  Points      : " << sc.points.size() << std::endl;
732     std::cout << "  Lines       : " << sc.lines.size() << std::endl;
733     std::cout << "  Grid lines  : " << sc.grid_lines.size()<< std::endl;
734     std::cout << "  Guide lines : " << sc.guide_lines.size()<< std::endl;
735     */
736         
737     // Store all snappoints
738     std::list<Inkscape::SnappedPoint> sp_list;
739     
740     // search for the closest snapped point
741     Inkscape::SnappedPoint closestPoint;
742     if (getClosestSP(sc.points, closestPoint)) {
743         sp_list.push_back(closestPoint);
744     } 
745     
746     // search for the closest snapped line segment
747     Inkscape::SnappedLineSegment closestLineSegment;
748     if (getClosestSLS(sc.lines, closestLineSegment)) {    
749         sp_list.push_back(Inkscape::SnappedPoint(closestLineSegment));
750     }
751     
752     if (_intersectionLS) {
753             // search for the closest snapped intersection of line segments
754             Inkscape::SnappedPoint closestLineSegmentIntersection;
755             if (getClosestIntersectionSLS(sc.lines, closestLineSegmentIntersection)) {
756                 sp_list.push_back(closestLineSegmentIntersection);
757             }
758     }    
760     // search for the closest snapped grid line
761     Inkscape::SnappedLine closestGridLine;
762     if (getClosestSL(sc.grid_lines, closestGridLine)) {    
763         sp_list.push_back(Inkscape::SnappedPoint(closestGridLine));
764     }
765     
766     // search for the closest snapped guide line
767     Inkscape::SnappedLine closestGuideLine;
768     if (getClosestSL(sc.guide_lines, closestGuideLine)) {
769         sp_list.push_back(Inkscape::SnappedPoint(closestGuideLine));
770     }
771     
772     // When freely snapping to a grid/guide/path, only one degree of freedom is eliminated
773     // Therefore we will try get fully constrained by finding an intersection with another grid/guide/path 
774     
775     // When doing a constrained snap however, we're already at an intersection of the constrained line and
776     // the grid/guide/path we're snapping to. This snappoint is therefore fully constrained, so there's
777     // no need to look for additional intersections
778     if (!constrained) {
779         // search for the closest snapped intersection of grid lines
780         Inkscape::SnappedPoint closestGridPoint;
781         if (getClosestIntersectionSL(sc.grid_lines, closestGridPoint)) {
782             sp_list.push_back(closestGridPoint);
783         }
784         
785         // search for the closest snapped intersection of guide lines
786         Inkscape::SnappedPoint closestGuidePoint;
787         if (getClosestIntersectionSL(sc.guide_lines, closestGuidePoint)) {
788             sp_list.push_back(closestGuidePoint);
789         }
790         
791         // search for the closest snapped intersection of grid with guide lines
792         if (_intersectionGG) {
793             Inkscape::SnappedPoint closestGridGuidePoint;
794             if (getClosestIntersectionSL(sc.grid_lines, sc.guide_lines, closestGridGuidePoint)) {
795                 sp_list.push_back(closestGridGuidePoint);
796             }
797         }
798     }
799     
800     // now let's see which snapped point gets a thumbs up
801     Inkscape::SnappedPoint bestSnappedPoint = Inkscape::SnappedPoint(p, NR_HUGE, 0, false);
802     for (std::list<Inkscape::SnappedPoint>::const_iterator i = sp_list.begin(); i != sp_list.end(); i++) {
803                 // first find out if this snapped point is within snapping range
804         if ((*i).getDistance() <= (*i).getTolerance()) {
805                 // if it's the first point
806                 bool c1 = (i == sp_list.begin());  
807                 // or, if it's closer
808                 bool c2 = (*i).getDistance() < bestSnappedPoint.getDistance();
809             // or, if it's for a snapper with "always snap" turned on, and the previous wasn't
810             bool c3 = (*i).getAlwaysSnap() && !bestSnappedPoint.getAlwaysSnap();
811                 // But in no case fall back from a snapper with "always snap" on to one with "always snap" off
812             bool c3n = !(*i).getAlwaysSnap() && bestSnappedPoint.getAlwaysSnap();
813             // or, if it's just as close then consider the second distance
814                 // (which is only relevant for points at an intersection)
815                 bool c4a = ((*i).getDistance() == bestSnappedPoint.getDistance()); 
816                 bool c4b = (*i).getSecondDistance() < bestSnappedPoint.getSecondDistance();
817                 // then prefer this point over the previous one
818             if ((c1 || c2 || c3 || (c4a && c4b)) && !c3n) {
819                 bestSnappedPoint = *i;
820             }
821         }
822     }
823     
824     return bestSnappedPoint;         
827 /*
828   Local Variables:
829   mode:c++
830   c-file-style:"stroustrup"
831   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
832   indent-tabs-mode:nil
833   fill-column:99
834   End:
835 */
836 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :