86fae1572520d8921ce2783b649b9e06fd54fd85
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 // Using " < 1e6" instead of " < NR::HUGE" for catching some rounding errors
305 // These rounding errors might be caused by NRRects, see bug #1584301
306 return std::make_pair(best_transformation, best_metric < 1e6);
307 }
310 /**
311 * Try to snap a list of points to any interested snappers after they have undergone
312 * a translation.
313 *
314 * \param t Type of points.
315 * \param p Points.
316 * \param it List of items to ignore when snapping.
317 * \param tr Proposed translation.
318 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
319 */
321 std::pair<NR::Point, bool> SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t,
322 std::vector<NR::Point> const &p,
323 std::list<SPItem const *> const &it,
324 NR::Point const &tr) const
325 {
326 return _snapTransformed(
327 t, p, it, false, NR::Point(), TRANSLATION, tr, NR::Point(), NR::X, false
328 );
329 }
332 /**
333 * Try to snap a list of points to any interested snappers after they have undergone a
334 * translation. A snap will only occur along a line described by a
335 * Inkscape::Snapper::ConstraintLine.
336 *
337 * \param t Type of points.
338 * \param p Points.
339 * \param it List of items to ignore when snapping.
340 * \param c Constraint line.
341 * \param tr Proposed translation.
342 * \return Snapped translation, if a snap occurred, and a flag indicating whether a snap occurred.
343 */
345 std::pair<NR::Point, bool> SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t,
346 std::vector<NR::Point> const &p,
347 std::list<SPItem const *> const &it,
348 Inkscape::Snapper::ConstraintLine const &c,
349 NR::Point const &tr) const
350 {
351 return _snapTransformed(
352 t, p, it, true, c, TRANSLATION, tr, NR::Point(), NR::X, false
353 );
354 }
357 /**
358 * Try to snap a list of points to any interested snappers after they have undergone
359 * a scale.
360 *
361 * \param t Type of points.
362 * \param p Points.
363 * \param it List of items to ignore when snapping.
364 * \param s Proposed scale.
365 * \param o Origin of proposed scale.
366 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
367 */
369 std::pair<NR::scale, bool> SnapManager::freeSnapScale(Inkscape::Snapper::PointType t,
370 std::vector<NR::Point> const &p,
371 std::list<SPItem const *> const &it,
372 NR::scale const &s,
373 NR::Point const &o) const
374 {
375 return _snapTransformed(
376 t, p, it, false, NR::Point(), SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
377 );
378 }
381 /**
382 * Try to snap a list of points to any interested snappers after they have undergone
383 * a scale. A snap will only occur along a line described by a
384 * Inkscape::Snapper::ConstraintLine.
385 *
386 * \param t Type of points.
387 * \param p Points.
388 * \param it List of items to ignore when snapping.
389 * \param s Proposed scale.
390 * \param o Origin of proposed scale.
391 * \return Snapped scale, if a snap occurred, and a flag indicating whether a snap occurred.
392 */
394 std::pair<NR::scale, bool> SnapManager::constrainedSnapScale(Inkscape::Snapper::PointType t,
395 std::vector<NR::Point> const &p,
396 std::list<SPItem const *> const &it,
397 Inkscape::Snapper::ConstraintLine const &c,
398 NR::scale const &s,
399 NR::Point const &o) const
400 {
401 return _snapTransformed(
402 t, p, it, true, c, SCALE, NR::Point(s[NR::X], s[NR::Y]), o, NR::X, false
403 );
404 }
407 /**
408 * Try to snap a list of points to any interested snappers after they have undergone
409 * a stretch.
410 *
411 * \param t Type of points.
412 * \param p Points.
413 * \param it List of items to ignore when snapping.
414 * \param s Proposed stretch.
415 * \param o Origin of proposed stretch.
416 * \param d Dimension in which to apply proposed stretch.
417 * \param u true if the stretch should be uniform (ie to be applied equally in both dimensions)
418 * \return Snapped stretch, if a snap occurred, and a flag indicating whether a snap occurred.
419 */
421 std::pair<NR::Coord, bool> SnapManager::freeSnapStretch(Inkscape::Snapper::PointType t,
422 std::vector<NR::Point> const &p,
423 std::list<SPItem const *> const &it,
424 NR::Coord const &s,
425 NR::Point const &o,
426 NR::Dim2 d,
427 bool u) const
428 {
429 std::pair<NR::Point, bool> const r = _snapTransformed(
430 t, p, it, false, NR::Point(), STRETCH, NR::Point(s, s), o, d, u
431 );
433 return std::make_pair(r.first[d], r.second);
434 }
437 /**
438 * Try to snap a list of points to any interested snappers after they have undergone
439 * a skew.
440 *
441 * \param t Type of points.
442 * \param p Points.
443 * \param it List of items to ignore when snapping.
444 * \param s Proposed skew.
445 * \param o Origin of proposed skew.
446 * \param d Dimension in which to apply proposed skew.
447 * \return Snapped skew, if a snap occurred, and a flag indicating whether a snap occurred.
448 */
450 std::pair<NR::Coord, bool> SnapManager::freeSnapSkew(Inkscape::Snapper::PointType t,
451 std::vector<NR::Point> const &p,
452 std::list<SPItem const *> const &it,
453 NR::Coord const &s,
454 NR::Point const &o,
455 NR::Dim2 d) const
456 {
457 std::pair<NR::Point, bool> const r = _snapTransformed(
458 t, p, it, false, NR::Point(), SKEW, NR::Point(s, s), o, d, false
459 );
461 return std::make_pair(r.first[d], r.second);
462 }
464 /*
465 Local Variables:
466 mode:c++
467 c-file-style:"stroustrup"
468 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
469 indent-tabs-mode:nil
470 fill-column:99
471 End:
472 */
473 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :