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) 1999-2002 Authors
13 *
14 * Released under GNU GPL, read the file 'COPYING' for more information
15 */
17 #include "sp-namedview.h"
18 #include "snap.h"
19 #include <libnr/nr-point-fns.h>
20 #include <libnr/nr-scale-ops.h>
21 #include <libnr/nr-values.h>
23 /**
24 * Construct a SnapManager for a SPNamedView.
25 *
26 * \param v `Owning' SPNamedView.
27 */
29 SnapManager::SnapManager(SPNamedView const *v) : grid(v, 0), guide(v, 0), object(v, 0)
30 {
32 }
35 /**
36 * \return List of snappers that we use.
37 */
39 SnapManager::SnapperList SnapManager::getSnappers() const
40 {
41 SnapManager::SnapperList s;
42 s.push_back(&grid);
43 s.push_back(&guide);
44 s.push_back(&object);
45 return s;
46 }
48 /**
49 * \return true if one of the snappers will try to snap something.
50 */
52 bool SnapManager::willSnapSomething() const
53 {
54 SnapperList const s = getSnappers();
55 SnapperList::const_iterator i = s.begin();
56 while (i != s.end() && (*i)->willSnapSomething() == false) {
57 i++;
58 }
60 return (i != s.end());
61 }
64 /**
65 * Try to snap a point to any interested snappers.
66 *
67 * \param t Type of point.
68 * \param p Point.
69 * \param it Item to ignore when snapping.
70 * \return Snapped point.
71 */
73 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
74 NR::Point const &p,
75 SPItem const *it) const
77 {
78 std::list<SPItem const *> lit;
79 lit.push_back(it);
80 return freeSnap(t, p, lit);
81 }
84 /**
85 * Try to snap a point to any interested snappers.
86 *
87 * \param t Type of point.
88 * \param p Point.
89 * \param it List of items to ignore when snapping.
90 * \return Snapped point.
91 */
93 Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t,
94 NR::Point const &p,
95 std::list<SPItem const *> const &it) const
96 {
97 Inkscape::SnappedPoint r(p, NR_HUGE);
99 SnapperList const snappers = getSnappers();
100 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
101 Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it);
102 if (s.getDistance() < r.getDistance()) {
103 r = s;
104 }
105 }
107 return r;
108 }
111 /**
112 * Try to snap a point to any interested snappers. A snap will only occur along
113 * a line described by a Inkscape::Snapper::ConstraintLine.
114 *
115 * \param t Type of point.
116 * \param p Point.
117 * \param c Constraint line.
118 * \param it Item to ignore when snapping.
119 * \return Snapped point.
120 */
122 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
123 NR::Point const &p,
124 Inkscape::Snapper::ConstraintLine const &c,
125 SPItem const *it) const
126 {
127 std::list<SPItem const *> lit;
128 lit.push_back(it);
129 return constrainedSnap(t, p, c, lit);
130 }
134 /**
135 * Try to snap a point to any interested snappers. A snap will only occur along
136 * a line described by a Inkscape::Snapper::ConstraintLine.
137 *
138 * \param t Type of point.
139 * \param p Point.
140 * \param c Constraint line.
141 * \param it List of items to ignore when snapping.
142 * \return Snapped point.
143 */
145 Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t,
146 NR::Point const &p,
147 Inkscape::Snapper::ConstraintLine const &c,
148 std::list<SPItem const *> const &it) const
149 {
150 Inkscape::SnappedPoint r(p, NR_HUGE);
152 SnapperList const snappers = getSnappers();
153 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
154 Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, p, c, it);
155 if (s.getDistance() < r.getDistance()) {
156 r = s;
157 }
158 }
160 return r;
161 }
165 /**
166 * Main internal snapping method, which is called by the other, friendlier, public
167 * methods. It's a bit hairy as it has lots of parameters, but it saves on a lot
168 * of duplicated code.
169 *
170 * \param type Type of points being snapped.
171 * \param points List of points to snap.
172 * \param ignore List of items to ignore while snapping.
173 * \param constrained true if the snap is constrained.
174 * \param constraint Constraint line to use, if `constrained' is true, otherwise undefined.
175 * \param transformation_type Type of transformation to apply to points before trying to snap them.
176 * \param transformation Description of the transformation; details depend on the type.
177 * \param origin Origin of the transformation, if applicable.
178 * \param dim Dimension of the transformation, if applicable.
179 * \param uniform true if the transformation should be uniform, if applicable.
180 */
182 std::pair<NR::Point, bool> SnapManager::_snapTransformed(
183 Inkscape::Snapper::PointType type,
184 std::vector<NR::Point> const &points,
185 std::list<SPItem const *> const &ignore,
186 bool constrained,
187 Inkscape::Snapper::ConstraintLine const &constraint,
188 Transformation transformation_type,
189 NR::Point const &transformation,
190 NR::Point const &origin,
191 NR::Dim2 dim,
192 bool uniform) const
193 {
194 /* We have a list of points, which we are proposing to transform in some way. We need to see
195 ** if any of these points, when transformed, snap to anything. If they do, we return the
196 ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
197 */
199 /* Quick check to see if we have any snappers that are enabled */
200 if (willSnapSomething() == false) {
201 return std::make_pair(transformation, false);
202 }
204 /* The current best transformation */
205 NR::Point best_transformation = transformation;
207 /* The current best metric for the best transformation; lower is better, NR_HUGE
208 ** means that we haven't snapped anything.
209 */
210 double best_metric = NR_HUGE;
212 for (std::vector<NR::Point>::const_iterator i = points.begin(); i != points.end(); i++) {
214 /* Work out the transformed version of this point */
215 NR::Point transformed;
216 switch (transformation_type) {
217 case TRANSLATION:
218 transformed = *i + transformation;
219 break;
220 case SCALE:
221 transformed = ((*i - origin) * NR::scale(transformation[NR::X], transformation[NR::Y])) + origin;
222 break;
223 case STRETCH:
224 {
225 NR::scale s(1, 1);
226 if (uniform)
227 s[NR::X] = s[NR::Y] = transformation[dim];
228 else {
229 s[dim] = transformation[dim];
230 s[1 - dim] = 1;
231 }
232 transformed = ((*i - origin) * s) + origin;
233 break;
234 }
235 case SKEW:
236 transformed = *i;
237 transformed[dim] += transformation[dim] * ((*i)[1 - dim] - origin[1 - dim]);
238 break;
239 default:
240 g_assert_not_reached();
241 }
243 /* Snap it */
244 Inkscape::SnappedPoint const snapped = constrained ?
245 constrainedSnap(type, transformed, constraint, ignore) : freeSnap(type, transformed, ignore);
247 if (snapped.getDistance() < NR_HUGE) {
248 /* We snapped. Find the transformation that describes where the snapped point has
249 ** ended up, and also the metric for this transformation.
250 */
251 NR::Point result;
252 NR::Coord metric;
253 switch (transformation_type) {
254 case TRANSLATION:
255 result = snapped.getPoint() - *i;
256 metric = NR::L2(result);
257 break;
258 case SCALE:
259 {
260 NR::Point const a = (snapped.getPoint() - origin);
261 NR::Point const b = (*i - origin);
262 result = NR::Point(a[NR::X] / b[NR::X], a[NR::Y] / b[NR::Y]);
263 metric = std::abs(NR::L2(result) - NR::L2(transformation));
264 break;
265 }
266 case STRETCH:
267 {
268 for (int j = 0; j < 2; j++) {
269 if (uniform || j == dim) {
270 result[j] = (snapped.getPoint()[dim] - origin[dim]) / ((*i)[dim] - origin[dim]);
271 } else {
272 result[j] = 1;
273 }
274 }
275 metric = std::abs(result[dim] - transformation[dim]);
276 break;
277 }
278 case SKEW:
279 result[dim] = (snapped.getPoint()[dim] - (*i)[dim]) / ((*i)[1 - dim] - origin[1 - dim]);
280 metric = std::abs(result[dim] - transformation[dim]);
281 break;
282 default:
283 g_assert_not_reached();
284 }
286 /* Note it if it's the best so far */
287 if (metric < best_metric && metric != 0) {
288 best_transformation = result;
289 best_metric = metric;
290 }
291 }
292 }
294 return std::make_pair(best_transformation, best_metric < NR_HUGE);
295 }
298 /**
299 * Try to snap a list of points to any interested snappers after they have undergone
300 * a translation.
301 *
302 * \param t Type of points.
303 * \param p Points.
304 * \param it List of items to ignore when snapping.
305 * \param tr Proposed translation.
306 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
307 */
309 std::pair<NR::Point, bool> SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t,
310 std::vector<NR::Point> const &p,
311 std::list<SPItem const *> const &it,
312 NR::Point const &tr) const
313 {
314 return _snapTransformed(
315 t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false
316 );
317 }
320 /**
321 * Try to snap a list of points to any interested snappers after they have undergone a
322 * translation. A snap will only occur along a line described by a
323 * Inkscape::Snapper::ConstraintLine.
324 *
325 * \param t Type of points.
326 * \param p Points.
327 * \param it List of items to ignore when snapping.
328 * \param c Constraint line.
329 * \param tr Proposed translation.
330 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
331 */
333 std::pair<NR::Point, bool> SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t,
334 std::vector<NR::Point> const &p,
335 std::list<SPItem const *> const &it,
336 Inkscape::Snapper::ConstraintLine const &c,
337 NR::Point const &tr) const
338 {
339 return _snapTransformed(
340 t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false
341 );
342 }
345 /**
346 * Try to snap a list of points to any interested snappers after they have undergone
347 * a scale.
348 *
349 * \param t Type of points.
350 * \param p Points.
351 * \param it List of items to ignore when snapping.
352 * \param s Proposed scale.
353 * \param o Origin of proposed scale.
354 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
355 */
357 std::pair<NR::scale, bool> SnapManager::freeSnapScale(Inkscape::Snapper::PointType t,
358 std::vector<NR::Point> const &p,
359 std::list<SPItem const *> const &it,
360 NR::scale const &s,
361 NR::Point const &o) const
362 {
363 return _snapTransformed(
364 t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
365 );
366 }
369 /**
370 * Try to snap a list of points to any interested snappers after they have undergone
371 * a scale. A snap will only occur along a line described by a
372 * Inkscape::Snapper::ConstraintLine.
373 *
374 * \param t Type of points.
375 * \param p Points.
376 * \param it List of items to ignore when snapping.
377 * \param s Proposed scale.
378 * \param o Origin of proposed scale.
379 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
380 */
382 std::pair<NR::scale, bool> SnapManager::constrainedSnapScale(Inkscape::Snapper::PointType t,
383 std::vector<NR::Point> const &p,
384 std::list<SPItem const *> const &it,
385 Inkscape::Snapper::ConstraintLine const &c,
386 NR::scale const &s,
387 NR::Point const &o) const
388 {
389 return _snapTransformed(
390 t, p, it, true, c, 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 stretch.
398 *
399 * \param t Type of points.
400 * \param p Points.
401 * \param it List of items to ignore when snapping.
402 * \param s Proposed stretch.
403 * \param o Origin of proposed stretch.
404 * \param d Dimension in which to apply proposed stretch.
405 * \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
406 * \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
407 */
409 std::pair<NR::Coord, bool> SnapManager::freeSnapStretch(Inkscape::Snapper::PointType t,
410 std::vector<NR::Point> const &p,
411 std::list<SPItem const *> const &it,
412 NR::Coord const &s,
413 NR::Point const &o,
414 NR::Dim2 d,
415 bool u) const
416 {
417 std::pair<NR::Point, bool> const r = _snapTransformed(
418 t, p, it, false, NR::Point(), STRETCH, NR::Point(s, s), o, d, u
419 );
421 return std::make_pair(r.first[d], r.second);
422 }
425 /**
426 * Try to snap a list of points to any interested snappers after they have undergone
427 * a skew.
428 *
429 * \param t Type of points.
430 * \param p Points.
431 * \param it List of items to ignore when snapping.
432 * \param s Proposed skew.
433 * \param o Origin of proposed skew.
434 * \param d Dimension in which to apply proposed skew.
435 * \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
436 */
438 std::pair<NR::Coord, bool> SnapManager::freeSnapSkew(Inkscape::Snapper::PointType t,
439 std::vector<NR::Point> const &p,
440 std::list<SPItem const *> const &it,
441 NR::Coord const &s,
442 NR::Point const &o,
443 NR::Dim2 d) const
444 {
445 std::pair<NR::Point, bool> const r = _snapTransformed(
446 t, p, it, false, NR::Point(), SKEW, NR::Point(s, s), o, d, false
447 );
449 return std::make_pair(r.first[d], r.second);
450 }
452 /*
453 Local Variables:
454 mode:c++
455 c-file-style:"stroustrup"
456 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
457 indent-tabs-mode:nil
458 fill-column:99
459 End:
460 */
461 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :