X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fpen-context.cpp;h=607bdaedc2fc9df9279e87d201b3f5c1d077ca78;hb=0dc33d4ce43e0bb49c63aa53b826ec4a1ff68e28;hp=eb399d191b43dfc1105bf630f9c21820b3946a53;hpb=e9b6af083e34e2397a8ddbe9781920733d09d151;p=inkscape.git diff --git a/src/pen-context.cpp b/src/pen-context.cpp index eb399d191..607bdaedc 100644 --- a/src/pen-context.cpp +++ b/src/pen-context.cpp @@ -31,6 +31,7 @@ #include "message-context.h" #include "preferences.h" #include "sp-path.h" +#include "display/sp-canvas.h" #include "display/curve.h" #include "pixmaps/cursor-pen.xpm" #include "display/canvas-bpath.h" @@ -76,8 +77,7 @@ static bool pen_within_tolerance = false; static SPDrawContextClass *pen_parent_class; static int pen_next_paraxial_direction(const SPPenContext *const pc, Geom::Point const &pt, Geom::Point const &origin, guint state); -static void pen_set_to_nearest_horiz_vert(const SPPenContext *const pc, Geom::Point &pt, guint const state); -static Geom::Point pen_get_intermediate_horiz_vert(const SPPenContext *const pc, Geom::Point const &pt, guint const state); +static void pen_set_to_nearest_horiz_vert(const SPPenContext *const pc, Geom::Point &pt, guint const state, bool snap); static int pen_last_paraxial_dir = 0; // last used direction in horizontal/vertical mode; 0 = horizontal, 1 = vertical @@ -148,7 +148,7 @@ sp_pen_context_init(SPPenContext *pc) pc->c1 = NULL; pc->cl0 = NULL; pc->cl1 = NULL; - + pc->events_disabled = 0; pc->num_clicks = 0; @@ -241,7 +241,7 @@ sp_pen_context_setup(SPEventContext *ec) } static void -pen_cancel (SPPenContext *const pc) +pen_cancel (SPPenContext *const pc) { pc->num_clicks = 0; pc->state = SP_PEN_CONTEXT_STOP; @@ -264,6 +264,8 @@ sp_pen_context_finish(SPEventContext *ec) { SPPenContext *pc = SP_PEN_CONTEXT(ec); + sp_event_context_discard_delayed_snap_event(ec); + if (pc->npoints != 0) { pen_cancel (pc); } @@ -297,21 +299,22 @@ sp_pen_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val) static void spdc_endpoint_snap(SPPenContext const *const pc, Geom::Point &p, guint const state) { - if ((state & GDK_CONTROL_MASK)) { //CTRL enables angular snapping + if ((state & GDK_CONTROL_MASK) && !pc->polylines_paraxial) { //CTRL enables angular snapping if (pc->npoints > 0) { spdc_endpoint_snap_rotation(pc, p, pc->p[0], state); } } else { - if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above - //After all, the user explicitely asked for angular snapping by - //pressing CTRL + // We cannot use shift here to disable snapping because the shift-key is already used + // to toggle the paraxial direction; if the user wants to disable snapping (s)he will + // have to use the %-key, the menu, or the snap toolbar + if ((pc->npoints > 0) && pc->polylines_paraxial) { + // snap constrained + pen_set_to_nearest_horiz_vert(pc, p, state, true); + } else { + // snap freely spdc_endpoint_snap_free(pc, p, state); } } - if (pc->polylines_paraxial) { - // TODO: must we avoid one of the snaps in the previous case distinction in some situations? - pen_set_to_nearest_horiz_vert(pc, p, state); - } } /** @@ -332,7 +335,7 @@ spdc_endpoint_snap_handle(SPPenContext const *const pc, Geom::Point &p, guint co } } -static gint +static gint sp_pen_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event) { SPPenContext *const pc = SP_PEN_CONTEXT(ec); @@ -417,7 +420,7 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const SPDrawContext * const dc = SP_DRAW_CONTEXT(pc); SPDesktop * const desktop = SP_EVENT_CONTEXT_DESKTOP(dc); Geom::Point const event_w(bevent.x, bevent.y); - Geom::Point const event_dt(desktop->w2d(event_w)); + Geom::Point event_dt(desktop->w2d(event_w)); SPEventContext *event_context = SP_EVENT_CONTEXT(pc); gint ret = FALSE; @@ -453,7 +456,7 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const case SP_PEN_CONTEXT_CLOSE: break; case SP_PEN_CONTEXT_STOP: - /* This is allowed, if we just cancelled curve */ + /* This is allowed, if we just canceled curve */ pc->state = SP_PEN_CONTEXT_POINT; break; default: @@ -463,14 +466,22 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const case SP_PEN_CONTEXT_MODE_DRAG: switch (pc->state) { case SP_PEN_CONTEXT_STOP: - /* This is allowed, if we just cancelled curve */ + /* This is allowed, if we just canceled curve */ case SP_PEN_CONTEXT_POINT: if (pc->npoints == 0) { - if (bevent.state & GDK_CONTROL_MASK) { - spdc_create_single_dot(event_context, event_dt, "/tools/freehand/pen", bevent.state); - ret = TRUE; - break; + Geom::Point p; + if ((bevent.state & GDK_CONTROL_MASK) && (pc->polylines_only || pc->polylines_paraxial)) { + p = event_dt; + if (!(bevent.state & GDK_SHIFT_MASK)) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + } + spdc_create_single_dot(event_context, p, "/tools/freehand/pen", bevent.state); + ret = TRUE; + break; } // TODO: Perhaps it would be nicer to rearrange the following case @@ -478,7 +489,6 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const /* Set start anchor */ pc->sa = anchor; - Geom::Point p; if (anchor && !sp_pen_context_has_waiting_LPE(pc)) { /* Adjust point to anchor if needed; if we have a waiting LPE, we need a fresh path to be created so don't continue an existing one */ @@ -500,11 +510,7 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const /* Create green anchor */ p = event_dt; - if (!pc->polylines_paraxial) { - // only snap the starting point if we're not in horizontal/vertical mode - // because otherwise it gets shifted; TODO: why do we snap here at all?? - spdc_endpoint_snap(pc, p, bevent.state); - } + spdc_endpoint_snap(pc, p, bevent.state); pc->green_anchor = sp_draw_anchor_new(pc, pc->green_curve, TRUE, p); } spdc_pen_set_initial_point(pc, p); @@ -531,9 +537,6 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const p = event_dt; spdc_endpoint_snap(pc, p, bevent.state); /* Snap node only if not hitting anchor. */ spdc_pen_set_subsequent_point(pc, p, true); - if (pc->polylines_only) { - spdc_pen_finish_segment(pc, p, bevent.state); - } } } @@ -553,20 +556,22 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const default: break; } - } else if (bevent.button == 3 || pc->expecting_clicks_for_LPE == 1) { // when the last click for a waiting LPE occurs we want to finish the path - if (pc->npoints != 0) { - - spdc_pen_finish_segment(pc, event_dt, bevent.state); - if (pc->green_closed) { - // finishing at the start anchor, close curve - spdc_pen_finish(pc, TRUE); - } else { - // finishing at some other anchor, finish curve but not close - spdc_pen_finish(pc, FALSE); - } - - ret = TRUE; + } else if (pc->expecting_clicks_for_LPE == 1 && pc->npoints != 0) { + // when the last click for a waiting LPE occurs we want to finish the path + spdc_pen_finish_segment(pc, event_dt, bevent.state); + if (pc->green_closed) { + // finishing at the start anchor, close curve + spdc_pen_finish(pc, TRUE); + } else { + // finishing at some other anchor, finish curve but not close + spdc_pen_finish(pc, FALSE); } + + ret = TRUE; + } else if (bevent.button == 3 && pc->npoints != 0) { + // right click - finish path + spdc_pen_finish(pc, FALSE); + ret = TRUE; } if (pc->expecting_clicks_for_LPE > 0) { @@ -591,7 +596,7 @@ pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent) // allow scrolling return FALSE; } - + if (pc->events_disabled) { // skip motion events if pen events are disabled return FALSE; @@ -602,7 +607,7 @@ pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent) if (pen_within_tolerance) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - if ( NR::LInfty( event_w - pen_drag_origin_w ) < tolerance ) { + if ( Geom::LInfty( event_w - pen_drag_origin_w ) < tolerance ) { return FALSE; // Do not drag if we're within tolerance from origin. } } @@ -626,6 +631,11 @@ pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent) spdc_endpoint_snap(pc, p, mevent.state); spdc_pen_set_subsequent_point(pc, p, true); ret = TRUE; + } else if (!sp_event_context_knot_mouseover(pc)) { + SnapManager &m = dt->namedview->snap_manager; + m.setup(dt); + m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); } break; case SP_PEN_CONTEXT_CONTROL: @@ -650,10 +660,11 @@ pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent) if (!anchor) { /* Snap node only if not hitting anchor */ spdc_endpoint_snap(pc, p, mevent.state); + spdc_pen_set_subsequent_point(pc, p, true, mevent.state); + } else { + spdc_pen_set_subsequent_point(pc, anchor->dp, false, mevent.state); } - spdc_pen_set_subsequent_point(pc, p, !anchor, mevent.state); - if (anchor && !pc->anchor_statusbar) { pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Click or click and drag to close and finish the path.")); pc->anchor_statusbar = true; @@ -671,6 +682,12 @@ pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent) pc->_message_context->clear(); pc->anchor_statusbar = false; } + if (!sp_event_context_knot_mouseover(pc)) { + SnapManager &m = dt->namedview->snap_manager; + m.setup(dt); + m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); + } } break; case SP_PEN_CONTEXT_CONTROL: @@ -692,6 +709,12 @@ pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent) /* This is perfectly valid */ break; default: + if (!sp_event_context_knot_mouseover(pc)) { + SnapManager &m = dt->namedview->snap_manager; + m.setup(dt); + m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); + } break; } break; @@ -765,7 +788,7 @@ pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent) ret = TRUE; break; case SP_PEN_CONTEXT_STOP: - /* This is allowed, if we just cancelled curve */ + /* This is allowed, if we just canceled curve */ pc->state = SP_PEN_CONTEXT_POINT; ret = TRUE; break; @@ -777,10 +800,8 @@ pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent) switch (pc->state) { case SP_PEN_CONTEXT_POINT: case SP_PEN_CONTEXT_CONTROL: - if (!pc->polylines_only) { - spdc_endpoint_snap(pc, p, revent.state); - spdc_pen_finish_segment(pc, p, revent.state); - } + spdc_endpoint_snap(pc, p, revent.state); + spdc_pen_finish_segment(pc, p, revent.state); break; case SP_PEN_CONTEXT_CLOSE: spdc_endpoint_snap(pc, p, revent.state); @@ -843,7 +864,8 @@ static gint pen_handle_2button_press(SPPenContext *const pc, GdkEventButton const &bevent) { gint ret = FALSE; - if (pc->npoints != 0 && bevent.button != 2) { + // only end on LMB double click. Otherwise horizontal scrolling causes ending of the path + if (pc->npoints != 0 && bevent.button == 1) { spdc_pen_finish(pc, FALSE); ret = TRUE; } @@ -964,6 +986,7 @@ pen_lastpoint_toline (SPPenContext *const pc) static gint pen_handle_key_press(SPPenContext *const pc, GdkEvent *event) { + gint ret = FALSE; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); gdouble const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000); // in px @@ -1031,6 +1054,7 @@ pen_handle_key_press(SPPenContext *const pc, GdkEvent *event) } break; +/* TODO: this is not yet enabled?? looks like some traces of the Geometry tool case GDK_P: case GDK_p: if (MOD__SHIFT_ONLY) { @@ -1062,6 +1086,7 @@ pen_handle_key_press(SPPenContext *const pc, GdkEvent *event) ret = TRUE; } break; +*/ case GDK_U: case GDK_u: @@ -1110,7 +1135,7 @@ pen_handle_key_press(SPPenContext *const pc, GdkEvent *event) case GDK_BackSpace: case GDK_Delete: case GDK_KP_Delete: - if (pc->green_curve->is_empty()) { + if ( pc->green_curve->is_empty() || (pc->green_curve->last_segment() == NULL) ) { if (!pc->red_curve->is_empty()) { pen_cancel (pc); ret = TRUE; @@ -1132,8 +1157,7 @@ pen_handle_key_press(SPPenContext *const pc, GdkEvent *event) break; } // The code below assumes that pc->green_curve has only ONE path ! - Geom::Path const & path = pc->green_curve->get_pathvector().back(); - Geom::Curve const * crv = &path.back_default(); + Geom::Curve const * crv = pc->green_curve->last_segment(); pc->p[0] = crv->initialPoint(); if ( Geom::CubicBezier const * cubic = dynamic_cast(crv)) { pc->p[1] = (*cubic)[1]; @@ -1151,6 +1175,7 @@ pen_handle_key_press(SPPenContext *const pc, GdkEvent *event) sp_canvas_item_hide(pc->cl1); pc->state = SP_PEN_CONTEXT_POINT; spdc_pen_set_subsequent_point(pc, pt, true); + pen_last_paraxial_dir = !pen_last_paraxial_dir; ret = TRUE; } break; @@ -1202,7 +1227,7 @@ spdc_pen_set_initial_point(SPPenContext *const pc, Geom::Point const p) * Show the status message for the current line/curve segment. * This type of message always shows angle/distance as the last * two parameters ("angle %3.2f°, distance %s"). - */ + */ static void spdc_pen_set_angle_distance_status_message(SPPenContext *const pc, Geom::Point const p, int pc_point_to_compare, gchar const *message) { @@ -1212,8 +1237,8 @@ spdc_pen_set_angle_distance_status_message(SPPenContext *const pc, Geom::Point c SPDesktop *desktop = SP_EVENT_CONTEXT(pc)->desktop; Geom::Point rel = p - pc->p[pc_point_to_compare]; - GString *dist = SP_PX_TO_METRIC_STRING(NR::L2(rel), desktop->namedview->getDefaultMetric()); - double angle = atan2(rel[NR::Y], rel[NR::X]) * 180 / M_PI; + GString *dist = SP_PX_TO_METRIC_STRING(Geom::L2(rel), desktop->namedview->getDefaultMetric()); + double angle = atan2(rel[Geom::Y], rel[Geom::X]) * 180 / M_PI; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (prefs->getBool("/options/compassangledisplay/value", 0) != 0) angle = angle_to_compass (angle); @@ -1236,16 +1261,20 @@ spdc_pen_set_subsequent_point(SPPenContext *const pc, Geom::Point const p, bool bool is_curve; pc->red_curve->moveto(pc->p[0]); if (pc->polylines_paraxial && !statusbar) { - // we are drawing horizontal/vertical lines and hit an anchor; draw an L-shaped path - Geom::Point intermed = p; - pen_set_to_nearest_horiz_vert(pc, intermed, status); - pc->red_curve->lineto(intermed); + // we are drawing horizontal/vertical lines and hit an anchor; + Geom::Point const origin = pc->p[0]; + // if the previous point and the anchor are not aligned either horizontally or vertically... + if ((abs(p[Geom::X] - origin[Geom::X]) > 1e-9) && (abs(p[Geom::Y] - origin[Geom::Y]) > 1e-9)) { + // ...then we should draw an L-shaped path, consisting of two paraxial segments + Geom::Point intermed = p; + pen_set_to_nearest_horiz_vert(pc, intermed, status, false); + pc->red_curve->lineto(intermed); + } pc->red_curve->lineto(p); is_curve = false; } else { // one of the 'regular' modes - if (pc->p[1] != pc->p[0]) - { + if (pc->p[1] != pc->p[0]) { pc->red_curve->curveto(pc->p[1], p, p); is_curve = true; } else { @@ -1313,6 +1342,7 @@ spdc_pen_finish_segment(SPPenContext *const pc, Geom::Point const p, guint const if (pc->polylines_paraxial) { pen_last_paraxial_dir = pen_next_paraxial_direction(pc, p, pc->p[0], state); } + ++pc->num_clicks; if (!pc->red_curve->is_empty()) { @@ -1344,7 +1374,7 @@ spdc_pen_finish(SPPenContext *const pc, gboolean const closed) pc->num_clicks = 0; pen_disable_events(pc); - + SPDesktop *const desktop = pc->desktop; pc->_message_context->clear(); desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Drawing finished")); @@ -1380,7 +1410,7 @@ pen_disable_events(SPPenContext *const pc) { static void pen_enable_events(SPPenContext *const pc) { g_return_if_fail(pc->events_disabled != 0); - + pc->events_disabled--; } @@ -1412,10 +1442,14 @@ static int pen_next_paraxial_direction(const SPPenContext *const pc, * horizontal or vertical segment; for all subsequent mouse clicks, we use the direction * orthogonal to the last one; pressing Shift toggles the direction */ - if (pc->num_clicks == 0) { + // num_clicks is not reliable because spdc_pen_finish_segment is sometimes called too early + // (on first mouse release), in which case num_clicks immediately becomes 1. + // if (pc->num_clicks == 0) { + + if (pc->green_curve->is_empty()) { // first mouse click - double dist_h = fabs(pt[NR::X] - origin[NR::X]); - double dist_v = fabs(pt[NR::Y] - origin[NR::Y]); + double dist_h = fabs(pt[Geom::X] - origin[Geom::X]); + double dist_v = fabs(pt[Geom::Y] - origin[Geom::Y]); int ret = (dist_h < dist_v) ? 1 : 0; // 0 = horizontal, 1 = vertical pen_last_paraxial_dir = (state & GDK_SHIFT_MASK) ? 1 - ret : ret; return pen_last_paraxial_dir; @@ -1425,29 +1459,34 @@ static int pen_next_paraxial_direction(const SPPenContext *const pc, } } -void pen_set_to_nearest_horiz_vert(const SPPenContext *const pc, Geom::Point &pt, guint const state) +void pen_set_to_nearest_horiz_vert(const SPPenContext *const pc, Geom::Point &pt, guint const state, bool snap) { - Geom::Point const &origin = pc->p[0]; + Geom::Point const origin = pc->p[0]; int next_dir = pen_next_paraxial_direction(pc, pt, origin, state); - if (next_dir == 0) { - // line is forced to be horizontal - pt[NR::Y] = origin[NR::Y]; + if (!snap) { + if (next_dir == 0) { + // line is forced to be horizontal + pt[Geom::Y] = origin[Geom::Y]; + } else { + // line is forced to be vertical + pt[Geom::X] = origin[Geom::X]; + } } else { - // line is forced to be vertical - pt[NR::X] = origin[NR::X]; - } -} + // Create a horizontal or vertical constraint line + Inkscape::Snapper::SnapConstraint cl(origin, next_dir ? Geom::Point(0, 1) : Geom::Point(1, 0)); -Geom::Point pen_get_intermediate_horiz_vert(const SPPenContext *const pc, Geom::Point const &pt) -{ - Geom::Point const &origin = pc->p[0]; + // Snap along the constraint line; if we didn't snap then still the constraint will be applied + SnapManager &m = pc->desktop->namedview->snap_manager; - if (pen_last_paraxial_dir == 0) { - return Geom::Point (origin[NR::X], pt[NR::Y]); - } else { - return Geom::Point (pt[NR::X], origin[NR::Y]); + Inkscape::Selection *selection = sp_desktop_selection (pc->desktop); + // 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(pc->desktop, true, selection->singleItem()); + m.constrainedSnapReturnByRef(pt, Inkscape::SNAPSOURCE_NODE_HANDLE, cl); + m.unSetup(); } } @@ -1460,4 +1499,4 @@ Geom::Point pen_get_intermediate_horiz_vert(const SPPenContext *const pc, Geom:: fill-column:99 End: */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :