Code

rationalize function names and remove spurious soft_ptr
[inkscape.git] / src / nodepath.cpp
index 5cb4ed7f8ca543e621fa9989bfb9ca3c41c0964b..081638f1dd83072d5edeb427ed25e3063c906804 100644 (file)
 #include "prefs-utils.h"
 #include "sp-metrics.h"
 #include "sp-path.h"
-#include <libnr/nr-matrix-ops.h>
+#include "libnr/nr-matrix-ops.h"
 #include "splivarot.h"
 #include "svg/svg.h"
+#include "display/bezier-utils.h"
+#include <vector>
+#include <algorithm>
 
 class NR::Matrix;
 
@@ -182,6 +185,7 @@ Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item)
     np->selected    = NULL;
     np->nodeContext = NULL; //Let the context that makes this set it
     np->livarot_path = NULL;
+    np->local_change = 0;
 
     // we need to update item's transform from the repr here,
     // because they may be out of sync when we respond
@@ -294,62 +298,6 @@ static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
     g_list_free(badSubPaths);
 }
 
-
-
-/**
- * \brief Returns true if the argument nodepath and the d attribute in
- * its repr do not match.
- *
- * This may happen if repr was changed in, e.g., XML editor or by undo.
- *
- * \todo
- * UGLY HACK, think how we can eliminate it. IDEA: try instead a local_change flag in node context?
- */
-gboolean nodepath_repr_d_changed(Inkscape::NodePath::Path *np, char const *newd)
-{
-    g_assert(np);
-
-    SPCurve *curve = create_curve(np);
-
-    gchar *svgpath = sp_svg_write_path(curve->bpath);
-
-    char const *attr_d = ( newd
-                           ? newd
-                           : SP_OBJECT(np->path)->repr->attribute("d") );
-
-    gboolean ret;
-    if (attr_d && svgpath)
-        ret = strcmp(attr_d, svgpath);
-    else
-        ret = TRUE;
-
-    g_free(svgpath);
-    sp_curve_unref(curve);
-
-    return ret;
-}
-
-/**
- * \brief Returns true if the argument nodepath and the sodipodi:nodetypes
- * attribute in its repr do not match.
- *
- * This may happen if repr was changed in, e.g., the XML editor or by undo.
- * IDEA: try instead a local_change flag in node context?
- */
-gboolean nodepath_repr_typestr_changed(Inkscape::NodePath::Path *np, char const *newtypestr)
-{
-    g_assert(np);
-    gchar *typestr = create_typestr(np);
-    char const *attr_typestr = ( newtypestr
-                                 ? newtypestr
-                                 : SP_OBJECT(np->path)->repr->attribute("sodipodi:nodetypes") );
-    gboolean const ret = (attr_typestr && strcmp(attr_typestr, typestr));
-
-    g_free(typestr);
-
-    return ret;
-}
-
 /**
  * Create new nodepath from b, make it subpath of np.
  * \param t The node type.
@@ -465,8 +413,15 @@ static void update_repr_internal(Inkscape::NodePath::Path *np)
     gchar *typestr = create_typestr(np);
     gchar *svgpath = sp_svg_write_path(curve->bpath);
 
-    repr->setAttribute("d", svgpath);
-    repr->setAttribute("sodipodi:nodetypes", typestr);
+    if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
+        np->local_change++;
+        repr->setAttribute("d", svgpath);
+    }
+
+    if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
+        np->local_change++;
+        repr->setAttribute("sodipodi:nodetypes", typestr);
+    }
 
     g_free(svgpath);
     g_free(typestr);
@@ -479,7 +434,7 @@ static void update_repr_internal(Inkscape::NodePath::Path *np)
 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np)
 {
     update_repr_internal(np);
-    sp_document_done(SP_DT_DOCUMENT(np->desktop));
+    sp_document_done(sp_desktop_document(np->desktop));
 
     if (np->livarot_path) {
         delete np->livarot_path;
@@ -499,7 +454,7 @@ void sp_nodepath_update_repr(Inkscape::NodePath::Path *np)
 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key)
 {
     update_repr_internal(np);
-    sp_document_maybe_done(SP_DT_DOCUMENT(np->desktop), key);
+    sp_document_maybe_done(sp_desktop_document(np->desktop), key);
 
     if (np->livarot_path) {
         delete np->livarot_path;
@@ -541,7 +496,7 @@ static void stamp_repr(Inkscape::NodePath::Path *np)
     // move to the saved position
     new_repr->setPosition(pos > 0 ? pos : 0);
 
-    sp_document_done(SP_DT_DOCUMENT(np->desktop));
+    sp_document_done(sp_desktop_document(np->desktop));
 
     Inkscape::GC::release(new_repr);
     g_free(svgpath);
@@ -854,7 +809,7 @@ static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode
 /**
  * Change node type, and its handles accordingly.
  */
-static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeType type)
+static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
 {
     g_assert(node);
     g_assert(node->subpath);
@@ -880,7 +835,15 @@ static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::N
         sp_knot_update_ctrl(node->knot);
     }
 
-    sp_node_adjust_handles(node);
+    // if one of handles is mouseovered, preserve its position
+    if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
+        sp_node_adjust_handle(node, 1);
+    } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
+        sp_node_adjust_handle(node, -1);
+    } else {
+        sp_node_adjust_handles(node);
+    }
+
     sp_node_update_handles(node);
 
     sp_nodepath_update_statusbar(node->subpath->nodepath);
@@ -1058,7 +1021,7 @@ static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::
     }
 
     if (!side->line) {
-        side->line = sp_canvas_item_new(SP_DT_CONTROLS(desktop),
+        side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
                                         SP_TYPE_CTRLLINE, NULL);
     }
 }
@@ -1489,16 +1452,22 @@ void sp_node_selected_join()
 
     /* a and b are endpoints */
 
-    NR::Point c = (a->pos + b->pos) / 2;
+    NR::Point c;
+    if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
+        c = a->pos;
+    } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
+        c = b->pos;
+    } else {
+        c = (a->pos + b->pos) / 2;
+    }
 
     if (a->subpath == b->subpath) {
        Inkscape::NodePath::SubPath *sp = a->subpath;
         sp_nodepath_subpath_close(sp);
+        sp_node_moveto (sp->first, c);
 
         sp_nodepath_update_handles(sp->nodepath);
-
         sp_nodepath_update_repr(nodepath);
-
         return;
     }
 
@@ -1654,6 +1623,126 @@ void sp_node_selected_join_segment()
     sp_nodepath_update_repr(nodepath);
 }
 
+/**
+ * Delete one or more selected nodes and preserve the shape of the path as much as possible.
+ */
+void sp_node_delete_preserve(GList *nodes_to_delete)
+{
+    
+    while (nodes_to_delete) {
+        Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
+        Inkscape::NodePath::SubPath *sp = node->subpath;
+        Inkscape::NodePath::Path *nodepath = sp->nodepath;
+        Inkscape::NodePath::Node *sample_cursor = NULL;
+        Inkscape::NodePath::Node *sample_end = NULL;
+        Inkscape::NodePath::Node *delete_cursor = node;
+        bool just_delete = false;
+        
+        //find the start of this contiguous selection
+        //move left to the first node that is not selected
+        //or the start of the non-closed path
+        for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
+            delete_cursor = curr;
+        }
+
+        //just delete at the beginning of an open path
+        if (!delete_cursor->p.other) {
+            sample_cursor = delete_cursor;
+            just_delete = true;
+        } else {
+            sample_cursor = delete_cursor->p.other;
+        }
+        
+        //calculate points for each segment
+        int rate = 5;
+        float period = 1.0 / rate;
+        std::vector<NR::Point> data;
+        if (!just_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) {
+                    just_delete = true;
+                    break;
+                }
+                
+                //sample points on the contiguous selected segment
+                NR::Point *bez;
+                bez = new NR::Point [4];
+                bez[0] = curr->pos;
+                bez[1] = curr->n.pos;
+                bez[2] = curr->n.other->p.pos;
+                bez[3] = curr->n.other->pos;
+                for (int i=1; i<rate; i++) {
+                    gdouble t = i * period;
+                    NR::Point p = bezier_pt(3, bez, t);
+                    data.push_back(p);
+                }
+                data.push_back(curr->n.other->pos);
+
+                sample_end = curr->n.other;
+                //break if we've come full circle or hit the end of the selection
+                if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
+                    break;
+                }
+            }
+        }
+
+        if (!just_delete) {
+            //calculate the best fitting single segment and adjust the endpoints
+            NR::Point *adata;
+            adata = new NR::Point [data.size()];
+            copy(data.begin(), data.end(), adata);
+            
+            NR::Point *bez;
+            bez = new NR::Point [4];
+            //would decreasing error create a better fitting approximation?
+            gdouble error = 1.0;
+            gint ret;
+            ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
+
+            //adjust endpoints
+            sample_cursor->n.pos = bez[1];
+            sample_end->p.pos = bez[2];
+        }
+       
+        //destroy this contiguous selection
+        while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
+            Inkscape::NodePath::Node *temp = delete_cursor;
+            if (delete_cursor->n.other == delete_cursor) {
+                // delete_cursor->n points to itself, which means this is the last node on a closed subpath
+                delete_cursor = NULL; 
+            } else {
+                delete_cursor = delete_cursor->n.other;
+            }
+            nodes_to_delete = g_list_remove(nodes_to_delete, temp);
+            sp_nodepath_node_destroy(temp);
+        }
+
+        //clean up the nodepath (such as for trivial subpaths)
+        sp_nodepath_cleanup(nodepath);
+
+        sp_nodepath_update_handles(nodepath);
+
+        // if the entire nodepath is removed, delete the selected object.
+        if (nodepath->subpaths == NULL ||
+            sp_nodepath_get_node_count(nodepath) < 2) {
+            SPDocument *document = sp_desktop_document (nodepath->desktop);
+            sp_nodepath_destroy(nodepath);
+            g_list_free(nodes_to_delete);
+            nodes_to_delete = NULL;
+            //is the next line necessary?
+            sp_selection_delete();
+            sp_document_done (document);
+            return;
+        }
+
+        sp_nodepath_update_repr(nodepath);
+
+        sp_nodepath_update_statusbar(nodepath);
+    }
+}
+
 /**
  * Delete one or more selected nodes.
  */
@@ -1678,7 +1767,7 @@ void sp_node_selected_delete()
     // if the entire nodepath is removed, delete the selected object.
     if (nodepath->subpaths == NULL ||
         sp_nodepath_get_node_count(nodepath) < 2) {
-        SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
+        SPDocument *document = sp_desktop_document (nodepath->desktop);
         sp_nodepath_destroy(nodepath);
         sp_selection_delete();
         sp_document_done (document);
@@ -2412,23 +2501,9 @@ static void node_clicked(SPKnot *knot, guint state, gpointer data)
             sp_nodepath_update_statusbar(nodepath);
 
         } else { //ctrl+alt+click: delete node
-            sp_nodepath_node_destroy(n);
-            //clean up the nodepath (such as for trivial subpaths)
-            sp_nodepath_cleanup(nodepath);
-
-            // if the entire nodepath is removed, delete the selected object.
-            if (nodepath->subpaths == NULL ||
-                sp_nodepath_get_node_count(nodepath) < 2) {
-                SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
-                sp_nodepath_destroy(nodepath);
-                sp_selection_delete();
-                sp_document_done (document);
-
-            } else {
-                sp_nodepath_update_handles(nodepath);
-                sp_nodepath_update_repr(nodepath);
-                sp_nodepath_update_statusbar(nodepath);
-            }
+            GList *node_to_delete = NULL;
+            node_to_delete = g_list_append(node_to_delete, n);
+            sp_node_delete_preserve(node_to_delete);
         }
 
     } else {
@@ -3285,7 +3360,7 @@ static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
     //Link the head to the tail
     sp->first->p.other = sp->last;
     sp->last->n.other  = sp->first;
-    sp->last->n.pos    = sp->first->n.pos;
+    sp->last->n.pos    = sp->last->pos + (sp->first->n.pos - sp->first->pos);
     sp->first          = sp->last;
 
     //Remove the extra end node