Code

Draw perspective lines for infinite VPs, too (they are updated during scrolling or...
[inkscape.git] / src / object-snapper.cpp
1 /**
2  *  \file object-snapper.cpp
3  *  \brief Snapping things to objects.
4  *
5  * Authors:
6  *   Carl Hetherington <inkscape@carlh.net>
7  *
8  * Copyright (C) 2005 Authors
9  *
10  * Released under GNU GPL, read the file 'COPYING' for more information
11  */
13 #include "libnr/n-art-bpath.h"
14 #include "libnr/nr-rect-ops.h"
15 #include "document.h"
16 #include "sp-namedview.h"
17 #include "sp-path.h"
18 #include "sp-image.h"
19 #include "sp-item-group.h"
20 #include "sp-item.h"
21 #include "sp-use.h"
22 #include "display/curve.h"
23 #include "desktop.h"
24 #include "inkscape.h"
25 #include "splivarot.h"
26 #include "prefs-utils.h"
29 Inkscape::ObjectSnapper::ObjectSnapper(SPNamedView const *nv, NR::Coord const d)
30     : Snapper(nv, d), _snap_to_itemnode(true), _snap_to_itempath(true), 
31     _snap_to_bboxnode(true), _snap_to_bboxpath(true), _strict_snapping(true)
32 {
34 }
37 /**
38  *  \param p Point we are trying to snap (desktop coordinates)
39  */
41 void Inkscape::ObjectSnapper::_findCandidates(std::list<SPItem*>& c,
42                                               SPObject* r,
43                                               std::list<SPItem const *> const &it,
44                                               NR::Point const &p) const
45 {
46     if (ThisSnapperMightSnap()) {    
47         SPDesktop const *desktop = SP_ACTIVE_DESKTOP;
48         for (SPObject* o = sp_object_first_child(r); o != NULL; o = SP_OBJECT_NEXT(o)) {
49             if (SP_IS_ITEM(o) && !SP_ITEM(o)->isLocked() && !desktop->itemIsHidden(SP_ITEM(o))) {
50     
51                 /* See if this item is on the ignore list */
52                 std::list<SPItem const *>::const_iterator i = it.begin();
53                 while (i != it.end() && *i != o) {
54                     i++;
55                 }
56     
57                 if (i == it.end()) {
58                     /* See if the item is within range */
59                     if (SP_IS_GROUP(o)) {
60                         _findCandidates(c, o, it, p);
61                     } else {
62                         NR::Maybe<NR::Rect> b = sp_item_bbox_desktop(SP_ITEM(o));
63                         if ( b && NR::expand(*b, -getDistance()).contains(p) ) {
64                             c.push_back(SP_ITEM(o));
65                         }
66                     }
67                 }
68     
69             }
70         }
71     }
72 }
75 void Inkscape::ObjectSnapper::_snapNodes(Inkscape::Snapper::PointType const &t,
76                                                                                  Inkscape::SnappedPoint &s,
77                                          NR::Point const &p,
78                                          std::list<SPItem*> const &cand) const
79 {
80     /* FIXME: this seems like a hack.  Perhaps Snappers should be
81     ** in SPDesktop rather than SPNamedView?
82     */
83     SPDesktop const *desktop = SP_ACTIVE_DESKTOP;
84     
85     // Determine the type of bounding box we should snap to
86     SPItem::BBoxType bbox_type = SPItem::GEOMETRIC_BBOX; 
87         if (_snap_to_bboxnode) {        
88                 gchar const *prefs_bbox = prefs_get_string_attribute("tools.select", "bounding_box");
89                 bbox_type = (prefs_bbox != NULL && strcmp(prefs_bbox, "geometric")==0)? SPItem::GEOMETRIC_BBOX : SPItem::APPROXIMATE_BBOX;
90         }        
91         
92         bool p_is_a_node = t & Inkscape::Snapper::SNAPPOINT_NODE;        
94     for (std::list<SPItem*>::const_iterator i = cand.begin(); i != cand.end(); i++) {
95         
96         NR::Matrix i2doc(NR::identity());
97         SPItem *root_item = NULL;
98         if (SP_IS_USE(*i)) {
99             i2doc = sp_use_get_root_transform(SP_USE(*i));
100             root_item = sp_use_root(SP_USE(*i));
101         } else { 
102             i2doc = sp_item_i2doc_affine(*i);
103             root_item = *i;
104         }
105         
106         SPCurve *curve = NULL;
107         
108         if (SP_IS_SHAPE(root_item)) {
109             SPShape const *sh = SP_SHAPE(root_item);
110             curve = sh->curve;
111         } else if (SP_IS_IMAGE(root_item)) {
112             SPImage const *im = SP_IMAGE(root_item);
113             curve = im->curve;
114         }
115             
116                 std::list<NR::Point> points_to_snap_to;
117         
118         //Collect all nodes so we can snap to them
119         if (_snap_to_itemnode) {
120                 if (!(_strict_snapping && !p_is_a_node)) {
121                         if (curve) {
122                             int j = 0;
123                             while (SP_CURVE_BPATH(curve)[j].code != NR_END) {        
124                                 /* Get this node in desktop coordinates */
125                                 NArtBpath const &bp = SP_CURVE_BPATH(curve)[j];
126                                 points_to_snap_to.push_back(desktop->doc2dt(bp.c(3) * i2doc));
127                                 j++;
128                             }
129                         }
130                 }
131         }
132         
133         //Collect the bounding box's corners so we can snap to them
134         if (_snap_to_bboxnode) {
135                 if (!(_strict_snapping && p_is_a_node)) {
136                         NR::Maybe<NR::Rect> b = sp_item_bbox_desktop(root_item, bbox_type);
137                         if (b) {
138                                 for ( unsigned k = 0 ; k < 4 ; k++ ) {
139                                     points_to_snap_to.push_back(b->corner(k));
140                                 }
141                         }
142                 }        
143         }
144         
145         //Do the snapping, using all the nodes and corners collected above
146         for (std::list<NR::Point>::const_iterator k = points_to_snap_to.begin(); k != points_to_snap_to.end(); k++) {
147             /* Try to snap to this node of the path */
148             NR::Coord const dist = NR::L2(*k - p);
149             if (dist < getDistance() && dist < s.getDistance()) {
150                 s = SnappedPoint(*k, dist);
151             }       
152         }
153     }
157 void Inkscape::ObjectSnapper::_snapPaths(Inkscape::Snapper::PointType const &t,
158                                                                                  Inkscape::SnappedPoint &s,
159                                          NR::Point const &p,
160                                          std::list<SPItem*> const &cand) const
162     /* FIXME: this seems like a hack.  Perhaps Snappers should be
163     ** in SPDesktop rather than SPNamedView?
164     */
165     SPDesktop const *desktop = SP_ACTIVE_DESKTOP;
167     NR::Point const p_doc = desktop->dt2doc(p);
168     
169     // Determine the type of bounding box we should snap to
170     SPItem::BBoxType bbox_type = SPItem::GEOMETRIC_BBOX; 
171         if (_snap_to_bboxpath) {        
172         gchar const *prefs_bbox = prefs_get_string_attribute("tools.select", "bounding_box");
173                 bbox_type = (prefs_bbox != NULL && strcmp(prefs_bbox, "geometric")==0)? SPItem::GEOMETRIC_BBOX : SPItem::APPROXIMATE_BBOX;        
174         }
175         
176         bool p_is_a_node = t & Inkscape::Snapper::SNAPPOINT_NODE;        
177         
178     for (std::list<SPItem*>::const_iterator i = cand.begin(); i != cand.end(); i++) {
180         /* Transform the requested snap point to this item's coordinates */
181         NR::Matrix i2doc(NR::identity());
182         SPItem *root_item = NULL;
183         /* We might have a clone at hand, so make sure we get the root item */
184         if (SP_IS_USE(*i)) {
185             i2doc = sp_use_get_root_transform(SP_USE(*i));
186             root_item = sp_use_root(SP_USE(*i));
187         } else {
188             i2doc = sp_item_i2doc_affine(*i);
189             root_item = *i;
190         }
191         
192         //Build a list of all paths considered for snapping to
193         std::list<Path*> paths_to_snap_to;
194         
195         //Add the item's path to snap to
196         if (_snap_to_itempath) {
197                 if (!(_strict_snapping && !p_is_a_node)) {
198                         paths_to_snap_to.push_back(Path_for_item(root_item, true, true));
199                 }
200         }
201                 
202         //Add the item's bounding box to snap to
203         if (_snap_to_bboxpath) {
204                 if (!(_strict_snapping && p_is_a_node)) {               
205                         //This will get ugly... rect -> curve -> bpath
206                         NRRect rect;
207                         sp_item_invoke_bbox(root_item, &rect, i2doc, TRUE, bbox_type);
208                         NR::Maybe<NR::Rect> bbox = rect.upgrade();
209                         SPCurve *curve = sp_curve_new_from_rect(bbox);
210                         NArtBpath *bpath = SP_CURVE_BPATH(curve);
211                         Path *path = bpath_to_Path(bpath);  
212                         paths_to_snap_to.push_back(path);
213                         delete curve;
214                         delete bpath;
215                 }
216         }
217         
218         //Now we can finally do the real snapping, using the paths collected above        
219         for (std::list<Path*>::const_iterator k = paths_to_snap_to.begin(); k != paths_to_snap_to.end(); k++) {
220                 if (*k) {
221                     (*k)->ConvertWithBackData(0.01);
222                 
223                         /* Look for the nearest position on this SPItem to our snap point */
224                         NR::Maybe<Path::cut_position> const o = get_nearest_position_on_Path(*k, p_doc);
225                         if (o && o->t >= 0 && o->t <= 1) {
226                 
227                             /* Convert the nearest point back to desktop coordinates */
228                             NR::Point const o_it = get_point_on_Path(*k, o->piece, o->t);                           
229                             NR::Point const o_dt = desktop->doc2dt(o_it);
230                                         
231                             NR::Coord const dist = NR::L2(o_dt - p);
232                             if (dist < getDistance() && dist < s.getDistance()) {
233                                 s = SnappedPoint(o_dt, dist);
234                             }
235                         }
236                 
237                         delete *k;
238                 }
239             }
240     }
244 Inkscape::SnappedPoint Inkscape::ObjectSnapper::_doFreeSnap(Inkscape::Snapper::PointType const &t,
245                                                                                                                         NR::Point const &p,
246                                                             std::list<SPItem const *> const &it) const
248     if ( NULL == _named_view ) {
249         return SnappedPoint(p, NR_HUGE);
250     }
252     /* Get a list of all the SPItems that we will try to snap to */
253     std::list<SPItem*> cand;
254     _findCandidates(cand, sp_document_root(_named_view->document), it, p);
256     SnappedPoint s(p, NR_HUGE);
258     if (_snap_to_itemnode || _snap_to_bboxnode) {
259         _snapNodes(t, s, p, cand);
260     }
261     if (_snap_to_itempath || _snap_to_bboxpath) {
262         _snapPaths(t, s, p, cand);
263     }
265     return s;
270 Inkscape::SnappedPoint Inkscape::ObjectSnapper::_doConstrainedSnap(Inkscape::Snapper::PointType const &t,
271                                                                                                                                    NR::Point const &p,
272                                                                    ConstraintLine const &c,
273                                                                    std::list<SPItem const *> const &it) const
275     /* FIXME: this needs implementing properly; I think we have to do the
276     ** intersection of c with the objects.
277     */
278     return _doFreeSnap(t, p, it);
281 /**
282  *  \return true if this Snapper will snap at least one kind of point.
283  */
284 bool Inkscape::ObjectSnapper::ThisSnapperMightSnap() const
286     bool snap_to_something = _snap_to_itempath || _snap_to_itemnode || _snap_to_bboxpath || _snap_to_bboxnode;
287     return (_enabled && _snap_from != 0 && snap_to_something);
291 /*
292   Local Variables:
293   mode:c++
294   c-file-style:"stroustrup"
295   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
296   indent-tabs-mode:nil
297   fill-column:99
298   End:
299 */
300 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :