diff --git a/src/nodepath.cpp b/src/nodepath.cpp
index af73a37c2a1fe84130faf7f4028ad8ed7f32ceb5..a5fd2fab8af6f5530d6b9ec343b30f38f1a79a93 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.
SPCurve *curve = create_curve(np);
gchar *typestr = create_typestr(np);
- gchar *svgpath = sp_svg_write_path(curve->bpath);
+ gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
- 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;
SPCurve *curve = create_curve(np);
gchar *typestr = create_typestr(np);
- gchar *svgpath = sp_svg_write_path(curve->bpath);
+ gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
new_repr->setAttribute("d", svgpath);
new_repr->setAttribute("sodipodi:nodetypes", typestr);
// 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);
@@ -881,9 +836,9 @@ static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::N
}
// if one of handles is mouseovered, preserve its position
- if (SP_KNOT_IS_MOSEOVER(node->p.knot)) {
+ if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
sp_node_adjust_handle(node, 1);
- } else if (SP_KNOT_IS_MOSEOVER(node->n.knot)) {
+ } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
sp_node_adjust_handle(node, -1);
} else {
sp_node_adjust_handles(node);
static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
bool const snap = true)
{
- NR::Coord best[2] = { NR_HUGE, NR_HUGE };
+ NR::Coord best = NR_HUGE;
NR::Point delta(dx, dy);
NR::Point best_pt = delta;
if (snap) {
+ SnapManager const &m = nodepath->desktop->namedview->snap_manager;
+
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- NR::Point p = n->pos + delta;
- for (int dim = 0; dim < 2; dim++) {
- NR::Coord dist = namedview_dim_snap(nodepath->desktop->namedview,
- Inkscape::Snapper::SNAP_POINT, p,
- NR::Dim2(dim), nodepath->path);
- if (dist < best[dim]) {
- best[dim] = dist;
- best_pt[dim] = p[dim] - n->pos[dim];
- }
+ Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
+ Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
+ if (s.getDistance() < best) {
+ best = s.getDistance();
+ best_pt = s.getPoint() - n->pos;
}
}
}
@@ -1066,7 +1018,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 {
@@ -2772,7 +2836,7 @@ static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpo
NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
- SnapManager const m(n->subpath->nodepath->desktop->namedview);
+ SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
/* We are smooth node adjacent with line */
@@ -2785,7 +2849,7 @@ static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpo
NR::Coord const scal = dot(delta, ndelta) / linelen;
(*p) = n->pos + (scal / linelen) * ndelta;
}
- *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, ndelta, NULL).getPoint();
+ *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
} else {
*p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
}
//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