X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fpen-context.cpp;h=607bdaedc2fc9df9279e87d201b3f5c1d077ca78;hb=0dc33d4ce43e0bb49c63aa53b826ec4a1ff68e28;hp=ab933fbbf7aea7024fee6a4c1b76735e91451e9a;hpb=50656054cfff930f05d3ea734e56b8c31a99fcc3;p=inkscape.git diff --git a/src/pen-context.cpp b/src/pen-context.cpp index ab933fbbf..607bdaedc 100644 --- a/src/pen-context.cpp +++ b/src/pen-context.cpp @@ -16,6 +16,8 @@ */ #include +#include +#include #include "pen-context.h" #include "sp-namedview.h" @@ -23,23 +25,24 @@ #include "desktop.h" #include "desktop-handles.h" #include "selection.h" +#include "selection-chemistry.h" #include "draw-anchor.h" #include "message-stack.h" #include "message-context.h" -#include "prefs-utils.h" +#include "preferences.h" #include "sp-path.h" - +#include "display/sp-canvas.h" +#include "display/curve.h" #include "pixmaps/cursor-pen.xpm" -#include "pixmaps/cursor-pen.pixbuf" #include "display/canvas-bpath.h" #include "display/sp-ctrlline.h" #include "display/sodipodi-ctrl.h" #include -#include "libnr/n-art-bpath.h" +#include "libnr/nr-point-ops.h" #include "helper/units.h" #include "macros.h" #include "context-fns.h" - +#include "tools-switch.h" static void sp_pen_context_class_init(SPPenContextClass *klass); static void sp_pen_context_init(SPPenContext *pc); @@ -47,31 +50,37 @@ static void sp_pen_context_dispose(GObject *object); static void sp_pen_context_setup(SPEventContext *ec); static void sp_pen_context_finish(SPEventContext *ec); -static void sp_pen_context_set(SPEventContext *ec, gchar const *key, gchar const *val); +static void sp_pen_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val); static gint sp_pen_context_root_handler(SPEventContext *ec, GdkEvent *event); +static gint sp_pen_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event); -static void spdc_pen_set_initial_point(SPPenContext *pc, NR::Point const p); -static void spdc_pen_set_subsequent_point(SPPenContext *pc, NR::Point const p, bool statusbar); -static void spdc_pen_set_ctrl(SPPenContext *pc, NR::Point const p, guint state); -static void spdc_pen_finish_segment(SPPenContext *pc, NR::Point p, guint state); +static void spdc_pen_set_initial_point(SPPenContext *pc, Geom::Point const p); +static void spdc_pen_set_subsequent_point(SPPenContext *const pc, Geom::Point const p, bool statusbar, guint status = 0); +static void spdc_pen_set_ctrl(SPPenContext *pc, Geom::Point const p, guint state); +static void spdc_pen_finish_segment(SPPenContext *pc, Geom::Point p, guint state); static void spdc_pen_finish(SPPenContext *pc, gboolean closed); static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const &bevent); static gint pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent); static gint pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent); -static gint pen_handle_2button_press(SPPenContext *const pc); +static gint pen_handle_2button_press(SPPenContext *const pc, GdkEventButton const &bevent); static gint pen_handle_key_press(SPPenContext *const pc, GdkEvent *event); static void spdc_reset_colors(SPPenContext *pc); static void pen_disable_events(SPPenContext *const pc); static void pen_enable_events(SPPenContext *const pc); -static NR::Point pen_drag_origin_w(0, 0); +static Geom::Point pen_drag_origin_w(0, 0); 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, bool snap); + +static int pen_last_paraxial_dir = 0; // last used direction in horizontal/vertical mode; 0 = horizontal, 1 = vertical + /** * Register SPPenContext with Gdk and return its type. */ @@ -115,6 +124,7 @@ sp_pen_context_class_init(SPPenContextClass *klass) event_context_class->finish = sp_pen_context_finish; event_context_class->set = sp_pen_context_set; event_context_class->root_handler = sp_pen_context_root_handler; + event_context_class->item_handler = sp_pen_context_item_handler; } /** @@ -127,11 +137,6 @@ sp_pen_context_init(SPPenContext *pc) SPEventContext *event_context = SP_EVENT_CONTEXT(pc); event_context->cursor_shape = cursor_pen_xpm; - event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline( - -1, - cursor_pen_pixbuf, - FALSE, - NULL); event_context->hot_x = 4; event_context->hot_y = 4; @@ -143,8 +148,12 @@ sp_pen_context_init(SPPenContext *pc) pc->c1 = NULL; pc->cl0 = NULL; pc->cl1 = NULL; - + pc->events_disabled = 0; + + pc->num_clicks = 0; + pc->waiting_LPE = NULL; + pc->waiting_item = NULL; } /** @@ -175,6 +184,19 @@ sp_pen_context_dispose(GObject *object) } G_OBJECT_CLASS(pen_parent_class)->dispose(object); + + if (pc->expecting_clicks_for_LPE > 0) { + // we received too few clicks to sanely set the parameter path so we remove the LPE from the item + sp_lpe_item_remove_current_path_effect(pc->waiting_item, false); + } +} + +void +sp_pen_context_set_polyline_mode(SPPenContext *const pc) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint mode = prefs->getInt("/tools/freehand/pen/freehand-mode", 0); + pc->polylines_only = (mode == 2 || mode == 3); + pc->polylines_paraxial = (mode == 3); } /** @@ -210,14 +232,18 @@ sp_pen_context_setup(SPEventContext *ec) pc->anchor_statusbar = false; - if (prefs_get_int_attribute("tools.freehand.pen", "selcue", 0) != 0) { + sp_pen_context_set_polyline_mode(pc); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/freehand/pen/selcue")) { ec->enableSelectionCue(); } } static void -pen_cancel (SPPenContext *const pc) +pen_cancel (SPPenContext *const pc) { + pc->num_clicks = 0; pc->state = SP_PEN_CONTEXT_STOP; spdc_reset_colors(pc); sp_canvas_item_hide(pc->c0); @@ -238,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); } @@ -251,12 +279,13 @@ sp_pen_context_finish(SPEventContext *ec) * Callback that sets key to value in pen context. */ static void -sp_pen_context_set(SPEventContext *ec, gchar const *key, gchar const *val) +sp_pen_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val) { SPPenContext *pc = SP_PEN_CONTEXT(ec); + Glib::ustring name = val->getEntryName(); - if (!strcmp(key, "mode")) { - if ( val && !strcmp(val, "drag") ) { + if (name == "mode") { + if ( val->getString() == "drag" ) { pc->mode = SP_PEN_CONTEXT_MODE_DRAG; } else { pc->mode = SP_PEN_CONTEXT_MODE_CLICK; @@ -268,26 +297,68 @@ sp_pen_context_set(SPEventContext *ec, gchar const *key, gchar const *val) * Snaps new node relative to the previous node. */ static void -spdc_endpoint_snap(SPPenContext const *const pc, NR::Point &p, guint const state) +spdc_endpoint_snap(SPPenContext const *const pc, Geom::Point &p, guint const state) { - if (pc->npoints > 0) { - spdc_endpoint_snap_rotation(pc, p, pc->p[0], state); + 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 { + // 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); + } } - - spdc_endpoint_snap_free(pc, p, state); } /** * Snaps new node's handle relative to the new node. */ static void -spdc_endpoint_snap_handle(SPPenContext const *const pc, NR::Point &p, guint const state) +spdc_endpoint_snap_handle(SPPenContext const *const pc, Geom::Point &p, guint const state) { g_return_if_fail(( pc->npoints == 2 || pc->npoints == 5 )); - spdc_endpoint_snap_rotation(pc, p, pc->p[pc->npoints - 2], state); - spdc_endpoint_snap_free(pc, p, state); + if ((state & GDK_CONTROL_MASK)) { //CTRL enables angular snapping + spdc_endpoint_snap_rotation(pc, p, pc->p[pc->npoints - 2], state); + } else { + if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above + spdc_endpoint_snap_free(pc, p, state); + } + } +} + +static gint +sp_pen_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event) +{ + SPPenContext *const pc = SP_PEN_CONTEXT(ec); + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + ret = pen_handle_button_press(pc, event->button); + break; + case GDK_BUTTON_RELEASE: + ret = pen_handle_button_release(pc, event->button); + break; + default: + break; + } + + if (!ret) { + if (((SPEventContextClass *) pen_parent_class)->item_handler) + ret = ((SPEventContextClass *) pen_parent_class)->item_handler(ec, item, event); + } + + return ret; } /** @@ -314,7 +385,7 @@ sp_pen_context_root_handler(SPEventContext *ec, GdkEvent *event) break; case GDK_2BUTTON_PRESS: - ret = pen_handle_2button_press(pc); + ret = pen_handle_2button_press(pc, event->button); break; case GDK_KEY_PRESS: @@ -346,24 +417,36 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const return FALSE; } - gint ret = FALSE; - if (bevent.button == 1) { + 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 event_dt(desktop->w2d(event_w)); + SPEventContext *event_context = SP_EVENT_CONTEXT(pc); - SPDrawContext * const dc = SP_DRAW_CONTEXT(pc); - SPDesktop * const desktop = SP_EVENT_CONTEXT_DESKTOP(dc); + gint ret = FALSE; + if (bevent.button == 1 && !event_context->space_panning + // make sure this is not the last click for a waiting LPE (otherwise we want to finish the path) + && pc->expecting_clicks_for_LPE != 1) { if (Inkscape::have_viable_layer(desktop, dc->_message_context) == false) { return TRUE; } - NR::Point const event_w(bevent.x, bevent.y); + if (!pc->grab ) { + /* Grab mouse, so release will not pass unnoticed */ + pc->grab = SP_CANVAS_ITEM(desktop->acetate); + sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK ), + NULL, bevent.time); + } + pen_drag_origin_w = event_w; pen_within_tolerance = true; /* Test whether we hit any anchor. */ SPDrawAnchor * const anchor = spdc_test_inside(pc, event_w); - NR::Point const event_dt(desktop->w2d(event_w)); switch (pc->mode) { case SP_PEN_CONTEXT_MODE_CLICK: /* In click mode we add point on release */ @@ -373,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: @@ -383,32 +466,45 @@ 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) { + 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 + // distinction so that the case of a waiting LPE is treated separately + /* Set start anchor */ pc->sa = anchor; - NR::Point p; - if (anchor) { - - /* Adjust point to anchor if needed */ + 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 */ p = anchor->dp; desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path")); - } else { - // 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) Inkscape::Selection * const selection = sp_desktop_selection(desktop); - if (!(bevent.state & GDK_SHIFT_MASK)) { - + if (!(bevent.state & GDK_SHIFT_MASK) || sp_pen_context_has_waiting_LPE(pc)) { + /* if we have a waiting LPE, we need a fresh path to be created + so don't append to an existing one */ selection->clear(); desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path")); - } else if (selection->singleItem() && SP_IS_PATH(selection->singleItem())) { - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path")); } @@ -422,9 +518,8 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const /* Set end anchor */ pc->ea = anchor; - NR::Point p; + Geom::Point p; if (anchor) { - p = anchor->dp; // we hit an anchor, will finish the curve (either with or without closing) // in release handler @@ -439,14 +534,13 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const break; } else { - 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); } - } - pc->state = SP_PEN_CONTEXT_CONTROL; + + pc->state = pc->polylines_only ? SP_PEN_CONTEXT_POINT : SP_PEN_CONTEXT_CONTROL; ret = TRUE; break; case SP_PEN_CONTEXT_CONTROL: @@ -462,11 +556,26 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const default: break; } - } else if (bevent.button == 3) { - if (pc->npoints != 0) { + } 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; } + + 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) { + --pc->expecting_clicks_for_LPE; } return ret; @@ -480,22 +589,25 @@ pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent) { gint ret = FALSE; - if (mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) { - // allow middle-button scrolling + SPEventContext *event_context = SP_EVENT_CONTEXT(pc); + SPDesktop * const dt = SP_EVENT_CONTEXT_DESKTOP(event_context); + + if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) { + // allow scrolling return FALSE; } - + if (pc->events_disabled) { // skip motion events if pen events are disabled return FALSE; } - NR::Point const event_w(mevent.x, + Geom::Point const event_w(mevent.x, mevent.y); if (pen_within_tolerance) { - gint const tolerance = prefs_get_int_attribute_limited("options.dragtolerance", - "value", 0, 0, 100); - if ( NR::LInfty( event_w - pen_drag_origin_w ) < tolerance ) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + if ( Geom::LInfty( event_w - pen_drag_origin_w ) < tolerance ) { return FALSE; // Do not drag if we're within tolerance from origin. } } @@ -504,18 +616,8 @@ pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent) // motion notify coordinates as given (no snapping back to origin) pen_within_tolerance = false; - SPDesktop *const dt = pc->desktop; - if ( ( mevent.state & GDK_BUTTON1_MASK ) && !pc->grab ) { - /* 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 */ - NR::Point p = dt->w2d(event_w); + Geom::Point p = dt->w2d(event_w); /* Test, whether we hit any anchor */ SPDrawAnchor *anchor = spdc_test_inside(pc, event_w); @@ -529,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: @@ -553,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); - 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; @@ -574,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: @@ -583,13 +697,24 @@ pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent) // snap the handle spdc_endpoint_snap_handle(pc, p, mevent.state); - spdc_pen_set_ctrl(pc, p, mevent.state); + if (!pc->polylines_only) { + spdc_pen_set_ctrl(pc, p, mevent.state); + } else { + spdc_pen_set_ctrl(pc, pc->p[1], mevent.state); + } + gobble_motion_events(GDK_BUTTON1_MASK); ret = TRUE; break; case SP_PEN_CONTEXT_STOP: /* 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; @@ -611,14 +736,15 @@ pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent) } gint ret = FALSE; - if ( revent.button == 1 ) { + SPEventContext *event_context = SP_EVENT_CONTEXT(pc); + if ( revent.button == 1 && !event_context->space_panning) { SPDrawContext *dc = SP_DRAW_CONTEXT (pc); - NR::Point const event_w(revent.x, + Geom::Point const event_w(revent.x, revent.y); /* Find desktop coordinates */ - NR::Point p = pc->desktop->w2d(event_w); + Geom::Point p = pc->desktop->w2d(event_w); /* Test whether we hit any anchor. */ SPDrawAnchor *anchor = spdc_test_inside(pc, event_w); @@ -662,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; @@ -712,14 +838,34 @@ pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent) dc->green_closed = FALSE; } + // TODO: can we be sure that the path was created correctly? + // TODO: should we offer an option to collect the clicks in a list? + if (pc->expecting_clicks_for_LPE == 0 && sp_pen_context_has_waiting_LPE(pc)) { + sp_pen_context_set_polyline_mode(pc); + + SPEventContext *ec = SP_EVENT_CONTEXT(pc); + Inkscape::Selection *selection = sp_desktop_selection (ec->desktop); + + if (pc->waiting_LPE) { + // we have an already created LPE waiting for a path + pc->waiting_LPE->acceptParamPath(SP_PATH(selection->singleItem())); + selection->add(SP_OBJECT(pc->waiting_item)); + pc->waiting_LPE = NULL; + } else { + // the case that we need to create a new LPE and apply it to the just-drawn path is + // handled in spdc_check_for_and_apply_waiting_LPE() in draw-context.cpp + } + } + return ret; } static gint -pen_handle_2button_press(SPPenContext *const pc) +pen_handle_2button_press(SPPenContext *const pc, GdkEventButton const &bevent) { gint ret = FALSE; - if (pc->npoints != 0) { + // 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; } @@ -747,9 +893,9 @@ pen_redraw_all (SPPenContext *const pc) if (pc->green_anchor) SP_CTRL(pc->green_anchor->ctrl)->moveto(pc->green_anchor->dp); - sp_curve_reset(pc->red_curve); - sp_curve_moveto(pc->red_curve, pc->p[0]); - sp_curve_curveto(pc->red_curve, pc->p[1], pc->p[2], pc->p[3]); + pc->red_curve->reset(); + pc->red_curve->moveto(pc->p[0]); + pc->red_curve->curveto(pc->p[1], pc->p[2], pc->p[3]); sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); // handles @@ -763,11 +909,15 @@ pen_redraw_all (SPPenContext *const pc) sp_canvas_item_hide (pc->cl1); } - NArtBpath *const bpath = sp_curve_last_bpath(pc->green_curve); - if (bpath) { - if (bpath->code == NR_CURVETO && NR::Point(bpath->x2, bpath->y2) != pc->p[0]) { - SP_CTRL(pc->c0)->moveto(NR::Point(bpath->x2, bpath->y2)); - sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl0), NR::Point(bpath->x2, bpath->y2), pc->p[0]); + Geom::Curve const * last_seg = pc->green_curve->last_segment(); + if (last_seg) { + Geom::CubicBezier const * cubic = dynamic_cast( last_seg ); + if ( cubic && + (*cubic)[2] != to_2geom(pc->p[0]) ) + { + Geom::Point p2 = (*cubic)[2]; + SP_CTRL(pc->c0)->moveto(p2); + sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl0), p2, pc->p[0]); sp_canvas_item_show (pc->c0); sp_canvas_item_show (pc->cl0); } else { @@ -784,24 +934,18 @@ pen_lastpoint_move (SPPenContext *const pc, gdouble x, gdouble y) return; // green - NArtBpath *const bpath = sp_curve_last_bpath(pc->green_curve); - if (bpath) { - if (bpath->code == NR_CURVETO) { - bpath->x2 += x; - bpath->y2 += y; - } - bpath->x3 += x; - bpath->y3 += y; + if (!pc->green_curve->is_empty()) { + pc->green_curve->last_point_additive_move( Geom::Point(x,y) ); } else { // start anchor too if (pc->green_anchor) { - pc->green_anchor->dp += NR::Point(x, y); + pc->green_anchor->dp += Geom::Point(x, y); } } // red - pc->p[0] += NR::Point(x, y); - pc->p[1] += NR::Point(x, y); + pc->p[0] += Geom::Point(x, y); + pc->p[1] += Geom::Point(x, y); pen_redraw_all(pc); } @@ -817,12 +961,11 @@ pen_lastpoint_tocurve (SPPenContext *const pc) if (pc->npoints != 5) return; - // red - NArtBpath *const bpath = sp_curve_last_bpath(pc->green_curve); - if (bpath && bpath->code == NR_CURVETO) { - pc->p[1] = pc->p[0] + (NR::Point(bpath->x3, bpath->y3) - NR::Point(bpath->x2, bpath->y2)); + Geom::CubicBezier const * cubic = dynamic_cast( pc->green_curve->last_segment() ); + if ( cubic ) { + pc->p[1] = pc->p[0] + (Geom::Point)( (*cubic)[3] - (*cubic)[2] ); } else { - pc->p[1] = pc->p[0] + (pc->p[3] - pc->p[0])*(1/3); + pc->p[1] = pc->p[0] + (1./3)*(pc->p[3] - pc->p[0]); } pen_redraw_all(pc); @@ -843,8 +986,10 @@ pen_lastpoint_toline (SPPenContext *const pc) static gint pen_handle_key_press(SPPenContext *const pc, GdkEvent *event) { + gint ret = FALSE; - gdouble const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gdouble const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000); // in px switch (get_group0_keyval (&event->key)) { @@ -909,6 +1054,40 @@ 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) { + sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::PARALLEL, 2); + ret = TRUE; + } + break; + + case GDK_C: + case GDK_c: + if (MOD__SHIFT_ONLY) { + sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::CIRCLE_3PTS, 3); + ret = TRUE; + } + break; + + case GDK_B: + case GDK_b: + if (MOD__SHIFT_ONLY) { + sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::PERP_BISECTOR, 2); + ret = TRUE; + } + break; + + case GDK_A: + case GDK_a: + if (MOD__SHIFT_ONLY) { + sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::ANGLE_BISECTOR, 3); + ret = TRUE; + } + break; +*/ + case GDK_U: case GDK_u: if (MOD__SHIFT_ONLY) { @@ -946,15 +1125,26 @@ pen_handle_key_press(SPPenContext *const pc, GdkEvent *event) ret = TRUE; } break; + case GDK_g: + case GDK_G: + if (MOD__SHIFT_ONLY) { + sp_selection_to_guides(SP_EVENT_CONTEXT(pc)->desktop); + ret = true; + } + break; case GDK_BackSpace: case GDK_Delete: case GDK_KP_Delete: - if (sp_curve_is_empty(pc->green_curve)) { - pen_cancel (pc); - ret = TRUE; + if ( pc->green_curve->is_empty() || (pc->green_curve->last_segment() == NULL) ) { + if (!pc->red_curve->is_empty()) { + pen_cancel (pc); + ret = TRUE; + } else { + // do nothing; this event should be handled upstream + } } else { /* Reset red curve */ - sp_curve_reset(pc->red_curve); + pc->red_curve->reset(); /* Destroy topmost green bpath */ if (pc->green_bpaths) { if (pc->green_bpaths->data) @@ -962,29 +1152,30 @@ pen_handle_key_press(SPPenContext *const pc, GdkEvent *event) pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); } /* Get last segment */ - NArtBpath const *const p = SP_CURVE_BPATH(pc->green_curve); - gint const e = SP_CURVE_LENGTH(pc->green_curve); - if ( e < 2 ) { - g_warning("Green curve length is %d", e); + if ( pc->green_curve->is_empty() ) { + g_warning("pen_handle_key_press, case GDK_KP_Delete: Green curve is empty"); break; } - pc->p[0] = p[e - 2].c(3); - if (p[e - 1].code == NR_CURVETO) { - pc->p[1] = p[e - 1].c(1); + // The code below assumes that pc->green_curve has only ONE path ! + 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]; } else { pc->p[1] = pc->p[0]; } - NR::Point const pt(( pc->npoints < 4 - ? p[e - 1].c(3) + Geom::Point const pt(( pc->npoints < 4 + ? (Geom::Point)(crv->finalPoint()) : pc->p[3] )); pc->npoints = 2; - sp_curve_backspace(pc->green_curve); + pc->green_curve->backspace(); sp_canvas_item_hide(pc->c0); sp_canvas_item_hide(pc->c1); sp_canvas_item_hide(pc->cl0); 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; @@ -998,17 +1189,17 @@ static void spdc_reset_colors(SPPenContext *pc) { /* Red */ - sp_curve_reset(pc->red_curve); + pc->red_curve->reset(); sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); /* Blue */ - sp_curve_reset(pc->blue_curve); + pc->blue_curve->reset(); sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->blue_bpath), NULL); /* Green */ while (pc->green_bpaths) { gtk_object_destroy(GTK_OBJECT(pc->green_bpaths->data)); pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); } - sp_curve_reset(pc->green_curve); + pc->green_curve->reset(); if (pc->green_anchor) { pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); } @@ -1020,7 +1211,7 @@ spdc_reset_colors(SPPenContext *pc) static void -spdc_pen_set_initial_point(SPPenContext *const pc, NR::Point const p) +spdc_pen_set_initial_point(SPPenContext *const pc, Geom::Point const p) { g_assert( pc->npoints == 0 ); @@ -1032,8 +1223,32 @@ spdc_pen_set_initial_point(SPPenContext *const pc, NR::Point const p) sp_canvas_force_full_redraw_after_interruptions(pc->desktop->canvas, 5); } +/** + * 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_subsequent_point(SPPenContext *const pc, NR::Point const p, bool statusbar) +spdc_pen_set_angle_distance_status_message(SPPenContext *const pc, Geom::Point const p, int pc_point_to_compare, gchar const *message) +{ + g_assert(pc != NULL); + g_assert((pc_point_to_compare == 0) || (pc_point_to_compare == 3)); // exclude control handles + g_assert(message != NULL); + + SPDesktop *desktop = SP_EVENT_CONTEXT(pc)->desktop; + Geom::Point rel = p - pc->p[pc_point_to_compare]; + 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); + + pc->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, message, angle, dist->str); + g_string_free(dist, FALSE); +} + +static void +spdc_pen_set_subsequent_point(SPPenContext *const pc, Geom::Point const p, bool statusbar, guint status) { g_assert( pc->npoints != 0 ); /* todo: Check callers to see whether 2 <= npoints is guaranteed. */ @@ -1042,36 +1257,44 @@ spdc_pen_set_subsequent_point(SPPenContext *const pc, NR::Point const p, bool st pc->p[3] = p; pc->p[4] = p; pc->npoints = 5; - sp_curve_reset(pc->red_curve); - sp_curve_moveto(pc->red_curve, pc->p[0]); + pc->red_curve->reset(); bool is_curve; - if ( (pc->onlycurves) - || ( pc->p[1] != pc->p[0] ) ) - { - sp_curve_curveto(pc->red_curve, pc->p[1], p, p); - is_curve = true; - } else { - sp_curve_lineto(pc->red_curve, p); + pc->red_curve->moveto(pc->p[0]); + if (pc->polylines_paraxial && !statusbar) { + // 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]) { + pc->red_curve->curveto(pc->p[1], p, p); + is_curve = true; + } else { + pc->red_curve->lineto(p); + is_curve = false; + } } sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); if (statusbar) { - // status text - SPDesktop *desktop = SP_EVENT_CONTEXT(pc)->desktop; - NR::Point rel = p - pc->p[0]; - 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; - if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0) - angle = angle_to_compass (angle); - pc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s: angle %3.2f°, distance %s; with Ctrl to snap angle, Enter to finish the path"), is_curve? "Curve segment" : "Line segment", angle, dist->str); - g_string_free(dist, FALSE); + gchar *message = is_curve ? + _("Curve segment: angle %3.2f°, distance %s; with Ctrl to snap angle, Enter to finish the path" ): + _("Line segment: angle %3.2f°, distance %s; with Ctrl to snap angle, Enter to finish the path"); + spdc_pen_set_angle_distance_status_message(pc, p, 0, message); } } static void -spdc_pen_set_ctrl(SPPenContext *const pc, NR::Point const p, guint const state) +spdc_pen_set_ctrl(SPPenContext *const pc, Geom::Point const p, guint const state) { sp_canvas_item_show(pc->c1); sp_canvas_item_show(pc->cl1); @@ -1083,16 +1306,7 @@ spdc_pen_set_ctrl(SPPenContext *const pc, NR::Point const p, guint const state) SP_CTRL(pc->c1)->moveto(pc->p[1]); sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl1), pc->p[0], pc->p[1]); - // status text - SPDesktop *desktop = SP_EVENT_CONTEXT(pc)->desktop; - NR::Point rel = p - pc->p[0]; - 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; - if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0) - angle = angle_to_compass (angle); - pc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Curve handle: angle %3.2f°, length %s; with Ctrl to snap angle"), angle, dist->str); - g_string_free(dist, FALSE); - + spdc_pen_set_angle_distance_status_message(pc, p, 0, _("Curve handle: angle %3.2f°, length %s; with Ctrl to snap angle")); } else if ( pc->npoints == 5 ) { pc->p[4] = p; sp_canvas_item_show(pc->c0); @@ -1100,12 +1314,12 @@ spdc_pen_set_ctrl(SPPenContext *const pc, NR::Point const p, guint const state) bool is_symm = false; if ( ( ( pc->mode == SP_PEN_CONTEXT_MODE_CLICK ) && ( state & GDK_CONTROL_MASK ) ) || ( ( pc->mode == SP_PEN_CONTEXT_MODE_DRAG ) && !( state & GDK_SHIFT_MASK ) ) ) { - NR::Point delta = p - pc->p[3]; + Geom::Point delta = p - pc->p[3]; pc->p[2] = pc->p[3] - delta; is_symm = true; - sp_curve_reset(pc->red_curve); - sp_curve_moveto(pc->red_curve, pc->p[0]); - sp_curve_curveto(pc->red_curve, pc->p[1], pc->p[2], pc->p[3]); + pc->red_curve->reset(); + pc->red_curve->moveto(pc->p[0]); + pc->red_curve->curveto(pc->p[1], pc->p[2], pc->p[3]); sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); } SP_CTRL(pc->c0)->moveto(pc->p[2]); @@ -1113,30 +1327,30 @@ spdc_pen_set_ctrl(SPPenContext *const pc, NR::Point const p, guint const state) SP_CTRL(pc->c1)->moveto(pc->p[4]); sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl1), pc->p[3], pc->p[4]); - // status text - SPDesktop *desktop = SP_EVENT_CONTEXT(pc)->desktop; - NR::Point rel = p - pc->p[3]; - 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; - if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0) - angle = angle_to_compass (angle); - pc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s: angle %3.2f°, length %s; with Ctrl to snap angle, with Shift to move this handle only"), is_symm? "Curve handle, symmetric" : "Curve handle", angle, dist->str); - g_string_free(dist, FALSE); - + gchar *message = is_symm ? + _("Curve handle, symmetric: angle %3.2f°, length %s; with Ctrl to snap angle, with Shift to move this handle only") : + _("Curve handle: angle %3.2f°, length %s; with Ctrl to snap angle, with Shift to move this handle only"); + spdc_pen_set_angle_distance_status_message(pc, p, 3, message); } else { g_warning("Something bad happened - npoints is %d", pc->npoints); } } static void -spdc_pen_finish_segment(SPPenContext *const pc, NR::Point const p, guint const state) +spdc_pen_finish_segment(SPPenContext *const pc, Geom::Point const p, guint const state) { - if (!sp_curve_empty(pc->red_curve)) { - sp_curve_append_continuous(pc->green_curve, pc->red_curve, 0.0625); - SPCurve *curve = sp_curve_copy(pc->red_curve); + 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()) { + pc->green_curve->append_continuous(pc->red_curve, 0.0625); + SPCurve *curve = pc->red_curve->copy(); /// \todo fixme: SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), curve); - sp_curve_unref(curve); + curve->unref(); sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape); @@ -1145,20 +1359,27 @@ spdc_pen_finish_segment(SPPenContext *const pc, NR::Point const p, guint const s pc->p[1] = pc->p[4]; pc->npoints = 2; - sp_curve_reset(pc->red_curve); + pc->red_curve->reset(); } } static void spdc_pen_finish(SPPenContext *const pc, gboolean const closed) { + if (pc->expecting_clicks_for_LPE > 1) { + // don't let the path be finished before we have collected the required number of mouse clicks + return; + } + + 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")); - sp_curve_reset(pc->red_curve); + pc->red_curve->reset(); spdc_concat_colors_and_flush(pc, closed); pc->sa = NULL; pc->ea = NULL; @@ -1189,10 +1410,86 @@ 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--; } +void +sp_pen_context_wait_for_LPE_mouse_clicks(SPPenContext *pc, Inkscape::LivePathEffect::EffectType effect_type, + unsigned int num_clicks, bool use_polylines) +{ + if (effect_type == Inkscape::LivePathEffect::INVALID_LPE) + return; + + pc->waiting_LPE_type = effect_type; + pc->expecting_clicks_for_LPE = num_clicks; + pc->polylines_only = use_polylines; + pc->polylines_paraxial = false; // TODO: think if this is correct for all cases +} + +void +sp_pen_context_cancel_waiting_for_LPE(SPPenContext *pc) +{ + pc->waiting_LPE_type = Inkscape::LivePathEffect::INVALID_LPE; + pc->expecting_clicks_for_LPE = 0; + sp_pen_context_set_polyline_mode(pc); +} + +static int pen_next_paraxial_direction(const SPPenContext *const pc, + Geom::Point const &pt, Geom::Point const &origin, guint state) { + /* + * after the first mouse click we determine whether the mouse pointer is closest to a + * horizontal or vertical segment; for all subsequent mouse clicks, we use the direction + * orthogonal to the last one; pressing Shift toggles the direction + */ + // 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[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; + } else { + // subsequent mouse click + return (state & GDK_SHIFT_MASK) ? pen_last_paraxial_dir : 1 - pen_last_paraxial_dir; + } +} + +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]; + + int next_dir = pen_next_paraxial_direction(pc, pt, origin, state); + + 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 { + // Create a horizontal or vertical constraint line + Inkscape::Snapper::SnapConstraint cl(origin, next_dir ? Geom::Point(0, 1) : Geom::Point(1, 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; + + 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(); + } +} + /* Local Variables: mode:c++ @@ -1202,4 +1499,4 @@ pen_enable_events(SPPenContext *const pc) { 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 :