diff --git a/src/nodepath.cpp b/src/nodepath.cpp
index 5cb4ed7f8ca543e621fa9989bfb9ca3c41c0964b..081638f1dd83072d5edeb427ed25e3063c906804 100644 (file)
--- a/src/nodepath.cpp
+++ b/src/nodepath.cpp
#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;
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
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.
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);
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;
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;
// 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);
/**
* 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);
}
}
/* 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;
}
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.
*/
// 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);
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 {
//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