Code

patch 1705533
[inkscape.git] / src / nodepath.cpp
index 3019b492e36e1a525471aab824efcbb5344d73ce..0ad4c66a030c190776b9c53cc158dd6c1ea7ae5e 100644 (file)
@@ -31,6 +31,7 @@
 #include "message-stack.h"
 #include "message-context.h"
 #include "node-context.h"
+#include "shape-editor.h"
 #include "selection-chemistry.h"
 #include "selection.h"
 #include "xml/repr.h"
@@ -98,6 +99,8 @@ static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean inc
 
 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
 
+static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
+
 /* Adjust handle placement, if the node or the other handle is moved */
 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
@@ -133,7 +136,7 @@ static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::N
 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
 
 // active_node indicates mouseover node
-static Inkscape::NodePath::Node *active_node = NULL;
+Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
 
 /**
  * \brief Creates new nodepath from item
@@ -184,7 +187,7 @@ Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item, bool
     np->path        = path;
     np->subpaths    = NULL;
     np->selected    = NULL;
-    np->nodeContext = NULL; //Let the context that makes this set it
+    np->shape_editor = NULL; //Let the shapeeditor that makes this set it
     np->livarot_path = NULL;
     np->local_change = 0;
     np->show_handles = show_handles;
@@ -211,15 +214,13 @@ Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item, bool
     sp_curve_unref(curve);
 
     // create the livarot representation from the same item
-    np->livarot_path = Path_for_item(item, true, true);
-    if (np->livarot_path)
-        np->livarot_path->ConvertWithBackData(0.01);
+    sp_nodepath_ensure_livarot_path(np);
 
     return np;
 }
 
 /**
- * Destroys nodepath's subpaths, then itself, also tell context about it.
+ * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
  */
 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
 
@@ -230,9 +231,9 @@ void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
         sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
     }
 
-    //Inform the context that made me, if any, that I am gone.
-    if (np->nodeContext)
-        np->nodeContext->nodepath = NULL;
+    //Inform the ShapeEditor that made me, if any, that I am gone.
+    if (np->shape_editor)
+        np->shape_editor->nodepath_destroyed();
 
     g_assert(!np->selected);
 
@@ -247,6 +248,16 @@ void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
 }
 
 
+void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
+{
+    if (np && np->livarot_path == NULL && np->path && SP_IS_ITEM(np->path)) {
+        np->livarot_path = Path_for_item (np->path, true, true);
+        if (np->livarot_path)
+            np->livarot_path->ConvertWithBackData(0.01);
+    }
+}
+
+
 /**
  *  Return the node count of a given NodeSubPath.
  */
@@ -479,20 +490,19 @@ static void update_repr_internal(Inkscape::NodePath::Path *np)
  */
 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
 {
-    update_repr_internal(np);
-    sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE, 
-                     annotation);
+    //fixme: np can be NULL, so check before proceeding
+    g_return_if_fail(np != NULL);
 
     if (np->livarot_path) {
         delete np->livarot_path;
         np->livarot_path = NULL;
     }
 
-    if (np->path && SP_IS_ITEM(np->path)) {
-        np->livarot_path = Path_for_item (np->path, true, true);
-        if (np->livarot_path)
-            np->livarot_path->ConvertWithBackData(0.01);
-    }
+    update_repr_internal(np);
+    sp_canvas_end_forced_full_redraws(np->desktop->canvas);
+    
+    sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE, 
+                     annotation);
 }
 
 /**
@@ -500,20 +510,14 @@ void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotati
  */
 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
 {
-    update_repr_internal(np);
-    sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE, 
-                           annotation);
-
     if (np->livarot_path) {
         delete np->livarot_path;
         np->livarot_path = NULL;
     }
 
-    if (np->path && SP_IS_ITEM(np->path)) {
-        np->livarot_path = Path_for_item (np->path, true, true);
-        if (np->livarot_path)
-            np->livarot_path->ConvertWithBackData(0.01);
-    }
+    update_repr_internal(np);
+    sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE, 
+                           annotation);
 }
 
 /**
@@ -524,7 +528,7 @@ static void stamp_repr(Inkscape::NodePath::Path *np)
     g_assert(np);
 
     Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
-    Inkscape::XML::Node *new_repr = old_repr->duplicate();
+    Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
 
     // remember the position of the item
     gint pos = old_repr->position();
@@ -661,7 +665,7 @@ static gchar *create_typestr(Inkscape::NodePath::Path *np)
 }
 
 /**
- * Returns current path in context.
+ * Returns current path in context. // later eliminate this function at all!
  */
 static Inkscape::NodePath::Path *sp_nodepath_current()
 {
@@ -675,7 +679,7 @@ static Inkscape::NodePath::Path *sp_nodepath_current()
         return NULL;
     }
 
-    return SP_NODE_CONTEXT(event_context)->nodepath;
+    return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
 }
 
 
@@ -696,7 +700,7 @@ static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscap
     if (end->code == NR_LINETO) {
         new_path->type =Inkscape::NodePath::NODE_CUSP;
         new_path->code = NR_LINETO;
-        new_path->pos  = (t * start->pos + (1 - t) * end->pos);
+        new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
     } else {
         new_path->type =Inkscape::NodePath::NODE_SMOOTH;
         new_path->code = NR_CURVETO;
@@ -735,13 +739,16 @@ static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::N
     g_assert( start->n.other == end );
    Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
                                                end,
-                                              Inkscape::NodePath::NODE_SMOOTH,
+                                               (NRPathcode)end->code == NR_LINETO? 
+                                                  Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
                                                (NRPathcode)end->code,
                                                &start->pos, &start->pos, &start->n.pos);
     sp_nodepath_line_midpoint(newnode, end, t);
 
+    sp_node_adjust_handles(start);
     sp_node_update_handles(start);
     sp_node_update_handles(newnode);
+    sp_node_adjust_handles(end);
     sp_node_update_handles(end);
 
     return newnode;
@@ -837,12 +844,14 @@ static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode
     end->code = code;
 
     if (code == NR_LINETO) {
-        if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
+        if (start->code == NR_LINETO) {
+            sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
+        }
         if (end->n.other) {
-            if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
+            if (end->n.other->code == NR_LINETO) {
+                sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
+            }
         }
-        sp_node_adjust_handle(start, -1);
-        sp_node_adjust_handle(end, 1);
     } else {
         NR::Point delta = end->pos - start->pos;
         start->n.pos = start->pos + delta / 3;
@@ -906,28 +915,28 @@ static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::N
 */
 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
 {
+    bool p_line = (node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos);
+    bool n_line = (node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos);
+
     if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
-        if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
-            // convert adjacent segment BEFORE to curve
+        if (p_line && n_line) {
+            // only if both adjacent segments are lines, 
+            // convert both to curves:
+
+            // BEFORE:
+            {
             node->code = NR_CURVETO;
-            NR::Point delta;
-            if (node->n.other != NULL)
-                delta = node->n.other->pos - node->p.other->pos;
-            else
-                delta = node->pos - node->p.other->pos;
+            NR::Point delta = node->n.other->pos - node->p.other->pos;
             node->p.pos = node->pos - delta / 4;
-            sp_node_update_handles(node);
-        }
+            }
 
-        if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
-            // convert adjacent segment AFTER to curve
+            // AFTER:
+            {
             node->n.other->code = NR_CURVETO;
-            NR::Point delta;
-            if (node->p.other != NULL)
-                delta = node->p.other->pos - node->n.other->pos;
-            else
-                delta = node->pos - node->n.other->pos;
+            NR::Point delta = node->p.other->pos - node->n.other->pos;
             node->n.pos = node->pos - delta / 4;
+            }
+
             sp_node_update_handles(node);
         }
     }
@@ -946,22 +955,33 @@ void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
     node->p.pos += delta;
     node->n.pos += delta;
 
+    Inkscape::NodePath::Node *node_p = NULL;
+    Inkscape::NodePath::Node *node_n = NULL;
+
     if (node->p.other) {
         if (node->code == NR_LINETO) {
             sp_node_adjust_handle(node, 1);
             sp_node_adjust_handle(node->p.other, -1);
+            node_p = node->p.other;
         }
     }
     if (node->n.other) {
         if (node->n.other->code == NR_LINETO) {
             sp_node_adjust_handle(node, -1);
             sp_node_adjust_handle(node->n.other, 1);
+            node_n = node->n.other;
         }
     }
 
     // this function is only called from batch movers that will update display at the end
     // themselves, so here we just move all the knots without emitting move signals, for speed
     sp_node_update_handles(node, false);
+    if (node_n) {
+        sp_node_update_handles(node_n, false);
+    }
+    if (node_p) {
+        sp_node_update_handles(node_p, false);
+    }
 }
 
 /**
@@ -979,7 +999,7 @@ static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath,
         
         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
-            Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
+            Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, n->subpath->nodepath->path);
             if (s.getDistance() < best) {
                 best = s.getDistance();
                 best_pt = s.getPoint() - n->pos;
@@ -988,7 +1008,7 @@ static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath,
     }
 
     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
-       Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
+        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
         sp_node_moveto(n, n->pos + best_pt);
     }
 
@@ -1546,11 +1566,19 @@ sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Po
         return;
     }
 
-    Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
+    sp_nodepath_ensure_livarot_path(nodepath);
+    NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
+    if (!maybe_position) {
+        return;
+    }
+    Path::cut_position position = *maybe_position;
 
     //find segment to segment
     Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
 
+    //fixme: this can return NULL, so check before proceeding.
+    g_return_if_fail(e != NULL);
+    
     gboolean force = FALSE;
     if (!(e->selected && (!e->p.other || e->p.other->selected))) {
         force = TRUE;
@@ -1574,7 +1602,12 @@ sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
         return;
     }
 
-    Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
+    sp_nodepath_ensure_livarot_path(nodepath);
+    NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
+    if (!maybe_position) {
+        return;
+    }
+    Path::cut_position position = *maybe_position;
 
     //find segment to split
     Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
@@ -1602,8 +1635,14 @@ sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
  * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
  */
 void
-sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
+sp_nodepath_curve_drag(int node, double t, NR::Point delta)
 {
+    Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
+
+    //fixme: e and e->p can be NULL, so check for those before proceeding
+    g_return_if_fail(e != NULL);
+    g_return_if_fail(&e->p != NULL);
+    
     /* feel good is an arbitrary parameter that distributes the delta between handles
      * if t of the drag point is less than 1/6 distance form the endpoint only
      * the corresponding hadle is adjusted. This matches the behavior in GIMP
@@ -1715,8 +1754,11 @@ void sp_node_selected_join()
    Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
 
     g_assert(a != b);
-    g_assert(a->p.other || a->n.other);
-    g_assert(b->p.other || b->n.other);
+    if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) { 
+        // someone tried to join an orphan node (i.e. a single-node subpath).
+        // this is not worth an error message, just fail silently.
+        return;
+    }
 
     if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
@@ -1814,8 +1856,11 @@ void sp_node_selected_join_segment()
    Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
 
     g_assert(a != b);
-    g_assert(a->p.other || a->n.other);
-    g_assert(b->p.other || b->n.other);
+    if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) { 
+        // someone tried to join an orphan node (i.e. a single-node subpath).
+        // this is not worth an error message, just fail silently.
+        return;
+    }
 
     if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
@@ -1935,7 +1980,7 @@ void sp_node_delete_preserve(GList *nodes_to_delete)
             data.push_back(sample_cursor->pos);
             for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
                 //just delete at the end of an open path
-                if (!sp->closed && curr->n.other == sp->last) {
+                if (!sp->closed && curr == sp->last) {
                     just_delete = true;
                     break;
                 }
@@ -1975,6 +2020,12 @@ void sp_node_delete_preserve(GList *nodes_to_delete)
             gint ret;
             ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
 
+            //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
+            //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
+            //the resulting nodes behave as expected.
+            sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
+            sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
+            
             //adjust endpoints
             sample_cursor->n.pos = bez[1];
             sample_end->p.pos = bez[2];
@@ -2195,7 +2246,10 @@ sp_node_selected_delete_segment(void)
         //Copy everything after 'end' to a new subpath
        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
         for (curr=end ; curr ; curr=curr->n.other) {
-            sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
+            NRPathcode code = (NRPathcode) curr->code;
+            if (curr == end)
+                code = NR_MOVETO;
+            sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
                                  &curr->p.pos, &curr->pos, &curr->n.pos);
         }
 
@@ -2843,10 +2897,37 @@ static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::No
     gboolean ret = FALSE;
     switch (event->type) {
         case GDK_ENTER_NOTIFY:
-            active_node = n;
+            Inkscape::NodePath::Path::active_node = n;
             break;
         case GDK_LEAVE_NOTIFY:
-            active_node = NULL;
+            Inkscape::NodePath::Path::active_node = NULL;
+            break;
+        case GDK_SCROLL:
+            if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
+                switch (event->scroll.direction) {
+                    case GDK_SCROLL_UP:
+                        nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
+                        break;
+                    case GDK_SCROLL_DOWN:
+                        nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
+                        break;
+                    default:
+                        break;
+                }
+                ret = TRUE;
+            } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
+                switch (event->scroll.direction) {
+                    case GDK_SCROLL_UP:
+                        nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
+                        break;
+                    case GDK_SCROLL_DOWN:
+                        nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
+                        break;
+                    default:
+                        break;
+                }
+                ret = TRUE;
+            }
             break;
         case GDK_KEY_PRESS:
             switch (get_group0_keyval (&event->key)) {
@@ -2859,16 +2940,16 @@ static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::No
                     break;
                 case GDK_Page_Up:
                     if (event->key.state & GDK_CONTROL_MASK) {
-                        nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
-                    } else {
                         nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
+                    } else {
+                        nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
                     }
                     break;
                 case GDK_Page_Down:
                     if (event->key.state & GDK_CONTROL_MASK) {
-                        nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
-                    } else {
                         nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
+                    } else {
+                        nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
                     }
                     break;
                 default:
@@ -2890,33 +2971,33 @@ gboolean node_key(GdkEvent *event)
     Inkscape::NodePath::Path *np;
 
     // there is no way to verify nodes so set active_node to nil when deleting!!
-    if (active_node == NULL) return FALSE;
+    if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
 
     if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
         gint ret = FALSE;
         switch (get_group0_keyval (&event->key)) {
             /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
             case GDK_BackSpace:
-                np = active_node->subpath->nodepath;
-                sp_nodepath_node_destroy(active_node);
+                np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
+                sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
                 sp_nodepath_update_repr(np, _("Delete node"));
-                active_node = NULL;
+                Inkscape::NodePath::Path::active_node = NULL;
                 ret = TRUE;
                 break;
             case GDK_c:
-                sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
+                sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
                 ret = TRUE;
                 break;
             case GDK_s:
-                sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
+                sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
                 ret = TRUE;
                 break;
             case GDK_y:
-                sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
+                sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
                 ret = TRUE;
                 break;
             case GDK_b:
-                sp_nodepath_node_break(active_node);
+                sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
                 ret = TRUE;
                 break;
         }
@@ -2968,6 +3049,9 @@ static void node_grabbed(SPKnot *knot, guint state, gpointer data)
         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
     }
 
+    n->is_dragging = true;
+    sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
+
     sp_nodepath_remember_origins (n->subpath->nodepath);
 }
 
@@ -2979,6 +3063,8 @@ static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
 
    n->dragging_out = NULL;
+   n->is_dragging = false;
+   sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
 
    sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
 }
@@ -3181,14 +3267,16 @@ node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
             }
         }
     } else { // move freely
-        if (state & GDK_MOD1_MASK) { // sculpt
-            sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
-        } else {
-            sp_nodepath_selected_nodes_move(n->subpath->nodepath,
-                                        (*p)[NR::X] - n->pos[NR::X],
-                                        (*p)[NR::Y] - n->pos[NR::Y],
-                                        (state & GDK_SHIFT_MASK) == 0);
-       }
+        if (n->is_dragging) {
+            if (state & GDK_MOD1_MASK) { // sculpt
+                sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
+            } else {
+                sp_nodepath_selected_nodes_move(n->subpath->nodepath,
+                                            (*p)[NR::X] - n->pos[NR::X],
+                                            (*p)[NR::Y] - n->pos[NR::Y],
+                                            (state & GDK_SHIFT_MASK) == 0);
+            }
+        }
     }
 
     n->subpath->nodepath->desktop->scroll_to_point(p);
@@ -3239,6 +3327,7 @@ static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
         g_assert_not_reached();
     }
 
+    sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
 }
 
 /**
@@ -3363,22 +3452,22 @@ static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer
     }
 
     if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
-        && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
+        && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
         // rotate the other handle correspondingly, if both old and new angles exist and are not the same
         rother.a += rnew.a - rme.a;
         other->pos = NR::Point(rother) + n->pos;
-        sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
-        sp_knot_set_position(other->knot, &other->pos, 0);
+        if (other->knot) {
+            sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
+            sp_knot_moveto(other->knot, &other->pos);
+        }
     }
 
     me->pos = NR::Point(rnew) + n->pos;
     sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
 
-    // this is what sp_knot_set_position does, but without emitting the signal:
+    // move knot, but without emitting the signal:
     // we cannot emit a "moved" signal because we're now processing it
-    if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
-
-    knot->desktop->set_coordinate_status(me->pos);
+    sp_knot_moveto(me->knot, &(me->pos));
 
     update_object(n->subpath->nodepath);
 
@@ -3424,6 +3513,16 @@ static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePa
                     break;
             }
             break;
+        case GDK_ENTER_NOTIFY:
+            // we use an experimentally determined threshold that seems to work fine
+            if (NR::L2(n->pos - knot->pos) < 0.75)
+                Inkscape::NodePath::Path::active_node = n;
+            break;
+        case GDK_LEAVE_NOTIFY:
+            // we use an experimentally determined threshold that seems to work fine
+            if (NR::L2(n->pos - knot->pos) < 0.75)
+                Inkscape::NodePath::Path::active_node = NULL;
+            break;
         default:
             break;
     }
@@ -3714,11 +3813,11 @@ void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath,
 /**
  * Flip selected nodes horizontally/vertically.
  */
-void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
+void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
 {
     if (!nodepath || !nodepath->selected) return;
 
-    if (g_list_length(nodepath->selected) == 1) {
+    if (g_list_length(nodepath->selected) == 1 && !center) {
         // flip handles of the single selected node
         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
         double temp = n->p.pos[axis];
@@ -3735,10 +3834,13 @@ void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
             box.expandTo (n->pos); // contain all selected nodes
         }
 
+        if (!center) {
+            center = box.midpoint();
+        }
         NR::Matrix t =
-            NR::Matrix (NR::translate(-box.midpoint())) *
+            NR::Matrix (NR::translate(- *center)) *
             NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
-            NR::Matrix (NR::translate(box.midpoint()));
+            NR::Matrix (NR::translate(*center));
 
         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
@@ -3960,11 +4062,34 @@ static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
 
     node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
 
+    g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
+    g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
+    g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
+    g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
+    g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
     g_object_unref(G_OBJECT(node->knot));
-    if (node->p.knot)
+
+    if (node->p.knot) {
+        g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
+        g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
+        g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
+        g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
+        g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
+        g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
         g_object_unref(G_OBJECT(node->p.knot));
-    if (node->n.knot)
+        node->p.knot = NULL;
+    }
+
+    if (node->n.knot) {
+        g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
+        g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
+        g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
+        g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
+        g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
+        g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
         g_object_unref(G_OBJECT(node->n.knot));
+        node->n.knot = NULL;
+    }
 
     if (node->p.line)
         gtk_object_destroy(GTK_OBJECT(node->p.line));
@@ -4057,7 +4182,7 @@ static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Ink
 }
 
 /**
- * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
+ * Return node with the given index
  */
 Inkscape::NodePath::Node *
 sp_nodepath_get_node_by_index(int index)
@@ -4148,7 +4273,7 @@ static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
  * Handles content of statusbar as long as node tool is active.
  */
 void
-sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
+sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
 {
     gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>Alt+drag</b> nodes to sculpt; <b>arrow</b> keys to move nodes, <b>&lt; &gt;</b> to scale, <b>[ ]</b> to rotate");
     gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");