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;
105 }
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
119 {
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());
131 }
133 /**
134 * \return true if one of the grids might be snapped to.
135 */
137 bool SnapManager::gridSnapperMightSnap() const
138 {
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());
150 }
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
180 {
181 Inkscape::SnappedPoint const s = freeSnap(Inkscape::SnapCandidatePoint(p, source_type), bbox_to_snap);
182 s.getPoint(p);
183 }
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
205 {
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);
218 }
220 void SnapManager::preSnap(Inkscape::SnapCandidatePoint const &p)
221 {
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 }
235 }
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)
257 {
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;
309 }
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
344 {
345 Inkscape::SnappedPoint const s = constrainedSnap(Inkscape::SnapCandidatePoint(p, source_type, 0), constraint, bbox_to_snap);
346 s.getPoint(p);
347 }
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
368 {
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;
395 }
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
409 {
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;
457 }
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
472 {
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);
502 }
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
519 {
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);
546 }
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
583 {
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;
822 }
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
837 {
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);
846 }
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
862 {
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);
869 }
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
886 {
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);
893 }
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
910 {
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);
918 }
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
938 {
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);
945 }
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
965 {
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);
983 }
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
999 {
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);
1013 }
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
1032 {
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;
1145 }
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)
1153 {
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;
1165 }
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)
1188 {
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;
1199 }
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)
1206 {
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 }
1224 }
1226 SPDocument *SnapManager::getDocument() const
1227 {
1228 return _named_view->document;
1229 }
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
1249 {
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;
1287 }
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 }
1311 }
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 :