Code

Split SPCanvasItem and SPCanvasGroup to individual .h files. Removed forward header.
[inkscape.git] / src / pen-context.cpp
index e62d45192ddba178bbbed0b1c1e382f59403279a..607bdaedc2fc9df9279e87d201b3f5c1d077ca78 100644 (file)
 #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 "display/canvas-bpath.h"
 #include "display/sp-ctrlline.h"
 #include "display/sodipodi-ctrl.h"
 #include <glibmm/i18n.h>
-#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);
@@ -49,14 +50,14 @@ 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);
 
@@ -70,11 +71,16 @@ 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.
  */
@@ -142,11 +148,12 @@ sp_pen_context_init(SPPenContext *pc)
     pc->c1 = NULL;
     pc->cl0 = NULL;
     pc->cl1 = NULL;
-    
+
     pc->events_disabled = 0;
 
-    pc->polylines_only = false;
+    pc->num_clicks = 0;
     pc->waiting_LPE = NULL;
+    pc->waiting_item = NULL;
 }
 
 /**
@@ -178,14 +185,20 @@ sp_pen_context_dispose(GObject *object)
 
     G_OBJECT_CLASS(pen_parent_class)->dispose(object);
 
-    pc->polylines_only = false;
-    pc->waiting_LPE = NULL;
     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);
+}
+
 /**
  * Callback to initialize SPPenContext object.
  */
@@ -219,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);
@@ -247,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);
     }
@@ -260,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;
@@ -277,29 +297,45 @@ 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 
+static gint
 sp_pen_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
 {
     SPPenContext *const pc = SP_PEN_CONTEXT(ec);
@@ -383,13 +419,13 @@ 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);
-    NR::Point const event_w(bevent.x, bevent.y);
-    NR::Point const event_dt(desktop->w2d(event_w));
+    Geom::Point const event_w(bevent.x, bevent.y);
+    Geom::Point event_dt(desktop->w2d(event_w));
     SPEventContext *event_context = SP_EVENT_CONTEXT(pc);
 
     gint ret = FALSE;
     if (bevent.button == 1 && !event_context->space_panning
-        // when the last click for a waiting LPE occurs we want to finish the path
+        // 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) {
@@ -420,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:
@@ -430,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) {
-                                freehand_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
@@ -445,7 +489,6 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const
 
                             /* Set start anchor */
                             pc->sa = anchor;
-                            NR::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 */
@@ -475,7 +518,7 @@ 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)
@@ -494,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);
-                                }
                             }
                         }
 
@@ -516,23 +556,25 @@ 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) {
+    if (pc->expecting_clicks_for_LPE > 0) {
         --pc->expecting_clicks_for_LPE;
     }
 
@@ -554,18 +596,18 @@ 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;
     }
 
-    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.
         }
     }
@@ -575,7 +617,7 @@ pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent)
     pen_within_tolerance = false;
 
     /* 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);
@@ -589,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:
@@ -613,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, _("<b>Click</b> or <b>click and drag</b> to close and finish the path."));
                             pc->anchor_statusbar = true;
@@ -634,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:
@@ -655,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;
@@ -681,10 +741,10 @@ pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent)
 
         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);
@@ -728,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;
@@ -740,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);
@@ -783,7 +841,7 @@ pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent)
     // 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)) {
-        pc->polylines_only = false;
+        sp_pen_context_set_polyline_mode(pc);
 
         SPEventContext *ec = SP_EVENT_CONTEXT(pc);
         Inkscape::Selection *selection = sp_desktop_selection (ec->desktop);
@@ -793,7 +851,6 @@ pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent)
             pc->waiting_LPE->acceptParamPath(SP_PATH(selection->singleItem()));
             selection->add(SP_OBJECT(pc->waiting_item));
             pc->waiting_LPE = NULL;
-            pc->polylines_only = false;
         } 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
@@ -807,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;
     }
@@ -857,7 +915,7 @@ pen_redraw_all (SPPenContext *const pc)
         if ( cubic &&
              (*cubic)[2] != to_2geom(pc->p[0]) )
         {
-            NR::Point p2 = from_2geom((*cubic)[2]);
+            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);
@@ -881,13 +939,13 @@ pen_lastpoint_move (SPPenContext *const pc, gdouble x, gdouble 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);
 }
 
@@ -905,7 +963,7 @@ pen_lastpoint_tocurve (SPPenContext *const pc)
 
     Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const *>( pc->green_curve->last_segment() );
     if ( cubic ) {
-        pc->p[1] = pc->p[0] + from_2geom( (*cubic)[3] - (*cubic)[2] );
+        pc->p[1] = pc->p[0] + (Geom::Point)( (*cubic)[3] - (*cubic)[2] );
     } else {
         pc->p[1] = pc->p[0] + (1./3)*(pc->p[3] - pc->p[0]);
     }
@@ -928,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)) {
 
@@ -994,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) {
@@ -1025,6 +1086,7 @@ pen_handle_key_press(SPPenContext *const pc, GdkEvent *event)
                 ret = TRUE;
             }
             break;
+*/
 
         case GDK_U:
         case GDK_u:
@@ -1066,14 +1128,14 @@ pen_handle_key_press(SPPenContext *const pc, GdkEvent *event)
         case GDK_g:
         case GDK_G:
             if (MOD__SHIFT_ONLY) {
-                sp_selection_to_guides();
+                sp_selection_to_guides(SP_EVENT_CONTEXT(pc)->desktop);
                 ret = true;
             }
             break;
         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;
@@ -1095,16 +1157,15 @@ 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<Geom::CubicBezier const *>(crv)) {
-                    pc->p[1] = from_2geom( (*cubic)[1] );
+                    pc->p[1] = (*cubic)[1];
                 } else {
                     pc->p[1] = pc->p[0];
                 }
-                NR::Point const pt(( pc->npoints < 4
-                                     ? crv->finalPoint()
+                Geom::Point const pt(( pc->npoints < 4
+                                     ? (Geom::Point)(crv->finalPoint())
                                      : pc->p[3] ));
                 pc->npoints = 2;
                 pc->green_curve->backspace();
@@ -1114,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;
@@ -1149,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 );
 
@@ -1165,19 +1227,20 @@ spdc_pen_set_initial_point(SPPenContext *const pc, NR::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&#176;, distance %s").
- */ 
+ */
 static void
-spdc_pen_set_angle_distance_status_message(SPPenContext *const pc, NR::Point const p, int pc_point_to_compare, gchar const *message)
+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;
-    NR::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;
-    if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
+    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);
@@ -1185,7 +1248,7 @@ spdc_pen_set_angle_distance_status_message(SPPenContext *const pc, NR::Point con
 }
 
 static void
-spdc_pen_set_subsequent_point(SPPenContext *const pc, NR::Point const p, bool statusbar)
+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. */
@@ -1195,15 +1258,29 @@ spdc_pen_set_subsequent_point(SPPenContext *const pc, NR::Point const p, bool st
     pc->p[4] = p;
     pc->npoints = 5;
     pc->red_curve->reset();
-    pc->red_curve->moveto(pc->p[0]);
     bool is_curve;
-    if (pc->p[1] != pc->p[0])
-    {
-        pc->red_curve->curveto(pc->p[1], p, p);
-        is_curve = true;
-    } else {
+    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);
@@ -1217,7 +1294,7 @@ spdc_pen_set_subsequent_point(SPPenContext *const pc, NR::Point const p, bool st
 }
 
 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);
@@ -1237,7 +1314,7 @@ 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;
             pc->red_curve->reset();
@@ -1260,8 +1337,14 @@ spdc_pen_set_ctrl(SPPenContext *const pc, NR::Point const p, guint const state)
 }
 
 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 (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();
@@ -1288,8 +1371,10 @@ spdc_pen_finish(SPPenContext *const pc, gboolean const closed)
         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"));
@@ -1325,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--;
 }
 
@@ -1333,11 +1418,76 @@ void
 sp_pen_context_wait_for_LPE_mouse_clicks(SPPenContext *pc, Inkscape::LivePathEffect::EffectType effect_type,
                                          unsigned int num_clicks, bool use_polylines)
 {
-    g_print ("Now waiting for %s to be applied\n",
-             Inkscape::LivePathEffect::LPETypeConverter.get_label(effect_type).c_str());
+    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->waiting_LPE_type = effect_type;
+    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();
+    }
 }
 
 /*
@@ -1349,4 +1499,4 @@ sp_pen_context_wait_for_LPE_mouse_clicks(SPPenContext *pc, Inkscape::LivePathEff
   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 :