summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDiederik van Lierop <mail@diedenrezi.nl>2012-01-14 13:35:29 +0000
committerDiederik van Lierop <mail@diedenrezi.nl>2012-01-14 13:35:29 +0000
commit62ab3cc3fe08cee5968581360d1509a6b01ff9f1 (patch)
tree74e36cf0869c7fb8cc6ee457c353b09fcb96ead7 /src
parentCleanup variable declarations in ruler, and a couple of GSEAL fixes. (diff)
downloadinkscape-62ab3cc3fe08cee5968581360d1509a6b01ff9f1.tar.gz
inkscape-62ab3cc3fe08cee5968581360d1509a6b01ff9f1.zip
Enable tangential and perpendicular snapping to paths (in the node-tool, pen-tool, pencil-tool, and for guide manipulation; cannot be toggled yet, will always be active when snapping to paths)
(bzr r10886)
Diffstat (limited to 'src')
-rw-r--r--src/2geom/sbasis-geometric.cpp17
-rw-r--r--src/2geom/sbasis-geometric.h2
-rw-r--r--src/desktop-events.cpp10
-rw-r--r--src/display/snap-indicator.cpp6
-rw-r--r--src/draw-context.cpp11
-rw-r--r--src/draw-context.h2
-rw-r--r--src/object-snapper.cpp107
-rw-r--r--src/object-snapper.h7
-rw-r--r--src/snap-candidate.h33
-rw-r--r--src/snap-enums.h6
-rw-r--r--src/snap-preferences.cpp39
-rw-r--r--src/snap.cpp17
-rw-r--r--src/snap.h18
-rw-r--r--src/ui/tool/node.cpp59
-rw-r--r--src/ui/tool/node.h4
15 files changed, 238 insertions, 100 deletions
diff --git a/src/2geom/sbasis-geometric.cpp b/src/2geom/sbasis-geometric.cpp
index 017b377c9..0987953de 100644
--- a/src/2geom/sbasis-geometric.cpp
+++ b/src/2geom/sbasis-geometric.cpp
@@ -764,6 +764,23 @@ std::vector<double> find_normals(Point P, D2<SBasis> const &A) {
return roots(crs);
}
+/**
+* \brief returns all the parameter values of A whose normal is parallel to vector V.
+* \relates D2
+*/
+std::vector<double> find_normals_by_vector(Point V, D2<SBasis> const &A) {
+ SBasis crs = dot(derivative(A), V);
+ return roots(crs);
+}
+/**
+* \brief returns all the parameter values of A whose tangent is parallel to vector V.
+* \relates D2
+*/
+std::vector<double> find_tangents_by_vector(Point V, D2<SBasis> const &A) {
+ SBasis crs = dot(derivative(A), rot90(V));
+ return roots(crs);
+}
+
}
//}; // namespace
diff --git a/src/2geom/sbasis-geometric.h b/src/2geom/sbasis-geometric.h
index 00b64b430..43c624fb4 100644
--- a/src/2geom/sbasis-geometric.h
+++ b/src/2geom/sbasis-geometric.h
@@ -101,7 +101,9 @@ cubics_with_prescribed_curvature(Point const &M0, Point const &M1,
std::vector<double> find_tangents(Point P, D2<SBasis> const &A);
+std::vector<double> find_tangents_by_vector(Point V, D2<SBasis> const &A);
std::vector<double> find_normals(Point P, D2<SBasis> const &A);
+std::vector<double> find_normals_by_vector(Point V, D2<SBasis> const &A);
};
diff --git a/src/desktop-events.cpp b/src/desktop-events.cpp
index 068e4aa9a..36294f824 100644
--- a/src/desktop-events.cpp
+++ b/src/desktop-events.cpp
@@ -152,7 +152,7 @@ static gint sp_dt_ruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidge
// We only have a temporary guide which is not stored in our document yet.
// Because the guide snapper only looks in the document for guides to snap to,
// we don't have to worry about a guide snapping to itself here
- m.guideFreeSnap(event_dt, normal, SP_DRAG_MOVE_ORIGIN);
+ m.guideFreeSnap(event_dt, SP_DRAG_MOVE_ORIGIN, Geom::rot90(normal));
m.unSetup();
}
@@ -175,7 +175,7 @@ static gint sp_dt_ruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidge
// We only have a temporary guide which is not stored in our document yet.
// Because the guide snapper only looks in the document for guides to snap to,
// we don't have to worry about a guide snapping to itself here
- m.guideFreeSnap(event_dt, normal, SP_DRAG_MOVE_ORIGIN);
+ m.guideFreeSnap(event_dt, SP_DRAG_MOVE_ORIGIN, Geom::rot90(normal));
m.unSetup();
}
@@ -303,7 +303,8 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data)
}
} else if (!((drag_type == SP_DRAG_ROTATE) && (event->motion.state & GDK_CONTROL_MASK))) {
// cannot use shift here to disable snapping, because we already use it for rotating the guide
- m.guideFreeSnap(motion_dt, guide->normal_to_line, drag_type);
+ Geom::Point origin = (drag_type == SP_DRAG_ROTATE) ? guide->point_on_line : Geom::rot90(guide->normal_to_line);
+ m.guideFreeSnap(motion_dt, drag_type, origin);
}
m.unSetup();
@@ -376,7 +377,8 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data)
}
} else if (!((drag_type == SP_DRAG_ROTATE) && (event->motion.state & GDK_CONTROL_MASK))) {
// cannot use shift here to disable snapping, because we already use it for rotating the guide
- m.guideFreeSnap(event_dt, guide->normal_to_line, drag_type);
+ Geom::Point origin = (drag_type == SP_DRAG_ROTATE) ? guide->point_on_line : Geom::rot90(guide->normal_to_line);
+ m.guideFreeSnap(event_dt, drag_type, origin);
}
m.unSetup();
diff --git a/src/display/snap-indicator.cpp b/src/display/snap-indicator.cpp
index 7864c0e53..ee719847f 100644
--- a/src/display/snap-indicator.cpp
+++ b/src/display/snap-indicator.cpp
@@ -100,6 +100,12 @@ SnapIndicator::set_new_snaptarget(Inkscape::SnappedPoint const &p, bool pre_snap
case SNAPTARGET_PATH:
target_name = _("path");
break;
+ case SNAPTARGET_PATH_PERPENDICULAR:
+ target_name = _("path (perpendicular)");
+ break;
+ case SNAPTARGET_PATH_TANGENTIAL:
+ target_name = _("path (tangential)");
+ break;
case SNAPTARGET_PATH_INTERSECTION:
target_name = _("path intersection");
break;
diff --git a/src/draw-context.cpp b/src/draw-context.cpp
index ebc6e320f..15b20f124 100644
--- a/src/draw-context.cpp
+++ b/src/draw-context.cpp
@@ -501,7 +501,7 @@ void spdc_endpoint_snap_rotation(SPEventContext const *const ec, Geom::Point &p,
}
-void spdc_endpoint_snap_free(SPEventContext const * const ec, Geom::Point& p, boost::optional<Geom::Point> &start_point, guint const /*state*/)
+void spdc_endpoint_snap_free(SPEventContext const * const ec, Geom::Point& p, boost::optional<Geom::Point> &start_of_line, guint const /*state*/)
{
SPDesktop *dt = SP_EVENT_CONTEXT_DESKTOP(ec);
SnapManager &m = dt->namedview->snap_manager;
@@ -511,7 +511,14 @@ void spdc_endpoint_snap_free(SPEventContext const * const ec, Geom::Point& p, bo
// TODO: Allow snapping to the stationary parts of the item, and only ignore the last segment
m.setup(dt, true, selection->singleItem());
- m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE, start_point);
+ Inkscape::SnapCandidatePoint scp(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ if (start_of_line) {
+ scp.addOrigin(*start_of_line);
+ }
+
+ Inkscape::SnappedPoint sp = m.freeSnap(scp);
+ p = sp.getPoint();
+
m.unSetup();
}
diff --git a/src/draw-context.h b/src/draw-context.h
index a6762bed4..98e4982dd 100644
--- a/src/draw-context.h
+++ b/src/draw-context.h
@@ -85,7 +85,7 @@ GType sp_draw_context_get_type(void);
SPDrawAnchor *spdc_test_inside(SPDrawContext *dc, Geom::Point p);
void spdc_concat_colors_and_flush(SPDrawContext *dc, gboolean forceclosed);
void spdc_endpoint_snap_rotation(SPEventContext const *const ec, Geom::Point &p, Geom::Point const &o, guint state);
-void spdc_endpoint_snap_free(SPEventContext const *ec, Geom::Point &p, boost::optional<Geom::Point> &start_point, guint state);
+void spdc_endpoint_snap_free(SPEventContext const *ec, Geom::Point &p, boost::optional<Geom::Point> &start_of_line, guint state);
void spdc_check_for_and_apply_waiting_LPE(SPDrawContext *dc, SPItem *item);
void spdc_create_single_dot(SPEventContext *ec, Geom::Point const &pt, char const *tool, guint event_state);
diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp
index b1c118e92..3b1b327ee 100644
--- a/src/object-snapper.cpp
+++ b/src/object-snapper.cpp
@@ -7,7 +7,7 @@
* Jon A. Cruz <jon@joncruz.org>
* Abhishek Sharma
*
- * Copyright (C) 2005 - 2011 Authors
+ * Copyright (C) 2005 - 2012 Authors
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
@@ -76,7 +76,8 @@ void Inkscape::ObjectSnapper::_findCandidates(SPObject* parent,
bool const clip_or_mask,
Geom::Affine const additional_affine) const // transformation of the item being clipped / masked
{
- if (_snapmanager->getDesktop() == NULL) {
+ SPDesktop const *dt = _snapmanager->getDesktop();
+ if (dt == NULL) {
g_warning("desktop == NULL, so we cannot snap; please inform the developers of this bug");
// Apparently the setup() method from the SnapManager class hasn't been called before trying to snap.
}
@@ -89,8 +90,8 @@ void Inkscape::ObjectSnapper::_findCandidates(SPObject* parent,
bbox_to_snap_incl.expandBy(getSnapperTolerance()); // see?
for ( SPObject *o = parent->firstChild(); o; o = o->getNext() ) {
- g_assert(_snapmanager->getDesktop() != NULL);
- if (SP_IS_ITEM(o) && !(_snapmanager->getDesktop()->itemIsHidden(SP_ITEM(o)) && !clip_or_mask)) {
+ g_assert(dt != NULL);
+ if (SP_IS_ITEM(o) && !(dt->itemIsHidden(SP_ITEM(o)) && !clip_or_mask)) {
// Snapping to items in a locked layer is allowed
// Don't snap to hidden objects, unless they're a clipped path or a mask
/* See if this item is on the ignore list */
@@ -135,7 +136,7 @@ void Inkscape::ObjectSnapper::_findCandidates(SPObject* parent,
if (clip_or_mask) {
// Oh oh, this will get ugly. We cannot use sp_item_i2d_affine directly because we need to
// insert an additional transformation in document coordinates (code copied from sp_item_i2d_affine)
- bbox_of_item = item->bounds(bbox_type, item->i2doc_affine() * additional_affine * _snapmanager->getDesktop()->doc2dt());
+ bbox_of_item = item->bounds(bbox_type, item->i2doc_affine() * additional_affine * dt->doc2dt());
} else {
bbox_of_item = item->desktopBounds(bbox_type);
}
@@ -464,8 +465,9 @@ void Inkscape::ObjectSnapper::_snapPaths(IntermSnapResults &isr,
_collectPaths(p.getPoint(), p.getSourceType(), p.getSourceNum() <= 0);
// Now we can finally do the real snapping, using the paths collected above
- g_assert(_snapmanager->getDesktop() != NULL);
- Geom::Point const p_doc = _snapmanager->getDesktop()->dt2doc(p.getPoint());
+ SPDesktop const *dt = _snapmanager->getDesktop();
+ g_assert(dt != NULL);
+ Geom::Point const p_doc = dt->dt2doc(p.getPoint());
bool const node_tool_active = _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION) && selected_path != NULL;
@@ -497,8 +499,10 @@ void Inkscape::ObjectSnapper::_snapPaths(IntermSnapResults &isr,
// continue counting
bool strict_snapping = _snapmanager->snapprefs.getStrictSnapping();
+ bool snap_perp = _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH_PERPENDICULAR);
+ bool snap_tang = _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH_TANGENTIAL);
- //_snapmanager->getDesktop()->snapindicator->remove_debugging_points();
+ //dt->snapindicator->remove_debugging_points();
for (std::vector<SnapCandidatePath >::const_iterator it_p = _paths_to_snap_to->begin(); it_p != _paths_to_snap_to->end(); it_p++) {
if (_allowSourceToSnapToTarget(p.getSourceType(), (*it_p).target_type, strict_snapping)) {
bool const being_edited = node_tool_active && (*it_p).currently_being_edited;
@@ -516,7 +520,7 @@ void Inkscape::ObjectSnapper::_snapPaths(IntermSnapResults &isr,
for (; np != anp.end(); np++, index++) {
Geom::Curve const *curve = &((*it_pv).at_index(index));
Geom::Point const sp_doc = curve->pointAt(*np);
- //_snapmanager->getDesktop()->snapindicator->set_new_debugging_point(sp_doc*_snapmanager->getDesktop()->doc2dt());
+ //dt->snapindicator->set_new_debugging_point(sp_doc*dt->doc2dt());
bool c1 = true;
bool c2 = true;
if (being_edited) {
@@ -526,8 +530,8 @@ void Inkscape::ObjectSnapper::_snapPaths(IntermSnapResults &isr,
* piece are unselected; if they are then this piece must be stationary
*/
g_assert(unselected_nodes != NULL);
- Geom::Point start_pt = _snapmanager->getDesktop()->doc2dt(curve->pointAt(0));
- Geom::Point end_pt = _snapmanager->getDesktop()->doc2dt(curve->pointAt(1));
+ Geom::Point start_pt = dt->doc2dt(curve->pointAt(0));
+ Geom::Point end_pt = dt->doc2dt(curve->pointAt(1));
c1 = isUnselectedNode(start_pt, unselected_nodes);
c2 = isUnselectedNode(end_pt, unselected_nodes);
/* Unfortunately, this might yield false positives for coincident nodes. Inkscape might therefore mistakenly
@@ -539,25 +543,17 @@ void Inkscape::ObjectSnapper::_snapPaths(IntermSnapResults &isr,
*/
}
- Geom::Point const sp_dt = _snapmanager->getDesktop()->doc2dt(sp_doc);
+ Geom::Point const sp_dt = dt->doc2dt(sp_doc);
if (!being_edited || (c1 && c2)) {
Geom::Coord dist = Geom::distance(sp_doc, p_doc);
// std::cout << " dist -> " << dist << std::endl;
if (dist < getSnapperTolerance()) {
// Add the curve we have snapped to
isr.curves.push_back(SnappedCurve(sp_dt, num_path, index, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, curve, p.getSourceType(), p.getSourceNum(), it_p->target_type, it_p->target_bbox));
- // Find all tangential points
-// boost::optional<Geom::Point> origin = p.getStartingPoint();
-// if (origin) {
-// Geom::Point origin_doc = _snapmanager->getDesktop()->dt2doc(*origin);
-// std::vector<double> atp = find_tangents(origin_doc, curve->toSBasis());
-// for (std::vector<double>::const_iterator t = atp.begin(); t != atp.end(); t++) {
-// Geom::Point const tp_doc = curve->pointAt(*t);
-// dist = Geom::distance(tp_doc, p_doc);
-// Geom::Point const tp_dt = _snapmanager->getDesktop()->doc2dt(tp_doc);
-// isr.points.push_back(SnappedPoint(tp_dt, p.getSourceType(), p.getSourceNum(), it_p->target_type, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, true, it_p->target_bbox));
-// }
-// }
+ if (snap_tang || snap_perp) {
+ // For each curve that's within snapping range, we will now also search for tangential and perpendicular snaps
+ _snapPathsTangPerp(snap_tang, snap_perp, isr, p, curve, dt);
+ }
}
}
}
@@ -597,7 +593,8 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(IntermSnapResults &isr,
// Now we can finally do the real snapping, using the paths collected above
- g_assert(_snapmanager->getDesktop() != NULL);
+ SPDesktop const *dt = _snapmanager->getDesktop();
+ g_assert(dt != NULL);
Geom::Point direction_vector = c.getDirection();
if (!is_zero(direction_vector)) {
@@ -606,8 +603,8 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(IntermSnapResults &isr,
// The intersection point of the constraint line with any path, must lie within two points on the
// SnapConstraint: p_min_on_cl and p_max_on_cl. The distance between those points is twice the snapping tolerance
- Geom::Point const p_min_on_cl = _snapmanager->getDesktop()->dt2doc(p_proj_on_constraint - getSnapperTolerance() * direction_vector);
- Geom::Point const p_max_on_cl = _snapmanager->getDesktop()->dt2doc(p_proj_on_constraint + getSnapperTolerance() * direction_vector);
+ Geom::Point const p_min_on_cl = dt->dt2doc(p_proj_on_constraint - getSnapperTolerance() * direction_vector);
+ Geom::Point const p_max_on_cl = dt->dt2doc(p_proj_on_constraint + getSnapperTolerance() * direction_vector);
Geom::Coord tolerance = getSnapperTolerance();
// PS: Because the paths we're about to snap to are all expressed relative to document coordinate system, we will have
@@ -615,7 +612,7 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(IntermSnapResults &isr,
std::vector<Geom::Path> constraint_path;
if (c.isCircular()) {
- Geom::Circle constraint_circle(_snapmanager->getDesktop()->dt2doc(c.getPoint()), c.getRadius());
+ Geom::Circle constraint_circle(dt->dt2doc(c.getPoint()), c.getRadius());
constraint_circle.getPath(constraint_path);
} else {
Geom::Path constraint_line;
@@ -667,7 +664,7 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(IntermSnapResults &isr,
// Convert the collected points of intersection to snapped points
for (std::vector<Geom::Point>::iterator p_inters = intersections.begin(); p_inters != intersections.end(); p_inters++) {
// Convert to desktop coordinates
- (*p_inters) = _snapmanager->getDesktop()->doc2dt(*p_inters);
+ (*p_inters) = dt->doc2dt(*p_inters);
// Construct a snapped point
Geom::Coord dist = Geom::L2(p.getPoint() - *p_inters);
SnappedPoint s = SnappedPoint(*p_inters, p.getSourceType(), p.getSourceNum(), k->target_type, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true, k->target_bbox);;
@@ -830,6 +827,58 @@ bool Inkscape::ObjectSnapper::_allowSourceToSnapToTarget(SnapSourceType source,
return allow_this_pair_to_snap;
}
+void Inkscape::ObjectSnapper::_snapPathsTangPerp(bool snap_tang, bool snap_perp, IntermSnapResults &isr, SnapCandidatePoint const &p, Geom::Curve const *curve, SPDesktop const *dt) const
+{
+ // Here we will try to snap either tangentially or perpendicularly to a single path; for this we need to know where the origin is located of the line that is currently being rotated,
+ // or we need to know the vector of the guide which is currently being translated
+ std::vector<std::pair<Geom::Point, bool> > const origins_and_vectors = p.getOriginsAndVectors();
+ // Now we will iterate over all the origins and vectors and see which of these will get use a tangential or perpendicular snap
+ for (std::vector<std::pair<Geom::Point, bool> >::const_iterator it_origin_or_vector = origins_and_vectors.begin(); it_origin_or_vector != origins_and_vectors.end(); it_origin_or_vector++) {
+ Geom::Point origin_or_vector_doc = dt->dt2doc((*it_origin_or_vector).first); // "first" contains a Geom::Point, denoting either a point or vector
+ if ((*it_origin_or_vector).second) { // if "second" is true then "first" is a vector, otherwise it's a point
+ // So we have a vector, which tells us what tangential or perpendicular direction we're looking for
+ if (curve->degreesOfFreedom() <= 2) { // A LineSegment has order one, and therefore 2 DOF
+ // When snapping to a point of a line segment that has a specific tangential or normal vector, then either all point
+ // along that line will be snapped to or no points at all will be snapped to. This is not very useful, so let's skip
+ // any line segments and lets only snap to higher order curves
+ continue;
+ }
+ // The vector is being treated as a point (relative to the origin), and has been translated to document coordinates accordingly
+ // We need however to make it a vector again, because also the origin has been transformed
+ origin_or_vector_doc -= dt->dt2doc(Geom::Point(0,0));
+ }
+
+ Geom::Point point_dt;
+ Geom::Coord dist;
+ std::vector<double> ts;
+
+ if (snap_tang) { // Find all points that lead to a tangential snap
+ if ((*it_origin_or_vector).second) { // if "second" is true then "first" is a vector, otherwise it's a point
+ ts = find_tangents_by_vector(origin_or_vector_doc, curve->toSBasis());
+ } else {
+ ts = find_tangents(origin_or_vector_doc, curve->toSBasis());
+ }
+ for (std::vector<double>::const_iterator t = ts.begin(); t != ts.end(); t++) {
+ point_dt = dt->doc2dt(curve->pointAt(*t));
+ dist = Geom::distance(point_dt, p.getPoint());
+ isr.points.push_back(SnappedPoint(point_dt, p.getSourceType(), p.getSourceNum(), SNAPTARGET_PATH_TANGENTIAL, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, true));
+ }
+ }
+
+ if (snap_perp) { // Find all points that lead to a perpendicular snap
+ if ((*it_origin_or_vector).second) {
+ ts = find_normals_by_vector(origin_or_vector_doc, curve->toSBasis());
+ } else {
+ ts = find_normals(origin_or_vector_doc, curve->toSBasis());
+ }
+ for (std::vector<double>::const_iterator t = ts.begin(); t != ts.end(); t++) {
+ point_dt = dt->doc2dt(curve->pointAt(*t));
+ dist = Geom::distance(point_dt, p.getPoint());
+ isr.points.push_back(SnappedPoint(point_dt, p.getSourceType(), p.getSourceNum(), SNAPTARGET_PATH_PERPENDICULAR, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, true));
+ }
+ }
+ }
+}
/*
Local Variables:
diff --git a/src/object-snapper.h b/src/object-snapper.h
index 59e2f10ce..31e1ee501 100644
--- a/src/object-snapper.h
+++ b/src/object-snapper.h
@@ -100,6 +100,13 @@ private:
SnapConstraint const &c,
Geom::Point const &p_proj_on_constraint) const;
+ void _snapPathsTangPerp(bool snap_tang,
+ bool snap_perp,
+ IntermSnapResults &isr,
+ SnapCandidatePoint const &p,
+ Geom::Curve const *curve,
+ SPDesktop const *dt) const;
+
bool isUnselectedNode(Geom::Point const &point, std::vector<Inkscape::SnapCandidatePoint> const *unselected_nodes) const;
/**
diff --git a/src/snap-candidate.h b/src/snap-candidate.h
index 5f17c8572..e97893c43 100644
--- a/src/snap-candidate.h
+++ b/src/snap-candidate.h
@@ -9,7 +9,7 @@
* Authors:
* Diederik van Lierop <mail@diedenrezi.nl>
*
- * Copyright (C) 2010 Authors
+ * Copyright (C) 2010 - 2012 Authors
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
@@ -32,7 +32,6 @@ public:
_source_num(source_num),
_target_bbox(bbox)
{
- _line_starting_point = boost::optional<Geom::Point>();
};
SnapCandidatePoint(Geom::Point const &point, Inkscape::SnapSourceType const source, Inkscape::SnapTargetType const target)
@@ -42,7 +41,6 @@ public:
{
_source_num = -1;
_target_bbox = Geom::OptRect();
- _line_starting_point = boost::optional<Geom::Point>();
}
SnapCandidatePoint(Geom::Point const &point, Inkscape::SnapSourceType const source)
@@ -52,35 +50,36 @@ public:
_source_num(-1)
{
_target_bbox = Geom::OptRect();
- _line_starting_point = boost::optional<Geom::Point>();
- }
-
- SnapCandidatePoint(Geom::Point const &point, Inkscape::SnapSourceType const source, boost::optional<Geom::Point> starting_point)
- : _point(point),
- _source_type(source),
- _target_type(Inkscape::SNAPTARGET_UNDEFINED),
- _source_num(-1)
- {
- _target_bbox = Geom::OptRect();
- _line_starting_point = starting_point;
}
inline Geom::Point const & getPoint() const {return _point;}
inline Inkscape::SnapSourceType getSourceType() const {return _source_type;}
- bool isSingleHandle() const {return (_source_type == SNAPSOURCE_NODE_HANDLE || _source_type == SNAPSOURCE_OTHER_HANDLE) && _source_num == -1;}
inline Inkscape::SnapTargetType getTargetType() const {return _target_type;}
+ bool isSingleHandle() const {return (_source_type == SNAPSOURCE_NODE_HANDLE || _source_type == SNAPSOURCE_OTHER_HANDLE) && _source_num == -1;}
+
inline long getSourceNum() const {return _source_num;}
void setSourceNum(long num) {_source_num = num;}
+
void setDistance(Geom::Coord dist) {_dist = dist;}
Geom::Coord getDistance() { return _dist;}
+
+ void addOrigin(Geom::Point &pt) { _origins_and_vectors.push_back(std::make_pair(pt, false)); }
+ void addVector(Geom::Point &v) { _origins_and_vectors.push_back(std::make_pair(v, true)); }
+ std::vector<std::pair<Geom::Point, bool> > const & getOriginsAndVectors() const {return _origins_and_vectors;}
+
bool operator <(const SnapCandidatePoint &other) const { return _dist < other._dist; } // Needed for sorting the SnapCandidatePoints
inline Geom::OptRect const getTargetBBox() const {return _target_bbox;}
- boost::optional<Geom::Point> const & getStartingPoint() const {return _line_starting_point;}
private:
// Coordinates of the point
Geom::Point _point;
- boost::optional<Geom::Point> _line_starting_point; // For perpendicular or tangential snapping we need to know the starting point of a line
+ // For perpendicular or tangential snapping of a ROTATING line we need to know its (stationary) starting point.
+ // In case of editing with the node tool, a node can be connected to two lines simultaneously, in which case we
+ // need to consider two starting points; Therefore a vector containing multiple starting points is used here. However,
+ // for perpendicular or tangential snapping of a TRANSLATING line we need to know its direction vector instead. This
+ // vector will be stored in the same way as the starting point is, i.e. as a Geom::Point. A boolean is paired to this
+ // point, which is true for vectors but false for origins
+ std::vector<std::pair<Geom::Point, bool> > _origins_and_vectors;
// If this SnapCandidatePoint is a snap source, then _source_type must be defined. If it
// is a snap target, then _target_type must be defined. If it's yet unknown whether it will
diff --git a/src/snap-enums.h b/src/snap-enums.h
index df1324001..22f1b9cdc 100644
--- a/src/snap-enums.h
+++ b/src/snap-enums.h
@@ -4,7 +4,7 @@
* Authors:
* Diederik van Lierop <mail@diedenrezi.nl>
*
- * Copyright (C) 2010 - 2011 Authors
+ * Copyright (C) 2010 - 2012 Authors
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
@@ -14,7 +14,7 @@ namespace Inkscape {
/**
* enumerations of snap source types and snap target types.
*/
-enum SnapSourceType {
+enum SnapSourceType { // When adding source types here, then also update Inkscape::SnapPreferences::source2target!
SNAPSOURCE_UNDEFINED = 0,
//-------------------------------------------------------------------
// Bbox points can be located at the edge of the stroke (for visual bboxes); they will therefore not snap
@@ -67,6 +67,8 @@ enum SnapTargetType {
SNAPTARGET_NODE_CUSP,
SNAPTARGET_LINE_MIDPOINT,
SNAPTARGET_PATH,
+ SNAPTARGET_PATH_PERPENDICULAR,
+ SNAPTARGET_PATH_TANGENTIAL,
SNAPTARGET_PATH_INTERSECTION,
SNAPTARGET_PATH_GUIDE_INTERSECTION,
SNAPTARGET_PATH_CLIP,
diff --git a/src/snap-preferences.cpp b/src/snap-preferences.cpp
index f3df788c9..821073fb5 100644
--- a/src/snap-preferences.cpp
+++ b/src/snap-preferences.cpp
@@ -54,9 +54,28 @@ void Inkscape::SnapPreferences::_mapTargetToArrayIndex(Inkscape::SnapTargetType
if (target & SNAPTARGET_BBOX_CATEGORY) {
group_on = isTargetSnappable(SNAPTARGET_BBOX_CATEGORY); // Only if the group with bbox sources/targets has been enabled, then we might snap to any of the bbox targets
+ return;
+ }
- } else if (target & SNAPTARGET_NODE_CATEGORY) {
+ if (target & SNAPTARGET_NODE_CATEGORY) {
group_on = isTargetSnappable(SNAPTARGET_NODE_CATEGORY); // Only if the group with path/node sources/targets has been enabled, then we might snap to any of the nodes/paths
+ switch (target) {
+ case SNAPTARGET_RECT_CORNER:
+ target = SNAPTARGET_NODE_CUSP;
+ break;
+ case SNAPTARGET_ELLIPSE_QUADRANT_POINT:
+ target = SNAPTARGET_NODE_SMOOTH;
+ break;
+ case SNAPTARGET_PATH_GUIDE_INTERSECTION:
+ target = SNAPTARGET_PATH_INTERSECTION;
+ break;
+ case SNAPTARGET_PATH_PERPENDICULAR:
+ case SNAPTARGET_PATH_TANGENTIAL:
+ target = SNAPTARGET_PATH;
+ break;
+ default:
+ break;
+ }
if (target == SNAPTARGET_RECT_CORNER) {
target = SNAPTARGET_NODE_CUSP;
} else if (target == SNAPTARGET_ELLIPSE_QUADRANT_POINT) {
@@ -65,11 +84,14 @@ void Inkscape::SnapPreferences::_mapTargetToArrayIndex(Inkscape::SnapTargetType
target = SNAPTARGET_PATH_INTERSECTION;
}
- } else if (target & SNAPTARGET_DATUMS_CATEGORY) {
+ return;
+ }
+
+ if (target & SNAPTARGET_DATUMS_CATEGORY) {
group_on = true; // These snap targets cannot be disabled as part of a disabled group;
switch (target) {
// Some snap targets don't have their own toggle. These targets are called "secondary targets". We will re-map
- // them to their cousin which does have a toggle, and which is called a "primary target"case SNAPTARGET_GRID_INTERSECTION:
+ // them to their cousin which does have a toggle, and which is called a "primary target"
case SNAPTARGET_GRID_INTERSECTION:
target = SNAPTARGET_GRID;
break;
@@ -96,9 +118,10 @@ void Inkscape::SnapPreferences::_mapTargetToArrayIndex(Inkscape::SnapTargetType
g_warning("Snap-preferences warning: Undefined snap target (#%i)", target);
break;
}
+ return;
+ }
-
- } else if (target & SNAPTARGET_OTHERS_CATEGORY) {
+ if (target & SNAPTARGET_OTHERS_CATEGORY) {
// Only if the group with "other" snap sources/targets has been enabled, then we might snap to any of those targets
// ... but this doesn't hold for the page border, grids, and guides
group_on = isTargetSnappable(SNAPTARGET_OTHERS_CATEGORY);
@@ -129,9 +152,13 @@ void Inkscape::SnapPreferences::_mapTargetToArrayIndex(Inkscape::SnapTargetType
break;
}
+ return;
+ }
- } else if (target == SNAPTARGET_UNDEFINED ) {
+ if (target == SNAPTARGET_UNDEFINED ) {
g_warning("Snap-preferences warning: Undefined snaptarget (#%i)", target);
+ } else {
+ g_warning("Snap-preferences warning: Snaptarget not handled (#%i)", target);
}
}
diff --git a/src/snap.cpp b/src/snap.cpp
index 36d17102d..eab9f2142 100644
--- a/src/snap.cpp
+++ b/src/snap.cpp
@@ -112,14 +112,6 @@ void SnapManager::freeSnapReturnByRef(Geom::Point &p,
s.getPointIfSnapped(p);
}
-void SnapManager::freeSnapReturnByRef(Geom::Point &p,
- Inkscape::SnapSourceType const source_type,
- boost::optional<Geom::Point> &starting_point) const
-{
- Inkscape::SnappedPoint const s = freeSnap(Inkscape::SnapCandidatePoint(p, source_type, starting_point), Geom::OptRect());
- s.getPointIfSnapped(p);
-}
-
Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::SnapCandidatePoint const &p,
Geom::OptRect const &bbox_to_snap) const
{
@@ -397,7 +389,7 @@ Inkscape::SnappedPoint SnapManager::constrainedAngularSnap(Inkscape::SnapCandida
return sp;
}
-void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &/*guide_normal*/, SPGuideDragType drag_type) const
+void SnapManager::guideFreeSnap(Geom::Point &p, SPGuideDragType drag_type, boost::optional<Geom::Point> origin_or_vector) const
{
if (!snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally() || !snapprefs.isTargetSnappable(Inkscape::SNAPTARGET_GUIDE)) {
return;
@@ -406,6 +398,13 @@ void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &/*guide_norma
Inkscape::SnapCandidatePoint candidate(p, Inkscape::SNAPSOURCE_GUIDE_ORIGIN);
if (drag_type == SP_DRAG_ROTATE) {
candidate = Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_GUIDE);
+ if (origin_or_vector) {
+ candidate.addOrigin(*origin_or_vector);
+ }
+ } else {
+ if (origin_or_vector) {
+ candidate.addVector(*origin_or_vector);
+ }
}
IntermSnapResults isr;
diff --git a/src/snap.h b/src/snap.h
index 3eb4c2b7a..8e7ab1872 100644
--- a/src/snap.h
+++ b/src/snap.h
@@ -9,7 +9,7 @@
*
* Copyright (C) 2006-2007 Johan Engelen <johan@shouraizou.nl>
* Copyright (C) 2000-2002 Lauris Kaplinski
- * Copyright (C) 2000-2010 Authors
+ * Copyright (C) 2000-2012 Authors
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
@@ -166,7 +166,7 @@ public:
*
* PS:
* 1) SnapManager::setup() must have been called before calling this method,
- * but only once for a set of points
+ * although only once for each set of points
* 2) Only to be used when a single source point is to be snapped; it assumes
* that source_num = 0, which is inefficient when snapping sets our source points
*
@@ -178,10 +178,6 @@ public:
Inkscape::SnapSourceType const source_type,
Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const;
- void freeSnapReturnByRef(Geom::Point &p,
- Inkscape::SnapSourceType const source_type,
- boost::optional<Geom::Point> &starting_point) const;
-
/**
* Try to snap a point to grids, guides or objects.
*
@@ -191,7 +187,7 @@ public:
* freeSnapReturnByRef(). Please read the comments of the latter for more details
*
* PS: SnapManager::setup() must have been called before calling this method,
- * but only once for a set of points
+ * although only once for each set of points
*
* @param p Source point to be snapped.
* @param bbox_to_snap Bounding box hulling the set of points, all from the same selection and having the same transformation.
@@ -244,7 +240,7 @@ public:
*
* PS:
* 1) SnapManager::setup() must have been called before calling this method,
- * but only once for a set of points
+ * although only once for each set of points
* 2) Only to be used when a single source point is to be snapped; it assumes
* that source_num = 0, which is inefficient when snapping sets our source points
@@ -268,7 +264,7 @@ public:
* constrainedSnapReturnByRef(). Please read the comments of the latter for more details.
*
* PS: SnapManager::setup() must have been called before calling this method,
- * but only once for a set of points
+ * although only once for each set of points
* PS: If there's nothing to snap to or if snapping has been disabled, then this
* method will still apply the constraint (but without snapping)
*
@@ -308,9 +304,8 @@ public:
* PS: SnapManager::setup() must have been called before calling this method,
*
* @param p Current position of the point on the guide that is to be snapped; will be overwritten by the position of the snap target if snapping has occurred.
- * @param guide_normal Vector normal to the guide line.
*/
- void guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal, SPGuideDragType drag_type) const;
+ void guideFreeSnap(Geom::Point &p, SPGuideDragType drag_type, boost::optional<Geom::Point> origin_or_vector = boost::optional<Geom::Point>()) const;
/**
* Wrapper method to make snapping of the guide origin a bit easier (i.e. simplifies the calling code).
@@ -318,7 +313,6 @@ public:
* PS: SnapManager::setup() must have been called before calling this method,
*
* @param p Current position of the point on the guide that is to be snapped; will be overwritten by the position of the snap target if snapping has occurred.
- * @param guide_normal Vector normal to the guide line.
*/
void guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline) const;
diff --git a/src/ui/tool/node.cpp b/src/ui/tool/node.cpp
index d268a9f14..b73566317 100644
--- a/src/ui/tool/node.cpp
+++ b/src/ui/tool/node.cpp
@@ -176,7 +176,6 @@ void Handle::move(Geom::Point const &new_pos)
void Handle::setPosition(Geom::Point const &p)
{
- Geom::Point old_pos = position();
ControlPoint::setPosition(p);
sp_ctrlline_set_coords(SP_CTRLLINE(_handle_line), _parent->position(), position());
@@ -1027,8 +1026,48 @@ void Node::dragged(Geom::Point &new_pos, GdkEventMotion *event)
sm.setupIgnoreSelection(_desktop, true, &unselected);
}
+ // Snap candidate point for free snapping; this will consider snapping tangentially
+ // and perpendicularly and therefore the origin or direction vector must be set
+ Inkscape::SnapCandidatePoint scp_free(new_pos, _snapSourceType());
+
+ boost::optional<Geom::Point> front_point, back_point;
+ Geom::Point origin = _last_drag_origin();
+ Geom::Point dummy_cp;
+ if (_front.isDegenerate()) {
+ if (_is_line_segment(this, _next())) {
+ front_point = _next()->position() - origin;
+ if (_next()->selected()) {
+ dummy_cp = _next()->position() - position();
+ scp_free.addVector(dummy_cp);
+ } else {
+ dummy_cp = _next()->position();
+ scp_free.addOrigin(dummy_cp);
+ }
+ }
+ } else {
+ front_point = _front.relativePos();
+ scp_free.addVector(*front_point);
+ }
+ if (_back.isDegenerate()) {
+ if (_is_line_segment(_prev(), this)) {
+ back_point = _prev()->position() - origin;
+ if (_prev()->selected()) {
+ dummy_cp = _prev()->position() - position();
+ scp_free.addVector(dummy_cp);
+ } else {
+ dummy_cp = _prev()->position();
+ scp_free.addOrigin(dummy_cp);
+ }
+ }
+ } else {
+ back_point = _back.relativePos();
+ scp_free.addVector(*back_point);
+ }
+
if (held_control(*event)) {
- Geom::Point origin = _last_drag_origin();
+ // We're about to consider a constrained snap, which is already limited to 1D
+ // Therefore tangential or perpendicular snapping will not be considered, and therefore
+ // all calls above to scp_free.addVector() and scp_free.addOrigin() can be neglected
std::vector<Inkscape::Snapper::SnapConstraint> constraints;
if (held_alt(*event)) {
// with Ctrl+Alt, constrain to handle lines
@@ -1039,19 +1078,7 @@ void Node::dragged(Geom::Point &new_pos, GdkEventMotion *event)
int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
double min_angle = M_PI / snaps;
- boost::optional<Geom::Point> front_point, back_point, fperp_point, bperp_point;
- if (_front.isDegenerate()) {
- if (_is_line_segment(this, _next()))
- front_point = _next()->position() - origin;
- } else {
- front_point = _front.relativePos();
- }
- if (_back.isDegenerate()) {
- if (_is_line_segment(_prev(), this))
- back_point = _prev()->position() - origin;
- } else {
- back_point = _back.relativePos();
- }
+ boost::optional<Geom::Point> fperp_point, bperp_point;
if (front_point) {
constraints.push_back(Inkscape::Snapper::SnapConstraint(origin, *front_point));
fperp_point = Geom::rot90(*front_point);
@@ -1084,7 +1111,7 @@ void Node::dragged(Geom::Point &new_pos, GdkEventMotion *event)
}
new_pos = sp.getPoint();
} else if (snap) {
- sp = sm.freeSnap(Inkscape::SnapCandidatePoint(new_pos, _snapSourceType()));
+ Inkscape::SnappedPoint sp = sm.freeSnap(scp_free);
new_pos = sp.getPoint();
}
diff --git a/src/ui/tool/node.h b/src/ui/tool/node.h
index f3416ed1c..8c59206b7 100644
--- a/src/ui/tool/node.h
+++ b/src/ui/tool/node.h
@@ -85,7 +85,7 @@ public:
virtual ~Handle();
inline Geom::Point relativePos();
inline double length();
- bool isDegenerate() { return _degenerate; }
+ bool isDegenerate() { return _degenerate; } // True if the handle is retracted, i.e. has zero length.
virtual void setVisible(bool);
virtual void move(Geom::Point const &p);
@@ -117,7 +117,7 @@ private:
Node *_parent; // the handle's lifetime does not extend beyond that of the parent node,
// so a naked pointer is OK and allows setting it during Node's construction
SPCanvasItem *_handle_line;
- bool _degenerate; // this is used often internally so it makes sense to cache this
+ bool _degenerate; // True if the handle is retracted, i.e. has zero length. This is used often internally so it makes sense to cache this
static Geom::Point _saved_other_pos;
static double _saved_length;