ff05858fd05f3b097ca17e60136f4458f3216e69
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 /**
27 * Construct a SnapManager for a SPNamedView.
28 *
29 * \param v `Owning' SPNamedView.
30 */
32 SnapManager::SnapManager(SPNamedView const *v) :
33 grid(v, 0),
34 axonomgrid(v, 0),
35 guide(v, 0),
36 object(v, 0),
37 _named_view(v)
38 {
40 }
43 /**
44 * \return List of snappers that we use.
45 */
47 SnapManager::SnapperList SnapManager::getSnappers() const
48 {
49 SnapManager::SnapperList s;
50 if (_named_view->gridtype == 0) {
51 s.push_back(&grid);
52 } else {
53 s.push_back(&axonomgrid);
54 }
55 s.push_back(&guide);
56 s.push_back(&object);
58 //add new grid snappers that are active for this desktop
59 // SPDesktop* desktop = SP_ACTIVE_DESKTOP;
60 // if (desktop) {
62 for ( GSList const *l = _named_view->grids; l != NULL; l = l->next) {
63 Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
64 s.push_back(grid->snapper);
65 }
67 // }
69 return s;
70 }
72 /**
73 * \return true if one of the snappers will try to snap something.
74 */
76 bool SnapManager::willSnapSomething() const
77 {
78 SnapperList const s = getSnappers();
79 SnapperList::const_iterator i = s.begin();
80 while (i != s.end() && (*i)->willSnapSomething() == false) {
81 i++;
82 }
84 return (i != s.end());
85 }
88 /**
89 * Try to snap a point to any interested snappers.
90 *
91 * \param t Type of point.
92 * \param p Point.
93 * \param it Item to ignore when snapping.
94 * \return Snapped point.
95 */
97 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
98 NR::Point const &p,
99 SPItem const *it) const
101 {
102 std::list<SPItem const *> lit;
103 lit.push_back(it);
104 return freeSnap(t, p, lit);
105 }
108 /**
109 * Try to snap a point to any interested snappers.
110 *
111 * \param t Type of point.
112 * \param p Point.
113 * \param it List of items to ignore when snapping.
114 * \return Snapped point.
115 */
117 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
118 NR::Point const &p,
119 std::list<SPItem const *> const &it) const
120 {
121 Inkscape::SnappedPoint r(p, NR_HUGE);
123 SnapperList const snappers = getSnappers();
124 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
125 Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
126 if (s.getDistance() < r.getDistance()) {
127 r = s;
128 }
129 }
131 return r;
132 }
135 /**
136 * Try to snap a point to any interested snappers. A snap will only occur along
137 * a line described by a Inkscape::Snapper::ConstraintLine.
138 *
139 * \param t Type of point.
140 * \param p Point.
141 * \param c Constraint line.
142 * \param it Item to ignore when snapping.
143 * \return Snapped point.
144 */
146 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
147 NR::Point const &p,
148 Inkscape::Snapper::ConstraintLine const &c,
149 SPItem const *it) const
150 {
151 std::list<SPItem const *> lit;
152 lit.push_back(it);
153 return constrainedSnap(t, p, c, lit);
154 }
158 /**
159 * Try to snap a point to any interested snappers. A snap will only occur along
160 * a line described by a Inkscape::Snapper::ConstraintLine.
161 *
162 * \param t Type of point.
163 * \param p Point.
164 * \param c Constraint line.
165 * \param it List of items to ignore when snapping.
166 * \return Snapped point.
167 */
169 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
170 NR::Point const &p,
171 Inkscape::Snapper::ConstraintLine const &c,
172 std::list<SPItem const *> const &it) const
173 {
174 Inkscape::SnappedPoint r(p, NR_HUGE);
176 SnapperList const snappers = getSnappers();
177 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
178 Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, p, c, it);
179 if (s.getDistance() < r.getDistance()) {
180 r = s;
181 }
182 }
184 return r;
185 }
189 /**
190 * Main internal snapping method, which is called by the other, friendlier, public
191 * methods. It's a bit hairy as it has lots of parameters, but it saves on a lot
192 * of duplicated code.
193 *
194 * \param type Type of points being snapped.
195 * \param points List of points to snap.
196 * \param ignore List of items to ignore while snapping.
197 * \param constrained true if the snap is constrained.
198 * \param constraint Constraint line to use, if `constrained' is true, otherwise undefined.
199 * \param transformation_type Type of transformation to apply to points before trying to snap them.
200 * \param transformation Description of the transformation; details depend on the type.
201 * \param origin Origin of the transformation, if applicable.
202 * \param dim Dimension of the transformation, if applicable.
203 * \param uniform true if the transformation should be uniform, if applicable.
204 */
206 std::pair<NR::Point, bool> SnapManager::_snapTransformed(
207 Inkscape::Snapper::PointType type,
208 std::vector<NR::Point> const &points,
209 std::list<SPItem const *> const &ignore,
210 bool constrained,
211 Inkscape::Snapper::ConstraintLine const &constraint,
212 Transformation transformation_type,
213 NR::Point const &transformation,
214 NR::Point const &origin,
215 NR::Dim2 dim,
216 bool uniform) const
217 {
218 /* We have a list of points, which we are proposing to transform in some way. We need to see
219 ** if any of these points, when transformed, snap to anything. If they do, we return the
220 ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
221 */
223 /* Quick check to see if we have any snappers that are enabled */
224 if (willSnapSomething() == false) {
225 return std::make_pair(transformation, false);
226 }
228 /* The current best transformation */
229 NR::Point best_transformation = transformation;
231 /* The current best metric for the best transformation; lower is better, NR_HUGE
232 ** means that we haven't snapped anything.
233 */
234 double best_metric = NR_HUGE;
236 for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
238 /* Work out the transformed version of this point */
239 NR::Point transformed;
240 switch (transformation_type) {
241 case TRANSLATION:
242 transformed = *i + transformation;
243 break;
244 case SCALE:
245 transformed = ((*i - origin) * NR::scale(transformation[NR::X], transformation[NR::Y])) + origin;
246 break;
247 case STRETCH:
248 {
249 NR::scale s(1, 1);
250 if (uniform)
251 s[NR::X] = s[NR::Y] = transformation[dim];
252 else {
253 s[dim] = transformation[dim];
254 s[1 - dim] = 1;
255 }
256 transformed = ((*i - origin) * s) + origin;
257 break;
258 }
259 case SKEW:
260 transformed = *i;
261 transformed[dim] += transformation[dim] * ((*i)[1 - dim] - origin[1 - dim]);
262 break;
263 default:
264 g_assert_not_reached();
265 }
267 /* Snap it */
268 Inkscape::SnappedPoint const snapped = constrained ?
269 constrainedSnap(type, transformed, constraint, ignore) : freeSnap(type, transformed, ignore);
271 if (snapped.getDistance() < NR_HUGE) {
272 /* We snapped. Find the transformation that describes where the snapped point has
273 ** ended up, and also the metric for this transformation.
274 */
275 NR::Point result;
276 NR::Coord metric;
277 switch (transformation_type) {
278 case TRANSLATION:
279 result = snapped.getPoint() - *i;
280 metric = NR::L2(result);
281 break;
282 case SCALE:
283 {
284 NR::Point const a = (snapped.getPoint() - origin);
285 NR::Point const b = (*i - origin);
286 result = NR::Point(a[NR::X] / b[NR::X], a[NR::Y] / b[NR::Y]);
287 metric = std::abs(NR::L2(result) - NR::L2(transformation));
288 break;
289 }
290 case STRETCH:
291 {
292 for (int j = 0; j < 2; j++) {
293 if (uniform || j == dim) {
294 result[j] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]);
295 } else {
296 result[j] = 1;
297 }
298 }
299 metric = std::abs(result[dim] - transformation[dim]);
300 break;
301 }
302 case SKEW:
303 result[dim] = (snapped.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]);
304 metric = std::abs(result[dim] - transformation[dim]);
305 break;
306 default:
307 g_assert_not_reached();
308 }
310 /* Note it if it's the best so far */
311 if (metric < best_metric && metric != 0) {
312 best_transformation = result;
313 best_metric = metric;
314 }
315 }
316 }
318 // Using " < 1e6" instead of " < NR::HUGE" for catching some rounding errors
319 // These rounding errors might be caused by NRRects, see bug #1584301
320 return std::make_pair(best_transformation, best_metric < 1e6);
321 }
324 /**
325 * Try to snap a list of points to any interested snappers after they have undergone
326 * a translation.
327 *
328 * \param t Type of points.
329 * \param p Points.
330 * \param it List of items to ignore when snapping.
331 * \param tr Proposed translation.
332 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
333 */
335 std::pair<NR::Point, bool> SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t,
336 std::vector<NR::Point> const &p,
337 std::list<SPItem const *> const &it,
338 NR::Point const &tr) const
339 {
340 return _snapTransformed(
341 t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false
342 );
343 }
346 /**
347 * Try to snap a list of points to any interested snappers after they have undergone a
348 * translation. A snap will only occur along a line described by a
349 * Inkscape::Snapper::ConstraintLine.
350 *
351 * \param t Type of points.
352 * \param p Points.
353 * \param it List of items to ignore when snapping.
354 * \param c Constraint line.
355 * \param tr Proposed translation.
356 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
357 */
359 std::pair<NR::Point, bool> SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t,
360 std::vector<NR::Point> const &p,
361 std::list<SPItem const *> const &it,
362 Inkscape::Snapper::ConstraintLine const &c,
363 NR::Point const &tr) const
364 {
365 return _snapTransformed(
366 t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false
367 );
368 }
371 /**
372 * Try to snap a list of points to any interested snappers after they have undergone
373 * a scale.
374 *
375 * \param t Type of points.
376 * \param p Points.
377 * \param it List of items to ignore when snapping.
378 * \param s Proposed scale.
379 * \param o Origin of proposed scale.
380 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
381 */
383 std::pair<NR::scale, bool> SnapManager::freeSnapScale(Inkscape::Snapper::PointType t,
384 std::vector<NR::Point> const &p,
385 std::list<SPItem const *> const &it,
386 NR::scale const &s,
387 NR::Point const &o) const
388 {
389 return _snapTransformed(
390 t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
391 );
392 }
395 /**
396 * Try to snap a list of points to any interested snappers after they have undergone
397 * a scale. A snap will only occur along a line described by a
398 * Inkscape::Snapper::ConstraintLine.
399 *
400 * \param t Type of points.
401 * \param p Points.
402 * \param it List of items to ignore when snapping.
403 * \param s Proposed scale.
404 * \param o Origin of proposed scale.
405 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
406 */
408 std::pair<NR::scale, bool> SnapManager::constrainedSnapScale(Inkscape::Snapper::PointType t,
409 std::vector<NR::Point> const &p,
410 std::list<SPItem const *> const &it,
411 Inkscape::Snapper::ConstraintLine const &c,
412 NR::scale const &s,
413 NR::Point const &o) const
414 {
415 return _snapTransformed(
416 t, p, it, true, c, SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
417 );
418 }
421 /**
422 * Try to snap a list of points to any interested snappers after they have undergone
423 * a stretch.
424 *
425 * \param t Type of points.
426 * \param p Points.
427 * \param it List of items to ignore when snapping.
428 * \param s Proposed stretch.
429 * \param o Origin of proposed stretch.
430 * \param d Dimension in which to apply proposed stretch.
431 * \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
432 * \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
433 */
435 std::pair<NR::Coord, bool> SnapManager::freeSnapStretch(Inkscape::Snapper::PointType t,
436 std::vector<NR::Point> const &p,
437 std::list<SPItem const *> const &it,
438 NR::Coord const &s,
439 NR::Point const &o,
440 NR::Dim2 d,
441 bool u) const
442 {
443 std::pair<NR::Point, bool> const r = _snapTransformed(
444 t, p, it, false, NR::Point(), STRETCH, NR::Point(s, s), o, d, u
445 );
447 return std::make_pair(r.first[d], r.second);
448 }
451 /**
452 * Try to snap a list of points to any interested snappers after they have undergone
453 * a skew.
454 *
455 * \param t Type of points.
456 * \param p Points.
457 * \param it List of items to ignore when snapping.
458 * \param s Proposed skew.
459 * \param o Origin of proposed skew.
460 * \param d Dimension in which to apply proposed skew.
461 * \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
462 */
464 std::pair<NR::Coord, bool> SnapManager::freeSnapSkew(Inkscape::Snapper::PointType t,
465 std::vector<NR::Point> const &p,
466 std::list<SPItem const *> const &it,
467 NR::Coord const &s,
468 NR::Point const &o,
469 NR::Dim2 d) const
470 {
471 std::pair<NR::Point, bool> const r = _snapTransformed(
472 t, p, it, false, NR::Point(), SKEW, NR::Point(s, s), o, d, false
473 );
475 return std::make_pair(r.first[d], r.second);
476 }
478 /*
479 Local Variables:
480 mode:c++
481 c-file-style:"stroustrup"
482 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
483 indent-tabs-mode:nil
484 fill-column:99
485 End:
486 */
487 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :