snap.cpp revision 17e8c293a1792714f56eba5547874234e9ea0dea
851N/A#define __SP_DESKTOP_SNAP_C__
550N/A#include "sp-namedview.h"
550N/A#include "snapped-line.h"
550N/A#include "snapped-curve.h"
550N/A#include "display/canvas-grid.h"
851N/A#include "display/snap-indicator.h"
550N/A#include "inkscape.h"
550N/A#include "sp-guide.h"
550N/A#include "preferences.h"
550N/A#include "event-context.h"
guide(this, 0),
object(this, 0),
snapprefs(),
_named_view(v)
SnapperList s;
//FIXME: this code should actually do this: add new grid snappers that are active for this desktop. now it just adds all gridsnappers
return (i != s.end());
return (i != s.end());
* \param points_to_snap The whole bunch of points, all from the same selection and having the same transformation
bool first_point,
s.getPoint(p);
* \param points_to_snap The whole bunch of points, all from the same selection and having the same transformation
bool first_point,
g_warning("The current tool tries to snap, but it hasn't yet opened the snap window. Please report this!");
// When the context goes into dragging-mode, then Inkscape should call this: sp_event_context_snap_window_open(event_context);
//std::cout << "SnapManager::freeSnap -> postponed: " << snapprefs.getSnapPostponedGlobally() << std::endl;
if (!someSnapperMightSnap()) {
return Inkscape::SnappedPoint(p, source_type, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false);
(*i)->freeSnap(sc, point_type, p, source_type, first_point, bbox_to_snap, items_to_ignore, _unselected_nodes);
if (_item_to_ignore) {
delete items_to_ignore;
// aligned to the grid at the time of copying, so we don't know which nodes to snap. If we'd snap an
// Instead we will make sure that the offset between the source and the copy is a multiple of the grid
if (!snapprefs.getSnapEnabledGlobally()) // No need to check for snapprefs.getSnapPostponedGlobally() here
//FIXME: this code should actually do this: add new grid snappers that are active for this desktop. now it just adds all gridsnappers
bool success = false;
snapper->freeSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_NODE, t_offset, Inkscape::SNAPSOURCE_UNDEFINED, TRUE, Geom::OptRect(), NULL, NULL);
success = true;
if (success)
return nearest_multiple;
* \param points_to_snap The whole bunch of points, all from the same selection and having the same transformation
bool const snap_projection,
bool first_point,
Inkscape::SnappedPoint const s = constrainedSnap(point_type, p, source_type, constraint, snap_projection, first_point, bbox_to_snap);
s.getPoint(p);
* \param points_to_snap The whole bunch of points, all from the same selection and having the same transformation
Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::PointType point_type,
bool first_point,
g_warning("The current tool tries to snap, but it hasn't yet opened the snap window. Please report this!");
// When the context goes into dragging-mode, then Inkscape should call this: sp_event_context_snap_window_open(event_context);
if (!someSnapperMightSnap()) {
return Inkscape::SnappedPoint(p, source_type, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false);
(*i)->constrainedSnap(sc, point_type, pp, source_type, first_point, bbox_to_snap, constraint, items_to_ignore);
if (_item_to_ignore) {
delete items_to_ignore;
// This method is used to snap a guide to nodes or to other guides, while dragging the guide around. Will not snap to grids!
g_warning("The current tool tries to snap, but it hasn't yet opened the snap window. Please report this!");
// When the context goes into dragging-mode, then Inkscape should call this: sp_event_context_snap_window_open(event_context);
guide.freeSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, Inkscape::SNAPSOURCE_GUIDE, true, Geom::OptRect(), NULL, NULL);
s.getPoint(p);
// This method is used to snap a guide to paths or to other guides, while dragging the origin of the guide around. Will not snap to grids!
g_warning("The current tool tries to snap, but it hasn't yet opened the snap window. Please report this!");
// When the context goes into dragging-mode, then Inkscape should call this: sp_event_context_snap_window_open(event_context);
Inkscape::Snapper::ConstraintLine cl(guideline.point_on_line, Geom::rot90(guideline.normal_to_line));
object.constrainedSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, Inkscape::SNAPSOURCE_GUIDE_ORIGIN, true, Geom::OptRect(), cl, NULL);
guide.constrainedSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, Inkscape::SNAPSOURCE_GUIDE_ORIGIN, true, Geom::OptRect(), cl, NULL);
s.getPoint(p);
* \param pointer Location of the mouse pointer, at the time when dragging started (i.e. "untransformed")
* \param uniform true if the transformation should be uniform; only applicable for stretching and scaling.
bool constrained,
bool uniform) const
if (someSnapperMightSnap() == false) {
for (std::vector<std::pair<Geom::Point, int> >::const_iterator i = points.begin(); i != points.end(); i++) {
Geom::Point transformed = _transformPoint(*i, transformation_type, transformation, origin, dim, uniform);
for (std::vector<std::pair<Geom::Point, int> >::const_iterator i = points.begin(); i != points.end(); i++) {
if (constrained) {
snapped_point = constrainedSnap(type, (*j).first, static_cast<Inkscape::SnapSourceType>((*j).second), dedicated_constraint, false, i == points.begin(), bbox);
snapped_point = constrainedSnap(type, (*j).first, static_cast<Inkscape::SnapSourceType>((*j).second), dedicated_constraint, false, i == points.begin(), bbox);
snapped_point = freeSnap(type, (*j).first, static_cast<Inkscape::SnapSourceType>((*j).second), i == points.begin(), bbox);
switch (transformation_type) {
case TRANSLATION:
case SCALE:
if (fabs(fabs(a[index]/b[index]) - fabs(transformation[index])) > 1e-12) { // if SNAPPING DID occur in this direction
case STRETCH:
case SKEW:
result[0] = (snapped_point.getPoint()[dim] - ((*i).first)[dim]) / (((*i).first)[1 - dim] - origin[1 - dim]); // skew factor
// When scaling, we're considering the best transformation in each direction separately. We will have a metric in each
// direction, whereas for all other transformation we only a single one-dimensional metric. That's why we need to handle
if (uniform) {
return best_snapped_point;
Inkscape::SnappedPoint SnapManager::freeSnapTranslation(Inkscape::SnapPreferences::PointType point_type,
_displaySnapsource(point_type, std::make_pair(_transformPoint(p.at(0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false), (p.at(0)).second));
return _snapTransformed(point_type, p, pointer, false, Geom::Point(0,0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false);
Inkscape::SnappedPoint SnapManager::constrainedSnapTranslation(Inkscape::SnapPreferences::PointType point_type,
_displaySnapsource(point_type, std::make_pair(_transformPoint(p.at(0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false), (p.at(0)).second));
return _snapTransformed(point_type, p, pointer, true, constraint, TRANSLATION, tr, Geom::Point(0,0), Geom::X, false);
_displaySnapsource(point_type, std::make_pair(_transformPoint(p.at(0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, false), (p.at(0)).second));
return _snapTransformed(point_type, p, pointer, false, Geom::Point(0,0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, false);
Inkscape::SnappedPoint SnapManager::constrainedSnapScale(Inkscape::SnapPreferences::PointType point_type,
_displaySnapsource(point_type, std::make_pair(_transformPoint(p.at(0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, true), (p.at(0)).second));
return _snapTransformed(point_type, p, pointer, true, Geom::Point(0,0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, true);
Inkscape::SnappedPoint SnapManager::constrainedSnapStretch(Inkscape::SnapPreferences::PointType point_type,
_displaySnapsource(point_type, std::make_pair(_transformPoint(p.at(0), STRETCH, Geom::Point(s, s), o, d, u), (p.at(0)).second));
return _snapTransformed(point_type, p, pointer, true, Geom::Point(0,0), STRETCH, Geom::Point(s, s), o, d, u);
Inkscape::SnappedPoint SnapManager::constrainedSnapSkew(Inkscape::SnapPreferences::PointType point_type,
// Snapping the nodes of the boundingbox of a selection that is being transformed, will only work if
// the transformation of the bounding box is equal to the transformation of the individual nodes. This is
// NOT the case for example when rotating or skewing. The bounding box itself cannot possibly rotate or skew,
// so it's corners have a different transformation. The snappers cannot handle this, therefore snapping
_displaySnapsource(point_type, std::make_pair(_transformPoint(p.at(0), SKEW, s, o, d, false), (p.at(0)).second));
Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, Inkscape::SnapSourceType const source_type, SnappedConstraints &sc, bool constrained) const
std::cout << " Points : " << sc.points.size() << std::endl;
std::cout << " Lines : " << sc.lines.size() << std::endl;
std::cout << " Grid lines : " << sc.grid_lines.size()<< std::endl;
std::cout << " Guide lines : " << sc.guide_lines.size()<< std::endl;
std::cout << " Curves : " << sc.curves.size()<< std::endl;
// Therefore we will try get fully constrained by finding an intersection with another grid/guide/path
// When doing a constrained snap however, we're already at an intersection of the constrained line and
if (!constrained) {
Inkscape::SnappedPoint bestSnappedPoint = Inkscape::SnappedPoint(p, Inkscape::SNAPSOURCE_UNDEFINED, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false);
for (std::list<Inkscape::SnappedPoint>::const_iterator i = sp_list.begin(); i != sp_list.end(); i++) {
bestSnappedPoint = *i;
if (_snapindicator) {
// std::cout << "findBestSnap = " << bestSnappedPoint.getPoint() << " | dist = " << bestSnappedPoint.getSnapDistance() << std::endl;
return bestSnappedPoint;
bool snapindicator,
bool snapindicator,
bool const uniform) const
switch (transformation_type) {
case TRANSLATION:
case SCALE:
transformed = (p.first - origin) * Geom::Scale(transformation[Geom::X], transformation[Geom::Y]) + origin;
case STRETCH:
if (uniform)
case SKEW:
// While skewing, mirroring and scaling (by integer multiples) in the opposite direction is also allowed.
return transformed;
void SnapManager::_displaySnapsource(Inkscape::SnapPreferences::PointType point_type, std::pair<Geom::Point, int> const &p) const {
if (snapprefs.getSnapEnabledGlobally() && ((p_is_a_node && snapprefs.getSnapModeNode()) || (p_is_a_bbox && snapprefs.getSnapModeBBox()))) {