void spdc_endpoint_snap_rotation(ToolBase const *const ec, Geom::Point &p, Geom::Point const &o, guint state) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); unsigned const snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12)); SnapManager &m = ec->desktop->namedview->snap_manager; m.setup(ec->desktop); bool snap_enabled = m.snapprefs.getSnapEnabledGlobally(); if (state & GDK_SHIFT_MASK) { // SHIFT disables all snapping, except the angular snapping. After all, the user explicitly asked for angular // snapping by pressing CTRL, otherwise we wouldn't have arrived here. But although we temporarily disable // the snapping here, we must still call for a constrained snap in order to apply the constraints (i.e. round // to the nearest angle increment) m.snapprefs.setSnapEnabledGlobally(false); } Inkscape::SnappedPoint dummy = m.constrainedAngularSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE), boost::optional<Geom::Point>(), o, snaps); p = dummy.getPoint(); if (state & GDK_SHIFT_MASK) { m.snapprefs.setSnapEnabledGlobally(snap_enabled); // restore the original setting } m.unSetup(); }
void SnapManager::constrainedSnapReturnByRef(Geom::Point &p, Inkscape::SnapSourceType const source_type, Inkscape::Snapper::SnapConstraint const &constraint, Geom::OptRect const &bbox_to_snap) const { Inkscape::SnappedPoint const s = constrainedSnap(Inkscape::SnapCandidatePoint(p, source_type), constraint, bbox_to_snap); p = s.getPoint(); // If we didn't snap, then we will return the point projected onto the constraint }
void CurveDragPoint::dragged(Geom::Point &new_pos, GdkEventMotion *event) { NodeList::iterator second = first.next(); // special cancel handling - retract handles when if the segment was degenerate if (_is_drag_cancelled(event) && _segment_was_degenerate) { first->front()->retract(); second->back()->retract(); _pm.update(); return; } if (_drag_initiated && !(event->state & GDK_SHIFT_MASK)) { SnapManager &m = _desktop->namedview->snap_manager; SPItem *path = static_cast<SPItem *>(_pm._path); m.setup(_desktop, true, path); // We will not try to snap to "path" itself Inkscape::SnapCandidatePoint scp(new_pos, Inkscape::SNAPSOURCE_OTHER_HANDLE); Inkscape::SnappedPoint sp = m.freeSnap(scp, Geom::OptRect(), false); new_pos = sp.getPoint(); m.unSetup(); } // Magic Bezier Drag Equations follow! // "weight" describes how the influence of the drag should be distributed // among the handles; 0 = front handle only, 1 = back handle only. double weight, t = _t; if (t <= 1.0 / 6.0) weight = 0; else if (t <= 0.5) weight = (pow((6 * t - 1) / 2.0, 3)) / 2; else if (t <= 5.0 / 6.0) weight = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5; else weight = 1; Geom::Point delta = new_pos - position(); Geom::Point offset0 = ((1-weight)/(3*t*(1-t)*(1-t))) * delta; Geom::Point offset1 = (weight/(3*t*t*(1-t))) * delta; //modified so that, if the trace is bspline, it only acts if the SHIFT key is pressed if(!_pm._isBSpline()){ first->front()->move(first->front()->position() + offset0); second->back()->move(second->back()->position() + offset1); }else if(weight>=0.8){ if(held_shift(*event)){ second->back()->move(new_pos); } else { second->move(second->position() + delta); } }else if(weight<=0.2){ if(held_shift(*event)){ first->back()->move(new_pos); } else { first->move(first->position() + delta); } }else{ first->move(first->position() + delta); second->move(second->position() + delta); } _pm.update(); }
Geom::Point SnapManager::multipleOfGridPitch(Geom::Point const &t, Geom::Point const &origin) { if (!snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally()) return t; if (_desktop && _desktop->gridsEnabled()) { bool success = false; Geom::Point nearest_multiple; Geom::Coord nearest_distance = Geom::infinity(); 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 // this, so when using multiple grids one can get unexpected results // Cannot use getGridSnappers() because we need both the grids AND their snappers // Therefore we iterate through all grids manually for (std::vector<Inkscape::CanvasGrid *>::const_iterator it = _named_view->grids.begin(); it != _named_view->grids.end(); ++it) { Inkscape::CanvasGrid *grid = (*it); const Inkscape::Snapper* snapper = grid->snapper; if (snapper && snapper->ThisSnapperMightSnap()) { // To find the nearest multiple of the grid pitch for a given translation t, we // will use the grid snapper. Simply snapping the value t to the grid will do, but // only if the origin of the grid is at (0,0). If it's not then compensate for this // in the translation t Geom::Point const t_offset = t + grid->origin; IntermSnapResults isr; // Only the first three parameters are being used for grid snappers snapper->freeSnap(isr, 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 bool old_val = _snapindicator; _snapindicator = false; Inkscape::SnappedPoint s = findBestSnap(Inkscape::SnapCandidatePoint(t_offset, Inkscape::SNAPSOURCE_GRID_PITCH), isr, 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() - grid->origin; nearest_distance = s.getSnapDistance(); bestSnappedPoint = s; } } } if (success) { bestSnappedPoint.setPoint(origin + nearest_multiple); _desktop->snapindicator->set_new_snaptarget(bestSnappedPoint); return nearest_multiple; } } return t; }
Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapCandidatePoint const &p, Inkscape::Snapper::SnapConstraint const &constraint, Geom::OptRect const &bbox_to_snap) const { // 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::infinity(), 0, false, true, false); if (!someSnapperMightSnap()) { // Always return point on constraint return no_snap; } Inkscape::SnappedPoint result = no_snap; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if ((prefs->getBool("/options/snapmousepointer/value", false)) && p.isSingleHandle()) { // Snapping the mouse pointer instead of the constrained position of the knot allows // to snap to things which don't intersect with the constraint line; this is basically // then just a freesnap with the constraint applied afterwards // We'll only do this if we're dragging a single handle, and for example not when transforming an object in the selector tool result = freeSnap(p, bbox_to_snap); if (result.getSnapped()) { // only change the snap indicator if we really snapped to something if (_snapindicator && _desktop) { _desktop->snapindicator->set_new_snaptarget(result); } // Apply the constraint result.setPoint(constraint.projection(result.getPoint())); return result; } return no_snap; } IntermSnapResults isr; SnapperList const snappers = getSnappers(); for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); ++i) { (*i)->constrainedSnap(isr, p, bbox_to_snap, constraint, &_items_to_ignore, _unselected_nodes); } result = findBestSnap(p, isr, true); if (result.getSnapped()) { // only change the snap indicator if we really snapped to something if (_snapindicator && _desktop) { _desktop->snapindicator->set_new_snaptarget(result); } return result; } return no_snap; }
void spdc_endpoint_snap_free(ToolBase const * const ec, Geom::Point& p, boost::optional<Geom::Point> &start_of_line, guint const /*state*/) { SPDesktop *dt = ec->desktop; SnapManager &m = dt->namedview->snap_manager; Inkscape::Selection *selection = dt->getSelection(); // selection->singleItem() is the item that is currently being drawn. This item will not be snapped to (to avoid self-snapping) // TODO: Allow snapping to the stationary parts of the item, and only ignore the last segment m.setup(dt, true, selection->singleItem()); 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(); }
Geom::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item, Geom::Point const &pt, Geom::Point const ¢er, int state) { Geom::Point p[2]; bool const shift = state & GDK_SHIFT_MASK; bool const control = state & GDK_CONTROL_MASK; SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop, false, item); Inkscape::SnappedPoint snappoint; if (control) { /* Control is down: we are constrained to producing integer-ratio rectangles */ /* Vector from the centre of the box to the point we are dragging to */ Geom::Point delta = pt - center; /* Round it so that we have an integer-ratio (or golden ratio) box */ if (fabs(delta[Geom::X]) > fabs(delta[Geom::Y]) && (delta[Geom::Y] != 0.0)) { double ratio = delta[Geom::X] / delta[Geom::Y]; double ratioabs = fabs (ratio); double sign = (ratio < 0 ? -1 : 1); if (midpt_1_goldenratio < ratioabs && ratioabs < midpt_goldenratio_2) { delta[Geom::X] = sign * goldenratio * delta[Geom::Y]; } else { delta[Geom::X] = floor(ratio + 0.5) * delta[Geom::Y]; } } else if (delta[Geom::X] != 0.0) { double ratio = delta[Geom::Y] / delta[Geom::X]; double ratioabs = fabs (ratio); double sign = (ratio < 0 ? -1 : 1); if (midpt_1_goldenratio < ratioabs && ratioabs < midpt_goldenratio_2) { delta[Geom::Y] = sign * goldenratio * delta[Geom::X]; } else { delta[Geom::Y] = floor(delta[Geom::Y] / delta[Geom::X] + 0.5) * delta[Geom::X]; } } /* p[1] is the dragged point with the integer-ratio constraint */ p[1] = center + delta; if (shift) { /* Shift is down, so our origin is the centre point rather than the corner ** point; this means that corner-point movements are bound to each other. */ /* p[0] is the opposite corner of our box */ p[0] = center - delta; Inkscape::SnappedPoint s[2]; /* Try to snap p[0] (the opposite corner) along the constraint vector */ s[0] = m.constrainedSnap(Inkscape::SnapCandidatePoint(p[0], Inkscape::SNAPSOURCE_NODE_HANDLE), Inkscape::Snapper::ConstraintLine(p[0] - p[1])); /* Try to snap p[1] (the dragged corner) along the constraint vector */ s[1] = m.constrainedSnap(Inkscape::SnapCandidatePoint(p[1], Inkscape::SNAPSOURCE_NODE_HANDLE), Inkscape::Snapper::ConstraintLine(p[1] - p[0])); /* Choose the best snap and update points accordingly */ if (s[0].getSnapDistance() < s[1].getSnapDistance()) { if (s[0].getSnapped()) { p[0] = s[0].getPoint(); p[1] = 2 * center - s[0].getPoint(); snappoint = s[0]; } } else { if (s[1].getSnapped()) { p[0] = 2 * center - s[1].getPoint(); p[1] = s[1].getPoint(); snappoint = s[1]; } } } else { /* Our origin is the opposite corner. Snap the drag point along the constraint vector */ p[0] = center; snappoint = m.constrainedSnap(Inkscape::SnapCandidatePoint(p[1], Inkscape::SNAPSOURCE_NODE_HANDLE), Inkscape::Snapper::ConstraintLine(p[1] - p[0])); if (snappoint.getSnapped()) { p[1] = snappoint.getPoint(); } } } else if (shift) { /* Shift is down, so our origin is the centre point rather than the corner point; ** this means that corner-point movements are bound to each other. */ p[1] = pt; p[0] = 2 * center - p[1]; Inkscape::SnappedPoint s[2]; s[0] = m.freeSnap(Inkscape::SnapCandidatePoint(p[0], Inkscape::SNAPSOURCE_NODE_HANDLE)); s[1] = m.freeSnap(Inkscape::SnapCandidatePoint(p[1], Inkscape::SNAPSOURCE_NODE_HANDLE)); if (s[0].getSnapDistance() < s[1].getSnapDistance()) { if (s[0].getSnapped()) { p[0] = s[0].getPoint(); p[1] = 2 * center - s[0].getPoint(); snappoint = s[0]; } } else { if (s[1].getSnapped()) { p[0] = 2 * center - s[1].getPoint(); p[1] = s[1].getPoint(); snappoint = s[1]; } } } else { /* There's no constraint on the corner point, so just snap it to anything */ p[0] = center; p[1] = pt; snappoint = m.freeSnap(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_NODE_HANDLE)); if (snappoint.getSnapped()) { p[1] = snappoint.getPoint(); } } if (snappoint.getSnapped()) { desktop->snapindicator->set_new_snaptarget(snappoint); } p[0] *= desktop->dt2doc(); p[1] *= desktop->dt2doc(); return Geom::Rect(Geom::Point(MIN(p[0][Geom::X], p[1][Geom::X]), MIN(p[0][Geom::Y], p[1][Geom::Y])), Geom::Point(MAX(p[0][Geom::X], p[1][Geom::X]), MAX(p[0][Geom::Y], p[1][Geom::Y]))); }
Inkscape::SnappedPoint SnapManager::multipleConstrainedSnaps(Inkscape::SnapCandidatePoint const &p, std::vector<Inkscape::Snapper::SnapConstraint> const &constraints, bool dont_snap, Geom::OptRect const &bbox_to_snap) const { Inkscape::SnappedPoint no_snap = Inkscape::SnappedPoint(p.getPoint(), p.getSourceType(), p.getSourceNum(), Inkscape::SNAPTARGET_CONSTRAINT, Geom::infinity(), 0, false, true, false); if (constraints.size() == 0) { return no_snap; } // We haven't tried to snap yet; we will first determine which constraint is closest to where we are now, // i.e. lets find out which of the constraints yields the closest projection of point p // Project the mouse pointer on each of the constraints std::vector<Geom::Point> projections; for (std::vector<Inkscape::Snapper::SnapConstraint>::const_iterator c = constraints.begin(); c != constraints.end(); ++c) { // Project the mouse pointer onto the constraint; In case we don't snap then we will // return the projection onto the constraint, such that the constraint is always enforced Geom::Point pp = (*c).projection(p.getPoint()); projections.push_back(pp); } // Select the closest constraint no_snap.setPoint(projections.front()); Inkscape::Snapper::SnapConstraint cc = constraints.front(); //closest constraint std::vector<Inkscape::Snapper::SnapConstraint>::const_iterator c = constraints.begin(); std::vector<Geom::Point>::iterator pp = projections.begin(); for (; pp != projections.end(); ++pp) { if (Geom::L2(*pp - p.getPoint()) < Geom::L2(no_snap.getPoint() - p.getPoint())) { no_snap.setPoint(*pp); // Remember the projection onto the closest constraint cc = *c; // Remember the closest constraint itself } ++c; } if (!someSnapperMightSnap() || dont_snap) { return no_snap; } IntermSnapResults isr; SnapperList const snappers = getSnappers(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool snap_mouse = prefs->getBool("/options/snapmousepointer/value", false); Inkscape::SnappedPoint result = no_snap; if (snap_mouse && p.isSingleHandle()) { // Snapping the mouse pointer instead of the constrained position of the knot allows // to snap to things which don't intersect with the constraint line; this is basically // then just a freesnap with the constraint applied afterwards // We'll only to this if we're dragging a single handle, and for example not when transforming an object in the selector tool result = freeSnap(p, bbox_to_snap); // Now apply the constraint afterwards result.setPoint(cc.projection(result.getPoint())); } else { // Try to snap along the closest constraint for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); ++i) { (*i)->constrainedSnap(isr, p, bbox_to_snap, cc, &_items_to_ignore,_unselected_nodes); } result = findBestSnap(p, isr, true); } return result.getSnapped() ? result : no_snap; }
static gint pencil_handle_motion_notify(SPPencilContext *const pc, GdkEventMotion const &mevent) { if ((mevent.state & GDK_CONTROL_MASK) && (mevent.state & GDK_BUTTON1_MASK)) { // mouse was accidentally moved during Ctrl+click; // ignore the motion and create a single point pc->is_drawing = false; return TRUE; } gint ret = FALSE; SPDesktop *const dt = pc->desktop; SPEventContext *event_context = SP_EVENT_CONTEXT(pc); if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) { // allow scrolling return FALSE; } if ( ( mevent.state & GDK_BUTTON1_MASK ) && !pc->grab && pc->is_drawing) { /* Grab mouse, so release will not pass unnoticed */ pc->grab = SP_CANVAS_ITEM(dt->acetate); sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK ), NULL, mevent.time); } /* Find desktop coordinates */ Geom::Point p = dt->w2d(Geom::Point(mevent.x, mevent.y)); /* Test whether we hit any anchor. */ SPDrawAnchor *anchor = spdc_test_inside(pc, Geom::Point(mevent.x, mevent.y)); if (pencil_within_tolerance) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); if ( Geom::LInfty( Geom::Point(mevent.x,mevent.y) - pencil_drag_origin_w ) < tolerance ) { return FALSE; // Do not drag if we're within tolerance from origin. } } // Once the user has moved farther than tolerance from the original location // (indicating they intend to move the object, not click), then always process the // motion notify coordinates as given (no snapping back to origin) pencil_within_tolerance = false; switch (pc->state) { case SP_PENCIL_CONTEXT_ADDLINE: /* Set red endpoint */ if (anchor) { p = anchor->dp; } else { Geom::Point ptnr(p); spdc_endpoint_snap(pc, ptnr, mevent.state); p = ptnr; } spdc_set_endpoint(pc, p); ret = TRUE; break; default: /* We may be idle or already freehand */ if ( mevent.state & GDK_BUTTON1_MASK && pc->is_drawing ) { pc->state = SP_PENCIL_CONTEXT_FREEHAND; if ( !pc->sa && !pc->green_anchor ) { /* Create green anchor */ pc->green_anchor = sp_draw_anchor_new(pc, pc->green_curve, TRUE, pc->p[0]); } /** \todo * fixme: I am not sure whether we want to snap to anchors * in middle of freehand (Lauris) */ SnapManager &m = dt->namedview->snap_manager; if (anchor) { p = anchor->dp; } else if ((mevent.state & GDK_SHIFT_MASK) == 0) { m.setup(dt); Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p); if (s.getSnapped()) { s.getPoint(p); pc->prev_snap_was_succesful = true; } else { pc->prev_snap_was_succesful = false; } } if ( pc->npoints != 0) { // buttonpress may have happened before we entered draw context! if (!(pc->prev_snap_was_succesful && m.snapprefs.getSnapPostponedGlobally())) { // When snapping is enabled but temporarily on hold because the mouse is moving // fast, then we don't want to add nodes off-grid spdc_add_freehand_point(pc, p, mevent.state); ret = TRUE; } } if (anchor && !pc->anchor_statusbar) { pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Release</b> here to close and finish the path.")); pc->anchor_statusbar = true; } else if (!anchor && pc->anchor_statusbar) { pc->_message_context->clear(); pc->anchor_statusbar = false; } else if (!anchor) { pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Drawing a freehand path")); } } else { if (anchor && !pc->anchor_statusbar) { pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Drag</b> to continue the path from this point.")); pc->anchor_statusbar = true; } else if (!anchor && pc->anchor_statusbar) { pc->_message_context->clear(); pc->anchor_statusbar = false; } } break; } return ret; }
static gint pencil_handle_button_press(SPPencilContext *const pc, GdkEventButton const &bevent) { gint ret = FALSE; SPEventContext *event_context = SP_EVENT_CONTEXT(pc); if ( bevent.button == 1 && !event_context->space_panning) { SPDrawContext *dc = SP_DRAW_CONTEXT (pc); SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(dc); Inkscape::Selection *selection = sp_desktop_selection(desktop); if (Inkscape::have_viable_layer(desktop, dc->_message_context) == false) { return TRUE; } Geom::Point const button_w(bevent.x, bevent.y); /* Find desktop coordinates */ Geom::Point p = pc->desktop->w2d(button_w); /* Test whether we hit any anchor. */ SPDrawAnchor *anchor = spdc_test_inside(pc, button_w); pencil_drag_origin_w = Geom::Point(bevent.x,bevent.y); pencil_within_tolerance = true; switch (pc->state) { case SP_PENCIL_CONTEXT_ADDLINE: /* Current segment will be finished with release */ ret = TRUE; break; default: /* Set first point of sequence */ if (bevent.state & GDK_CONTROL_MASK) { spdc_create_single_dot(event_context, p, "/tools/freehand/pencil", bevent.state); ret = true; break; } if (anchor) { p = anchor->dp; desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path")); } else { if (!(bevent.state & GDK_SHIFT_MASK)) { // This is the first click of a new curve; deselect item so that // this curve is not combined with it (unless it is drawn from its // anchor, which is handled by the sibling branch above) selection->clear(); desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path")); SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop); Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p); if (s.getSnapped()) { s.getPoint(p); pc->prev_snap_was_succesful = true; } else { pc->prev_snap_was_succesful = false; } } else if (selection->singleItem() && SP_IS_PATH(selection->singleItem())) { desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path")); } } pc->sa = anchor; spdc_set_startpoint(pc, p); ret = TRUE; break; } pc->is_drawing = true; } return ret; }