~valavanisalex/ubuntu/precise/inkscape/fix-943984

« back to all changes in this revision

Viewing changes to inkscape-0.47pre1/src/object-snapper.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2009-07-02 17:09:45 UTC
  • mfrom: (1.1.9 upstream)
  • Revision ID: james.westby@ubuntu.com-20090702170945-nn6d6zswovbwju1t
Tags: 0.47~pre1-0ubuntu1
* New upstream release.
  - Don't constrain maximization on small resolution devices (pre0)
    (LP: #348842)
  - Fixes segfault on startup (pre0)
    (LP: #391149)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 *  \file object-snapper.cpp
 
3
 *  \brief Snapping things to objects.
 
4
 *
 
5
 * Authors:
 
6
 *   Carl Hetherington <inkscape@carlh.net>
 
7
 *   Diederik van Lierop <mail@diedenrezi.nl>
 
8
 *
 
9
 * Copyright (C) 2005 - 2008 Authors
 
10
 *
 
11
 * Released under GNU GPL, read the file 'COPYING' for more information
 
12
 */
 
13
 
 
14
#include "svg/svg.h"
 
15
#include <2geom/path-intersection.h>
 
16
#include <2geom/point.h>
 
17
#include <2geom/rect.h>
 
18
#include <2geom/line.h>
 
19
#include "document.h"
 
20
#include "sp-namedview.h"
 
21
#include "sp-image.h"
 
22
#include "sp-item-group.h"
 
23
#include "sp-item.h"
 
24
#include "sp-use.h"
 
25
#include "display/curve.h"
 
26
#include "inkscape.h"
 
27
#include "preferences.h"
 
28
#include "sp-text.h"
 
29
#include "sp-flowtext.h"
 
30
#include "text-editing.h"
 
31
#include "sp-clippath.h"
 
32
#include "sp-mask.h"
 
33
#include "helper/geom-curves.h"
 
34
#include "desktop.h"
 
35
 
 
36
Inkscape::SnapCandidate::SnapCandidate(SPItem* item, bool clip_or_mask, Geom::Matrix additional_affine)
 
37
    : item(item), clip_or_mask(clip_or_mask), additional_affine(additional_affine)
 
38
{
 
39
}
 
40
 
 
41
Inkscape::SnapCandidate::~SnapCandidate()
 
42
{
 
43
}
 
44
 
 
45
Inkscape::ObjectSnapper::ObjectSnapper(SnapManager *sm, Geom::Coord const d)
 
46
    : Snapper(sm, d)
 
47
{
 
48
    _candidates = new std::vector<SnapCandidate>;
 
49
    _points_to_snap_to = new std::vector<std::pair<Geom::Point, int> >;
 
50
    _paths_to_snap_to = new std::vector<std::pair<Geom::PathVector*, SnapTargetType> >;
 
51
}
 
52
 
 
53
Inkscape::ObjectSnapper::~ObjectSnapper()
 
54
{
 
55
    _candidates->clear();
 
56
    delete _candidates;
 
57
 
 
58
    _points_to_snap_to->clear();
 
59
    delete _points_to_snap_to;
 
60
 
 
61
    _clear_paths();
 
62
    delete _paths_to_snap_to;
 
63
}
 
64
 
 
65
/**
 
66
 *  \return Snap tolerance (desktop coordinates); depends on current zoom so that it's always the same in screen pixels
 
67
 */
 
68
Geom::Coord Inkscape::ObjectSnapper::getSnapperTolerance() const
 
69
{
 
70
        SPDesktop const *dt = _snapmanager->getDesktop();
 
71
        double const zoom =  dt ? dt->current_zoom() : 1;
 
72
        return _snapmanager->snapprefs.getObjectTolerance() / zoom;
 
73
}
 
74
 
 
75
bool Inkscape::ObjectSnapper::getSnapperAlwaysSnap() const
 
76
{
 
77
    return _snapmanager->snapprefs.getObjectTolerance() == 10000; //TODO: Replace this threshold of 10000 by a constant; see also tolerance-slider.cpp
 
78
}
 
79
 
 
80
/**
 
81
 *  Find all items within snapping range.
 
82
 *  \param parent Pointer to the document's root, or to a clipped path or mask object
 
83
 *  \param it List of items to ignore
 
84
 *  \param first_point If true then this point is the first one from a whole bunch of points
 
85
 *  \param bbox_to_snap Bounding box hulling the whole bunch of points, all from the same selection and having the same transformation
 
86
 *  \param DimensionToSnap Snap in X, Y, or both directions.
 
87
 */
 
88
 
 
89
void Inkscape::ObjectSnapper::_findCandidates(SPObject* parent,
 
90
                                              std::vector<SPItem const *> const *it,
 
91
                                              bool const &first_point,
 
92
                                              Geom::Rect const &bbox_to_snap,
 
93
                                              DimensionToSnap const snap_dim,
 
94
                                              bool const clip_or_mask,
 
95
                                              Geom::Matrix const additional_affine) const // transformation of the item being clipped / masked
 
96
{
 
97
    bool const c1 = (snap_dim == TRANSL_SNAP_XY) && ThisSnapperMightSnap();
 
98
    bool const c2 = (snap_dim != TRANSL_SNAP_XY) && GuidesMightSnap();
 
99
 
 
100
    if (!(c1 || c2)) {
 
101
        return;
 
102
    }
 
103
 
 
104
    if (first_point) {
 
105
        _candidates->clear();
 
106
    }
 
107
 
 
108
    Geom::Rect bbox_to_snap_incl = bbox_to_snap; // _incl means: will include the snapper tolerance
 
109
    bbox_to_snap_incl.expandBy(getSnapperTolerance()); // see?
 
110
 
 
111
    for (SPObject* o = sp_object_first_child(parent); o != NULL; o = SP_OBJECT_NEXT(o)) {
 
112
        g_assert(_snapmanager->getDesktop() != NULL);
 
113
        if (SP_IS_ITEM(o) && !(_snapmanager->getDesktop()->itemIsHidden(SP_ITEM(o)) && !clip_or_mask)) {
 
114
            // Snapping to items in a locked layer is allowed
 
115
            // Don't snap to hidden objects, unless they're a clipped path or a mask
 
116
            /* See if this item is on the ignore list */
 
117
            std::vector<SPItem const *>::const_iterator i;
 
118
            if (it != NULL) {
 
119
                i = it->begin();
 
120
                while (i != it->end() && *i != o) {
 
121
                    i++;
 
122
                }
 
123
            }
 
124
 
 
125
            if (it == NULL || i == it->end()) {
 
126
                SPItem *item = SP_ITEM(o);
 
127
                if (item) {
 
128
                    SPObject *obj = NULL;
 
129
                    if (!clip_or_mask) { // cannot clip or mask more than once
 
130
                        // The current item is not a clipping path or a mask, but might
 
131
                        // still be the subject of clipping or masking itself ; if so, then
 
132
                        // we should also consider that path or mask for snapping to
 
133
                        obj = SP_OBJECT(item->clip_ref->getObject());
 
134
                        if (obj) {
 
135
                            _findCandidates(obj, it, false, bbox_to_snap, snap_dim, true, sp_item_i2doc_affine(item));
 
136
                        }
 
137
                        obj = SP_OBJECT(item->mask_ref->getObject());
 
138
                        if (obj) {
 
139
                            _findCandidates(obj, it, false, bbox_to_snap, snap_dim, true, sp_item_i2doc_affine(item));
 
140
                        }
 
141
                    }
 
142
                }
 
143
 
 
144
                if (SP_IS_GROUP(o)) {
 
145
                    _findCandidates(o, it, false, bbox_to_snap, snap_dim, clip_or_mask, additional_affine);
 
146
                } else {
 
147
                    Geom::OptRect bbox_of_item = Geom::Rect();
 
148
                    if (clip_or_mask) {
 
149
                        // Oh oh, this will get ugly. We cannot use sp_item_i2d_affine directly because we need to
 
150
                        // insert an additional transformation in document coordinates (code copied from sp_item_i2d_affine)
 
151
                        sp_item_invoke_bbox(item,
 
152
                            bbox_of_item,
 
153
                            sp_item_i2doc_affine(item) * additional_affine * _snapmanager->getDesktop()->doc2dt(),
 
154
                            true);
 
155
                    } else {
 
156
                        sp_item_invoke_bbox(item, bbox_of_item, sp_item_i2d_affine(item), true);
 
157
                    }
 
158
                    if (bbox_of_item) {
 
159
                        // See if the item is within range
 
160
                        if (bbox_to_snap_incl.intersects(*bbox_of_item)) {
 
161
                            // This item is within snapping range, so record it as a candidate
 
162
                            _candidates->push_back(SnapCandidate(item, clip_or_mask, additional_affine));
 
163
                            // For debugging: print the id of the candidate to the console
 
164
                            // SPObject *obj = (SPObject*)item;
 
165
                            // std::cout << "Snap candidate added: " << obj->id << std::endl;
 
166
                        }
 
167
                    }
 
168
                }
 
169
            }
 
170
        }
 
171
    }
 
172
}
 
173
 
 
174
 
 
175
void Inkscape::ObjectSnapper::_collectNodes(Inkscape::SnapPreferences::PointType const &t,
 
176
                                         bool const &first_point) const
 
177
{
 
178
    // Now, let's first collect all points to snap to. If we have a whole bunch of points to snap,
 
179
    // e.g. when translating an item using the selector tool, then we will only do this for the
 
180
    // first point and store the collection for later use. This significantly improves the performance
 
181
    if (first_point) {
 
182
        _points_to_snap_to->clear();
 
183
 
 
184
         // Determine the type of bounding box we should snap to
 
185
        SPItem::BBoxType bbox_type = SPItem::GEOMETRIC_BBOX;
 
186
 
 
187
        bool p_is_a_node = t & Inkscape::SnapPreferences::SNAPPOINT_NODE;
 
188
        bool p_is_a_bbox = t & Inkscape::SnapPreferences::SNAPPOINT_BBOX;
 
189
        bool p_is_a_guide = t & Inkscape::SnapPreferences::SNAPPOINT_GUIDE;
 
190
 
 
191
        // A point considered for snapping should be either a node, a bbox corner or a guide. Pick only ONE!
 
192
        g_assert(!((p_is_a_node && p_is_a_bbox) || (p_is_a_bbox && p_is_a_guide) || (p_is_a_node && p_is_a_guide)));
 
193
 
 
194
        if (_snapmanager->snapprefs.getSnapToBBoxNode() || _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() || _snapmanager->snapprefs.getSnapBBoxMidpoints()) {
 
195
            Inkscape::Preferences *prefs = Inkscape::Preferences::get();
 
196
            bool prefs_bbox = prefs->getBool("/tools/bounding_box");
 
197
            bbox_type = !prefs_bbox ?
 
198
                SPItem::APPROXIMATE_BBOX : SPItem::GEOMETRIC_BBOX;
 
199
        }
 
200
 
 
201
        // Consider the page border for snapping to
 
202
        if (_snapmanager->snapprefs.getSnapToPageBorder()) {
 
203
            _getBorderNodes(_points_to_snap_to);
 
204
        }
 
205
 
 
206
        for (std::vector<SnapCandidate>::const_iterator i = _candidates->begin(); i != _candidates->end(); i++) {
 
207
            //Geom::Matrix i2doc(Geom::identity());
 
208
            SPItem *root_item = (*i).item;
 
209
            if (SP_IS_USE((*i).item)) {
 
210
                root_item = sp_use_root(SP_USE((*i).item));
 
211
            }
 
212
            g_return_if_fail(root_item);
 
213
 
 
214
            //Collect all nodes so we can snap to them
 
215
                        if (p_is_a_node || !(_snapmanager->snapprefs.getStrictSnapping() && !p_is_a_node) || p_is_a_guide) {
 
216
                                // Note: there are two ways in which intersections are considered:
 
217
                                // Method 1: Intersections are calculated for each shape individually, for both the
 
218
                                //           snap source and snap target (see sp_shape_snappoints)
 
219
                                // Method 2: Intersections are calculated for each curve or line that we've snapped to, i.e. only for
 
220
                                //           the target (see the intersect() method in the SnappedCurve and SnappedLine classes)
 
221
                                // Some differences:
 
222
                                // - Method 1 doesn't find intersections within a set of multiple objects
 
223
                                // - Method 2 only works for targets
 
224
                                // When considering intersections as snap targets:
 
225
                                // - Method 1 only works when snapping to nodes, whereas
 
226
                                // - Method 2 only works when snapping to paths
 
227
                                // - There will be performance differences too!
 
228
                                // If both methods are being used simultaneously, then this might lead to duplicate targets!
 
229
 
 
230
                                // Well, here we will be looking for snap TARGETS. Both methods can therefore be used.
 
231
                                // When snapping to paths, we will get a collection of snapped lines and snapped curves. findBestSnap() will
 
232
                                // go hunting for intersections (but only when asked to in the prefs of course). In that case we can just
 
233
                                // temporarily block the intersections in sp_item_snappoints, we don't need duplicates. If we're not snapping to
 
234
                                // paths though but only to item nodes then we should still look for the intersections in sp_item_snappoints()
 
235
                                bool old_pref = _snapmanager->snapprefs.getSnapIntersectionCS();
 
236
                                if (_snapmanager->snapprefs.getSnapToItemPath()) {
 
237
                                        _snapmanager->snapprefs.setSnapIntersectionCS(false);
 
238
                                }
 
239
 
 
240
                                sp_item_snappoints(root_item, true, *_points_to_snap_to, &_snapmanager->snapprefs);
 
241
 
 
242
                                if (_snapmanager->snapprefs.getSnapToItemPath()) {
 
243
                                        _snapmanager->snapprefs.setSnapIntersectionCS(old_pref);
 
244
                                }
 
245
                        }
 
246
 
 
247
            //Collect the bounding box's corners so we can snap to them
 
248
                        if (p_is_a_bbox || !(_snapmanager->snapprefs.getStrictSnapping() && !p_is_a_bbox) || p_is_a_guide) {
 
249
                                // Discard the bbox of a clipped path / mask, because we don't want to snap to both the bbox
 
250
                                // of the item AND the bbox of the clipping path at the same time
 
251
                                if (!(*i).clip_or_mask) {
 
252
                                        Geom::OptRect b = sp_item_bbox_desktop(root_item, bbox_type);
 
253
                                        getBBoxPoints(b, _points_to_snap_to, true, _snapmanager->snapprefs.getSnapToBBoxNode(), _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints(), _snapmanager->snapprefs.getSnapBBoxMidpoints());
 
254
                                }
 
255
                        }
 
256
        }
 
257
    }
 
258
}
 
259
 
 
260
void Inkscape::ObjectSnapper::_snapNodes(SnappedConstraints &sc,
 
261
                                         Inkscape::SnapPreferences::PointType const &t,
 
262
                                         Geom::Point const &p,
 
263
                                         SnapSourceType const &source_type,
 
264
                                         bool const &first_point,
 
265
                                         std::vector<std::pair<Geom::Point, int> > *unselected_nodes) const
 
266
{
 
267
    // Iterate through all nodes, find out which one is the closest to p, and snap to it!
 
268
 
 
269
    _collectNodes(t, first_point);
 
270
 
 
271
    if (unselected_nodes != NULL) {
 
272
        _points_to_snap_to->insert(_points_to_snap_to->end(), unselected_nodes->begin(), unselected_nodes->end());
 
273
    }
 
274
 
 
275
    SnappedPoint s;
 
276
    bool success = false;
 
277
 
 
278
    for (std::vector<std::pair<Geom::Point, int> >::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); k++) {
 
279
        Geom::Coord dist = Geom::L2((*k).first - p);
 
280
        if (dist < getSnapperTolerance() && dist < s.getSnapDistance()) {
 
281
            s = SnappedPoint((*k).first, source_type, static_cast<Inkscape::SnapTargetType>((*k).second), dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true);
 
282
            success = true;
 
283
        }
 
284
    }
 
285
 
 
286
    if (success) {
 
287
        sc.points.push_back(s);
 
288
    }
 
289
}
 
290
 
 
291
void Inkscape::ObjectSnapper::_snapTranslatingGuideToNodes(SnappedConstraints &sc,
 
292
                                         Inkscape::SnapPreferences::PointType const &t,
 
293
                                         Geom::Point const &p,
 
294
                                         Geom::Point const &guide_normal) const
 
295
{
 
296
    // Iterate through all nodes, find out which one is the closest to this guide, and snap to it!
 
297
    _collectNodes(t, true);
 
298
 
 
299
    SnappedPoint s;
 
300
 
 
301
    Geom::Coord tol = getSnapperTolerance();
 
302
 
 
303
    for (std::vector<std::pair<Geom::Point, int> >::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); k++) {
 
304
        // Project each node (*k) on the guide line (running through point p)
 
305
        Geom::Point p_proj = Geom::projection((*k).first, Geom::Line(p, p + Geom::rot90(guide_normal)));
 
306
        Geom::Coord dist = Geom::L2((*k).first - p_proj); // distance from node to the guide
 
307
        Geom::Coord dist2 = Geom::L2(p - p_proj); // distance from projection of node on the guide, to the mouse location
 
308
        if ((dist < tol && dist2 < tol) || getSnapperAlwaysSnap()) {
 
309
            s = SnappedPoint((*k).first, SNAPSOURCE_GUIDE, static_cast<Inkscape::SnapTargetType>((*k).second), dist, tol, getSnapperAlwaysSnap(), true);
 
310
            sc.points.push_back(s);
 
311
        }
 
312
    }
 
313
}
 
314
 
 
315
 
 
316
/**
 
317
 * Returns index of first NR_END bpath in array.
 
318
 */
 
319
 
 
320
void Inkscape::ObjectSnapper::_collectPaths(Inkscape::SnapPreferences::PointType const &t,
 
321
                                         bool const &first_point) const
 
322
{
 
323
    // Now, let's first collect all paths to snap to. If we have a whole bunch of points to snap,
 
324
    // e.g. when translating an item using the selector tool, then we will only do this for the
 
325
    // first point and store the collection for later use. This significantly improves the performance
 
326
    if (first_point) {
 
327
        _clear_paths();
 
328
 
 
329
        // Determine the type of bounding box we should snap to
 
330
        SPItem::BBoxType bbox_type = SPItem::GEOMETRIC_BBOX;
 
331
 
 
332
        bool p_is_a_node = t & Inkscape::SnapPreferences::SNAPPOINT_NODE;
 
333
        bool p_is_a_guide = t & Inkscape::SnapPreferences::SNAPPOINT_GUIDE;
 
334
 
 
335
        if (_snapmanager->snapprefs.getSnapToBBoxPath()) {
 
336
            Inkscape::Preferences *prefs = Inkscape::Preferences::get();
 
337
            int prefs_bbox = prefs->getBool("/tools/bounding_box", 0);
 
338
            bbox_type = !prefs_bbox ?
 
339
                SPItem::APPROXIMATE_BBOX : SPItem::GEOMETRIC_BBOX;
 
340
        }
 
341
 
 
342
        // Consider the page border for snapping
 
343
        if (_snapmanager->snapprefs.getSnapToPageBorder()) {
 
344
            Geom::PathVector *border_path = _getBorderPathv();
 
345
            if (border_path != NULL) {
 
346
                _paths_to_snap_to->push_back(std::make_pair(border_path, SNAPTARGET_PAGE_BORDER));
 
347
            }
 
348
        }
 
349
 
 
350
        for (std::vector<SnapCandidate>::const_iterator i = _candidates->begin(); i != _candidates->end(); i++) {
 
351
 
 
352
            /* Transform the requested snap point to this item's coordinates */
 
353
            Geom::Matrix i2doc(Geom::identity());
 
354
            SPItem *root_item = NULL;
 
355
            /* We might have a clone at hand, so make sure we get the root item */
 
356
            if (SP_IS_USE((*i).item)) {
 
357
                i2doc = sp_use_get_root_transform(SP_USE((*i).item));
 
358
                root_item = sp_use_root(SP_USE((*i).item));
 
359
                g_return_if_fail(root_item);
 
360
            } else {
 
361
                i2doc = sp_item_i2doc_affine((*i).item);
 
362
                root_item = (*i).item;
 
363
            }
 
364
 
 
365
            //Build a list of all paths considered for snapping to
 
366
 
 
367
            //Add the item's path to snap to
 
368
            if (_snapmanager->snapprefs.getSnapToItemPath()) {
 
369
                if (p_is_a_guide || !(_snapmanager->snapprefs.getStrictSnapping() && !p_is_a_node)) {
 
370
                    // Snapping to the path of characters is very cool, but for a large
 
371
                    // chunk of text this will take ages! So limit snapping to text paths
 
372
                    // containing max. 240 characters. Snapping the bbox will not be affected
 
373
                    bool very_lenghty_prose = false;
 
374
                    if (SP_IS_TEXT(root_item) || SP_IS_FLOWTEXT(root_item)) {
 
375
                        very_lenghty_prose =  sp_text_get_length(SP_TEXT(root_item)) > 240;
 
376
                    }
 
377
                    // On my AMD 3000+, the snapping lag becomes annoying at approx. 240 chars
 
378
                    // which corresponds to a lag of 500 msec. This is for snapping a rect
 
379
                    // to a single line of text.
 
380
 
 
381
                    // Snapping for example to a traced bitmap is also very stressing for
 
382
                    // the CPU, so we'll only snap to paths having no more than 500 nodes
 
383
                    // This also leads to a lag of approx. 500 msec (in my lousy test set-up).
 
384
                    bool very_complex_path = false;
 
385
                    if (SP_IS_PATH(root_item)) {
 
386
                        very_complex_path = sp_nodes_in_path(SP_PATH(root_item)) > 500;
 
387
                    }
 
388
 
 
389
                    if (!very_lenghty_prose && !very_complex_path) {
 
390
                        SPCurve *curve = curve_for_item(root_item);
 
391
                        if (curve) {
 
392
                            // We will get our own copy of the path, which must be freed at some point
 
393
                            Geom::PathVector *borderpathv = pathvector_for_curve(root_item, curve, true, true, Geom::identity(), (*i).additional_affine);
 
394
                            _paths_to_snap_to->push_back(std::make_pair(borderpathv, SNAPTARGET_PATH)); // Perhaps for speed, get a reference to the Geom::pathvector, and store the transformation besides it.
 
395
                            curve->unref();
 
396
                        }
 
397
                    }
 
398
                }
 
399
            }
 
400
 
 
401
            //Add the item's bounding box to snap to
 
402
            if (_snapmanager->snapprefs.getSnapToBBoxPath()) {
 
403
                if (p_is_a_guide || !(_snapmanager->snapprefs.getStrictSnapping() && p_is_a_node)) {
 
404
                    // Discard the bbox of a clipped path / mask, because we don't want to snap to both the bbox
 
405
                    // of the item AND the bbox of the clipping path at the same time
 
406
                    if (!(*i).clip_or_mask) {
 
407
                        Geom::OptRect rect;
 
408
                        sp_item_invoke_bbox(root_item, rect, i2doc, TRUE, bbox_type);
 
409
                        if (rect) {
 
410
                            Geom::PathVector *path = _getPathvFromRect(*rect);
 
411
                            _paths_to_snap_to->push_back(std::make_pair(path, SNAPTARGET_BBOX_EDGE));
 
412
                        }
 
413
                    }
 
414
                }
 
415
            }
 
416
        }
 
417
    }
 
418
}
 
419
 
 
420
void Inkscape::ObjectSnapper::_snapPaths(SnappedConstraints &sc,
 
421
                                     Inkscape::SnapPreferences::PointType const &t,
 
422
                                     Geom::Point const &p,
 
423
                                     SnapSourceType const &source_type,
 
424
                                     bool const &first_point,
 
425
                                     std::vector<std::pair<Geom::Point, int> > *unselected_nodes,
 
426
                                     SPPath const *selected_path) const
 
427
{
 
428
    _collectPaths(t, first_point);
 
429
    // Now we can finally do the real snapping, using the paths collected above
 
430
 
 
431
    g_assert(_snapmanager->getDesktop() != NULL);
 
432
    Geom::Point const p_doc = _snapmanager->getDesktop()->dt2doc(p);
 
433
 
 
434
    bool const node_tool_active = _snapmanager->snapprefs.getSnapToItemPath() && selected_path != NULL;
 
435
 
 
436
    if (first_point) {
 
437
        /* findCandidates() is used for snapping to both paths and nodes. It ignores the path that is
 
438
         * currently being edited, because that path requires special care: when snapping to nodes
 
439
         * only the unselected nodes of that path should be considered, and these will be passed on separately.
 
440
         * This path must not be ignored however when snapping to the paths, so we add it here
 
441
         * manually when applicable.
 
442
         *
 
443
         * Note that this path must be the last in line!
 
444
         * */
 
445
        if (node_tool_active) {
 
446
            SPCurve *curve = curve_for_item(SP_ITEM(selected_path));
 
447
            if (curve) {
 
448
                Geom::PathVector *pathv = pathvector_for_curve(SP_ITEM(selected_path), curve, true, true, Geom::identity(), Geom::identity()); // We will get our own copy of the path, which must be freed at some point
 
449
                _paths_to_snap_to->push_back(std::make_pair(pathv, SNAPTARGET_PATH));
 
450
                curve->unref();
 
451
            }
 
452
        }
 
453
    }
 
454
 
 
455
    for (std::vector<std::pair<Geom::PathVector*, SnapTargetType> >::const_iterator it_p = _paths_to_snap_to->begin(); it_p != _paths_to_snap_to->end(); it_p++) {
 
456
        bool const being_edited = (node_tool_active && (*it_p) == _paths_to_snap_to->back());
 
457
        //if true then this pathvector it_pv is currently being edited in the node tool
 
458
 
 
459
        // char * svgd = sp_svg_write_path(**it_p->first);
 
460
        // std::cout << "Dumping the pathvector: " << svgd << std::endl;
 
461
 
 
462
        for(Geom::PathVector::iterator it_pv = (it_p->first)->begin(); it_pv != (it_p->first)->end(); ++it_pv) {
 
463
            // Find a nearest point for each curve within this path
 
464
            // n curves will return n time values with 0 <= t <= 1
 
465
            std::vector<double> anp = (*it_pv).nearestPointPerCurve(p_doc);
 
466
 
 
467
            std::vector<double>::const_iterator np = anp.begin();
 
468
            unsigned int index = 0;
 
469
            for (; np != anp.end(); np++, index++) {
 
470
                Geom::Curve const *curve = &((*it_pv).at_index(index));
 
471
                Geom::Point const sp_doc = curve->pointAt(*np);
 
472
 
 
473
                bool c1 = true;
 
474
                bool c2 = true;
 
475
                if (being_edited) {
 
476
                    /* If the path is being edited, then we should only snap though to stationary pieces of the path
 
477
                     * and not to the pieces that are being dragged around. This way we avoid
 
478
                     * self-snapping. For this we check whether the nodes at both ends of the current
 
479
                     * piece are unselected; if they are then this piece must be stationary
 
480
                     */
 
481
                    g_assert(unselected_nodes != NULL);
 
482
                    Geom::Point start_pt = _snapmanager->getDesktop()->doc2dt(curve->pointAt(0));
 
483
                    Geom::Point end_pt = _snapmanager->getDesktop()->doc2dt(curve->pointAt(1));
 
484
                    c1 = isUnselectedNode(start_pt, unselected_nodes);
 
485
                    c2 = isUnselectedNode(end_pt, unselected_nodes);
 
486
                    /* Unfortunately, this might yield false positives for coincident nodes. Inkscape might therefore mistakenly
 
487
                     * snap to path segments that are not stationary. There are at least two possible ways to overcome this:
 
488
                     * - Linking the individual nodes of the SPPath we have here, to the nodes of the NodePath::SubPath class as being
 
489
                     *   used in sp_nodepath_selected_nodes_move. This class has a member variable called "selected". For this the nodes
 
490
                     *   should be in the exact same order for both classes, so we can index them
 
491
                     * - Replacing the SPPath being used here by the the NodePath::SubPath class; but how?
 
492
                     */
 
493
                }
 
494
 
 
495
                Geom::Point const sp_dt = _snapmanager->getDesktop()->doc2dt(sp_doc);
 
496
                if (!being_edited || (c1 && c2)) {
 
497
                    Geom::Coord const dist = Geom::distance(sp_doc, p_doc);
 
498
                    if (dist < getSnapperTolerance()) {
 
499
                        sc.curves.push_back(Inkscape::SnappedCurve(sp_dt, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, curve, source_type, it_p->second));
 
500
                    }
 
501
                }
 
502
            }
 
503
        } // End of: for (Geom::PathVector::iterator ....)
 
504
    }
 
505
}
 
506
 
 
507
/* Returns true if point is coincident with one of the unselected nodes */
 
508
bool Inkscape::ObjectSnapper::isUnselectedNode(Geom::Point const &point, std::vector<std::pair<Geom::Point, int> > const *unselected_nodes) const
 
509
{
 
510
    if (unselected_nodes == NULL) {
 
511
        return false;
 
512
    }
 
513
 
 
514
    if (unselected_nodes->size() == 0) {
 
515
        return false;
 
516
    }
 
517
 
 
518
    for (std::vector<std::pair<Geom::Point, int> >::const_iterator i = unselected_nodes->begin(); i != unselected_nodes->end(); i++) {
 
519
        if (Geom::L2(point - (*i).first) < 1e-4) {
 
520
            return true;
 
521
        }
 
522
    }
 
523
 
 
524
    return false;
 
525
}
 
526
 
 
527
void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc,
 
528
                                     Inkscape::SnapPreferences::PointType const &t,
 
529
                                     Geom::Point const &p,
 
530
                                     SnapSourceType const source_type,
 
531
                                     bool const &first_point,
 
532
                                     ConstraintLine const &c) const
 
533
{
 
534
 
 
535
    _collectPaths(t, first_point);
 
536
 
 
537
    // Now we can finally do the real snapping, using the paths collected above
 
538
 
 
539
    g_assert(_snapmanager->getDesktop() != NULL);
 
540
    Geom::Point const p_doc = _snapmanager->getDesktop()->dt2doc(p);
 
541
 
 
542
    Geom::Point direction_vector = c.getDirection();
 
543
    if (!is_zero(direction_vector)) {
 
544
        direction_vector = Geom::unit_vector(direction_vector);
 
545
    }
 
546
 
 
547
    // The intersection point of the constraint line with any path,
 
548
    // must lie within two points on the constraintline: p_min_on_cl and p_max_on_cl
 
549
    // The distance between those points is twice the snapping tolerance
 
550
    Geom::Point const p_proj_on_cl = c.projection(p);
 
551
    Geom::Point const p_min_on_cl = _snapmanager->getDesktop()->dt2doc(p_proj_on_cl - getSnapperTolerance() * direction_vector);
 
552
    Geom::Point const p_max_on_cl = _snapmanager->getDesktop()->dt2doc(p_proj_on_cl + getSnapperTolerance() * direction_vector);
 
553
 
 
554
    Geom::Path cl;
 
555
    std::vector<Geom::Path> clv;
 
556
    cl.start(p_min_on_cl);
 
557
    cl.appendNew<Geom::LineSegment>(p_max_on_cl);
 
558
    clv.push_back(cl);
 
559
 
 
560
    for (std::vector<std::pair<Geom::PathVector*, SnapTargetType> >::const_iterator k = _paths_to_snap_to->begin(); k != _paths_to_snap_to->end(); k++) {
 
561
        if (k->first) {
 
562
            Geom::CrossingSet cs = Geom::crossings(clv, *(k->first));
 
563
            if (cs.size() > 0) {
 
564
                // We need only the first element of cs, because cl is only a single straight linesegment
 
565
                // This first element contains a vector filled with crossings of cl with k->first
 
566
                for (std::vector<Geom::Crossing>::const_iterator m = cs[0].begin(); m != cs[0].end(); m++) {
 
567
                    if ((*m).ta >= 0 && (*m).ta <= 1 ) {
 
568
                        // Reconstruct the point of intersection
 
569
                        Geom::Point p_inters = p_min_on_cl + ((*m).ta) * (p_max_on_cl - p_min_on_cl);
 
570
                        // When it's within snapping range, then return it
 
571
                        // (within snapping range == between p_min_on_cl and p_max_on_cl == 0 < ta < 1)
 
572
                        Geom::Coord dist = Geom::L2(_snapmanager->getDesktop()->dt2doc(p_proj_on_cl) - p_inters);
 
573
                        SnappedPoint s(_snapmanager->getDesktop()->doc2dt(p_inters), source_type, k->second, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true);
 
574
                        sc.points.push_back(s);
 
575
                    }
 
576
                }
 
577
            }
 
578
        }
 
579
    }
 
580
}
 
581
 
 
582
 
 
583
void Inkscape::ObjectSnapper::freeSnap(SnappedConstraints &sc,
 
584
                                            Inkscape::SnapPreferences::PointType const &t,
 
585
                                            Geom::Point const &p,
 
586
                                            SnapSourceType const &source_type,
 
587
                                            bool const &first_point,
 
588
                                            Geom::OptRect const &bbox_to_snap,
 
589
                                            std::vector<SPItem const *> const *it,
 
590
                                            std::vector<std::pair<Geom::Point, int> > *unselected_nodes) const
 
591
{
 
592
    if (_snap_enabled == false || _snapmanager->snapprefs.getSnapFrom(t) == false ) {
 
593
        return;
 
594
    }
 
595
 
 
596
    /* Get a list of all the SPItems that we will try to snap to */
 
597
    if (first_point) {
 
598
        Geom::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : Geom::Rect(p, p);
 
599
        _findCandidates(sp_document_root(_snapmanager->getDocument()), it, first_point, local_bbox_to_snap, TRANSL_SNAP_XY, false, Geom::identity());
 
600
    }
 
601
 
 
602
    if (_snapmanager->snapprefs.getSnapToItemNode() || _snapmanager->snapprefs.getSnapSmoothNodes()
 
603
        || _snapmanager->snapprefs.getSnapToBBoxNode() || _snapmanager->snapprefs.getSnapToPageBorder()
 
604
                || _snapmanager->snapprefs.getSnapLineMidpoints() || _snapmanager->snapprefs.getSnapObjectMidpoints()
 
605
                || _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() || _snapmanager->snapprefs.getSnapBBoxMidpoints()
 
606
                || _snapmanager->snapprefs.getIncludeItemCenter()) {
 
607
        _snapNodes(sc, t, p, source_type, first_point, unselected_nodes);
 
608
    }
 
609
 
 
610
    if (_snapmanager->snapprefs.getSnapToItemPath() || _snapmanager->snapprefs.getSnapToBBoxPath() || _snapmanager->snapprefs.getSnapToPageBorder()) {
 
611
        unsigned n = (unselected_nodes == NULL) ? 0 : unselected_nodes->size();
 
612
        if (n > 0) {
 
613
            /* While editing a path in the node tool, findCandidates must ignore that path because
 
614
             * of the node snapping requirements (i.e. only unselected nodes must be snapable).
 
615
             * That path must not be ignored however when snapping to the paths, so we add it here
 
616
             * manually when applicable
 
617
             */
 
618
            SPPath *path = NULL;
 
619
            if (it != NULL) {
 
620
                if (it->size() == 1 && SP_IS_PATH(*it->begin())) {
 
621
                                        path = SP_PATH(*it->begin());
 
622
                } // else: *it->begin() might be a SPGroup, e.g. when editing a LPE of text that has been converted to a group of paths
 
623
                // as reported in bug #356743. In that case we can just ignore it, i.e. not snap to this item
 
624
            }
 
625
            _snapPaths(sc, t, p, source_type, first_point, unselected_nodes, path);
 
626
        } else {
 
627
            _snapPaths(sc, t, p, source_type, first_point, NULL, NULL);
 
628
        }
 
629
    }
 
630
}
 
631
 
 
632
void Inkscape::ObjectSnapper::constrainedSnap( SnappedConstraints &sc,
 
633
                                                  Inkscape::SnapPreferences::PointType const &t,
 
634
                                                  Geom::Point const &p,
 
635
                                                  SnapSourceType const &source_type,
 
636
                                                  bool const &first_point,
 
637
                                                  Geom::OptRect const &bbox_to_snap,
 
638
                                                  ConstraintLine const &c,
 
639
                                                  std::vector<SPItem const *> const *it) const
 
640
{
 
641
    if (_snap_enabled == false || _snapmanager->snapprefs.getSnapFrom(t) == false) {
 
642
        return;
 
643
    }
 
644
 
 
645
    /* Get a list of all the SPItems that we will try to snap to */
 
646
    if (first_point) {
 
647
        Geom::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : Geom::Rect(p, p);
 
648
        _findCandidates(sp_document_root(_snapmanager->getDocument()), it, first_point, local_bbox_to_snap, TRANSL_SNAP_XY, false, Geom::identity());
 
649
    }
 
650
 
 
651
    // A constrained snap, is a snap in only one degree of freedom (specified by the constraint line).
 
652
    // This is useful for example when scaling an object while maintaining a fixed aspect ratio. It's
 
653
    // nodes are only allowed to move in one direction (i.e. in one degree of freedom).
 
654
 
 
655
    // When snapping to objects, we either snap to their nodes or their paths. It is however very
 
656
    // unlikely that any node will be exactly at the constrained line, so for a constrained snap
 
657
    // to objects we will only consider the object's paths. Beside, the nodes will be at these paths,
 
658
    // so we will more or less snap to them anyhow.
 
659
 
 
660
    if (_snapmanager->snapprefs.getSnapToItemPath() || _snapmanager->snapprefs.getSnapToBBoxPath() || _snapmanager->snapprefs.getSnapToPageBorder()) {
 
661
        _snapPathsConstrained(sc, t, p, source_type, first_point, c);
 
662
    }
 
663
}
 
664
 
 
665
 
 
666
// This method is used to snap a guide to nodes, while dragging the guide around
 
667
void Inkscape::ObjectSnapper::guideFreeSnap(SnappedConstraints &sc,
 
668
                                        Geom::Point const &p,
 
669
                                        Geom::Point const &guide_normal) const
 
670
{
 
671
    /* Get a list of all the SPItems that we will try to snap to */
 
672
    std::vector<SPItem*> cand;
 
673
    std::vector<SPItem const *> const it; //just an empty list
 
674
 
 
675
    DimensionToSnap snap_dim;
 
676
    if (guide_normal == to_2geom(component_vectors[Geom::Y])) {
 
677
        snap_dim = GUIDE_TRANSL_SNAP_Y;
 
678
    } else if (guide_normal == to_2geom(component_vectors[Geom::X])) {
 
679
        snap_dim = GUIDE_TRANSL_SNAP_X;
 
680
    } else {
 
681
        snap_dim = ANGLED_GUIDE_TRANSL_SNAP;
 
682
    }
 
683
 
 
684
    _findCandidates(sp_document_root(_snapmanager->getDocument()), &it, true, Geom::Rect(p, p), snap_dim, false, Geom::identity());
 
685
    _snapTranslatingGuideToNodes(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, guide_normal);
 
686
    // _snapRotatingGuideToNodes has not been implemented yet.
 
687
}
 
688
 
 
689
// This method is used to snap the origin of a guide to nodes/paths, while dragging the origin along the guide
 
690
void Inkscape::ObjectSnapper::guideConstrainedSnap(SnappedConstraints &sc,
 
691
                                        Geom::Point const &p,
 
692
                                        Geom::Point const &guide_normal,
 
693
                                        ConstraintLine const &c) const
 
694
{
 
695
    /* Get a list of all the SPItems that we will try to snap to */
 
696
    std::vector<SPItem*> cand;
 
697
    std::vector<SPItem const *> const it; //just an empty list
 
698
 
 
699
    DimensionToSnap snap_dim;
 
700
    if (guide_normal == to_2geom(component_vectors[Geom::Y])) {
 
701
        snap_dim = GUIDE_TRANSL_SNAP_Y;
 
702
    } else if (guide_normal == to_2geom(component_vectors[Geom::X])) {
 
703
        snap_dim = GUIDE_TRANSL_SNAP_X;
 
704
    } else {
 
705
        snap_dim = ANGLED_GUIDE_TRANSL_SNAP;
 
706
    }
 
707
 
 
708
    _findCandidates(sp_document_root(_snapmanager->getDocument()), &it, true, Geom::Rect(p, p), snap_dim, false, Geom::identity());
 
709
    _snapTranslatingGuideToNodes(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, guide_normal);
 
710
    // _snapRotatingGuideToNodes has not been implemented yet.
 
711
}
 
712
 
 
713
/**
 
714
 *  \return true if this Snapper will snap at least one kind of point.
 
715
 */
 
716
bool Inkscape::ObjectSnapper::ThisSnapperMightSnap() const
 
717
{
 
718
    bool snap_to_something = _snapmanager->snapprefs.getSnapToItemPath()
 
719
                                                || _snapmanager->snapprefs.getSnapToItemNode()
 
720
                                                || _snapmanager->snapprefs.getSnapToBBoxPath()
 
721
                                                || _snapmanager->snapprefs.getSnapToBBoxNode()
 
722
                                                || _snapmanager->snapprefs.getSnapToPageBorder()
 
723
                                                || _snapmanager->snapprefs.getSnapLineMidpoints() || _snapmanager->snapprefs.getSnapObjectMidpoints()
 
724
                                                || _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() || _snapmanager->snapprefs.getSnapBBoxMidpoints()
 
725
                                                || _snapmanager->snapprefs.getIncludeItemCenter();
 
726
 
 
727
    return (_snap_enabled && _snapmanager->snapprefs.getSnapModeBBoxOrNodes() && snap_to_something);
 
728
}
 
729
 
 
730
bool Inkscape::ObjectSnapper::GuidesMightSnap() const // almost the same as ThisSnapperMightSnap above, but only looking at points (and not paths)
 
731
{
 
732
    bool snap_to_something = _snapmanager->snapprefs.getSnapToItemNode()
 
733
                                                || _snapmanager->snapprefs.getSnapToPageBorder()
 
734
                                                || (_snapmanager->snapprefs.getSnapModeBBox() && _snapmanager->snapprefs.getSnapToBBoxNode())
 
735
                                                || (_snapmanager->snapprefs.getSnapModeBBox() && (_snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() || _snapmanager->snapprefs.getSnapBBoxMidpoints()))
 
736
                                                || (_snapmanager->snapprefs.getSnapModeNode() && (_snapmanager->snapprefs.getSnapLineMidpoints() || _snapmanager->snapprefs.getSnapObjectMidpoints()))
 
737
                                                || (_snapmanager->snapprefs.getSnapModeNode() && _snapmanager->snapprefs.getIncludeItemCenter());
 
738
 
 
739
    return (_snap_enabled && _snapmanager->snapprefs.getSnapModeGuide() && snap_to_something);
 
740
}
 
741
 
 
742
void Inkscape::ObjectSnapper::_clear_paths() const
 
743
{
 
744
    for (std::vector<std::pair<Geom::PathVector*, SnapTargetType> >::const_iterator k = _paths_to_snap_to->begin(); k != _paths_to_snap_to->end(); k++) {
 
745
        g_free(k->first);
 
746
    }
 
747
    _paths_to_snap_to->clear();
 
748
}
 
749
 
 
750
Geom::PathVector* Inkscape::ObjectSnapper::_getBorderPathv() const
 
751
{
 
752
    Geom::Rect const border_rect = Geom::Rect(Geom::Point(0,0), Geom::Point(sp_document_width(_snapmanager->getDocument()),sp_document_height(_snapmanager->getDocument())));
 
753
    return _getPathvFromRect(border_rect);
 
754
}
 
755
 
 
756
Geom::PathVector* Inkscape::ObjectSnapper::_getPathvFromRect(Geom::Rect const rect) const
 
757
{
 
758
    SPCurve const *border_curve = SPCurve::new_from_rect(rect);
 
759
    if (border_curve) {
 
760
        Geom::PathVector *dummy = new Geom::PathVector(border_curve->get_pathvector());
 
761
        return dummy;
 
762
    } else {
 
763
        return NULL;
 
764
    }
 
765
}
 
766
 
 
767
void Inkscape::ObjectSnapper::_getBorderNodes(std::vector<std::pair<Geom::Point, int> > *points) const
 
768
{
 
769
    Geom::Coord w = sp_document_width(_snapmanager->getDocument());
 
770
    Geom::Coord h = sp_document_height(_snapmanager->getDocument());
 
771
    points->push_back(std::make_pair(Geom::Point(0,0), SNAPTARGET_PAGE_CORNER));
 
772
    points->push_back(std::make_pair(Geom::Point(0,h), SNAPTARGET_PAGE_CORNER));
 
773
    points->push_back(std::make_pair(Geom::Point(w,h), SNAPTARGET_PAGE_CORNER));
 
774
    points->push_back(std::make_pair(Geom::Point(w,0), SNAPTARGET_PAGE_CORNER));
 
775
}
 
776
 
 
777
void Inkscape::getBBoxPoints(Geom::OptRect const bbox, std::vector<std::pair<Geom::Point, int> > *points, bool const isTarget, bool const includeCorners, bool const includeLineMidpoints, bool const includeObjectMidpoints)
 
778
{
 
779
        if (bbox) {
 
780
                // collect the corners of the bounding box
 
781
                for ( unsigned k = 0 ; k < 4 ; k++ ) {
 
782
                        if (includeCorners) {
 
783
                                points->push_back(std::make_pair((bbox->corner(k)), isTarget ? int(Inkscape::SNAPTARGET_BBOX_CORNER) : int(Inkscape::SNAPSOURCE_BBOX_CORNER)));
 
784
                        }
 
785
                        // optionally, collect the midpoints of the bounding box's edges too
 
786
                        if (includeLineMidpoints) {
 
787
                                points->push_back(std::make_pair((bbox->corner(k) + bbox->corner((k+1) % 4))/2, isTarget ? int(Inkscape::SNAPTARGET_BBOX_EDGE_MIDPOINT) : int(Inkscape::SNAPSOURCE_BBOX_EDGE_MIDPOINT)));
 
788
                        }
 
789
                }
 
790
                if (includeObjectMidpoints) {
 
791
                        points->push_back(std::make_pair(bbox->midpoint(), isTarget ? int(Inkscape::SNAPTARGET_BBOX_MIDPOINT) : int(Inkscape::SNAPSOURCE_BBOX_MIDPOINT)));
 
792
                }
 
793
        }
 
794
}
 
795
 
 
796
/*
 
797
  Local Variables:
 
798
  mode:c++
 
799
  c-file-style:"stroustrup"
 
800
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
 
801
  indent-tabs-mode:nil
 
802
  fill-column:99
 
803
  End:
 
804
*/
 
805
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :