Code

Fix bbox snapping as reported in LP bug #562205
[inkscape.git] / src / snap.cpp
index 3cac53ebe6d74eeb90dac9b01ccb5d93f28c126c..8704ce3bb69200752fed6dba11088f10523053dc 100644 (file)
@@ -224,7 +224,7 @@ void SnapManager::preSnap(Inkscape::SnapCandidatePoint const &p)
         if (s.getSnapped()) {
             _desktop->snapindicator->set_new_snaptarget(s, true);
         } else {
-            _desktop->snapindicator->remove_snaptarget();
+            _desktop->snapindicator->remove_snaptarget(true);
         }
         _snapindicator = true; // restore the original value
     }
@@ -249,15 +249,16 @@ void SnapManager::preSnap(Inkscape::SnapCandidatePoint const &p)
  * \return Offset vector after snapping to the closest multiple of a grid pitch
  */
 
-Geom::Point SnapManager::multipleOfGridPitch(Geom::Point const &t) const
+Geom::Point SnapManager::multipleOfGridPitch(Geom::Point const &t, Geom::Point const &origin)
 {
-    if (!snapprefs.getSnapEnabledGlobally()) // No need to check for snapprefs.getSnapPostponedGlobally() here
+    if (!snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally())
         return t;
 
     if (_desktop && _desktop->gridsEnabled()) {
         bool success = false;
         Geom::Point nearest_multiple;
         Geom::Coord nearest_distance = NR_HUGE;
+        Inkscape::SnappedPoint bestSnappedPoint(t);
 
         // It will snap to the grid for which we find the closest snap. This might be a different
         // grid than to which the objects were initially aligned. I don't see an easy way to fix
@@ -276,21 +277,28 @@ Geom::Point SnapManager::multipleOfGridPitch(Geom::Point const &t) const
                 Geom::Point const t_offset = t + grid->origin;
                 SnappedConstraints sc;
                 // Only the first three parameters are being used for grid snappers
-                snapper->freeSnap(sc, Inkscape::SnapCandidatePoint(t_offset, Inkscape::SNAPSOURCE_UNDEFINED),Geom::OptRect(), NULL, NULL);
+                snapper->freeSnap(sc, Inkscape::SnapCandidatePoint(t_offset, Inkscape::SNAPSOURCE_GRID_PITCH),Geom::OptRect(), NULL, NULL);
                 // Find the best snap for this grid, including intersections of the grid-lines
-                Inkscape::SnappedPoint s = findBestSnap(Inkscape::SnapCandidatePoint(t_offset, Inkscape::SNAPSOURCE_UNDEFINED), sc, false);
+                bool old_val = _snapindicator;
+                _snapindicator = false;
+                Inkscape::SnappedPoint s = findBestSnap(Inkscape::SnapCandidatePoint(t_offset, Inkscape::SNAPSOURCE_GRID_PITCH), sc, false, false, true);
+                _snapindicator = old_val;
                 if (s.getSnapped() && (s.getSnapDistance() < nearest_distance)) {
                     // use getSnapDistance() instead of getWeightedDistance() here because the pointer's position
                     // doesn't tell us anything about which node to snap
                     success = true;
                     nearest_multiple = s.getPoint() - to_2geom(grid->origin);
                     nearest_distance = s.getSnapDistance();
+                    bestSnappedPoint = s;
                 }
             }
         }
 
-        if (success)
+        if (success) {
+            bestSnappedPoint.setPoint(origin + nearest_multiple);
+            _desktop->snapindicator->set_new_snaptarget(bestSnappedPoint);
             return nearest_multiple;
+        }
     }
 
     return t;
@@ -357,21 +365,29 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapCandidatePoint
     // First project the mouse pointer onto the constraint
     Geom::Point pp = constraint.projection(p.getPoint());
 
+    Inkscape::SnappedPoint no_snap = Inkscape::SnappedPoint(pp, p.getSourceType(), p.getSourceNum(), Inkscape::SNAPTARGET_CONSTRAINT, Geom::L2(pp - p.getPoint()), 0, false, false);
+
     if (!someSnapperMightSnap()) {
-        // The constraint should always be enforce, so we return pp here instead of p
-        return Inkscape::SnappedPoint(pp, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false);
+        // Always return point on constraint
+        return no_snap;
     }
 
-    // Then try to snap the projected point
-    Inkscape::SnapCandidatePoint candidate(pp, p.getSourceType(), p.getSourceNum(), Inkscape::SNAPTARGET_UNDEFINED, Geom::Rect());
-
     SnappedConstraints sc;
     SnapperList const snappers = getSnappers();
     for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
-        (*i)->constrainedSnap(sc, candidate, bbox_to_snap, constraint, &_items_to_ignore);
+        (*i)->constrainedSnap(sc, p, bbox_to_snap, constraint, &_items_to_ignore);
     }
 
-    return findBestSnap(candidate, sc, true);
+    Inkscape::SnappedPoint result = findBestSnap(p, sc, true);
+
+    if (result.getSnapped()) {
+        // only change the snap indicator if we really snapped to something
+        if (_snapindicator) {
+            _desktop->snapindicator->set_new_snaptarget(result);
+        }
+        return result;
+    }
+    return no_snap;
 }
 
 /**
@@ -508,7 +524,7 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed(
     /* Quick check to see if we have any snappers that are enabled
     ** Also used to globally disable all snapping
     */
-    if (someSnapperMightSnap() == false) {
+    if (someSnapperMightSnap() == false || points.size() == 0) {
         return Inkscape::SnappedPoint(pointer);
     }
 
@@ -543,10 +559,11 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed(
     g_assert(best_snapped_point.getAlwaysSnap() == false); // Check initialization of snapped point
     g_assert(best_snapped_point.getAtIntersection() == false);
 
-    std::vector<Inkscape::SnapCandidatePoint>::const_iterator j = transformed_points.begin();
+    std::vector<Inkscape::SnapCandidatePoint>::iterator j = transformed_points.begin();
 
 
     // std::cout << std::endl;
+    bool first_free_snap = true;
     for (std::vector<Inkscape::SnapCandidatePoint>::const_iterator i = points.begin(); i != points.end(); i++) {
 
         /* Snap it */
@@ -583,6 +600,17 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed(
                 dedicated_constraint = Inkscape::Snapper::ConstraintLine(origin, component_vectors[c1]);
                 snapped_point = constrainedSnap(*j, dedicated_constraint, bbox);
             } else {
+                // If we have a collection of SnapCandidatePoints, with mixed constrained snapping and free snapping
+                // requirements, then freeSnap might never see the SnapCandidatePoint with source_num == 0. The freeSnap()
+                // method in the object snapper depends on this, because only for source-num == 0 the target nodes will
+                // be collected. Therefore we enforce that the first SnapCandidatePoint that is to be freeSnapped always
+                // has source_num == 0;
+                // TODO: This is a bit ugly so fix this; do we need sourcenum for anything else? if we don't then get rid
+                // of it and explicitely communicate to the object snapper that this is a first point
+                if (first_free_snap) {
+                    (*j).setSourceNum(0);
+                    first_free_snap = false;
+                }
                 snapped_point = freeSnap(*j, bbox);
             }
         }
@@ -875,15 +903,18 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapSkew(std::vector<Inkscape::Sn
  * \param sc A structure holding all snap targets that have been found so far
  * \param constrained True if the snap is constrained, e.g. for stretching or for purely horizontal translation.
  * \param noCurves If true, then do consider snapping to intersections of curves, but not to the curves themselves
+ * \param allowOffScreen If true, then snapping to points which are off the screen is allowed (needed for example when pasting to the grid)
  * \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics
  */
 
 Inkscape::SnappedPoint SnapManager::findBestSnap(Inkscape::SnapCandidatePoint const &p,
                                                  SnappedConstraints const &sc,
                                                  bool constrained,
-                                                 bool noCurves) const
+                                                 bool noCurves,
+                                                 bool allowOffScreen) const
 {
 
+
     /*
     std::cout << "Type and number of snapped constraints: " << std::endl;
     std::cout << "  Points      : " << sc.points.size() << std::endl;
@@ -969,13 +1000,15 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Inkscape::SnapCandidatePoint co
     Inkscape::SnappedPoint bestSnappedPoint(p.getPoint());
     // std::cout << "Finding the best snap..." << std::endl;
     for (std::list<Inkscape::SnappedPoint>::const_iterator i = sp_list.begin(); i != sp_list.end(); i++) {
-        // first find out if this snapped point is within snapping range
         // std::cout << "sp = " << (*i).getPoint() << " | source = " << (*i).getSource() << " | target = " << (*i).getTarget();
-        if ((*i).getSnapDistance() <= (*i).getTolerance()) {
-            // if it's the first point, or if it is closer than the best snapped point so far
-            if (i == sp_list.begin() || bestSnappedPoint.isOtherSnapBetter(*i, false)) {
-                // then prefer this point over the previous one
-                bestSnappedPoint = *i;
+        bool onScreen = _desktop->get_display_area().contains((*i).getPoint());
+        if (onScreen || allowOffScreen) { // Only snap to points which are not off the screen
+            if ((*i).getSnapDistance() <= (*i).getTolerance()) { // Only snap to points within snapping range
+                // if it's the first point, or if it is closer than the best snapped point so far
+                if (i == sp_list.begin() || bestSnappedPoint.isOtherSnapBetter(*i, false)) {
+                    // then prefer this point over the previous one
+                    bestSnappedPoint = *i;
+                }
             }
         }
         // std::cout << std::endl;