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