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 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 /**
25 * Construct a SnapManager for a SPNamedView.
26 *
27 * \param v `Owning' SPNamedView.
28 */
30 SnapManager::SnapManager(SPNamedView const *v) : _named_view(v), grid(v, 0), axonomgrid(v, 0), guide(v, 0), object(v, 0)
31 {
33 }
36 /**
37 * \return List of snappers that we use.
38 */
40 SnapManager::SnapperList SnapManager::getSnappers() const
41 {
42 SnapManager::SnapperList s;
43 if (_named_view->gridtype == 0) {
44 s.push_back(&grid);
45 } else {
46 s.push_back(&axonomgrid);
47 }
48 s.push_back(&guide);
49 s.push_back(&object);
50 return s;
51 }
53 /**
54 * \return true if one of the snappers will try to snap something.
55 */
57 bool SnapManager::willSnapSomething() const
58 {
59 SnapperList const s = getSnappers();
60 SnapperList::const_iterator i = s.begin();
61 while (i != s.end() && (*i)->willSnapSomething() == false) {
62 i++;
63 }
65 return (i != s.end());
66 }
69 /**
70 * Try to snap a point to any interested snappers.
71 *
72 * \param t Type of point.
73 * \param p Point.
74 * \param it Item to ignore when snapping.
75 * \return Snapped point.
76 */
78 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
79 NR::Point const &p,
80 SPItem const *it) const
82 {
83 std::list<SPItem const *> lit;
84 lit.push_back(it);
85 return freeSnap(t, p, lit);
86 }
89 /**
90 * Try to snap a point to any interested snappers.
91 *
92 * \param t Type of point.
93 * \param p Point.
94 * \param it List of items to ignore when snapping.
95 * \return Snapped point.
96 */
98 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
99 NR::Point const &p,
100 std::list<SPItem const *> const &it) const
101 {
102 Inkscape::SnappedPoint r(p, NR_HUGE);
104 SnapperList const snappers = getSnappers();
105 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
106 Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
107 if (s.getDistance() < r.getDistance()) {
108 r = s;
109 }
110 }
112 return r;
113 }
116 /**
117 * Try to snap a point to any interested snappers. A snap will only occur along
118 * a line described by a Inkscape::Snapper::ConstraintLine.
119 *
120 * \param t Type of point.
121 * \param p Point.
122 * \param c Constraint line.
123 * \param it Item to ignore when snapping.
124 * \return Snapped point.
125 */
127 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
128 NR::Point const &p,
129 Inkscape::Snapper::ConstraintLine const &c,
130 SPItem const *it) const
131 {
132 std::list<SPItem const *> lit;
133 lit.push_back(it);
134 return constrainedSnap(t, p, c, lit);
135 }
139 /**
140 * Try to snap a point to any interested snappers. A snap will only occur along
141 * a line described by a Inkscape::Snapper::ConstraintLine.
142 *
143 * \param t Type of point.
144 * \param p Point.
145 * \param c Constraint line.
146 * \param it List of items to ignore when snapping.
147 * \return Snapped point.
148 */
150 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
151 NR::Point const &p,
152 Inkscape::Snapper::ConstraintLine const &c,
153 std::list<SPItem const *> const &it) const
154 {
155 Inkscape::SnappedPoint r(p, NR_HUGE);
157 SnapperList const snappers = getSnappers();
158 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
159 Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, p, c, it);
160 if (s.getDistance() < r.getDistance()) {
161 r = s;
162 }
163 }
165 return r;
166 }
170 /**
171 * Main internal snapping method, which is called by the other, friendlier, public
172 * methods. It's a bit hairy as it has lots of parameters, but it saves on a lot
173 * of duplicated code.
174 *
175 * \param type Type of points being snapped.
176 * \param points List of points to snap.
177 * \param ignore List of items to ignore while snapping.
178 * \param constrained true if the snap is constrained.
179 * \param constraint Constraint line to use, if `constrained' is true, otherwise undefined.
180 * \param transformation_type Type of transformation to apply to points before trying to snap them.
181 * \param transformation Description of the transformation; details depend on the type.
182 * \param origin Origin of the transformation, if applicable.
183 * \param dim Dimension of the transformation, if applicable.
184 * \param uniform true if the transformation should be uniform, if applicable.
185 */
187 std::pair<NR::Point, bool> SnapManager::_snapTransformed(
188 Inkscape::Snapper::PointType type,
189 std::vector<NR::Point> const &points,
190 std::list<SPItem const *> const &ignore,
191 bool constrained,
192 Inkscape::Snapper::ConstraintLine const &constraint,
193 Transformation transformation_type,
194 NR::Point const &transformation,
195 NR::Point const &origin,
196 NR::Dim2 dim,
197 bool uniform) const
198 {
199 /* We have a list of points, which we are proposing to transform in some way. We need to see
200 ** if any of these points, when transformed, snap to anything. If they do, we return the
201 ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
202 */
204 /* Quick check to see if we have any snappers that are enabled */
205 if (willSnapSomething() == false) {
206 return std::make_pair(transformation, false);
207 }
209 /* The current best transformation */
210 NR::Point best_transformation = transformation;
212 /* The current best metric for the best transformation; lower is better, NR_HUGE
213 ** means that we haven't snapped anything.
214 */
215 double best_metric = NR_HUGE;
217 for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
219 /* Work out the transformed version of this point */
220 NR::Point transformed;
221 switch (transformation_type) {
222 case TRANSLATION:
223 transformed = *i + transformation;
224 break;
225 case SCALE:
226 transformed = ((*i - origin) * NR::scale(transformation[NR::X], transformation[NR::Y])) + origin;
227 break;
228 case STRETCH:
229 {
230 NR::scale s(1, 1);
231 if (uniform)
232 s[NR::X] = s[NR::Y] = transformation[dim];
233 else {
234 s[dim] = transformation[dim];
235 s[1 - dim] = 1;
236 }
237 transformed = ((*i - origin) * s) + origin;
238 break;
239 }
240 case SKEW:
241 transformed = *i;
242 transformed[dim] += transformation[dim] * ((*i)[1 - dim] - origin[1 - dim]);
243 break;
244 default:
245 g_assert_not_reached();
246 }
248 /* Snap it */
249 Inkscape::SnappedPoint const snapped = constrained ?
250 constrainedSnap(type, transformed, constraint, ignore) : freeSnap(type, transformed, ignore);
252 if (snapped.getDistance() < NR_HUGE) {
253 /* We snapped. Find the transformation that describes where the snapped point has
254 ** ended up, and also the metric for this transformation.
255 */
256 NR::Point result;
257 NR::Coord metric;
258 switch (transformation_type) {
259 case TRANSLATION:
260 result = snapped.getPoint() - *i;
261 metric = NR::L2(result);
262 break;
263 case SCALE:
264 {
265 NR::Point const a = (snapped.getPoint() - origin);
266 NR::Point const b = (*i - origin);
267 result = NR::Point(a[NR::X] / b[NR::X], a[NR::Y] / b[NR::Y]);
268 metric = std::abs(NR::L2(result) - NR::L2(transformation));
269 break;
270 }
271 case STRETCH:
272 {
273 for (int j = 0; j < 2; j++) {
274 if (uniform || j == dim) {
275 result[j] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]);
276 } else {
277 result[j] = 1;
278 }
279 }
280 metric = std::abs(result[dim] - transformation[dim]);
281 break;
282 }
283 case SKEW:
284 result[dim] = (snapped.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]);
285 metric = std::abs(result[dim] - transformation[dim]);
286 break;
287 default:
288 g_assert_not_reached();
289 }
291 /* Note it if it's the best so far */
292 if (metric < best_metric && metric != 0) {
293 best_transformation = result;
294 best_metric = metric;
295 }
296 }
297 }
299 return std::make_pair(best_transformation, best_metric < NR_HUGE);
300 }
303 /**
304 * Try to snap a list of points to any interested snappers after they have undergone
305 * a translation.
306 *
307 * \param t Type of points.
308 * \param p Points.
309 * \param it List of items to ignore when snapping.
310 * \param tr Proposed translation.
311 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
312 */
314 std::pair<NR::Point, bool> SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t,
315 std::vector<NR::Point> const &p,
316 std::list<SPItem const *> const &it,
317 NR::Point const &tr) const
318 {
319 return _snapTransformed(
320 t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false
321 );
322 }
325 /**
326 * Try to snap a list of points to any interested snappers after they have undergone a
327 * translation. A snap will only occur along a line described by a
328 * Inkscape::Snapper::ConstraintLine.
329 *
330 * \param t Type of points.
331 * \param p Points.
332 * \param it List of items to ignore when snapping.
333 * \param c Constraint line.
334 * \param tr Proposed translation.
335 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
336 */
338 std::pair<NR::Point, bool> SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t,
339 std::vector<NR::Point> const &p,
340 std::list<SPItem const *> const &it,
341 Inkscape::Snapper::ConstraintLine const &c,
342 NR::Point const &tr) const
343 {
344 return _snapTransformed(
345 t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false
346 );
347 }
350 /**
351 * Try to snap a list of points to any interested snappers after they have undergone
352 * a scale.
353 *
354 * \param t Type of points.
355 * \param p Points.
356 * \param it List of items to ignore when snapping.
357 * \param s Proposed scale.
358 * \param o Origin of proposed scale.
359 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
360 */
362 std::pair<NR::scale, bool> SnapManager::freeSnapScale(Inkscape::Snapper::PointType t,
363 std::vector<NR::Point> const &p,
364 std::list<SPItem const *> const &it,
365 NR::scale const &s,
366 NR::Point const &o) const
367 {
368 return _snapTransformed(
369 t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
370 );
371 }
374 /**
375 * Try to snap a list of points to any interested snappers after they have undergone
376 * a scale. A snap will only occur along a line described by a
377 * Inkscape::Snapper::ConstraintLine.
378 *
379 * \param t Type of points.
380 * \param p Points.
381 * \param it List of items to ignore when snapping.
382 * \param s Proposed scale.
383 * \param o Origin of proposed scale.
384 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
385 */
387 std::pair<NR::scale, bool> SnapManager::constrainedSnapScale(Inkscape::Snapper::PointType t,
388 std::vector<NR::Point> const &p,
389 std::list<SPItem const *> const &it,
390 Inkscape::Snapper::ConstraintLine const &c,
391 NR::scale const &s,
392 NR::Point const &o) const
393 {
394 return _snapTransformed(
395 t, p, it, true, c, SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
396 );
397 }
400 /**
401 * Try to snap a list of points to any interested snappers after they have undergone
402 * a stretch.
403 *
404 * \param t Type of points.
405 * \param p Points.
406 * \param it List of items to ignore when snapping.
407 * \param s Proposed stretch.
408 * \param o Origin of proposed stretch.
409 * \param d Dimension in which to apply proposed stretch.
410 * \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
411 * \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
412 */
414 std::pair<NR::Coord, bool> SnapManager::freeSnapStretch(Inkscape::Snapper::PointType t,
415 std::vector<NR::Point> const &p,
416 std::list<SPItem const *> const &it,
417 NR::Coord const &s,
418 NR::Point const &o,
419 NR::Dim2 d,
420 bool u) const
421 {
422 std::pair<NR::Point, bool> const r = _snapTransformed(
423 t, p, it, false, NR::Point(), STRETCH, NR::Point(s, s), o, d, u
424 );
426 return std::make_pair(r.first[d], r.second);
427 }
430 /**
431 * Try to snap a list of points to any interested snappers after they have undergone
432 * a skew.
433 *
434 * \param t Type of points.
435 * \param p Points.
436 * \param it List of items to ignore when snapping.
437 * \param s Proposed skew.
438 * \param o Origin of proposed skew.
439 * \param d Dimension in which to apply proposed skew.
440 * \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
441 */
443 std::pair<NR::Coord, bool> SnapManager::freeSnapSkew(Inkscape::Snapper::PointType t,
444 std::vector<NR::Point> const &p,
445 std::list<SPItem const *> const &it,
446 NR::Coord const &s,
447 NR::Point const &o,
448 NR::Dim2 d) const
449 {
450 std::pair<NR::Point, bool> const r = _snapTransformed(
451 t, p, it, false, NR::Point(), SKEW, NR::Point(s, s), o, d, false
452 );
454 return std::make_pair(r.first[d], r.second);
455 }
457 /*
458 Local Variables:
459 mode:c++
460 c-file-style:"stroustrup"
461 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
462 indent-tabs-mode:nil
463 fill-column:99
464 End:
465 */
466 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :