218cbb0293de6f42c082f78fbacd57f3e8d3e0bc
1 #define __SP_DESKTOP_SNAP_C__
3 /**
4 * \file snap.cpp
5 * \brief SnapManager class.
6 *
7 * Authors:
8 * Lauris Kaplinski <lauris@kaplinski.com>
9 * Frank Felfe <innerspace@iname.com>
10 * Carl Hetherington <inkscape@carlh.net>
11 *
12 * Copyright (C) 2006-2007 Johan Engelen <johan@shouraizou.nl>
13 * Copyright (C) 1999-2002 Authors
14 *
15 * Released under GNU GPL, read the file 'COPYING' for more information
16 */
18 #include "sp-namedview.h"
19 #include "snap.h"
21 #include <libnr/nr-point-fns.h>
22 #include <libnr/nr-scale-ops.h>
23 #include <libnr/nr-values.h>
25 #include "display/canvas-grid.h"
27 #include "inkscape.h"
28 #include "desktop.h"
30 /**
31 * Construct a SnapManager for a SPNamedView.
32 *
33 * \param v `Owning' SPNamedView.
34 */
36 SnapManager::SnapManager(SPNamedView const *v) :
37 guide(v, 0),
38 object(v, 0),
39 _named_view(v)
40 {
42 }
45 /**
46 * \return List of snappers that we use.
47 */
48 SnapManager::SnapperList
49 SnapManager::getSnappers() const
50 {
51 SnapManager::SnapperList s;
52 s.push_back(&guide);
53 s.push_back(&object);
55 SnapManager::SnapperList gs = getGridSnappers();
56 s.splice(s.begin(), gs);
58 return s;
59 }
61 /**
62 * \return List of gridsnappers that we use.
63 */
64 SnapManager::SnapperList
65 SnapManager::getGridSnappers() const
66 {
67 SnapperList s;
69 //FIXME: this code should actually do this: add new grid snappers that are active for this desktop. now it just adds all gridsnappers
70 SPDesktop* desktop = SP_ACTIVE_DESKTOP;
71 if (desktop && desktop->gridsEnabled()) {
72 for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
73 Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
74 s.push_back(grid->snapper);
75 }
76 }
78 return s;
79 }
81 /**
82 * \return true if one of the snappers will try to snap something.
83 */
85 bool SnapManager::SomeSnapperMightSnap() const
86 {
87 SnapperList const s = getSnappers();
88 SnapperList::const_iterator i = s.begin();
89 while (i != s.end() && (*i)->ThisSnapperMightSnap() == false) {
90 i++;
91 }
94 return (i != s.end());
95 }
97 void SnapManager::setSnapModeBBox(bool enabled)
98 {
99 guide.setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
100 object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX, enabled);
101 object.setSnapToBBoxNodes(enabled);
102 object.setSnapToBBoxPaths(enabled);
103 object.setStrictSnapping(true);
104 }
106 bool SnapManager::getSnapModeBBox() const
107 {
108 return guide.getSnapFrom(Inkscape::Snapper::SNAPPOINT_BBOX);
109 }
112 void SnapManager::setSnapModeNodes(bool enabled)
113 {
114 guide.setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
115 object.setSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE, enabled);
116 object.setSnapToItemNodes(enabled);
117 object.setSnapToItemPaths(enabled);
118 object.setStrictSnapping(true);
119 }
121 bool SnapManager::getSnapModeNodes() const
122 {
123 return guide.getSnapFrom(Inkscape::Snapper::SNAPPOINT_NODE);
124 }
126 /**
127 * Try to snap a point to any interested snappers.
128 *
129 * \param t Type of point.
130 * \param p Point.
131 * \param it Item to ignore when snapping.
132 * \return Snapped point.
133 */
135 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
136 NR::Point const &p,
137 SPItem const *it) const
139 {
140 std::list<SPItem const *> lit;
141 lit.push_back(it);
142 return freeSnap(t, p, lit);
143 }
146 /**
147 * Try to snap a point to any interested snappers.
148 *
149 * \param t Type of point.
150 * \param p Point.
151 * \param it List of items to ignore when snapping.
152 * \return Snapped point.
153 */
155 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
156 NR::Point const &p,
157 std::list<SPItem const *> const &it) const
158 {
159 SnapperList const snappers = getSnappers();
161 return freeSnap(t, p, it, snappers);
162 }
164 /**
165 * Try to snap a point to any of the specified snappers.
166 *
167 * \param t Type of point.
168 * \param p Point.
169 * \param it List of items to ignore when snapping.
170 * \param snappers List of snappers to try to snap to
171 * \return Snapped point.
172 */
174 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
175 NR::Point const &p,
176 std::list<SPItem const *> const &it,
177 SnapperList const &snappers) const
178 {
179 Inkscape::SnappedPoint r(p, NR_HUGE);
181 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
182 Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
183 if (s.getDistance() < r.getDistance()) {
184 r = s;
185 }
186 }
188 return r;
189 }
191 /**
192 * Try to snap a point to any of the specified snappers. Snap always, ignoring the snap-distance
193 *
194 * \param t Type of point.
195 * \param p Point.
196 * \param it Item to ignore when snapping.
197 * \param snappers List of snappers to try to snap to
198 * \return Snapped point.
199 */
201 Inkscape::SnappedPoint
202 SnapManager::freeSnapAlways( Inkscape::Snapper::PointType t,
203 NR::Point const &p,
204 SPItem const *it,
205 SnapperList &snappers )
206 {
207 std::list<SPItem const *> lit;
208 lit.push_back(it);
209 return freeSnapAlways(t, p, lit, snappers);
210 }
212 /**
213 * Try to snap a point to any of the specified snappers. Snap always, ignoring the snap-distance
214 *
215 * \param t Type of point.
216 * \param p Point.
217 * \param it List of items to ignore when snapping.
218 * \param snappers List of snappers to try to snap to
219 * \return Snapped point.
220 */
222 Inkscape::SnappedPoint
223 SnapManager::freeSnapAlways( Inkscape::Snapper::PointType t,
224 NR::Point const &p,
225 std::list<SPItem const *> const &it,
226 SnapperList &snappers )
227 {
228 Inkscape::SnappedPoint r(p, NR_HUGE);
230 for (SnapperList::iterator i = snappers.begin(); i != snappers.end(); i++) {
231 gdouble const curr_gridsnap = (*i)->getDistance();
232 const_cast<Inkscape::Snapper*> (*i)->setDistance(NR_HUGE);
233 Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
234 const_cast<Inkscape::Snapper*> (*i)->setDistance(curr_gridsnap);
236 if (s.getDistance() < r.getDistance()) {
237 r = s;
238 }
239 }
241 return r;
242 }
246 /**
247 * Try to snap a point to any interested snappers. A snap will only occur along
248 * a line described by a Inkscape::Snapper::ConstraintLine.
249 *
250 * \param t Type of point.
251 * \param p Point.
252 * \param c Constraint line.
253 * \param it Item to ignore when snapping.
254 * \return Snapped point.
255 */
257 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
258 NR::Point const &p,
259 Inkscape::Snapper::ConstraintLine const &c,
260 SPItem const *it) const
261 {
262 std::list<SPItem const *> lit;
263 lit.push_back(it);
264 return constrainedSnap(t, p, c, lit);
265 }
269 /**
270 * Try to snap a point to any interested snappers. A snap will only occur along
271 * a line described by a Inkscape::Snapper::ConstraintLine.
272 *
273 * \param t Type of point.
274 * \param p Point.
275 * \param c Constraint line.
276 * \param it List of items to ignore when snapping.
277 * \return Snapped point.
278 */
280 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
281 NR::Point const &p,
282 Inkscape::Snapper::ConstraintLine const &c,
283 std::list<SPItem const *> const &it) const
284 {
285 Inkscape::SnappedPoint r(p, NR_HUGE);
287 SnapperList const snappers = getSnappers();
288 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
289 Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, p, c, it);
290 if (s.getDistance() < r.getDistance()) {
291 r = s;
292 }
293 }
295 return r;
296 }
300 /**
301 * Main internal snapping method, which is called by the other, friendlier, public
302 * methods. It's a bit hairy as it has lots of parameters, but it saves on a lot
303 * of duplicated code.
304 *
305 * \param type Type of points being snapped.
306 * \param points List of points to snap.
307 * \param ignore List of items to ignore while snapping.
308 * \param constrained true if the snap is constrained.
309 * \param constraint Constraint line to use, if `constrained' is true, otherwise undefined.
310 * \param transformation_type Type of transformation to apply to points before trying to snap them.
311 * \param transformation Description of the transformation; details depend on the type.
312 * \param origin Origin of the transformation, if applicable.
313 * \param dim Dimension of the transformation, if applicable.
314 * \param uniform true if the transformation should be uniform, if applicable.
315 */
317 std::pair<NR::Point, bool> SnapManager::_snapTransformed(
318 Inkscape::Snapper::PointType type,
319 std::vector<NR::Point> const &points,
320 std::list<SPItem const *> const &ignore,
321 bool constrained,
322 Inkscape::Snapper::ConstraintLine const &constraint,
323 Transformation transformation_type,
324 NR::Point const &transformation,
325 NR::Point const &origin,
326 NR::Dim2 dim,
327 bool uniform) const
328 {
329 /* We have a list of points, which we are proposing to transform in some way. We need to see
330 ** if any of these points, when transformed, snap to anything. If they do, we return the
331 ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
332 */
334 /* Quick check to see if we have any snappers that are enabled */
335 if (SomeSnapperMightSnap() == false) {
336 return std::make_pair(transformation, false);
337 }
339 /* The current best transformation */
340 NR::Point best_transformation = transformation;
342 /* The current best metric for the best transformation; lower is better, NR_HUGE
343 ** means that we haven't snapped anything.
344 */
345 double best_metric = NR_HUGE;
347 for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
349 /* Work out the transformed version of this point */
350 NR::Point transformed;
351 switch (transformation_type) {
352 case TRANSLATION:
353 transformed = *i + transformation;
354 break;
355 case SCALE:
356 transformed = ((*i - origin) * NR::scale(transformation[NR::X], transformation[NR::Y])) + origin;
357 break;
358 case STRETCH:
359 {
360 NR::scale s(1, 1);
361 if (uniform)
362 s[NR::X] = s[NR::Y] = transformation[dim];
363 else {
364 s[dim] = transformation[dim];
365 s[1 - dim] = 1;
366 }
367 transformed = ((*i - origin) * s) + origin;
368 break;
369 }
370 case SKEW:
371 transformed = *i;
372 transformed[dim] += transformation[dim] * ((*i)[1 - dim] - origin[1 - dim]);
373 break;
374 default:
375 g_assert_not_reached();
376 }
378 /* Snap it */
379 Inkscape::SnappedPoint const snapped = constrained ?
380 constrainedSnap(type, transformed, constraint, ignore) : freeSnap(type, transformed, ignore);
382 if (snapped.getDistance() < NR_HUGE) {
383 /* We snapped. Find the transformation that describes where the snapped point has
384 ** ended up, and also the metric for this transformation.
385 */
386 NR::Point result;
387 NR::Coord metric;
388 switch (transformation_type) {
389 case TRANSLATION:
390 result = snapped.getPoint() - *i;
391 metric = NR::L2(result);
392 break;
393 case SCALE:
394 {
395 NR::Point const a = (snapped.getPoint() - origin);
396 NR::Point const b = (*i - origin);
397 result = NR::Point(a[NR::X] / b[NR::X], a[NR::Y] / b[NR::Y]);
398 metric = std::abs(NR::L2(result) - NR::L2(transformation));
399 break;
400 }
401 case STRETCH:
402 {
403 for (int j = 0; j < 2; j++) {
404 if (uniform || j == dim) {
405 result[j] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]);
406 } else {
407 result[j] = 1;
408 }
409 }
410 metric = std::abs(result[dim] - transformation[dim]);
411 break;
412 }
413 case SKEW:
414 result[dim] = (snapped.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]);
415 metric = std::abs(result[dim] - transformation[dim]);
416 break;
417 default:
418 g_assert_not_reached();
419 }
421 /* Note it if it's the best so far */
422 if (metric < best_metric && metric != 0) {
423 best_transformation = result;
424 best_metric = metric;
425 }
426 }
427 }
429 // Using " < 1e6" instead of " < NR::HUGE" for catching some rounding errors
430 // These rounding errors might be caused by NRRects, see bug #1584301
431 return std::make_pair(best_transformation, best_metric < 1e6);
432 }
435 /**
436 * Try to snap a list of points to any interested snappers after they have undergone
437 * a translation.
438 *
439 * \param t Type of points.
440 * \param p Points.
441 * \param it List of items to ignore when snapping.
442 * \param tr Proposed translation.
443 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
444 */
446 std::pair<NR::Point, bool> SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t,
447 std::vector<NR::Point> const &p,
448 std::list<SPItem const *> const &it,
449 NR::Point const &tr) const
450 {
451 return _snapTransformed(
452 t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false
453 );
454 }
457 /**
458 * Try to snap a list of points to any interested snappers after they have undergone a
459 * translation. A snap will only occur along a line described by a
460 * Inkscape::Snapper::ConstraintLine.
461 *
462 * \param t Type of points.
463 * \param p Points.
464 * \param it List of items to ignore when snapping.
465 * \param c Constraint line.
466 * \param tr Proposed translation.
467 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
468 */
470 std::pair<NR::Point, bool> SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t,
471 std::vector<NR::Point> const &p,
472 std::list<SPItem const *> const &it,
473 Inkscape::Snapper::ConstraintLine const &c,
474 NR::Point const &tr) const
475 {
476 return _snapTransformed(
477 t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false
478 );
479 }
482 /**
483 * Try to snap a list of points to any interested snappers after they have undergone
484 * a scale.
485 *
486 * \param t Type of points.
487 * \param p Points.
488 * \param it List of items to ignore when snapping.
489 * \param s Proposed scale.
490 * \param o Origin of proposed scale.
491 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
492 */
494 std::pair<NR::scale, bool> SnapManager::freeSnapScale(Inkscape::Snapper::PointType t,
495 std::vector<NR::Point> const &p,
496 std::list<SPItem const *> const &it,
497 NR::scale const &s,
498 NR::Point const &o) const
499 {
500 return _snapTransformed(
501 t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
502 );
503 }
506 /**
507 * Try to snap a list of points to any interested snappers after they have undergone
508 * a scale. A snap will only occur along a line described by a
509 * Inkscape::Snapper::ConstraintLine.
510 *
511 * \param t Type of points.
512 * \param p Points.
513 * \param it List of items to ignore when snapping.
514 * \param s Proposed scale.
515 * \param o Origin of proposed scale.
516 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
517 */
519 std::pair<NR::scale, bool> SnapManager::constrainedSnapScale(Inkscape::Snapper::PointType t,
520 std::vector<NR::Point> const &p,
521 std::list<SPItem const *> const &it,
522 Inkscape::Snapper::ConstraintLine const &c,
523 NR::scale const &s,
524 NR::Point const &o) const
525 {
526 return _snapTransformed(
527 t, p, it, true, c, SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
528 );
529 }
532 /**
533 * Try to snap a list of points to any interested snappers after they have undergone
534 * a stretch.
535 *
536 * \param t Type of points.
537 * \param p Points.
538 * \param it List of items to ignore when snapping.
539 * \param s Proposed stretch.
540 * \param o Origin of proposed stretch.
541 * \param d Dimension in which to apply proposed stretch.
542 * \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
543 * \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
544 */
546 std::pair<NR::Coord, bool> SnapManager::freeSnapStretch(Inkscape::Snapper::PointType t,
547 std::vector<NR::Point> const &p,
548 std::list<SPItem const *> const &it,
549 NR::Coord const &s,
550 NR::Point const &o,
551 NR::Dim2 d,
552 bool u) const
553 {
554 std::pair<NR::Point, bool> const r = _snapTransformed(
555 t, p, it, false, NR::Point(), STRETCH, NR::Point(s, s), o, d, u
556 );
558 return std::make_pair(r.first[d], r.second);
559 }
562 /**
563 * Try to snap a list of points to any interested snappers after they have undergone
564 * a skew.
565 *
566 * \param t Type of points.
567 * \param p Points.
568 * \param it List of items to ignore when snapping.
569 * \param s Proposed skew.
570 * \param o Origin of proposed skew.
571 * \param d Dimension in which to apply proposed skew.
572 * \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
573 */
575 std::pair<NR::Coord, bool> SnapManager::freeSnapSkew(Inkscape::Snapper::PointType t,
576 std::vector<NR::Point> const &p,
577 std::list<SPItem const *> const &it,
578 NR::Coord const &s,
579 NR::Point const &o,
580 NR::Dim2 d) const
581 {
582 std::pair<NR::Point, bool> const r = _snapTransformed(
583 t, p, it, false, NR::Point(), SKEW, NR::Point(s, s), o, d, false
584 );
586 return std::make_pair(r.first[d], r.second);
587 }
589 /*
590 Local Variables:
591 mode:c++
592 c-file-style:"stroustrup"
593 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
594 indent-tabs-mode:nil
595 fill-column:99
596 End:
597 */
598 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :