Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
snap.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * SnapManager class.
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * Frank Felfe <innerspace@iname.com>
8 * Nathan Hurst <njh@njhurst.com>
9 * Carl Hetherington <inkscape@carlh.net>
10 * Diederik van Lierop <mail@diedenrezi.nl>
11 *
12 * Copyright (C) 2006-2007 Johan Engelen <johan@shouraizou.nl>
13 * Copyright (C) 2004 Nathan Hurst
14 * Copyright (C) 1999-2012 Authors
15 *
16 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
17 */
18
19#include "snap.h"
20
21#include <memory>
22#include <vector>
23
24#include <glib.h> // for g_assert
25#include <glibmm/timer.h>
26
27#include <2geom/transforms.h>
28
29#include "desktop.h"
30#include "preferences.h"
31#include "pure-transform.h"
32#include "selection.h"
33#include "snap-enums.h"
34#include "style.h"
35
37#include "helper/mathfns.h"
39#include "object/sp-clippath.h"
40#include "object/sp-filter.h"
41#include "object/sp-grid.h"
42#include "object/sp-guide.h"
43#include "object/sp-mask.h"
44#include "object/sp-namedview.h"
45#include "object/sp-object.h"
46#include "object/sp-page.h"
47
50
52 snapprefs(preferences),
53 guide(this, 0),
54 object(this, 0),
55 alignment(this, 0),
56 distribution(this, 0),
57 _named_view(v),
58 _rotation_center_source_items(std::vector<SPItem*>()),
59 _desktop(nullptr),
60 _snapindicator(true),
61 _unselected_nodes(nullptr)
62{
63 _obj_snapper_candidates = std::make_unique<std::vector<Inkscape::SnapCandidateItem>>();
64 _align_snapper_candidates = std::make_unique<std::vector<Inkscape::SnapCandidateItem>>();
65}
66
72
74{
76 s.push_back(&guide);
77 s.push_back(&object);
78 s.push_back(&alignment);
79 s.push_back(&distribution);
80
82 s.splice(s.begin(), gs);
83
84 return s;
85}
86
88{
90
92 for(auto grid : _named_view->grids) {
93 s.push_back(grid->snapper());
94 }
95 }
96
97 return s;
98}
99
100bool SnapManager::someSnapperMightSnap(bool immediately) const
101{
103 return false;
104 }
105
106 // If we're asking if some snapper might snap RIGHT NOW (without the snap being postponed)...
107 if ( immediately && snapprefs.getSnapPostponedGlobally() ) {
108 return false;
109 }
110
111 SnapperList const s = getSnappers();
112 SnapperList::const_iterator i = s.begin();
113 while (i != s.end() && (*i)->ThisSnapperMightSnap() == false) {
114 ++i;
115 }
116
117 return (i != s.end());
118}
119
121{
123 return false;
124 }
125
126 SnapperList const s = getGridSnappers();
127 SnapperList::const_iterator i = s.begin();
128 while (i != s.end() && (*i)->ThisSnapperMightSnap() == false) {
129 ++i;
130 }
131
132 return (i != s.end());
133}
134
136 Inkscape::SnapSourceType const source_type,
137 Geom::OptRect const &bbox_to_snap) const
138{
141}
142
144 Geom::OptRect const &bbox_to_snap,
145 bool to_paths_only) const
146{
147 if (!someSnapperMightSnap()) {
148 return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, Geom::infinity(), 0, false, false, false);
149 }
150
152 SnapperList const snappers = getSnappers();
153
154 for (auto snapper : snappers) {
155 snapper->freeSnap(isr, p, bbox_to_snap, &_objects_to_ignore, _unselected_nodes);
156 }
157
158 return findBestSnap(p, isr, false, false, to_paths_only);
159}
160
161void SnapManager::preSnap(Inkscape::SnapCandidatePoint const &p, bool to_paths_only)
162{
163 // setup() must have been called before calling this method!
164
165 if (_snapindicator) {
166 _snapindicator = false; // prevent other methods from drawing a snap indicator; we want to control this here
167 Inkscape::SnappedPoint s = freeSnap(p, Geom::OptRect(), to_paths_only);
168 g_assert(_desktop != nullptr);
169 if (s.getSnapped()) {
171 } else {
173 }
174 _snapindicator = true; // restore the original value
175 }
176}
177
179{
181 return t;
182
183 // get from pref
185 bool success = false;
186 Geom::Point nearest_multiple;
187 Geom::Coord nearest_distance = Geom::infinity();
188 Inkscape::SnappedPoint bestSnappedPoint(t);
189
190 // It will snap to the grid for which we find the closest snap. This might be a different
191 // grid than to which the objects were initially aligned. I don't see an easy way to fix
192 // this, so when using multiple grids one can get unexpected results
193
194 // Cannot use getGridSnappers() because we need both the grids AND their snappers
195 // Therefore we iterate through all grids manually
196 for (auto grid : _named_view->grids) {
197 const Inkscape::Snapper* snapper = grid->snapper();
198 if (snapper && snapper->ThisSnapperMightSnap()) {
199 // To find the nearest multiple of the grid pitch for a given translation t, we
200 // will use the grid snapper. Simply snapping the value t to the grid will do, but
201 // only if the origin of the grid is at (0,0). If it's not then compensate for this
202 // in the translation t
203 Geom::Point const t_offset = t + grid->getOrigin();
205 // Only the first three parameters are being used for grid snappers
206 snapper->freeSnap(isr, Inkscape::SnapCandidatePoint(t_offset, Inkscape::SNAPSOURCE_GRID_PITCH),Geom::OptRect(), nullptr, nullptr);
207 // Find the best snap for this grid, including intersections of the grid-lines
208 bool old_val = _snapindicator;
209 _snapindicator = false;
211 _snapindicator = old_val;
212 if (s.getSnapped() && (s.getSnapDistance() < nearest_distance)) {
213 // use getSnapDistance() instead of getWeightedDistance() here because the pointer's position
214 // doesn't tell us anything about which node to snap
215 success = true;
216 nearest_multiple = s.getPoint() - grid->getOrigin();
217 nearest_distance = s.getSnapDistance();
218 bestSnappedPoint = s;
219 }
220 }
221 }
222
223 if (success) {
224 bestSnappedPoint.setPoint(origin + nearest_multiple);
225 _desktop->getSnapIndicator()->set_new_snaptarget(bestSnappedPoint);
226 return nearest_multiple;
227 }
228 }
229
230 return t;
231}
232
234 Inkscape::SnapSourceType const source_type,
235 Inkscape::Snapper::SnapConstraint const &constraint,
236 Geom::OptRect const &bbox_to_snap) const
237{
238 Inkscape::SnappedPoint const s = constrainedSnap(Inkscape::SnapCandidatePoint(p, source_type), constraint, bbox_to_snap);
239 p = s.getPoint(); // If we didn't snap, then we will return the point projected onto the constraint
240}
241
243 Inkscape::Snapper::SnapConstraint const &constraint,
244 Geom::OptRect const &bbox_to_snap) const
245{
246 // First project the mouse pointer onto the constraint
247 Geom::Point pp = constraint.projection(p.getPoint());
248
250
251 if (!someSnapperMightSnap()) {
252 // Always return point on constraint
253 return no_snap;
254 }
255
257
259 if ((prefs->getBool("/options/snapmousepointer/value", false)) && p.isSingleHandle()) {
260 // Snapping the mouse pointer instead of the constrained position of the knot allows
261 // to snap to things which don't intersect with the constraint line; this is basically
262 // then just a freesnap with the constraint applied afterwards
263 // We'll only do this if we're dragging a single handle, and for example not when transforming an object in the selector tool
264 result = freeSnap(p, bbox_to_snap);
265 if (result.getSnapped()) {
266 // only change the snap indicator if we really snapped to something
267 if (_snapindicator && _desktop) {
269 }
270 // Apply the constraint
271 result.setPoint(constraint.projection(result.getPoint()));
272 return result;
273 }
274 return no_snap;
275 }
276
278 SnapperList const snappers = getSnappers();
279 for (auto snapper : snappers) {
280 snapper->constrainedSnap(isr, p, bbox_to_snap, constraint, &_objects_to_ignore, _unselected_nodes);
281 }
282
283 result = findBestSnap(p, isr, true);
284
285
286 if (result.getSnapped()) {
287 // only change the snap indicator if we really snapped to something
288 if (_snapindicator && _desktop) {
290 }
291 return result;
292 }
293 return no_snap;
294}
295
296/* See the documentation for constrainedSnap() directly above for more details.
297 * The difference is that multipleConstrainedSnaps() will take a list of constraints instead of a single one,
298 * and will try to snap the SnapCandidatePoint to only the closest constraint
299 * \param p Source point to be snapped
300 * \param constraints List of directions or lines along which snapping must occur
301 * \param dont_snap If true then we will only apply the constraint, without snapping
302 * \param bbox_to_snap Bounding box hulling the set of points, all from the same selection and having the same transformation
303 */
304
305
307 std::vector<Inkscape::Snapper::SnapConstraint> const &constraints,
308 bool dont_snap,
309 Geom::OptRect const &bbox_to_snap) const
310{
311
313 if (constraints.size() == 0) {
314 return no_snap;
315 }
316
317 // We haven't tried to snap yet; we will first determine which constraint is closest to where we are now,
318 // i.e. lets find out which of the constraints yields the closest projection of point p
319
320 // Project the mouse pointer on each of the constraints
321 std::vector<Geom::Point> projections;
322 for (const auto & constraint : constraints) {
323 // Project the mouse pointer onto the constraint; In case we don't snap then we will
324 // return the projection onto the constraint, such that the constraint is always enforced
325 Geom::Point pp = constraint.projection(p.getPoint());
326 projections.push_back(pp);
327 }
328
329 // Select the closest constraint
330 no_snap.setPoint(projections.front());
331 Inkscape::Snapper::SnapConstraint cc = constraints.front(); //closest constraint
332
333 std::vector<Inkscape::Snapper::SnapConstraint>::const_iterator c = constraints.begin();
334 std::vector<Geom::Point>::iterator pp = projections.begin();
335 for (; pp != projections.end(); ++pp) {
336 if (Geom::L2(*pp - p.getPoint()) < Geom::L2(no_snap.getPoint() - p.getPoint())) {
337 no_snap.setPoint(*pp); // Remember the projection onto the closest constraint
338 cc = *c; // Remember the closest constraint itself
339 }
340 ++c;
341 }
342
343 if (!someSnapperMightSnap() || dont_snap) {
344 return no_snap;
345 }
346
348 SnapperList const snappers = getSnappers();
350 bool snap_mouse = prefs->getBool("/options/snapmousepointer/value", false);
351
353 if (snap_mouse && p.isSingleHandle()) {
354 // Snapping the mouse pointer instead of the constrained position of the knot allows
355 // to snap to things which don't intersect with the constraint line; this is basically
356 // then just a freesnap with the constraint applied afterwards
357 // We'll only to this if we're dragging a single handle, and for example not when transforming an object in the selector tool
358 result = freeSnap(p, bbox_to_snap);
359 // Now apply the constraint afterwards
360 result.setPoint(cc.projection(result.getPoint()));
361 } else {
362 // Try to snap along the closest constraint
363 for (auto snapper : snappers) {
364 snapper->constrainedSnap(isr, p, bbox_to_snap, cc, &_objects_to_ignore,_unselected_nodes);
365 }
366 result = findBestSnap(p, isr, true);
367 }
368
369 return result.getSnapped() ? result : no_snap;
370}
371
373 std::optional<Geom::Point> const &p_ref,
374 Geom::Point const &o,
375 unsigned const snaps) const
376{
378 if (snaps > 0) { // 0 means no angular snapping
379 // p is at an arbitrary angle. Now we should snap this angle to specific increments.
380 // For this we'll calculate the closest two angles, one at each side of the current angle
381 Geom::Line y_axis(Geom::Point(0, 0), Geom::Point(0, 1));
382 Geom::Line p_line(o, p.getPoint());
383 double angle = Geom::angle_between(y_axis, p_line);
384 double angle_incr = M_PI / snaps;
385 double angle_offset = 0;
386 if (p_ref) {
387 Geom::Line p_line_ref(o, *p_ref);
388 angle_offset = Geom::angle_between(y_axis, p_line_ref);
389 }
390 double angle_ceil = round_to_upper_multiple_plus(angle, angle_incr, angle_offset);
391 double angle_floor = round_to_lower_multiple_plus(angle, angle_incr, angle_offset);
392 // We have two angles now. The constrained snapper will try each of them and return the closest
393
394 // Now do the snapping...
395 std::vector<Inkscape::Snapper::SnapConstraint> constraints;
396 constraints.emplace_back(Geom::Line(o, angle_ceil - M_PI/2));
397 constraints.emplace_back(Geom::Line(o, angle_floor - M_PI/2));
398 sp = multipleConstrainedSnaps(p, constraints); // Constraints will always be applied, even if we didn't snap
399 if (!sp.getSnapped()) { // If we haven't snapped then we only had the constraint applied;
401 }
402 } else {
403 sp = freeSnap(p);
404 }
405 return sp;
406}
407
408void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point &origin_or_vector, bool origin, bool freeze_angle) const
409{
410 if (freeze_angle && origin) {
411 g_warning("Dear developer, when snapping guides you shouldn't ask me to freeze the guide's vector when you haven't specified one");
412 // You've supplied me with an origin instead of a vector
413 }
414
416 return;
417 }
418
420 if (origin) {
421 candidate.addOrigin(origin_or_vector);
422 } else {
424 candidate.addVector(Geom::rot90(origin_or_vector));
425 }
426
428 SnapperList snappers = getSnappers();
429 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); ++i) {
430 (*i)->freeSnap(isr, candidate, Geom::OptRect(), nullptr, nullptr);
431 }
432
433 Inkscape::SnappedPoint const s = findBestSnap(candidate, isr, false);
434
436
437 if (!freeze_angle && s.getSnapped()) {
438 if (!Geom::are_near(s.getTangent(), Geom::Point(0,0))) { // If the tangent has been set ...
439 origin_or_vector = Geom::rot90(s.getTangent()); // then use it to update the normal of the guide
440 // PS: The tangent might not have been set if we snapped for example to a node
441 }
442 }
443}
444
446{
448 return;
449 }
450
452
455
456 SnapperList snappers = getSnappers();
457 for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); ++i) {
458 (*i)->constrainedSnap(isr, candidate, Geom::OptRect(), cl, nullptr, nullptr);
459 }
460
461 Inkscape::SnappedPoint const s = findBestSnap(candidate, isr, false);
463}
464
466 std::vector<Inkscape::SnapCandidatePoint> const &points,
467 Geom::Point const &pointer,
468 Inkscape::PureTransform &transform
469 )
470{
471 /* We have a list of points, which we are proposing to transform in some way. We need to see
472 ** if any of these points, when transformed, snap to anything. If they do, we return the
473 ** appropriate transformation with `true'; otherwise we return the original scale with `false'.
474 */
475
476 if (points.size() == 0) {
477 transform.best_snapped_point = Inkscape::SnappedPoint(pointer);
478 return;
479 }
480
481 // We will try to snap a set of points, but we don't want to have a snap indicator displayed
482 // for each of them. That's why it's temporarily disabled here, and re-enabled again after we
483 // have finished calling the freeSnap() and constrainedSnap() methods
484 bool _orig_snapindicator_status = _snapindicator;
485 _snapindicator = false;
486
487 transform.snap(this, points, pointer);
488
489 // Allow the snapindicator to be displayed again
490 _snapindicator = _orig_snapindicator_status;
491
492 if (_snapindicator) {
493 if (transform.best_snapped_point.getSnapped()) {
495 } else {
497 }
498 }
499
500 if (points.size() == 1) {
501 displaySnapsource(Inkscape::SnapCandidatePoint(transform.best_snapped_point.getPoint(), points.at(0).getSourceType()));
502 }
503}
504
506 IntermSnapResults const &isr,
507 bool constrained,
508 bool allowOffScreen,
509 bool to_path_only) const
510{
511 g_assert(_desktop != nullptr);
512
513 /*
514 std::cout << "Type and number of snapped constraints: " << std::endl;
515 std::cout << " Points : " << isr.points.size() << std::endl;
516 std::cout << " Grid lines : " << isr.grid_lines.size()<< std::endl;
517 std::cout << " Guide lines : " << isr.guide_lines.size()<< std::endl;
518 std::cout << " Curves : " << isr.curves.size()<< std::endl;
519 */
520
521 /*
522 // Display all snap candidates on the canvas
523 _desktop->getSnapIndicator()->remove_debugging_points();
524 for (std::list<Inkscape::SnappedPoint>::const_iterator i = isr.points.begin(); i != isr.points.end(); i++) {
525 _desktop->getSnapIndicator()->set_new_debugging_point((*i).getPoint());
526 }
527 for (std::list<Inkscape::SnappedCurve>::const_iterator i = isr.curves.begin(); i != isr.curves.end(); i++) {
528 _desktop->getSnapIndicator()->set_new_debugging_point((*i).getPoint());
529 }
530 for (std::list<Inkscape::SnappedLine>::const_iterator i = isr.grid_lines.begin(); i != isr.grid_lines.end(); i++) {
531 _desktop->getSnapIndicator()->set_new_debugging_point((*i).getPoint());
532 }
533 for (std::list<Inkscape::SnappedLine>::const_iterator i = isr.guide_lines.begin(); i != isr.guide_lines.end(); i++) {
534 _desktop->getSnapIndicator()->set_new_debugging_point((*i).getPoint());
535 }
536 */
537
538 // Store all snappoints
539 std::list<Inkscape::SnappedPoint> sp_list;
540
541 // search for the closest snapped point
542 Inkscape::SnappedPoint closestPoint;
543 if (getClosestSP(isr.points, closestPoint)) {
544 sp_list.push_back(closestPoint);
545 }
546
547 // search for the closest snapped curve
548 Inkscape::SnappedCurve closestCurve;
549 // We might have collected the paths only to snap to their intersection, without the intention to snap to the paths themselves
550 // Therefore we explicitly check whether the paths should be considered as snap targets themselves
552 if (getClosestCurve(isr.curves, closestCurve, exclude_paths, to_path_only)) {
553 sp_list.emplace_back(closestCurve);
554 }
555
556 // search for the closest snapped grid line
558 Inkscape::SnappedLine closestGridLine;
559 if (getClosestSL(isr.grid_lines, closestGridLine)) {
560 closestGridLine.setSource(p.getSourceType());
562 sp_list.emplace_back(closestGridLine);
563 }
564 }
565
566 // search for the closest snapped guide line
567 Inkscape::SnappedLine closestGuideLine;
568 if (getClosestSL(isr.guide_lines, closestGuideLine)) {
569 sp_list.emplace_back(closestGuideLine);
570 }
571
572 // When freely snapping to a grid/guide/path, only one degree of freedom is eliminated
573 // Therefore we will try get fully constrained by finding an intersection with another grid/guide/path
574
575 // When doing a constrained snap however, we're already at an intersection of the constrained line and
576 // the grid/guide/path we're snapping to. This snappoint is therefore fully constrained, so there's
577 // no need to look for additional intersections
578 if (!constrained) {
580 // search for the closest snapped intersection of curves
581 Inkscape::SnappedPoint closestCurvesIntersection;
582 if (getClosestIntersectionCS(isr.curves, p.getPoint(), closestCurvesIntersection, _desktop->dt2doc())) {
583 closestCurvesIntersection.setSource(p.getSourceType());
584 sp_list.push_back(closestCurvesIntersection);
585 }
586 }
587
589 // search for the closest snapped intersection of a guide with a curve
590 Inkscape::SnappedPoint closestCurveGuideIntersection;
591 if (getClosestIntersectionCL(isr.curves, isr.guide_lines, p.getPoint(), closestCurveGuideIntersection, _desktop->dt2doc())) {
592 closestCurveGuideIntersection.setSource(p.getSourceType());
593 sp_list.push_back(closestCurveGuideIntersection);
594 }
595 }
596
597 // search for the closest snapped intersection of grid lines
598 Inkscape::SnappedPoint closestGridPoint;
599 if (getClosestIntersectionSL(isr.grid_lines, closestGridPoint)) {
600 closestGridPoint.setSource(p.getSourceType());
602 sp_list.push_back(closestGridPoint);
603 }
604
605 // search for the closest snapped intersection of guide lines
606 Inkscape::SnappedPoint closestGuidePoint;
607 if (getClosestIntersectionSL(isr.guide_lines, closestGuidePoint)) {
608 closestGuidePoint.setSource(p.getSourceType());
610 sp_list.push_back(closestGuidePoint);
611 }
612
613 // search for the closest snapped intersection of grid with guide lines
615 Inkscape::SnappedPoint closestGridGuidePoint;
616 if (getClosestIntersectionSL(isr.grid_lines, isr.guide_lines, closestGridGuidePoint)) {
617 closestGridGuidePoint.setSource(p.getSourceType());
619 sp_list.push_back(closestGridGuidePoint);
620 }
621 }
622 }
623
624 // Filter out all snap targets that do NOT include a path; this is useful when we try to insert
625 // a node in a path (on doubleclick in the node tool). We don't want to change the shape of the
626 // path, so the snapped point must be on a path, and not e.g. on a grid intersection or on the
627 // bounding box
628 if (to_path_only) {
629 sp_list.remove_if([](Inkscape::SnappedPoint sp) { return !sp.getOnPath(); });
630 }
631
632 // now let's see which snapped point gets a thumbs up
633 Inkscape::SnappedPoint bestSnappedPoint(p.getPoint());
634 // std::cout << "Finding the best snap..." << std::endl;
635 for (std::list<Inkscape::SnappedPoint>::const_iterator i = sp_list.begin(); i != sp_list.end(); ++i) {
636 // std::cout << "sp = " << (*i).getPoint() << " | source = " << (*i).getSource() << " | target = " << (*i).getTarget();
637 bool onScreen = _desktop->get_display_area().contains((*i).getPoint());
638 if (onScreen || allowOffScreen) { // Only snap to points which are not off the screen
639 if ((*i).getAlwaysSnap() || (*i).getSnapDistance() <= (*i).getTolerance()) { // Only snap to points within snapping range
640 // if it's the first point, or if it is closer than the best snapped point so far
641 if (i == sp_list.begin() || bestSnappedPoint.isOtherSnapBetter(*i, false)) {
642 // then prefer this point over the previous one
643 bestSnappedPoint = *i;
644 }
645 }
646 }
647 // std::cout << std::endl;
648 }
649
650 // Update the snap indicator, if requested
651 if (_snapindicator) {
652 if (bestSnappedPoint.getSnapped()) {
653 _desktop->getSnapIndicator()->set_new_snaptarget(bestSnappedPoint);
654 } else {
656 }
657 }
658
659 // std::cout << "findBestSnap = " << bestSnappedPoint.getPoint() << " | dist = " << bestSnappedPoint.getSnapDistance() << std::endl;
660 return bestSnappedPoint;
661}
662
664 bool snapindicator,
665 SPObject const *item_to_ignore,
666 std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes)
667{
668 g_assert(desktop != nullptr);
669 if (_desktop != nullptr) {
670 g_warning("The snapmanager has been set up before, but unSetup() hasn't been called afterwards. It possibly held invalid pointers");
671 }
672 _objects_to_ignore.clear();
673 if (item_to_ignore) {
674 _objects_to_ignore.push_back(item_to_ignore);
675 }
677 _snapindicator = snapindicator;
678 _unselected_nodes = unselected_nodes;
681}
682
684 bool snapindicator,
685 std::vector<SPObject const *> &objects_to_ignore,
686 std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes)
687{
688 g_assert(desktop != nullptr);
689 if (_desktop != nullptr) {
690 g_warning("The snapmanager has been set up before, but unSetup() hasn't been called afterwards. It possibly held invalid pointers");
691 }
692 _objects_to_ignore = objects_to_ignore;
694 _snapindicator = snapindicator;
695 _unselected_nodes = unselected_nodes;
698}
699
702 bool snapindicator,
703 std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes)
704{
705 g_assert(desktop != nullptr);
706 if (_desktop != nullptr) {
707 // Someone has been naughty here! This is dangerous
708 g_warning("The snapmanager has been set up before, but unSetup() hasn't been called afterwards. It possibly held invalid pointers");
709 }
711 _snapindicator = snapindicator;
712 _unselected_nodes = unselected_nodes;
715 _objects_to_ignore.clear();
716
718 auto items = sel->items();
719 for (auto i=items.begin();i!=items.end();++i) {
720 _objects_to_ignore.push_back(*i);
721 }
722}
723
728
729//Geom::Point SnapManager::_transformPoint(Inkscape::SnapCandidatePoint const &p,
730// Transformation const transformation_type,
731// Geom::Point const &transformation,
732// Geom::Point const &origin,
733// Geom::Dim2 const dim,
734// bool const uniform) const
735//{
736// /* Work out the transformed version of this point */
737// Geom::Point transformed;
738// switch (transformation_type) {
739// case TRANSLATE:
740// transformed = p.getPoint() + transformation;
741// break;
742// case SCALE:
743// transformed = (p.getPoint() - origin) * Geom::Scale(transformation[Geom::X], transformation[Geom::Y]) + origin;
744// break;
745// case STRETCH:
746// {
747// Geom::Scale s(1, 1);
748// if (uniform)
749// s[Geom::X] = s[Geom::Y] = transformation[dim];
750// else {
751// s[dim] = transformation[dim];
752// s[1 - dim] = 1;
753// }
754// transformed = ((p.getPoint() - origin) * s) + origin;
755// break;
756// }
757// case SKEW:
758// // Apply the skew factor
759// transformed[dim] = (p.getPoint())[dim] + transformation[0] * ((p.getPoint())[1 - dim] - origin[1 - dim]);
760// // While skewing, mirroring and scaling (by integer multiples) in the opposite direction is also allowed.
761// // Apply that scale factor here
762// transformed[1-dim] = (p.getPoint() - origin)[1 - dim] * transformation[1] + origin[1 - dim];
763// break;
764// case ROTATE:
765// // for rotations: transformation[0] stores the angle in radians
766// transformed = (p.getPoint() - origin) * Geom::Rotate(transformation[0]) + origin;
767// break;
768// default:
769// g_assert_not_reached();
770// }
771//
772// return transformed;
773//}
774
783 if (prefs->getBool("/options/snapclosestonly/value")) {
785 bool p_is_a_node = t & Inkscape::SNAPSOURCE_NODE_CATEGORY;
786 bool p_is_a_bbox = t & Inkscape::SNAPSOURCE_BBOX_CATEGORY;
788
789 g_assert(_desktop != nullptr);
792 } else {
794 }
795 }
796}
797
799{
800 for (auto item : _objects_to_ignore) {
801 if (auto guide = cast<SPGuide>(item)) {
802 return guide;
803 }
804 }
805 return nullptr;
806}
808{
809 for (auto item : _objects_to_ignore) {
810 if (auto page = cast<SPPage>(item)) {
811 return page;
812 }
813 }
814 return nullptr;
815}
816
817
819 std::vector<SPObject const *> const *it,
820 Geom::Rect const &bbox_to_snap,
821 bool const clip_or_mask,
822 Geom::Affine const additional_affine)
823{
824 SPDesktop const *dt = getDesktop();
825 if (dt == nullptr) {
826 g_error("desktop == NULL, so we cannot snap; please inform the developers of this bug");
827 return;
828 // Apparently the setup() method from the SnapManager class hasn't been called before trying to snap.
829 }
830
831 static int recursion_level = 0;
832
833 if (recursion_level == 0) {
834 if (_findCandidates_already_called) { // In case we have already been called by another snapper,
835 return; // then we don't need to search for candidates again
836 }
840 }
841 recursion_level++;
842
843 Geom::Rect bbox_to_snap_incl = bbox_to_snap; // _incl means: will include the snapper tolerance
844 bbox_to_snap_incl.expandBy(object.getSnapperTolerance()); // see?
845
846 for (auto& o: parent->children) {
847 auto item = cast<SPItem>(&o);
848 if (item && !(dt->itemIsHidden(item) && !clip_or_mask)) {
849 // Fix LPE boolops self-snapping
850 bool stop = false;
851 if (item->style) {
852 SPFilter *filt = item->style->getFilter();
853 if (filt && filt->getId() && strcmp(filt->getId(), "selectable_hidder_filter") == 0) {
854 stop = true;
855 }
856 auto lpeitem = cast<SPLPEItem>(item);
857 if (lpeitem && lpeitem->hasPathEffectOfType(Inkscape::LivePathEffect::EffectType::BOOL_OP)) {
858 stop = true;
859 }
860 }
861 if (stop && it) {
862 stop = false;
863 for (auto skipitem : *it) {
864 if (skipitem && skipitem->style) {
865 auto toskip = cast<SPItem>(const_cast<SPObject *>(skipitem));
866 if (toskip) {
867 SPFilter *filt = toskip->style->getFilter();
868 if (filt && filt->getId() && strcmp(filt->getId(), "selectable_hidder_filter") == 0) {
869 stop = true;
870 break;
871 }
872
873 auto lpeitem = cast<SPLPEItem>(toskip);
874 if (!stop && lpeitem &&
875 lpeitem->hasPathEffectOfType(Inkscape::LivePathEffect::EffectType::BOOL_OP)) {
876 stop = true;
877 break;
878 }
879 }
880 }
881 }
882 if (stop) {
883 continue;
884 }
885 }
886 // Snapping to items in a locked layer is allowed
887 // Don't snap to hidden objects, unless they're a clipped path or a mask
888 /* See if this item is on the ignore list */
889 std::vector<SPObject const *>::const_iterator i;
890 if (it != nullptr) {
891 i = it->begin();
892 while (i != it->end() && *i != &o) {
893 ++i;
894 }
895 }
896
897 if (it == nullptr || i == it->end()) {
898 if (item) {
899 if (!clip_or_mask) { // cannot clip or mask more than once
900 // The current item is not a clipping path or a mask, but might
901 // still be the subject of clipping or masking itself ; if so, then
902 // we should also consider that path or mask for snapping to
903 SPObject *obj = item->getClipObject();
905 _findCandidates(obj, it, bbox_to_snap, true, item->i2doc_affine());
906 }
907 obj = item->getMaskObject();
909 _findCandidates(obj, it, bbox_to_snap, true, item->i2doc_affine());
910 }
911 }
912
913 if (is<SPGroup>(item)) {
914 _findCandidates(&o, it, bbox_to_snap, clip_or_mask, additional_affine);
915 } else {
916 Geom::OptRect bbox_of_item;
918 int prefs_bbox = prefs->getBool("/tools/bounding_box", false);
919 // We'll only need to obtain the visual bounding box if the user preferences tell
920 // us to, AND if we are snapping to the bounding box itself. If we're snapping to
921 // paths only, then we can just as well use the geometric bounding box (which is faster)
924 if (clip_or_mask) {
925 // Oh oh, this will get ugly. We cannot use sp_item_i2d_affine directly because we need to
926 // insert an additional transformation in document coordinates (code copied from sp_item_i2d_affine)
927 bbox_of_item = item->bounds(bbox_type, item->i2doc_affine() * additional_affine * dt->doc2dt());
928 } else {
929 bbox_of_item = item->desktopBounds(bbox_type);
930 }
931 if (bbox_of_item) {
932 bool overflow = false;
933 // See if the item is within range
934 auto display_area = getDesktop()->get_display_area().bounds();
935 if (display_area.intersects(*bbox_of_item)) {
936 // Finally add the object to _candidates.
937 _align_snapper_candidates->emplace_back(item, clip_or_mask, additional_affine);
938 // For debugging: print the id of the candidate to the console
939 // SPObject *obj = (SPObject*)item;
940 // std::cout << "Snap candidate added: " << obj->getId() << std::endl;
941
942 if (bbox_to_snap_incl.intersects(*bbox_of_item)
943 || (snapprefs.isTargetSnappable(Inkscape::SNAPTARGET_ROTATION_CENTER) && bbox_to_snap_incl.contains(item->getCenter()))) { // rotation center might be outside of the bounding box
944 // This item is within snapping range, so record it as a candidate
945 _obj_snapper_candidates->emplace_back(item, clip_or_mask, additional_affine);
946 // For debugging: print the id of the candidate to the console
947 // SPObject *obj = (SPObject*)item;
948 // std::cout << "Snap candidate added: " << obj->getId() << std::endl;
949 }
950
951 if (_align_snapper_candidates->size() > 200) { // This makes Inkscape crawl already
952 overflow = true;
953 }
954 }
955
956 if (overflow) {
957 static Glib::Timer timer;
958 if (timer.elapsed() > 1.0) {
959 timer.reset();
960 std::cerr << "Warning: limit of 200 snap target paths reached, some will be ignored" << std::endl;
961 }
962 break;
963 }
964 }
965 }
966 }
967 }
968 }
969 }
970
971 recursion_level--;
972}
973/*
974 Local Variables:
975 mode:c++
976 c-file-style:"stroustrup"
977 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
978 indent-tabs-mode:nil
979 fill-column:99
980 End:
981*/
982// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Point origin
Definition aa.cpp:227
uint64_t page
Definition canvas.cpp:171
3x3 matrix representing an affine transformation.
Definition affine.h:70
bool contains(GenericRect< C > const &r) const
Check whether the rectangle includes all points in the given rectangle.
bool intersects(GenericRect< C > const &r) const
Check whether the rectangles have any common points.
void expandBy(C amount)
Expand the rectangle in both directions by the specified amount.
Infinite line on a plane.
Definition line.h:53
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Rect bounds() const
Axis-aligned bounding box.
bool contains(Point const &) const
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
void set_new_snapsource(Inkscape::SnapCandidatePoint const &p)
void remove_snaptarget(bool only_if_presnap=false)
void set_new_snaptarget(Inkscape::SnappedPoint const &p, bool pre_snap=false)
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:255
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
SnappedPoint best_snapped_point
virtual SnappedPoint snap(::SnapManager *sm, SnapCandidatePoint const &p, Geom::Point pt_orig, Geom::OptRect const &bbox_to_snap) const =0
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
Class to store data for points which are snap candidates, either as a source or as a target.
Inkscape::SnapSourceType getSourceType() const
Geom::Point const & getPoint() const
void addOrigin(Geom::Point pt)
void addVector(Geom::Point v)
Storing of snapping preferences.
bool getSnapPostponedGlobally() const
bool isTargetSnappable(Inkscape::SnapTargetType const target) const
Class describing the result of an attempt to snap to a curve.
Class describing the result of an attempt to snap to a line.
Class describing the result of an attempt to snap.
void setPoint(Geom::Point const &p)
void getPointIfSnapped(Geom::Point &p) const
void setSource(SnapSourceType const source)
Geom::Point getPoint() const
Geom::Coord getSnapDistance() const
Geom::Point getTangent() const
void setTarget(SnapTargetType const target)
bool isOtherSnapBetter(SnappedPoint const &other_one, bool weighted) const
Geom::Point projection(Geom::Point const &p) const
Definition snapper.h:109
Parent for classes that can snap points to something.
Definition snapper.h:39
virtual bool ThisSnapperMightSnap() const
Definition snapper.h:51
virtual void freeSnap(IntermSnapResults &, Inkscape::SnapCandidatePoint const &, Geom::OptRect const &, std::vector< SPObject const * > const *, std::vector< SnapCandidatePoint > *) const
Definition snapper.h:59
To do: update description of desktop.
Definition desktop.h:149
Geom::Parallelogram get_display_area() const
Return canvas viewbox in desktop coordinates.
Definition desktop.cpp:521
bool itemIsHidden(SPItem const *item) const
Definition desktop.cpp:264
Geom::Affine const & dt2doc() const
Definition desktop.cpp:1343
Inkscape::Display::SnapIndicator * getSnapIndicator() const
Definition desktop.h:193
SPNamedView * getNamedView() const
Definition desktop.h:191
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Geom::Affine const & doc2dt() const
Definition desktop.cpp:1337
Typed SVG document implementation.
Definition document.h:101
Geom::Point getNormal() const
Definition sp-guide.h:55
Geom::Point getPoint() const
Definition sp-guide.h:54
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::OptRect bounds(BBoxType type, Geom::Affine const &transform=Geom::identity()) const
Definition sp-item.cpp:1011
Geom::OptRect desktopBounds(BBoxType type) const
Definition sp-item.cpp:1083
Geom::Point getCenter(bool ensure_uptodate=true) const
Definition sp-item.cpp:384
SPMask * getMaskObject() const
Definition sp-item.cpp:177
@ VISUAL_BBOX
Definition sp-item.h:118
@ GEOMETRIC_BBOX
Definition sp-item.h:116
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1832
SPClipPath * getClipObject() const
Definition sp-item.cpp:102
bool getShowGrids()
std::vector< SPGrid * > grids
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPDocument * document
Definition sp-object.h:188
char const * getId() const
Returns the objects current ID string.
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
SPFilter * getFilter()
Definition style.h:335
~SnapManager()
Definition snap.cpp:67
SPDesktop const * _desktop
Definition snap.h:427
SnapperList getGridSnappers() const
Return a list of gridsnappers.
Definition snap.cpp:87
void guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline) const
Wrapper method to make snapping of the guide origin a bit easier (i.e.
Definition snap.cpp:445
bool _findCandidates_already_called
Definition snap.h:443
void displaySnapsource(Inkscape::SnapCandidatePoint const &p) const
Mark the location of the snap source (not the snap target!) on the canvas by drawing a symbol.
Definition snap.cpp:781
std::vector< SPItem * > _rotation_center_source_items
Definition snap.h:426
std::unique_ptr< std::vector< Inkscape::SnapCandidateItem > > _obj_snapper_candidates
Definition snap.h:445
void constrainedSnapReturnByRef(Geom::Point &p, Inkscape::SnapSourceType const source_type, Inkscape::Snapper::SnapConstraint const &constraint, Geom::OptRect const &bbox_to_snap=Geom::OptRect()) const
Try to snap a point along a constraint line to grids, guides or objects.
Definition snap.cpp:233
void setup(SPDesktop const *desktop, bool snapindicator=true, SPObject const *item_to_ignore=nullptr, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes=nullptr)
Convenience shortcut when there is only one item to ignore.
Definition snap.cpp:663
void _findCandidates(SPObject *parent, std::vector< SPObject const * > const *it, Geom::Rect const &bbox_to_snap, bool const _clip_or_mask, Geom::Affine const additional_affine)
Find all items within snapping range.
Definition snap.cpp:818
std::unique_ptr< std::vector< Inkscape::SnapCandidateItem > > _align_snapper_candidates
Definition snap.h:446
Inkscape::AlignmentSnapper alignment
snapper to align with other objects
Definition snap.h:340
void guideFreeSnap(Geom::Point &p, Geom::Point &origin_or_vector, bool origin, bool freeze_angle) const
Wrapper method to make snapping of the guide origin a bit easier (i.e.
Definition snap.cpp:408
Geom::Point multipleOfGridPitch(Geom::Point const &t, Geom::Point const &origin)
Snap to the closest multiple of a grid pitch.
Definition snap.cpp:178
SPGuide const * getGuideToIgnore() const
Definition snap.cpp:798
std::vector< SPObject const * > _objects_to_ignore
Items that should not be snapped to, for example the items that are currently being dragged....
Definition snap.h:425
void setupIgnoreSelection(SPDesktop const *desktop, bool snapindicator=true, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes=nullptr)
Setup, taking the list of items to ignore from the desktop's selection.
Definition snap.cpp:701
Inkscape::SnappedPoint findBestSnap(Inkscape::SnapCandidatePoint const &p, IntermSnapResults const &isr, bool constrained, bool allowOffScreen=false, bool to_paths_only=false) const
Given a set of possible snap targets, find the best target (which is not necessarily also the nearest...
Definition snap.cpp:505
bool gridSnapperMightSnap() const
Definition snap.cpp:120
void freeSnapReturnByRef(Geom::Point &p, Inkscape::SnapSourceType const source_type, Geom::OptRect const &bbox_to_snap=Geom::OptRect()) const
Try to snap a point to grids, guides or objects.
Definition snap.cpp:135
std::list< const Inkscape::Snapper * > SnapperList
Definition snap.h:97
void preSnap(Inkscape::SnapCandidatePoint const &p, bool to_path_only=false)
Definition snap.cpp:161
SPDocument * getDocument() const
Definition snap.cpp:724
Inkscape::SnappedPoint constrainedSnap(Inkscape::SnapCandidatePoint const &p, Inkscape::Snapper::SnapConstraint const &constraint, Geom::OptRect const &bbox_to_snap=Geom::OptRect()) const
Try to snap a point along a constraint line to grids, guides or objects.
Definition snap.cpp:242
SnapManager(SPNamedView const *v, Inkscape::SnapPreferences &preferences)
Construct a SnapManager for a SPNamedView.
Definition snap.cpp:51
bool _snapindicator
When true, an indicator will be drawn at the position that was being snapped to.
Definition snap.h:428
Inkscape::SnappedPoint constrainedAngularSnap(Inkscape::SnapCandidatePoint const &p, std::optional< Geom::Point > const &p_ref, Geom::Point const &o, unsigned const snaps) const
Try to snap a point to something at a specific angle.
Definition snap.cpp:372
Inkscape::DistributionSnapper distribution
Definition snap.h:341
SnapperList getSnappers() const
Return a list of snappers.
Definition snap.cpp:73
void snapTransformed(std::vector< Inkscape::SnapCandidatePoint > const &points, Geom::Point const &pointer, Inkscape::PureTransform &transform)
Method for snapping sets of points while they are being transformed.
Definition snap.cpp:465
Inkscape::SnappedPoint multipleConstrainedSnaps(Inkscape::SnapCandidatePoint const &p, std::vector< Inkscape::Snapper::SnapConstraint > const &constraints, bool dont_snap=false, Geom::OptRect const &bbox_to_snap=Geom::OptRect()) const
Definition snap.cpp:306
Inkscape::SnapPreferences & snapprefs
Definition snap.h:342
SPPage const * getPageToIgnore() const
Definition snap.cpp:807
SPNamedView const * _named_view
Definition snap.h:422
Inkscape::GuideSnapper guide
guide snapper
Definition snap.h:338
Inkscape::SnappedPoint freeSnap(Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap=Geom::OptRect(), bool to_path_only=false) const
Try to snap a point to grids, guides or objects.
Definition snap.cpp:143
bool someSnapperMightSnap(bool immediately=true) const
Return true if any snapping might occur, whether its to grids, guides or objects.
Definition snap.cpp:100
SPDesktop const * getDesktop() const
Definition snap.h:370
std::vector< Inkscape::SnapCandidatePoint > * _unselected_nodes
Nodes of the path that is currently being edited and which have not been selected and which will ther...
Definition snap.h:429
Css & result
double c[8][4]
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
SPItem * item
double angle_between(Line const &l1, Line const &l2)
Definition line.h:456
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
double round_to_upper_multiple_plus(double x, double const c1, double const c0=0)
Definition mathfns.h:53
double round_to_lower_multiple_plus(double x, double c1, double c0=0.0)
Definition mathfns.h:41
SnapSourceType
enumerations of snap source types and snap target types.
Definition snap-enums.h:18
@ SNAPSOURCE_OTHERS_CATEGORY
Definition snap-enums.h:51
@ SNAPSOURCE_GUIDE
Definition snap-enums.h:47
@ SNAPSOURCE_DATUMS_CATEGORY
Definition snap-enums.h:46
@ SNAPSOURCE_GUIDE_ORIGIN
Definition snap-enums.h:48
@ SNAPSOURCE_GRID_PITCH
Definition snap-enums.h:57
@ SNAPSOURCE_NODE_CATEGORY
Definition snap-enums.h:35
@ SNAPSOURCE_BBOX_CATEGORY
Definition snap-enums.h:23
@ SNAPTARGET_UNDEFINED
Definition snap-enums.h:71
@ SNAPTARGET_PATH_GUIDE_INTERSECTION
Definition snap-enums.h:89
@ SNAPTARGET_CONSTRAINT
Definition snap-enums.h:121
@ SNAPTARGET_PATH
Definition snap-enums.h:85
@ SNAPTARGET_ROTATION_CENTER
Definition snap-enums.h:117
@ SNAPTARGET_GRID_INTERSECTION
Definition snap-enums.h:98
@ SNAPTARGET_PATH_CLIP
Definition snap-enums.h:90
@ SNAPTARGET_GUIDE
Definition snap-enums.h:100
@ SNAPTARGET_NODE_CATEGORY
Definition snap-enums.h:81
@ SNAPTARGET_GRID
Definition snap-enums.h:96
@ SNAPTARGET_GRID_GUIDE_INTERSECTION
Definition snap-enums.h:104
@ SNAPTARGET_GUIDE_INTERSECTION
Definition snap-enums.h:101
@ SNAPTARGET_PATH_MASK
Definition snap-enums.h:91
@ SNAPTARGET_GRID_LINE
Definition snap-enums.h:97
@ SNAPTARGET_PATH_INTERSECTION
Definition snap-enums.h:88
@ SNAPTARGET_BBOX_CATEGORY
Definition snap-enums.h:73
@ SNAPTARGET_CONSTRAINED_ANGLE
Definition snap-enums.h:120
STL namespace.
Singleton class to access the preferences file in a convenient way.
unsigned long gs
Definition quantize.cpp:38
GList * items
Provides a class that shows a temporary indicator on the canvas of where the snap was,...
bool getClosestIntersectionCS(std::list< Inkscape::SnappedCurve > const &list, Geom::Point const &p, Inkscape::SnappedPoint &result, Geom::Affine dt2doc)
bool getClosestIntersectionCL(std::list< Inkscape::SnappedCurve > const &curve_list, std::list< Inkscape::SnappedLine > const &line_list, Geom::Point const &p, Inkscape::SnappedPoint &result, Geom::Affine dt2doc)
bool getClosestCurve(std::list< Inkscape::SnappedCurve > const &list, Inkscape::SnappedCurve &result, bool exclude_paths, bool paths_only)
bool getClosestSL(std::list< Inkscape::SnappedLine > const &list, Inkscape::SnappedLine &result)
bool getClosestIntersectionSL(std::list< Inkscape::SnappedLine > const &list, Inkscape::SnappedPoint &result)
bool getClosestSP(std::list< Inkscape::SnappedPoint > const &list, Inkscape::SnappedPoint &result)
SVG <filter> element.
SPGuide – a guideline.
SPPage – a page object.
std::list< Inkscape::SnappedCurve > curves
Definition snapper.h:29
std::list< Inkscape::SnappedLine > grid_lines
Definition snapper.h:27
std::list< Inkscape::SnappedPoint > points
Definition snapper.h:26
std::list< Inkscape::SnappedLine > guide_lines
Definition snapper.h:28
SPStyle - a style object for SPItem objects.
SPDesktop * desktop
Affine transformation classes.