b03b3f89d9a335936388c54a399641de1da93b2f
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"
20 #include <libnr/nr-point-fns.h>
21 #include <libnr/nr-scale-ops.h>
22 #include <libnr/nr-values.h>
24 #include "display/canvas-grid.h"
26 #include "inkscape.h"
27 #include "desktop.h"
29 /**
30 * Construct a SnapManager for a SPNamedView.
31 *
32 * \param v `Owning' SPNamedView.
33 */
35 SnapManager::SnapManager(SPNamedView const *v) :
36 guide(v, 0),
37 object(v, 0),
38 _named_view(v)
39 {
41 }
44 /**
45 * \return List of snappers that we use.
46 */
47 SnapManager::SnapperList
48 SnapManager::getSnappers() const
49 {
50 SnapManager::SnapperList s;
51 s.push_back(&guide);
52 s.push_back(&object);
54 SnapManager::SnapperList gs = getGridSnappers();
55 s.splice(s.begin(), gs);
57 return s;
58 }
60 /**
61 * \return List of gridsnappers that we use.
62 */
63 SnapManager::SnapperList
64 SnapManager::getGridSnappers() const
65 {
66 SnapperList s;
68 //FIXME: this code should actually do this: add new grid snappers that are active for this desktop. now it just adds all gridsnappers
69 SPDesktop* desktop = SP_ACTIVE_DESKTOP;
70 if (desktop && desktop->gridsEnabled()) {
71 for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
72 Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
73 s.push_back(grid->snapper);
74 }
75 }
77 return s;
78 }
80 /**
81 * \return true if one of the snappers will try to snap something.
82 */
84 bool SnapManager::willSnapSomething() const
85 {
86 SnapperList const s = getSnappers();
87 SnapperList::const_iterator i = s.begin();
88 while (i != s.end() && (*i)->willSnapSomething() == false) {
89 i++;
90 }
93 return (i != s.end());
94 }
97 /**
98 * Try to snap a point to any interested snappers.
99 *
100 * \param t Type of point.
101 * \param p Point.
102 * \param it Item to ignore when snapping.
103 * \return Snapped point.
104 */
106 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
107 NR::Point const &p,
108 SPItem const *it) const
110 {
111 std::list<SPItem const *> lit;
112 lit.push_back(it);
113 return freeSnap(t, p, lit);
114 }
117 /**
118 * Try to snap a point to any interested snappers.
119 *
120 * \param t Type of point.
121 * \param p Point.
122 * \param it List of items to ignore when snapping.
123 * \return Snapped point.
124 */
126 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
127 NR::Point const &p,
128 std::list<SPItem const *> const &it) const
129 {
130 SnapperList const snappers = getSnappers();
132 return freeSnap(t, p, it, snappers);
133 }
135 /**
136 * Try to snap a point to any of the specified snappers.
137 *
138 * \param t Type of point.
139 * \param p Point.
140 * \param it List of items to ignore when snapping.
141 * \param snappers List of snappers to try to snap to
142 * \return Snapped point.
143 */
145 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
146 NR::Point const &p,
147 std::list<SPItem const *> const &it,
148 SnapperList const &snappers) const
149 {
150 Inkscape::SnappedPoint r(p, NR_HUGE);
152 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
153 Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
154 if (s.getDistance() < r.getDistance()) {
155 r = s;
156 }
157 }
159 return r;
160 }
162 /**
163 * Try to snap a point to any of the specified snappers. Snap always, ignoring the snap-distance
164 *
165 * \param t Type of point.
166 * \param p Point.
167 * \param it Item to ignore when snapping.
168 * \param snappers List of snappers to try to snap to
169 * \return Snapped point.
170 */
172 Inkscape::SnappedPoint
173 SnapManager::freeSnapAlways( Inkscape::Snapper::PointType t,
174 NR::Point const &p,
175 SPItem const *it,
176 SnapperList &snappers )
177 {
178 std::list<SPItem const *> lit;
179 lit.push_back(it);
180 return freeSnapAlways(t, p, lit, snappers);
181 }
183 /**
184 * Try to snap a point to any of the specified snappers. Snap always, ignoring the snap-distance
185 *
186 * \param t Type of point.
187 * \param p Point.
188 * \param it List of items to ignore when snapping.
189 * \param snappers List of snappers to try to snap to
190 * \return Snapped point.
191 */
193 Inkscape::SnappedPoint
194 SnapManager::freeSnapAlways( Inkscape::Snapper::PointType t,
195 NR::Point const &p,
196 std::list<SPItem const *> const &it,
197 SnapperList &snappers )
198 {
199 Inkscape::SnappedPoint r(p, NR_HUGE);
201 for (SnapperList::iterator i = snappers.begin(); i != snappers.end(); i++) {
202 gdouble const curr_gridsnap = (*i)->getDistance();
203 const_cast<Inkscape::Snapper*> (*i)->setDistance(NR_HUGE);
204 Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
205 const_cast<Inkscape::Snapper*> (*i)->setDistance(curr_gridsnap);
207 if (s.getDistance() < r.getDistance()) {
208 r = s;
209 }
210 }
212 return r;
213 }
217 /**
218 * Try to snap a point to any interested snappers. A snap will only occur along
219 * a line described by a Inkscape::Snapper::ConstraintLine.
220 *
221 * \param t Type of point.
222 * \param p Point.
223 * \param c Constraint line.
224 * \param it Item to ignore when snapping.
225 * \return Snapped point.
226 */
228 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
229 NR::Point const &p,
230 Inkscape::Snapper::ConstraintLine const &c,
231 SPItem const *it) const
232 {
233 std::list<SPItem const *> lit;
234 lit.push_back(it);
235 return constrainedSnap(t, p, c, lit);
236 }
240 /**
241 * Try to snap a point to any interested snappers. A snap will only occur along
242 * a line described by a Inkscape::Snapper::ConstraintLine.
243 *
244 * \param t Type of point.
245 * \param p Point.
246 * \param c Constraint line.
247 * \param it List of items to ignore when snapping.
248 * \return Snapped point.
249 */
251 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
252 NR::Point const &p,
253 Inkscape::Snapper::ConstraintLine const &c,
254 std::list<SPItem const *> const &it) const
255 {
256 Inkscape::SnappedPoint r(p, NR_HUGE);
258 SnapperList const snappers = getSnappers();
259 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
260 Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, p, c, it);
261 if (s.getDistance() < r.getDistance()) {
262 r = s;
263 }
264 }
266 return r;
267 }
271 /**
272 * Main internal snapping method, which is called by the other, friendlier, public
273 * methods. It's a bit hairy as it has lots of parameters, but it saves on a lot
274 * of duplicated code.
275 *
276 * \param type Type of points being snapped.
277 * \param points List of points to snap.
278 * \param ignore List of items to ignore while snapping.
279 * \param constrained true if the snap is constrained.
280 * \param constraint Constraint line to use, if `constrained' is true, otherwise undefined.
281 * \param transformation_type Type of transformation to apply to points before trying to snap them.
282 * \param transformation Description of the transformation; details depend on the type.
283 * \param origin Origin of the transformation, if applicable.
284 * \param dim Dimension of the transformation, if applicable.
285 * \param uniform true if the transformation should be uniform, if applicable.
286 */
288 std::pair<NR::Point, bool> SnapManager::_snapTransformed(
289 Inkscape::Snapper::PointType type,
290 std::vector<NR::Point> const &points,
291 std::list<SPItem const *> const &ignore,
292 bool constrained,
293 Inkscape::Snapper::ConstraintLine const &constraint,
294 Transformation transformation_type,
295 NR::Point const &transformation,
296 NR::Point const &origin,
297 NR::Dim2 dim,
298 bool uniform) const
299 {
300 /* We have a list of points, which we are proposing to transform in some way. We need to see
301 ** if any of these points, when transformed, snap to anything. If they do, we return the
302 ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
303 */
305 /* Quick check to see if we have any snappers that are enabled */
306 if (willSnapSomething() == false) {
307 return std::make_pair(transformation, false);
308 }
310 /* The current best transformation */
311 NR::Point best_transformation = transformation;
313 /* The current best metric for the best transformation; lower is better, NR_HUGE
314 ** means that we haven't snapped anything.
315 */
316 double best_metric = NR_HUGE;
318 for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
320 /* Work out the transformed version of this point */
321 NR::Point transformed;
322 switch (transformation_type) {
323 case TRANSLATION:
324 transformed = *i + transformation;
325 break;
326 case SCALE:
327 transformed = ((*i - origin) * NR::scale(transformation[NR::X], transformation[NR::Y])) + origin;
328 break;
329 case STRETCH:
330 {
331 NR::scale s(1, 1);
332 if (uniform)
333 s[NR::X] = s[NR::Y] = transformation[dim];
334 else {
335 s[dim] = transformation[dim];
336 s[1 - dim] = 1;
337 }
338 transformed = ((*i - origin) * s) + origin;
339 break;
340 }
341 case SKEW:
342 transformed = *i;
343 transformed[dim] += transformation[dim] * ((*i)[1 - dim] - origin[1 - dim]);
344 break;
345 default:
346 g_assert_not_reached();
347 }
349 /* Snap it */
350 Inkscape::SnappedPoint const snapped = constrained ?
351 constrainedSnap(type, transformed, constraint, ignore) : freeSnap(type, transformed, ignore);
353 if (snapped.getDistance() < NR_HUGE) {
354 /* We snapped. Find the transformation that describes where the snapped point has
355 ** ended up, and also the metric for this transformation.
356 */
357 NR::Point result;
358 NR::Coord metric;
359 switch (transformation_type) {
360 case TRANSLATION:
361 result = snapped.getPoint() - *i;
362 metric = NR::L2(result);
363 break;
364 case SCALE:
365 {
366 NR::Point const a = (snapped.getPoint() - origin);
367 NR::Point const b = (*i - origin);
368 result = NR::Point(a[NR::X] / b[NR::X], a[NR::Y] / b[NR::Y]);
369 metric = std::abs(NR::L2(result) - NR::L2(transformation));
370 break;
371 }
372 case STRETCH:
373 {
374 for (int j = 0; j < 2; j++) {
375 if (uniform || j == dim) {
376 result[j] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]);
377 } else {
378 result[j] = 1;
379 }
380 }
381 metric = std::abs(result[dim] - transformation[dim]);
382 break;
383 }
384 case SKEW:
385 result[dim] = (snapped.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]);
386 metric = std::abs(result[dim] - transformation[dim]);
387 break;
388 default:
389 g_assert_not_reached();
390 }
392 /* Note it if it's the best so far */
393 if (metric < best_metric && metric != 0) {
394 best_transformation = result;
395 best_metric = metric;
396 }
397 }
398 }
400 // Using " < 1e6" instead of " < NR::HUGE" for catching some rounding errors
401 // These rounding errors might be caused by NRRects, see bug #1584301
402 return std::make_pair(best_transformation, best_metric < 1e6);
403 }
406 /**
407 * Try to snap a list of points to any interested snappers after they have undergone
408 * a translation.
409 *
410 * \param t Type of points.
411 * \param p Points.
412 * \param it List of items to ignore when snapping.
413 * \param tr Proposed translation.
414 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
415 */
417 std::pair<NR::Point, bool> SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t,
418 std::vector<NR::Point> const &p,
419 std::list<SPItem const *> const &it,
420 NR::Point const &tr) const
421 {
422 return _snapTransformed(
423 t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false
424 );
425 }
428 /**
429 * Try to snap a list of points to any interested snappers after they have undergone a
430 * translation. A snap will only occur along a line described by a
431 * Inkscape::Snapper::ConstraintLine.
432 *
433 * \param t Type of points.
434 * \param p Points.
435 * \param it List of items to ignore when snapping.
436 * \param c Constraint line.
437 * \param tr Proposed translation.
438 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
439 */
441 std::pair<NR::Point, bool> SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t,
442 std::vector<NR::Point> const &p,
443 std::list<SPItem const *> const &it,
444 Inkscape::Snapper::ConstraintLine const &c,
445 NR::Point const &tr) const
446 {
447 return _snapTransformed(
448 t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false
449 );
450 }
453 /**
454 * Try to snap a list of points to any interested snappers after they have undergone
455 * a scale.
456 *
457 * \param t Type of points.
458 * \param p Points.
459 * \param it List of items to ignore when snapping.
460 * \param s Proposed scale.
461 * \param o Origin of proposed scale.
462 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
463 */
465 std::pair<NR::scale, bool> SnapManager::freeSnapScale(Inkscape::Snapper::PointType t,
466 std::vector<NR::Point> const &p,
467 std::list<SPItem const *> const &it,
468 NR::scale const &s,
469 NR::Point const &o) const
470 {
471 return _snapTransformed(
472 t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
473 );
474 }
477 /**
478 * Try to snap a list of points to any interested snappers after they have undergone
479 * a scale. A snap will only occur along a line described by a
480 * Inkscape::Snapper::ConstraintLine.
481 *
482 * \param t Type of points.
483 * \param p Points.
484 * \param it List of items to ignore when snapping.
485 * \param s Proposed scale.
486 * \param o Origin of proposed scale.
487 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
488 */
490 std::pair<NR::scale, bool> SnapManager::constrainedSnapScale(Inkscape::Snapper::PointType t,
491 std::vector<NR::Point> const &p,
492 std::list<SPItem const *> const &it,
493 Inkscape::Snapper::ConstraintLine const &c,
494 NR::scale const &s,
495 NR::Point const &o) const
496 {
497 return _snapTransformed(
498 t, p, it, true, c, SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
499 );
500 }
503 /**
504 * Try to snap a list of points to any interested snappers after they have undergone
505 * a stretch.
506 *
507 * \param t Type of points.
508 * \param p Points.
509 * \param it List of items to ignore when snapping.
510 * \param s Proposed stretch.
511 * \param o Origin of proposed stretch.
512 * \param d Dimension in which to apply proposed stretch.
513 * \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
514 * \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
515 */
517 std::pair<NR::Coord, bool> SnapManager::freeSnapStretch(Inkscape::Snapper::PointType t,
518 std::vector<NR::Point> const &p,
519 std::list<SPItem const *> const &it,
520 NR::Coord const &s,
521 NR::Point const &o,
522 NR::Dim2 d,
523 bool u) const
524 {
525 std::pair<NR::Point, bool> const r = _snapTransformed(
526 t, p, it, false, NR::Point(), STRETCH, NR::Point(s, s), o, d, u
527 );
529 return std::make_pair(r.first[d], r.second);
530 }
533 /**
534 * Try to snap a list of points to any interested snappers after they have undergone
535 * a skew.
536 *
537 * \param t Type of points.
538 * \param p Points.
539 * \param it List of items to ignore when snapping.
540 * \param s Proposed skew.
541 * \param o Origin of proposed skew.
542 * \param d Dimension in which to apply proposed skew.
543 * \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
544 */
546 std::pair<NR::Coord, bool> SnapManager::freeSnapSkew(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) const
552 {
553 std::pair<NR::Point, bool> const r = _snapTransformed(
554 t, p, it, false, NR::Point(), SKEW, NR::Point(s, s), o, d, false
555 );
557 return std::make_pair(r.first[d], r.second);
558 }
560 /*
561 Local Variables:
562 mode:c++
563 c-file-style:"stroustrup"
564 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
565 indent-tabs-mode:nil
566 fill-column:99
567 End:
568 */
569 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :