diff options
| author | Diederik van Lierop <mail@diedenrezi.nl> | 2012-01-14 13:35:29 +0000 |
|---|---|---|
| committer | Diederik van Lierop <mail@diedenrezi.nl> | 2012-01-14 13:35:29 +0000 |
| commit | 62ab3cc3fe08cee5968581360d1509a6b01ff9f1 (patch) | |
| tree | 74e36cf0869c7fb8cc6ee457c353b09fcb96ead7 | |
| parent | Cleanup variable declarations in ruler, and a couple of GSEAL fixes. (diff) | |
| download | inkscape-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)
| -rw-r--r-- | src/2geom/sbasis-geometric.cpp | 17 | ||||
| -rw-r--r-- | src/2geom/sbasis-geometric.h | 2 | ||||
| -rw-r--r-- | src/desktop-events.cpp | 10 | ||||
| -rw-r--r-- | src/display/snap-indicator.cpp | 6 | ||||
| -rw-r--r-- | src/draw-context.cpp | 11 | ||||
| -rw-r--r-- | src/draw-context.h | 2 | ||||
| -rw-r--r-- | src/object-snapper.cpp | 107 | ||||
| -rw-r--r-- | src/object-snapper.h | 7 | ||||
| -rw-r--r-- | src/snap-candidate.h | 33 | ||||
| -rw-r--r-- | src/snap-enums.h | 6 | ||||
| -rw-r--r-- | src/snap-preferences.cpp | 39 | ||||
| -rw-r--r-- | src/snap.cpp | 17 | ||||
| -rw-r--r-- | src/snap.h | 18 | ||||
| -rw-r--r-- | src/ui/tool/node.cpp | 59 | ||||
| -rw-r--r-- | src/ui/tool/node.h | 4 |
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; |
