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"
26 #include "snapped-curve.h"
28 #include <libnr/nr-point-fns.h>
29 #include <libnr/nr-scale-ops.h>
30 #include <libnr/nr-values.h>
32 #include "display/canvas-grid.h"
33 #include "display/snap-indicator.h"
35 #include "inkscape.h"
36 #include "desktop.h"
37 #include "sp-guide.h"
38 #include "preferences.h"
39 using std::vector;
41 /**
42 * Construct a SnapManager for a SPNamedView.
43 *
44 * \param v `Owning' SPNamedView.
45 */
47 SnapManager::SnapManager(SPNamedView const *v) :
48 guide(this, 0),
49 object(this, 0),
50 snapprefs(),
51 _named_view(v)
52 {
53 }
55 /**
56 * \return List of snappers that we use.
57 */
58 SnapManager::SnapperList
59 SnapManager::getSnappers() const
60 {
61 SnapManager::SnapperList s;
62 s.push_back(&guide);
63 s.push_back(&object);
65 SnapManager::SnapperList gs = getGridSnappers();
66 s.splice(s.begin(), gs);
68 return s;
69 }
71 /**
72 * \return List of gridsnappers that we use.
73 */
74 SnapManager::SnapperList
75 SnapManager::getGridSnappers() const
76 {
77 SnapperList s;
79 //FIXME: this code should actually do this: add new grid snappers that are active for this desktop. now it just adds all gridsnappers
80 SPDesktop* desktop = SP_ACTIVE_DESKTOP;
81 if (desktop && desktop->gridsEnabled()) {
82 for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
83 Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
84 s.push_back(grid->snapper);
85 }
86 }
88 return s;
89 }
91 /**
92 * \return true if one of the snappers will try to snap something.
93 */
95 bool SnapManager::someSnapperMightSnap() const
96 {
97 if ( !snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally() ) {
98 return false;
99 }
101 SnapperList const s = getSnappers();
102 SnapperList::const_iterator i = s.begin();
103 while (i != s.end() && (*i)->ThisSnapperMightSnap() == false) {
104 i++;
105 }
107 return (i != s.end());
108 }
110 /**
111 * Try to snap a point to any of the specified snappers.
112 *
113 * \param point_type Type of point.
114 * \param p Point.
115 * \param first_point If true then this point is the first one from a whole bunch of points
116 * \param points_to_snap The whole bunch of points, all from the same selection and having the same transformation
117 * \param snappers List of snappers to try to snap to
118 * \return Snapped point.
119 */
121 void SnapManager::freeSnapReturnByRef(Inkscape::SnapPreferences::PointType point_type,
122 Geom::Point &p,
123 bool first_point,
124 Geom::OptRect const &bbox_to_snap) const
125 {
126 Inkscape::SnappedPoint const s = freeSnap(point_type, p, first_point, bbox_to_snap);
127 s.getPoint(p);
128 }
130 /**
131 * Try to snap a point to any of the specified snappers.
132 *
133 * \param point_type Type of point.
134 * \param p Point.
135 * \param first_point If true then this point is the first one from a whole bunch of points
136 * \param points_to_snap The whole bunch of points, all from the same selection and having the same transformation
137 * \param snappers List of snappers to try to snap to
138 * \return Snapped point.
139 */
141 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::SnapPreferences::PointType point_type,
142 Geom::Point const &p,
143 bool first_point,
144 Geom::OptRect const &bbox_to_snap) const
145 {
146 if (!someSnapperMightSnap()) {
147 return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false);
148 }
150 std::vector<SPItem const *> *items_to_ignore;
151 if (_item_to_ignore) { // If we have only a single item to ignore
152 // then build a list containing this single item;
153 // This single-item list will prevail over any other _items_to_ignore list, should that exist
154 items_to_ignore = new std::vector<SPItem const *>;
155 items_to_ignore->push_back(_item_to_ignore);
156 } else {
157 items_to_ignore = _items_to_ignore;
158 }
160 SnappedConstraints sc;
161 SnapperList const snappers = getSnappers();
163 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
164 (*i)->freeSnap(sc, point_type, p, first_point, bbox_to_snap, items_to_ignore, _unselected_nodes);
165 }
167 if (_item_to_ignore) {
168 delete items_to_ignore;
169 }
171 return findBestSnap(p, sc, false);
172 }
174 // When pasting, we would like to snap to the grid. Problem is that we don't know which nodes were
175 // aligned to the grid at the time of copying, so we don't know which nodes to snap. If we'd snap an
176 // unaligned node to the grid, previously aligned nodes would become unaligned. That's undesirable.
177 // Instead we will make sure that the offset between the source and the copy is a multiple of the grid
178 // pitch. If the source was aligned, then the copy will therefore also be aligned
179 // PS: Wether we really find a multiple also depends on the snapping range!
180 Geom::Point SnapManager::multipleOfGridPitch(Geom::Point const &t) const
181 {
182 if (!snapprefs.getSnapEnabledGlobally()) // No need to check for snapprefs.getSnapPostponedGlobally() here
183 return t;
185 //FIXME: this code should actually do this: add new grid snappers that are active for this desktop. now it just adds all gridsnappers
186 SPDesktop* desktop = SP_ACTIVE_DESKTOP;
188 if (desktop && desktop->gridsEnabled()) {
189 bool success = false;
190 Geom::Point nearest_multiple;
191 Geom::Coord nearest_distance = NR_HUGE;
193 // It will snap to the grid for which we find the closest snap. This might be a different
194 // grid than to which the objects were initially aligned. I don't see an easy way to fix
195 // this, so when using multiple grids one can get unexpected results
197 // Cannot use getGridSnappers() because we need both the grids AND their snappers
198 // Therefor we iterate through all grids manually
199 for (GSList const *l = _named_view->grids; l != NULL; l = l->next) {
200 Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
201 const Inkscape::Snapper* snapper = grid->snapper;
202 if (snapper && snapper->ThisSnapperMightSnap()) {
203 // To find the nearest multiple of the grid pitch for a given translation t, we
204 // will use the grid snapper. Simply snapping the value t to the grid will do, but
205 // only if the origin of the grid is at (0,0). If it's not then compensate for this
206 // in the translation t
207 Geom::Point const t_offset = t + grid->origin;
208 SnappedConstraints sc;
209 // Only the first three parameters are being used for grid snappers
210 snapper->freeSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_NODE, t_offset, TRUE, Geom::OptRect(), NULL, NULL);
211 // Find the best snap for this grid, including intersections of the grid-lines
212 Inkscape::SnappedPoint s = findBestSnap(t_offset, sc, false);
213 if (s.getSnapped() && (s.getSnapDistance() < nearest_distance)) {
214 // use getSnapDistance() instead of getWeightedDistance() here because the pointer's position
215 // doesn't tell us anything about which node to snap
216 success = true;
217 nearest_multiple = s.getPoint() - to_2geom(grid->origin);
218 nearest_distance = s.getSnapDistance();
219 }
220 }
221 }
223 if (success)
224 return nearest_multiple;
225 }
227 return t;
228 }
230 /**
231 * Try to snap a point to any interested snappers. A snap will only occur along
232 * a line described by a Inkscape::Snapper::ConstraintLine.
233 *
234 * \param point_type Type of point.
235 * \param p Point.
236 * \param first_point If true then this point is the first one from a whole bunch of points
237 * \param points_to_snap The whole bunch of points, all from the same selection and having the same transformation
238 * \param constraint Constraint line.
239 * \return Snapped point.
240 */
242 void SnapManager::constrainedSnapReturnByRef(Inkscape::SnapPreferences::PointType point_type,
243 Geom::Point &p,
244 Inkscape::Snapper::ConstraintLine const &constraint,
245 bool first_point,
246 Geom::OptRect const &bbox_to_snap) const
247 {
248 Inkscape::SnappedPoint const s = constrainedSnap(point_type, p, constraint, first_point, bbox_to_snap);
249 s.getPoint(p);
250 }
252 /**
253 * Try to snap a point to any interested snappers. A snap will only occur along
254 * a line described by a Inkscape::Snapper::ConstraintLine.
255 *
256 * \param point_type Type of point.
257 * \param p Point.
258 * \param first_point If true then this point is the first one from a whole bunch of points
259 * \param points_to_snap The whole bunch of points, all from the same selection and having the same transformation
260 * \param constraint Constraint line.
261 * \return Snapped point.
262 */
264 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::PointType point_type,
265 Geom::Point const &p,
266 Inkscape::Snapper::ConstraintLine const &constraint,
267 bool first_point,
268 Geom::OptRect const &bbox_to_snap) const
269 {
270 if (!someSnapperMightSnap()) {
271 return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false);
272 }
274 std::vector<SPItem const *> *items_to_ignore;
275 if (_item_to_ignore) { // If we have only a single item to ignore
276 // then build a list containing this single item;
277 // This single-item list will prevail over any other _items_to_ignore list, should that exist
278 items_to_ignore = new std::vector<SPItem const *>;
279 items_to_ignore->push_back(_item_to_ignore);
280 } else {
281 items_to_ignore = _items_to_ignore;
282 }
284 SnappedConstraints sc;
285 SnapperList const snappers = getSnappers();
286 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
287 (*i)->constrainedSnap(sc, point_type, p, first_point, bbox_to_snap, constraint, items_to_ignore);
288 }
290 if (_item_to_ignore) {
291 delete items_to_ignore;
292 }
294 return findBestSnap(p, sc, true);
295 }
297 void SnapManager::guideSnap(Geom::Point &p, Geom::Point const &guide_normal) const
298 {
299 // This method is used to snap a guide to nodes, while dragging the guide around
301 if ( !(object.GuidesMightSnap() && snapprefs.getSnapEnabledGlobally()) || snapprefs.getSnapPostponedGlobally() ) {
302 return;
303 }
305 SnappedConstraints sc;
306 object.guideSnap(sc, p, guide_normal);
308 Inkscape::SnappedPoint const s = findBestSnap(p, sc, false);
309 s.getPoint(p);
310 }
313 /**
314 * Main internal snapping method, which is called by the other, friendlier, public
315 * methods. It's a bit hairy as it has lots of parameters, but it saves on a lot
316 * of duplicated code.
317 *
318 * \param type Type of points being snapped.
319 * \param points List of points to snap (i.e. untransformed).
320 * \param pointer Location of the mouse pointer, at the time when dragging started (i.e. "untransformed")
321 * \param constrained true if the snap is constrained.
322 * \param constraint Constraint line to use, if `constrained' is true, otherwise undefined.
323 * \param transformation_type Type of transformation to apply to points before trying to snap them.
324 * \param transformation Description of the transformation; details depend on the type.
325 * \param origin Origin of the transformation, if applicable.
326 * \param dim Dimension of the transformation, if applicable.
327 * \param uniform true if the transformation should be uniform; only applicable for stretching and scaling.
328 */
330 Inkscape::SnappedPoint SnapManager::_snapTransformed(
331 Inkscape::SnapPreferences::PointType type,
332 std::vector<Geom::Point> const &points,
333 Geom::Point const &pointer,
334 bool constrained,
335 Inkscape::Snapper::ConstraintLine const &constraint,
336 Transformation transformation_type,
337 Geom::Point const &transformation,
338 Geom::Point const &origin,
339 Geom::Dim2 dim,
340 bool uniform) const
341 {
342 /* We have a list of points, which we are proposing to transform in some way. We need to see
343 ** if any of these points, when transformed, snap to anything. If they do, we return the
344 ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
345 */
347 /* Quick check to see if we have any snappers that are enabled
348 ** Also used to globally disable all snapping
349 */
350 if (someSnapperMightSnap() == false) {
351 return Inkscape::SnappedPoint();
352 }
354 std::vector<Geom::Point> transformed_points;
355 Geom::Rect bbox;
357 for (std::vector<Geom::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
359 /* Work out the transformed version of this point */
360 Geom::Point transformed = _transformPoint(*i, transformation_type, transformation, origin, dim, uniform);
362 // add the current transformed point to the box hulling all transformed points
363 if (i == points.begin()) {
364 bbox = Geom::Rect(transformed, transformed);
365 } else {
366 bbox.expandTo(transformed);
367 }
369 transformed_points.push_back(transformed);
370 }
372 /* The current best transformation */
373 Geom::Point best_transformation = transformation;
375 /* The current best metric for the best transformation; lower is better, NR_HUGE
376 ** means that we haven't snapped anything.
377 */
378 Geom::Point best_scale_metric(NR_HUGE, NR_HUGE);
379 Inkscape::SnappedPoint best_snapped_point;
380 g_assert(best_snapped_point.getAlwaysSnap() == false); // Check initialization of snapped point
381 g_assert(best_snapped_point.getAtIntersection() == false);
383 std::vector<Geom::Point>::const_iterator j = transformed_points.begin();
385 // std::cout << std::endl;
386 for (std::vector<Geom::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
388 /* Snap it */
389 Inkscape::SnappedPoint snapped_point;
390 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
391 Geom::Point const b = (*i - origin); // vector to original point
393 if (constrained) {
394 if ((transformation_type == SCALE || transformation_type == STRETCH) && uniform) {
395 // When uniformly scaling, each point will have its own unique constraint line,
396 // running from the scaling origin to the original untransformed point. We will
397 // calculate that line here
398 dedicated_constraint = Inkscape::Snapper::ConstraintLine(origin, b);
399 } else if (transformation_type == STRETCH) { // when non-uniform stretching {
400 dedicated_constraint = Inkscape::Snapper::ConstraintLine((*i), component_vectors[dim]);
401 } else if (transformation_type == TRANSLATION) {
402 // When doing a constrained translation, all points will move in the same direction, i.e.
403 // either horizontally or vertically. The lines along which they move are therefore all
404 // parallel, but might not be colinear. Therefore we will have to set the point through
405 // which the constraint-line runs here, for each point individually.
406 dedicated_constraint.setPoint(*i);
407 } // else: leave the original constraint, e.g. for skewing
408 if (transformation_type == SCALE && !uniform) {
409 g_warning("Non-uniform constrained scaling is not supported!");
410 }
411 snapped_point = constrainedSnap(type, *j, dedicated_constraint, i == points.begin(), bbox);
412 } else {
413 bool const c1 = fabs(b[Geom::X]) < 1e-6;
414 bool const c2 = fabs(b[Geom::Y]) < 1e-6;
415 if (transformation_type == SCALE && (c1 || c2) && !(c1 && c2)) {
416 // When scaling, a point aligned either horizontally or vertically with the origin can only
417 // move in that specific direction; therefore it should only snap in that direction, otherwise
418 // we will get snapped points with an invalid transformation
419 dedicated_constraint = Inkscape::Snapper::ConstraintLine(origin, component_vectors[c1]);
420 snapped_point = constrainedSnap(type, *j, dedicated_constraint, i == points.begin(), bbox);
421 } else {
422 snapped_point = freeSnap(type, *j, i == points.begin(), bbox);
423 }
424 }
425 // std::cout << "dist = " << snapped_point.getSnapDistance() << std::endl;
426 snapped_point.setPointerDistance(Geom::L2(pointer - *i));
428 Geom::Point result;
429 Geom::Point scale_metric(NR_HUGE, NR_HUGE);
431 if (snapped_point.getSnapped()) {
432 /* We snapped. Find the transformation that describes where the snapped point has
433 ** ended up, and also the metric for this transformation.
434 */
435 Geom::Point const a = (snapped_point.getPoint() - origin); // vector to snapped point
436 //Geom::Point const b = (*i - origin); // vector to original point
438 switch (transformation_type) {
439 case TRANSLATION:
440 result = snapped_point.getPoint() - *i;
441 /* Consider the case in which a box is almost aligned with a grid in both
442 * horizontal and vertical directions. The distance to the intersection of
443 * the grid lines will always be larger then the distance to a single grid
444 * line. If we prefer snapping to an intersection instead of to a single
445 * grid line, then we cannot use "metric = Geom::L2(result)". Therefore the
446 * snapped distance will be used as a metric. Please note that the snapped
447 * distance is defined as the distance to the nearest line of the intersection,
448 * and not to the intersection itself!
449 */
450 // Only for translations, the relevant metric will be the real snapped distance,
451 // so we don't have to do anything special here
452 break;
453 case SCALE:
454 {
455 result = Geom::Point(NR_HUGE, NR_HUGE);
456 // If this point *i is horizontally or vertically aligned with
457 // the origin of the scaling, then it will scale purely in X or Y
458 // We can therefore only calculate the scaling in this direction
459 // and the scaling factor for the other direction should remain
460 // untouched (unless scaling is uniform ofcourse)
461 for (int index = 0; index < 2; index++) {
462 if (fabs(b[index]) > 1e-6) { // if SCALING CAN occur in this direction
463 if (fabs(fabs(a[index]/b[index]) - fabs(transformation[index])) > 1e-12) { // if SNAPPING DID occur in this direction
464 result[index] = a[index] / b[index]; // then calculate it!
465 }
466 // we might leave result[1-index] = NR_HUGE
467 // if scaling didn't occur in the other direction
468 }
469 }
470 // Compare the resulting scaling with the desired scaling
471 scale_metric = result - transformation; // One or both of its components might be NR_HUGE
472 break;
473 }
474 case STRETCH:
475 result = Geom::Point(NR_HUGE, NR_HUGE);
476 if (fabs(b[dim]) > 1e-6) { // if STRETCHING will occur for this point
477 result[dim] = a[dim] / b[dim];
478 result[1-dim] = uniform ? result[dim] : 1;
479 } else { // STRETCHING might occur for this point, but only when the stretching is uniform
480 if (uniform && fabs(b[1-dim]) > 1e-6) {
481 result[1-dim] = a[1-dim] / b[1-dim];
482 result[dim] = result[1-dim];
483 }
484 }
485 // Store the metric for this transformation as a virtual distance
486 snapped_point.setSnapDistance(std::abs(result[dim] - transformation[dim]));
487 snapped_point.setSecondSnapDistance(NR_HUGE);
488 break;
489 case SKEW:
490 result[0] = (snapped_point.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]); // skew factor
491 result[1] = transformation[1]; // scale factor
492 // Store the metric for this transformation as a virtual distance
493 snapped_point.setSnapDistance(std::abs(result[0] - transformation[0]));
494 snapped_point.setSecondSnapDistance(NR_HUGE);
495 break;
496 default:
497 g_assert_not_reached();
498 }
500 // When scaling, we're considering the best transformation in each direction separately. We will have a metric in each
501 // direction, whereas for all other transformation we only a single one-dimensional metric. That's why we need to handle
502 // the scaling metric differently
503 if (transformation_type == SCALE) {
504 for (int index = 0; index < 2; index++) {
505 if (fabs(scale_metric[index]) < fabs(best_scale_metric[index])) {
506 best_transformation[index] = result[index];
507 best_scale_metric[index] = fabs(scale_metric[index]);
508 // When scaling, we're considering the best transformation in each direction separately
509 // Therefore two different snapped points might together make a single best transformation
510 // We will however return only a single snapped point (e.g. to display the snapping indicator)
511 best_snapped_point = snapped_point;
512 // std::cout << "SEL ";
513 } // else { std::cout << " ";}
514 }
515 if (uniform) {
516 if (best_scale_metric[0] < best_scale_metric[1]) {
517 best_transformation[1] = best_transformation[0];
518 best_scale_metric[1] = best_scale_metric[0];
519 } else {
520 best_transformation[0] = best_transformation[1];
521 best_scale_metric[0] = best_scale_metric[1];
522 }
523 }
524 } else { // For all transformations other than scaling
525 if (best_snapped_point.isOtherSnapBetter(snapped_point, true)) {
526 best_transformation = result;
527 best_snapped_point = snapped_point;
528 }
529 }
530 }
532 j++;
533 }
535 Geom::Coord best_metric;
536 if (transformation_type == SCALE) {
537 // When scaling, don't ever exit with one of scaling components set to NR_HUGE
538 for (int index = 0; index < 2; index++) {
539 if (best_transformation[index] == NR_HUGE) {
540 if (uniform && best_transformation[1-index] < NR_HUGE) {
541 best_transformation[index] = best_transformation[1-index];
542 } else {
543 best_transformation[index] = transformation[index];
544 }
545 }
546 }
547 best_metric = std::min(best_scale_metric[0], best_scale_metric[1]);
548 } else { // For all transformations other than scaling
549 best_metric = best_snapped_point.getSnapDistance();
550 }
552 best_snapped_point.setTransformation(best_transformation);
553 // Using " < 1e6" instead of " < NR_HUGE" for catching some rounding errors
554 // These rounding errors might be caused by NRRects, see bug #1584301
555 best_snapped_point.setSnapDistance(best_metric < 1e6 ? best_metric : NR_HUGE);
556 return best_snapped_point;
557 }
560 /**
561 * Try to snap a list of points to any interested snappers after they have undergone
562 * a translation.
563 *
564 * \param point_type Type of points.
565 * \param p Points.
566 * \param tr Proposed translation.
567 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
568 */
570 Inkscape::SnappedPoint SnapManager::freeSnapTranslation(Inkscape::SnapPreferences::PointType point_type,
571 std::vector<Geom::Point> const &p,
572 Geom::Point const &pointer,
573 Geom::Point const &tr) const
574 {
575 if (p.size() == 1) {
576 _displaySnapsource(point_type, _transformPoint(p.at(0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false));
577 }
579 return _snapTransformed(point_type, p, pointer, false, Geom::Point(0,0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false);
580 }
583 /**
584 * Try to snap a list of points to any interested snappers after they have undergone a
585 * translation. A snap will only occur along a line described by a
586 * Inkscape::Snapper::ConstraintLine.
587 *
588 * \param point_type Type of points.
589 * \param p Points.
590 * \param constraint Constraint line.
591 * \param tr Proposed translation.
592 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
593 */
595 Inkscape::SnappedPoint SnapManager::constrainedSnapTranslation(Inkscape::SnapPreferences::PointType point_type,
596 std::vector<Geom::Point> const &p,
597 Geom::Point const &pointer,
598 Inkscape::Snapper::ConstraintLine const &constraint,
599 Geom::Point const &tr) const
600 {
601 if (p.size() == 1) {
602 _displaySnapsource(point_type, _transformPoint(p.at(0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false));
603 }
605 return _snapTransformed(point_type, p, pointer, true, constraint, TRANSLATION, tr, Geom::Point(0,0), Geom::X, false);
606 }
609 /**
610 * Try to snap a list of points to any interested snappers after they have undergone
611 * a scale.
612 *
613 * \param point_type Type of points.
614 * \param p Points.
615 * \param s Proposed scale.
616 * \param o Origin of proposed scale.
617 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
618 */
620 Inkscape::SnappedPoint SnapManager::freeSnapScale(Inkscape::SnapPreferences::PointType point_type,
621 std::vector<Geom::Point> const &p,
622 Geom::Point const &pointer,
623 Geom::Scale const &s,
624 Geom::Point const &o) const
625 {
626 if (p.size() == 1) {
627 _displaySnapsource(point_type, _transformPoint(p.at(0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, false));
628 }
630 return _snapTransformed(point_type, p, pointer, false, Geom::Point(0,0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, false);
631 }
634 /**
635 * Try to snap a list of points to any interested snappers after they have undergone
636 * a scale. A snap will only occur along a line described by a
637 * Inkscape::Snapper::ConstraintLine.
638 *
639 * \param point_type Type of points.
640 * \param p Points.
641 * \param s Proposed scale.
642 * \param o Origin of proposed scale.
643 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
644 */
646 Inkscape::SnappedPoint SnapManager::constrainedSnapScale(Inkscape::SnapPreferences::PointType point_type,
647 std::vector<Geom::Point> const &p,
648 Geom::Point const &pointer,
649 Geom::Scale const &s,
650 Geom::Point const &o) const
651 {
652 // When constrained scaling, only uniform scaling is supported.
653 if (p.size() == 1) {
654 _displaySnapsource(point_type, _transformPoint(p.at(0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, true));
655 }
657 return _snapTransformed(point_type, p, pointer, true, Geom::Point(0,0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, true);
658 }
661 /**
662 * Try to snap a list of points to any interested snappers after they have undergone
663 * a stretch.
664 *
665 * \param point_type Type of points.
666 * \param p Points.
667 * \param s Proposed stretch.
668 * \param o Origin of proposed stretch.
669 * \param d Dimension in which to apply proposed stretch.
670 * \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
671 * \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
672 */
674 Inkscape::SnappedPoint SnapManager::constrainedSnapStretch(Inkscape::SnapPreferences::PointType point_type,
675 std::vector<Geom::Point> const &p,
676 Geom::Point const &pointer,
677 Geom::Coord const &s,
678 Geom::Point const &o,
679 Geom::Dim2 d,
680 bool u) const
681 {
682 if (p.size() == 1) {
683 _displaySnapsource(point_type, _transformPoint(p.at(0), STRETCH, Geom::Point(s, s), o, d, u));
684 }
686 return _snapTransformed(point_type, p, pointer, true, Geom::Point(0,0), STRETCH, Geom::Point(s, s), o, d, u);
687 }
690 /**
691 * Try to snap a list of points to any interested snappers after they have undergone
692 * a skew.
693 *
694 * \param point_type Type of points.
695 * \param p Points.
696 * \param s Proposed skew.
697 * \param o Origin of proposed skew.
698 * \param d Dimension in which to apply proposed skew.
699 * \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
700 */
702 Inkscape::SnappedPoint SnapManager::constrainedSnapSkew(Inkscape::SnapPreferences::PointType point_type,
703 std::vector<Geom::Point> const &p,
704 Geom::Point const &pointer,
705 Inkscape::Snapper::ConstraintLine const &constraint,
706 Geom::Point const &s,
707 Geom::Point const &o,
708 Geom::Dim2 d) const
709 {
710 // "s" contains skew factor in s[0], and scale factor in s[1]
712 // Snapping the nodes of the boundingbox of a selection that is being transformed, will only work if
713 // the transformation of the bounding box is equal to the transformation of the individual nodes. This is
714 // NOT the case for example when rotating or skewing. The bounding box itself cannot possibly rotate or skew,
715 // so it's corners have a different transformation. The snappers cannot handle this, therefore snapping
716 // of bounding boxes is not allowed here.
717 g_assert(!(point_type & Inkscape::SnapPreferences::SNAPPOINT_BBOX));
719 if (p.size() == 1) {
720 _displaySnapsource(point_type, _transformPoint(p.at(0), SKEW, s, o, d, false));
721 }
723 return _snapTransformed(point_type, p, pointer, true, constraint, SKEW, s, o, d, false);
724 }
726 Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, SnappedConstraints &sc, bool constrained) const
727 {
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 std::cout << " Curves : " << sc.curves.size()<< std::endl;
736 */
738 // Store all snappoints
739 std::list<Inkscape::SnappedPoint> sp_list;
741 // search for the closest snapped point
742 Inkscape::SnappedPoint closestPoint;
743 if (getClosestSP(sc.points, closestPoint)) {
744 sp_list.push_back(closestPoint);
745 }
747 // search for the closest snapped curve
748 Inkscape::SnappedCurve closestCurve;
749 if (getClosestCurve(sc.curves, closestCurve)) {
750 sp_list.push_back(Inkscape::SnappedPoint(closestCurve));
751 }
753 if (snapprefs.getSnapIntersectionCS()) {
754 // search for the closest snapped intersection of curves
755 Inkscape::SnappedPoint closestCurvesIntersection;
756 if (getClosestIntersectionCS(sc.curves, p, closestCurvesIntersection)) {
757 sp_list.push_back(closestCurvesIntersection);
758 }
759 }
761 // search for the closest snapped grid line
762 Inkscape::SnappedLine closestGridLine;
763 if (getClosestSL(sc.grid_lines, closestGridLine)) {
764 closestGridLine.setTarget(Inkscape::SNAPTARGET_GRID);
765 sp_list.push_back(Inkscape::SnappedPoint(closestGridLine));
766 }
768 // search for the closest snapped guide line
769 Inkscape::SnappedLine closestGuideLine;
770 if (getClosestSL(sc.guide_lines, closestGuideLine)) {
771 closestGuideLine.setTarget(Inkscape::SNAPTARGET_GUIDE);
772 sp_list.push_back(Inkscape::SnappedPoint(closestGuideLine));
773 }
775 // When freely snapping to a grid/guide/path, only one degree of freedom is eliminated
776 // Therefore we will try get fully constrained by finding an intersection with another grid/guide/path
778 // When doing a constrained snap however, we're already at an intersection of the constrained line and
779 // the grid/guide/path we're snapping to. This snappoint is therefore fully constrained, so there's
780 // no need to look for additional intersections
781 if (!constrained) {
782 // search for the closest snapped intersection of grid lines
783 Inkscape::SnappedPoint closestGridPoint;
784 if (getClosestIntersectionSL(sc.grid_lines, closestGridPoint)) {
785 closestGridPoint.setTarget(Inkscape::SNAPTARGET_GRID_INTERSECTION);
786 sp_list.push_back(closestGridPoint);
787 }
789 // search for the closest snapped intersection of guide lines
790 Inkscape::SnappedPoint closestGuidePoint;
791 if (getClosestIntersectionSL(sc.guide_lines, closestGuidePoint)) {
792 closestGuidePoint.setTarget(Inkscape::SNAPTARGET_GUIDE_INTERSECTION);
793 sp_list.push_back(closestGuidePoint);
794 }
796 // search for the closest snapped intersection of grid with guide lines
797 if (snapprefs.getSnapIntersectionGG()) {
798 Inkscape::SnappedPoint closestGridGuidePoint;
799 if (getClosestIntersectionSL(sc.grid_lines, sc.guide_lines, closestGridGuidePoint)) {
800 closestGridGuidePoint.setTarget(Inkscape::SNAPTARGET_GRID_GUIDE_INTERSECTION);
801 sp_list.push_back(closestGridGuidePoint);
802 }
803 }
804 }
806 // now let's see which snapped point gets a thumbs up
807 Inkscape::SnappedPoint bestSnappedPoint = Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false);
808 // std::cout << "Finding the best snap..." << std::endl;
809 for (std::list<Inkscape::SnappedPoint>::const_iterator i = sp_list.begin(); i != sp_list.end(); i++) {
810 // first find out if this snapped point is within snapping range
811 // std::cout << "sp = " << from_2geom((*i).getPoint());
812 if ((*i).getSnapDistance() <= (*i).getTolerance()) {
813 // if it's the first point, or if it is closer than the best snapped point so far
814 if (i == sp_list.begin() || bestSnappedPoint.isOtherSnapBetter(*i, false)) {
815 // then prefer this point over the previous one
816 bestSnappedPoint = *i;
817 }
818 }
819 // std::cout << std::endl;
820 }
822 // Update the snap indicator, if requested
823 if (_snapindicator) {
824 if (bestSnappedPoint.getSnapped()) {
825 _desktop->snapindicator->set_new_snaptarget(bestSnappedPoint);
826 } else {
827 _desktop->snapindicator->remove_snaptarget();
828 }
829 }
831 // std::cout << "findBestSnap = " << bestSnappedPoint.getPoint() << " | dist = " << bestSnappedPoint.getSnapDistance() << std::endl;
832 return bestSnappedPoint;
833 }
835 void SnapManager::setup(SPDesktop const *desktop, bool snapindicator, SPItem const *item_to_ignore, std::vector<Geom::Point> *unselected_nodes)
836 {
837 g_assert(desktop != NULL);
838 _item_to_ignore = item_to_ignore;
839 _items_to_ignore = NULL;
840 _desktop = desktop;
841 _snapindicator = snapindicator;
842 _unselected_nodes = unselected_nodes;
843 }
845 void SnapManager::setup(SPDesktop const *desktop, bool snapindicator, std::vector<SPItem const *> &items_to_ignore, std::vector<Geom::Point> *unselected_nodes)
846 {
847 g_assert(desktop != NULL);
848 _item_to_ignore = NULL;
849 _items_to_ignore = &items_to_ignore;
850 _desktop = desktop;
851 _snapindicator = snapindicator;
852 _unselected_nodes = unselected_nodes;
853 }
855 SPDocument *SnapManager::getDocument() const
856 {
857 return _named_view->document;
858 }
860 Geom::Point SnapManager::_transformPoint(Geom::Point const &p,
861 Transformation const transformation_type,
862 Geom::Point const &transformation,
863 Geom::Point const &origin,
864 Geom::Dim2 const dim,
865 bool const uniform) const
866 {
867 /* Work out the transformed version of this point */
868 Geom::Point transformed;
869 switch (transformation_type) {
870 case TRANSLATION:
871 transformed = p + transformation;
872 break;
873 case SCALE:
874 transformed = (p - origin) * Geom::Scale(transformation[Geom::X], transformation[Geom::Y]) + origin;
875 break;
876 case STRETCH:
877 {
878 Geom::Scale s(1, 1);
879 if (uniform)
880 s[Geom::X] = s[Geom::Y] = transformation[dim];
881 else {
882 s[dim] = transformation[dim];
883 s[1 - dim] = 1;
884 }
885 transformed = ((p - origin) * s) + origin;
886 break;
887 }
888 case SKEW:
889 // Apply the skew factor
890 transformed[dim] = p[dim] + transformation[0] * (p[1 - dim] - origin[1 - dim]);
891 // While skewing, mirroring and scaling (by integer multiples) in the opposite direction is also allowed.
892 // Apply that scale factor here
893 transformed[1-dim] = (p - origin)[1 - dim] * transformation[1] + origin[1 - dim];
894 break;
895 default:
896 g_assert_not_reached();
897 }
899 return transformed;
900 }
902 void SnapManager::_displaySnapsource(Inkscape::SnapPreferences::PointType point_type, Geom::Point const &p) const {
904 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
905 if (prefs->getBool("/options/snapclosestonly/value")) {
906 bool p_is_a_node = point_type & Inkscape::SnapPreferences::SNAPPOINT_NODE;
907 bool p_is_a_bbox = point_type & Inkscape::SnapPreferences::SNAPPOINT_BBOX;
908 if ((p_is_a_node && snapprefs.getSnapModeNode()) || (p_is_a_bbox && snapprefs.getSnapModeBBox())) {
909 _desktop->snapindicator->set_new_snapsource(p);
910 } else {
911 _desktop->snapindicator->remove_snapsource();
912 }
913 }
914 }
916 /*
917 Local Variables:
918 mode:c++
919 c-file-style:"stroustrup"
920 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
921 indent-tabs-mode:nil
922 fill-column:99
923 End:
924 */
925 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :