924d57595be1993cd3452c8e60cf5d6f5fe82d7e
1 #define __SP_NODEPATH_C__
3 /** \file
4 * Path handler in node edit mode
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * Portions of this code are in public domain; node sculpting functions written by bulia byak are under GNU GPL
11 */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "display/canvas-bpath.h"
19 #include "display/curve.h"
20 #include "display/sp-ctrlline.h"
21 #include "display/sodipodi-ctrl.h"
22 #include <glibmm/i18n.h>
23 #include "libnr/n-art-bpath.h"
24 #include "libnr/nr-path.h"
25 #include "helper/units.h"
26 #include "knot.h"
27 #include "inkscape.h"
28 #include "document.h"
29 #include "sp-namedview.h"
30 #include "desktop.h"
31 #include "desktop-handles.h"
32 #include "snap.h"
33 #include "message-stack.h"
34 #include "message-context.h"
35 #include "node-context.h"
36 #include "shape-editor.h"
37 #include "selection-chemistry.h"
38 #include "selection.h"
39 #include "xml/repr.h"
40 #include "prefs-utils.h"
41 #include "sp-metrics.h"
42 #include "sp-path.h"
43 #include "libnr/nr-matrix-ops.h"
44 #include "splivarot.h"
45 #include "svg/svg.h"
46 #include "verbs.h"
47 #include "display/bezier-utils.h"
48 #include <vector>
49 #include <algorithm>
50 #include <cstring>
51 #include <string>
52 #include "live_effects/lpeobject.h"
53 #include "live_effects/parameter/parameter.h"
54 #include "util/mathfns.h"
55 #include "display/snap-indicator.h"
57 class NR::Matrix;
59 /// \todo
60 /// evil evil evil. FIXME: conflict of two different Path classes!
61 /// There is a conflict in the namespace between two classes named Path.
62 /// #include "sp-flowtext.h"
63 /// #include "sp-flowregion.h"
65 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
66 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
67 GType sp_flowregion_get_type (void);
68 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
69 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
70 GType sp_flowtext_get_type (void);
71 // end evil workaround
73 #include "helper/stlport.h"
76 /// \todo fixme: Implement these via preferences */
78 #define NODE_FILL 0xbfbfbf00
79 #define NODE_STROKE 0x000000ff
80 #define NODE_FILL_HI 0xff000000
81 #define NODE_STROKE_HI 0x000000ff
82 #define NODE_FILL_SEL 0x0000ffff
83 #define NODE_STROKE_SEL 0x000000ff
84 #define NODE_FILL_SEL_HI 0xff000000
85 #define NODE_STROKE_SEL_HI 0x000000ff
86 #define KNOT_FILL 0xffffffff
87 #define KNOT_STROKE 0x000000ff
88 #define KNOT_FILL_HI 0xff000000
89 #define KNOT_STROKE_HI 0x000000ff
91 static GMemChunk *nodechunk = NULL;
93 /* Creation from object */
95 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
96 static gchar *parse_nodetypes(gchar const *types, gint length);
98 /* Object updating */
100 static void stamp_repr(Inkscape::NodePath::Path *np);
101 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
102 static gchar *create_typestr(Inkscape::NodePath::Path *np);
104 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
106 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
108 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
110 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
112 /* Adjust handle placement, if the node or the other handle is moved */
113 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
114 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
116 /* Node event callbacks */
117 static void node_clicked(SPKnot *knot, guint state, gpointer data);
118 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
119 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
120 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
122 /* Handle event callbacks */
123 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
124 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
125 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
126 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
127 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
128 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
130 /* Constructors and destructors */
132 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
133 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
134 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
135 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
136 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
137 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
138 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
140 /* Helpers */
142 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
143 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
144 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
146 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
147 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
149 // active_node indicates mouseover node
150 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
152 /**
153 * \brief Creates new nodepath from item
154 */
155 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
156 {
157 Inkscape::XML::Node *repr = object->repr;
159 /** \todo
160 * FIXME: remove this. We don't want to edit paths inside flowtext.
161 * Instead we will build our flowtext with cloned paths, so that the
162 * real paths are outside the flowtext and thus editable as usual.
163 */
164 if (SP_IS_FLOWTEXT(object)) {
165 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
166 if SP_IS_FLOWREGION(child) {
167 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
168 if (grandchild && SP_IS_PATH(grandchild)) {
169 object = SP_ITEM(grandchild);
170 break;
171 }
172 }
173 }
174 }
176 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
178 if (curve == NULL)
179 return NULL;
181 NArtBpath *bpath = sp_curve_first_bpath(curve);
182 gint length = curve->end;
183 if (length == 0) {
184 sp_curve_unref(curve);
185 return NULL; // prevent crash for one-node paths
186 }
188 //Create new nodepath
189 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
190 if (!np) {
191 sp_curve_unref(curve);
192 return NULL;
193 }
195 // Set defaults
196 np->desktop = desktop;
197 np->object = object;
198 np->subpaths = NULL;
199 np->selected = NULL;
200 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
201 np->livarot_path = NULL;
202 np->local_change = 0;
203 np->show_handles = show_handles;
204 np->helper_path = NULL;
205 np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
206 np->helperpath_width = 1.0;
207 np->curve = sp_curve_copy(curve);
208 np->show_helperpath = prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1;
209 np->straight_path = false;
210 if (IS_LIVEPATHEFFECT(object) && item) {
211 np->item = item;
212 } else {
213 np->item = SP_ITEM(object);
214 }
216 // we need to update item's transform from the repr here,
217 // because they may be out of sync when we respond
218 // to a change in repr by regenerating nodepath --bb
219 sp_object_read_attr(SP_OBJECT(np->item), "transform");
221 np->i2d = sp_item_i2d_affine(np->item);
222 np->d2i = np->i2d.inverse();
224 np->repr = repr;
225 if (repr_key_in) { // apparantly the object is an LPEObject
226 np->repr_key = g_strdup(repr_key_in);
227 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
228 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
229 if (lpeparam) {
230 lpeparam->param_setup_nodepath(np);
231 }
232 } else {
233 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
234 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
235 np->repr_key = g_strdup("inkscape:original-d");
237 LivePathEffectObject *lpeobj = sp_lpe_item_get_livepatheffectobject(SP_LPE_ITEM(np->object));
238 if (lpeobj && lpeobj->lpe) {
239 lpeobj->lpe->setup_nodepath(np);
240 }
241 } else {
242 np->repr_key = g_strdup("d");
243 }
244 }
246 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
247 gchar *typestr = parse_nodetypes(nodetypes, length);
249 // create the subpath(s) from the bpath
250 NArtBpath *b = bpath;
251 while (b->code != NR_END) {
252 b = subpath_from_bpath(np, b, typestr + (b - bpath));
253 }
255 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
256 np->subpaths = g_list_reverse(np->subpaths);
258 g_free(typestr);
259 sp_curve_unref(curve);
261 // create the livarot representation from the same item
262 sp_nodepath_ensure_livarot_path(np);
264 // Draw helper curve
265 if (np->show_helperpath) {
266 SPCurve *helper_curve = sp_curve_copy(np->curve);
267 sp_curve_transform(helper_curve, np->i2d );
268 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
269 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
270 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
271 sp_canvas_item_show(np->helper_path);
272 sp_curve_unref(helper_curve);
273 }
275 return np;
276 }
278 /**
279 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
280 */
281 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
283 if (!np) //soft fail, like delete
284 return;
286 while (np->subpaths) {
287 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
288 }
290 //Inform the ShapeEditor that made me, if any, that I am gone.
291 if (np->shape_editor)
292 np->shape_editor->nodepath_destroyed();
294 g_assert(!np->selected);
296 if (np->livarot_path) {
297 delete np->livarot_path;
298 np->livarot_path = NULL;
299 }
301 if (np->helper_path) {
302 GtkObject *temp = np->helper_path;
303 np->helper_path = NULL;
304 gtk_object_destroy(temp);
305 }
306 if (np->curve) {
307 sp_curve_unref(np->curve);
308 np->curve = NULL;
309 }
311 if (np->repr_key) {
312 g_free(np->repr_key);
313 np->repr_key = NULL;
314 }
315 if (np->repr_nodetypes_key) {
316 g_free(np->repr_nodetypes_key);
317 np->repr_nodetypes_key = NULL;
318 }
320 np->desktop = NULL;
322 g_free(np);
323 }
326 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
327 {
328 if (np && np->livarot_path == NULL) {
329 SPCurve *curve = create_curve(np);
330 NArtBpath *bpath = SP_CURVE_BPATH(curve);
331 np->livarot_path = bpath_to_Path(bpath);
333 if (np->livarot_path)
334 np->livarot_path->ConvertWithBackData(0.01);
336 sp_curve_unref(curve);
337 }
338 }
341 /**
342 * Return the node count of a given NodeSubPath.
343 */
344 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
345 {
346 if (!subpath)
347 return 0;
348 gint nodeCount = g_list_length(subpath->nodes);
349 return nodeCount;
350 }
352 /**
353 * Return the node count of a given NodePath.
354 */
355 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
356 {
357 if (!np)
358 return 0;
359 gint nodeCount = 0;
360 for (GList *item = np->subpaths ; item ; item=item->next) {
361 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
362 nodeCount += g_list_length(subpath->nodes);
363 }
364 return nodeCount;
365 }
367 /**
368 * Return the subpath count of a given NodePath.
369 */
370 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
371 {
372 if (!np)
373 return 0;
374 return g_list_length (np->subpaths);
375 }
377 /**
378 * Return the selected node count of a given NodePath.
379 */
380 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
381 {
382 if (!np)
383 return 0;
384 return g_list_length (np->selected);
385 }
387 /**
388 * Return the number of subpaths where nodes are selected in a given NodePath.
389 */
390 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
391 {
392 if (!np)
393 return 0;
394 if (!np->selected)
395 return 0;
396 if (!np->selected->next)
397 return 1;
398 gint count = 0;
399 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
400 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
401 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
402 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
403 if (node->selected) {
404 count ++;
405 break;
406 }
407 }
408 }
409 return count;
410 }
412 /**
413 * Clean up a nodepath after editing.
414 *
415 * Currently we are deleting trivial subpaths.
416 */
417 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
418 {
419 GList *badSubPaths = NULL;
421 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
422 for (GList *l = nodepath->subpaths; l ; l=l->next) {
423 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
424 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
425 badSubPaths = g_list_append(badSubPaths, sp);
426 }
428 //Delete them. This second step is because sp_nodepath_subpath_destroy()
429 //also removes the subpath from nodepath->subpaths
430 for (GList *l = badSubPaths; l ; l=l->next) {
431 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
432 sp_nodepath_subpath_destroy(sp);
433 }
435 g_list_free(badSubPaths);
436 }
438 /**
439 * Create new nodepath from b, make it subpath of np.
440 * \param t The node type.
441 * \todo Fixme: t should be a proper type, rather than gchar
442 */
443 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
444 {
445 NR::Point ppos, pos, npos;
447 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
449 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
450 bool const closed = (b->code == NR_MOVETO);
452 pos = NR::Point(b->x3, b->y3) * np->i2d;
453 if (b[1].code == NR_CURVETO) {
454 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
455 } else {
456 npos = pos;
457 }
458 Inkscape::NodePath::Node *n;
459 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
460 g_assert(sp->first == n);
461 g_assert(sp->last == n);
463 b++;
464 t++;
465 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
466 pos = NR::Point(b->x3, b->y3) * np->i2d;
467 if (b->code == NR_CURVETO) {
468 ppos = NR::Point(b->x2, b->y2) * np->i2d;
469 } else {
470 ppos = pos;
471 }
472 if (b[1].code == NR_CURVETO) {
473 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
474 } else {
475 npos = pos;
476 }
477 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
478 b++;
479 t++;
480 }
482 if (closed) sp_nodepath_subpath_close(sp);
484 return b;
485 }
487 /**
488 * Convert from sodipodi:nodetypes to new style type string.
489 */
490 static gchar *parse_nodetypes(gchar const *types, gint length)
491 {
492 g_assert(length > 0);
494 gchar *typestr = g_new(gchar, length + 1);
496 gint pos = 0;
498 if (types) {
499 for (gint i = 0; types[i] && ( i < length ); i++) {
500 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
501 if (types[i] != '\0') {
502 switch (types[i]) {
503 case 's':
504 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
505 break;
506 case 'z':
507 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
508 break;
509 case 'c':
510 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
511 break;
512 default:
513 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
514 break;
515 }
516 }
517 }
518 }
520 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
522 return typestr;
523 }
525 /**
526 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
527 * updated but repr is not (for speed). Used during curve and node drag.
528 */
529 static void update_object(Inkscape::NodePath::Path *np)
530 {
531 g_assert(np);
533 sp_curve_unref(np->curve);
534 np->curve = create_curve(np);
536 sp_nodepath_set_curve(np, np->curve);
538 if (np->show_helperpath) {
539 SPCurve * helper_curve = sp_curve_copy(np->curve);
540 sp_curve_transform(helper_curve, np->i2d );
541 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
542 sp_curve_unref(helper_curve);
543 }
544 }
546 /**
547 * Update XML path node with data from path object.
548 */
549 static void update_repr_internal(Inkscape::NodePath::Path *np)
550 {
551 g_assert(np);
553 Inkscape::XML::Node *repr = np->object->repr;
555 sp_curve_unref(np->curve);
556 np->curve = create_curve(np);
558 gchar *typestr = create_typestr(np);
559 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
561 // determine if path has an effect applied and write to correct "d" attribute.
562 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
563 np->local_change++;
564 repr->setAttribute(np->repr_key, svgpath);
565 }
567 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
568 np->local_change++;
569 repr->setAttribute(np->repr_nodetypes_key, typestr);
570 }
572 g_free(svgpath);
573 g_free(typestr);
575 if (np->show_helperpath) {
576 SPCurve * helper_curve = sp_curve_copy(np->curve);
577 sp_curve_transform(helper_curve, np->i2d );
578 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
579 sp_curve_unref(helper_curve);
580 }
581 }
583 /**
584 * Update XML path node with data from path object, commit changes forever.
585 */
586 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
587 {
588 //fixme: np can be NULL, so check before proceeding
589 g_return_if_fail(np != NULL);
591 if (np->livarot_path) {
592 delete np->livarot_path;
593 np->livarot_path = NULL;
594 }
596 update_repr_internal(np);
597 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
599 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
600 annotation);
601 }
603 /**
604 * Update XML path node with data from path object, commit changes with undo.
605 */
606 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
607 {
608 if (np->livarot_path) {
609 delete np->livarot_path;
610 np->livarot_path = NULL;
611 }
613 update_repr_internal(np);
614 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
615 annotation);
616 }
618 /**
619 * Make duplicate of path, replace corresponding XML node in tree, commit.
620 */
621 static void stamp_repr(Inkscape::NodePath::Path *np)
622 {
623 g_assert(np);
625 Inkscape::XML::Node *old_repr = np->object->repr;
626 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
628 // remember the position of the item
629 gint pos = old_repr->position();
630 // remember parent
631 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
633 SPCurve *curve = create_curve(np);
634 gchar *typestr = create_typestr(np);
636 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
638 new_repr->setAttribute(np->repr_key, svgpath);
639 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
641 // add the new repr to the parent
642 parent->appendChild(new_repr);
643 // move to the saved position
644 new_repr->setPosition(pos > 0 ? pos : 0);
646 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
647 _("Stamp"));
649 Inkscape::GC::release(new_repr);
650 g_free(svgpath);
651 g_free(typestr);
652 sp_curve_unref(curve);
653 }
655 /**
656 * Create curve from path.
657 */
658 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
659 {
660 SPCurve *curve = sp_curve_new();
662 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
663 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
664 sp_curve_moveto(curve,
665 sp->first->pos * np->d2i);
666 Inkscape::NodePath::Node *n = sp->first->n.other;
667 while (n) {
668 NR::Point const end_pt = n->pos * np->d2i;
669 switch (n->code) {
670 case NR_LINETO:
671 sp_curve_lineto(curve, end_pt);
672 break;
673 case NR_CURVETO:
674 sp_curve_curveto(curve,
675 n->p.other->n.pos * np->d2i,
676 n->p.pos * np->d2i,
677 end_pt);
678 break;
679 default:
680 g_assert_not_reached();
681 break;
682 }
683 if (n != sp->last) {
684 n = n->n.other;
685 } else {
686 n = NULL;
687 }
688 }
689 if (sp->closed) {
690 sp_curve_closepath(curve);
691 }
692 }
694 return curve;
695 }
697 /**
698 * Convert path type string to sodipodi:nodetypes style.
699 */
700 static gchar *create_typestr(Inkscape::NodePath::Path *np)
701 {
702 gchar *typestr = g_new(gchar, 32);
703 gint len = 32;
704 gint pos = 0;
706 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
707 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
709 if (pos >= len) {
710 typestr = g_renew(gchar, typestr, len + 32);
711 len += 32;
712 }
714 typestr[pos++] = 'c';
716 Inkscape::NodePath::Node *n;
717 n = sp->first->n.other;
718 while (n) {
719 gchar code;
721 switch (n->type) {
722 case Inkscape::NodePath::NODE_CUSP:
723 code = 'c';
724 break;
725 case Inkscape::NodePath::NODE_SMOOTH:
726 code = 's';
727 break;
728 case Inkscape::NodePath::NODE_SYMM:
729 code = 'z';
730 break;
731 default:
732 g_assert_not_reached();
733 code = '\0';
734 break;
735 }
737 if (pos >= len) {
738 typestr = g_renew(gchar, typestr, len + 32);
739 len += 32;
740 }
742 typestr[pos++] = code;
744 if (n != sp->last) {
745 n = n->n.other;
746 } else {
747 n = NULL;
748 }
749 }
750 }
752 if (pos >= len) {
753 typestr = g_renew(gchar, typestr, len + 1);
754 len += 1;
755 }
757 typestr[pos++] = '\0';
759 return typestr;
760 }
762 /**
763 * Returns current path in context. // later eliminate this function at all!
764 */
765 static Inkscape::NodePath::Path *sp_nodepath_current()
766 {
767 if (!SP_ACTIVE_DESKTOP) {
768 return NULL;
769 }
771 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
773 if (!SP_IS_NODE_CONTEXT(event_context)) {
774 return NULL;
775 }
777 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
778 }
782 /**
783 \brief Fills node and handle positions for three nodes, splitting line
784 marked by end at distance t.
785 */
786 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
787 {
788 g_assert(new_path != NULL);
789 g_assert(end != NULL);
791 g_assert(end->p.other == new_path);
792 Inkscape::NodePath::Node *start = new_path->p.other;
793 g_assert(start);
795 if (end->code == NR_LINETO) {
796 new_path->type =Inkscape::NodePath::NODE_CUSP;
797 new_path->code = NR_LINETO;
798 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
799 } else {
800 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
801 new_path->code = NR_CURVETO;
802 gdouble s = 1 - t;
803 for (int dim = 0; dim < 2; dim++) {
804 NR::Coord const f000 = start->pos[dim];
805 NR::Coord const f001 = start->n.pos[dim];
806 NR::Coord const f011 = end->p.pos[dim];
807 NR::Coord const f111 = end->pos[dim];
808 NR::Coord const f00t = s * f000 + t * f001;
809 NR::Coord const f01t = s * f001 + t * f011;
810 NR::Coord const f11t = s * f011 + t * f111;
811 NR::Coord const f0tt = s * f00t + t * f01t;
812 NR::Coord const f1tt = s * f01t + t * f11t;
813 NR::Coord const fttt = s * f0tt + t * f1tt;
814 start->n.pos[dim] = f00t;
815 new_path->p.pos[dim] = f0tt;
816 new_path->pos[dim] = fttt;
817 new_path->n.pos[dim] = f1tt;
818 end->p.pos[dim] = f11t;
819 }
820 }
821 }
823 /**
824 * Adds new node on direct line between two nodes, activates handles of all
825 * three nodes.
826 */
827 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
828 {
829 g_assert(end);
830 g_assert(end->subpath);
831 g_assert(g_list_find(end->subpath->nodes, end));
833 Inkscape::NodePath::Node *start = end->p.other;
834 g_assert( start->n.other == end );
835 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
836 end,
837 (NRPathcode)end->code == NR_LINETO?
838 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
839 (NRPathcode)end->code,
840 &start->pos, &start->pos, &start->n.pos);
841 sp_nodepath_line_midpoint(newnode, end, t);
843 sp_node_adjust_handles(start);
844 sp_node_update_handles(start);
845 sp_node_update_handles(newnode);
846 sp_node_adjust_handles(end);
847 sp_node_update_handles(end);
849 return newnode;
850 }
852 /**
853 \brief Break the path at the node: duplicate the argument node, start a new subpath with the duplicate, and copy all nodes after the argument node to it
854 */
855 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
856 {
857 g_assert(node);
858 g_assert(node->subpath);
859 g_assert(g_list_find(node->subpath->nodes, node));
861 Inkscape::NodePath::SubPath *sp = node->subpath;
862 Inkscape::NodePath::Path *np = sp->nodepath;
864 if (sp->closed) {
865 sp_nodepath_subpath_open(sp, node);
866 return sp->first;
867 } else {
868 // no break for end nodes
869 if (node == sp->first) return NULL;
870 if (node == sp->last ) return NULL;
872 // create a new subpath
873 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
875 // duplicate the break node as start of the new subpath
876 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
878 while (node->n.other) { // copy the remaining nodes into the new subpath
879 Inkscape::NodePath::Node *n = node->n.other;
880 Inkscape::NodePath::Node *nn = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
881 if (n->selected) {
882 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
883 }
884 sp_nodepath_node_destroy(n); // remove the point on the original subpath
885 }
887 return newnode;
888 }
889 }
891 /**
892 * Duplicate node and connect to neighbours.
893 */
894 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
895 {
896 g_assert(node);
897 g_assert(node->subpath);
898 g_assert(g_list_find(node->subpath->nodes, node));
900 Inkscape::NodePath::SubPath *sp = node->subpath;
902 NRPathcode code = (NRPathcode) node->code;
903 if (code == NR_MOVETO) { // if node is the endnode,
904 node->code = NR_LINETO; // new one is inserted before it, so change that to line
905 }
907 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
909 if (!node->n.other || !node->p.other) // if node is an endnode, select it
910 return node;
911 else
912 return newnode; // otherwise select the newly created node
913 }
915 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
916 {
917 node->p.pos = (node->pos + (node->pos - node->n.pos));
918 }
920 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
921 {
922 node->n.pos = (node->pos + (node->pos - node->p.pos));
923 }
925 /**
926 * Change line type at node, with side effects on neighbours.
927 */
928 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
929 {
930 g_assert(end);
931 g_assert(end->subpath);
932 g_assert(end->p.other);
934 if (end->code == static_cast< guint > ( code ) )
935 return;
937 Inkscape::NodePath::Node *start = end->p.other;
939 end->code = code;
941 if (code == NR_LINETO) {
942 if (start->code == NR_LINETO) {
943 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
944 }
945 if (end->n.other) {
946 if (end->n.other->code == NR_LINETO) {
947 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
948 }
949 }
950 } else {
951 NR::Point delta = end->pos - start->pos;
952 start->n.pos = start->pos + delta / 3;
953 end->p.pos = end->pos - delta / 3;
954 sp_node_adjust_handle(start, 1);
955 sp_node_adjust_handle(end, -1);
956 }
958 sp_node_update_handles(start);
959 sp_node_update_handles(end);
960 }
962 /**
963 * Change node type, and its handles accordingly.
964 */
965 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
966 {
967 g_assert(node);
968 g_assert(node->subpath);
970 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
971 return node;
973 if ((node->p.other != NULL) && (node->n.other != NULL)) {
974 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
975 type =Inkscape::NodePath::NODE_CUSP;
976 }
977 }
979 node->type = type;
981 if (node->type == Inkscape::NodePath::NODE_CUSP) {
982 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
983 node->knot->setSize (node->selected? 11 : 9);
984 sp_knot_update_ctrl(node->knot);
985 } else {
986 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
987 node->knot->setSize (node->selected? 9 : 7);
988 sp_knot_update_ctrl(node->knot);
989 }
991 // if one of handles is mouseovered, preserve its position
992 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
993 sp_node_adjust_handle(node, 1);
994 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
995 sp_node_adjust_handle(node, -1);
996 } else {
997 sp_node_adjust_handles(node);
998 }
1000 sp_node_update_handles(node);
1002 sp_nodepath_update_statusbar(node->subpath->nodepath);
1004 return node;
1005 }
1007 /**
1008 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
1009 * adjacent segments from lines to curves.
1010 */
1011 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1012 {
1013 bool p_line = (node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos);
1014 bool n_line = (node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos);
1016 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1017 if (p_line && n_line) {
1018 // only if both adjacent segments are lines,
1019 // convert both to curves:
1021 node->code = NR_CURVETO;
1022 node->n.other->code = NR_CURVETO;
1024 NR::Point leg_prev = node->pos - node->p.other->pos;
1025 NR::Point leg_next = node->pos - node->n.other->pos;
1027 double norm_leg_prev = L2(leg_prev);
1028 double norm_leg_next = L2(leg_next);
1030 // delta has length 1 and is orthogonal to bisecting line
1031 NR::Point delta;
1032 if (norm_leg_next > 0.0) {
1033 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1034 (&delta)->normalize();
1035 }
1037 if (type == Inkscape::NodePath::NODE_SYMM) {
1038 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1039 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1040 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1041 } else {
1042 // length of handle is proportional to distance to adjacent node
1043 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1044 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1045 }
1047 sp_node_update_handles(node);
1048 }
1049 }
1051 sp_nodepath_set_node_type (node, type);
1052 }
1054 /**
1055 * Move node to point, and adjust its and neighbouring handles.
1056 */
1057 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1058 {
1059 NR::Point delta = p - node->pos;
1060 node->pos = p;
1062 node->p.pos += delta;
1063 node->n.pos += delta;
1065 Inkscape::NodePath::Node *node_p = NULL;
1066 Inkscape::NodePath::Node *node_n = NULL;
1068 if (node->p.other) {
1069 if (node->code == NR_LINETO) {
1070 sp_node_adjust_handle(node, 1);
1071 sp_node_adjust_handle(node->p.other, -1);
1072 node_p = node->p.other;
1073 }
1074 }
1075 if (node->n.other) {
1076 if (node->n.other->code == NR_LINETO) {
1077 sp_node_adjust_handle(node, -1);
1078 sp_node_adjust_handle(node->n.other, 1);
1079 node_n = node->n.other;
1080 }
1081 }
1083 // this function is only called from batch movers that will update display at the end
1084 // themselves, so here we just move all the knots without emitting move signals, for speed
1085 sp_node_update_handles(node, false);
1086 if (node_n) {
1087 sp_node_update_handles(node_n, false);
1088 }
1089 if (node_p) {
1090 sp_node_update_handles(node_p, false);
1091 }
1092 }
1094 /**
1095 * Call sp_node_moveto() for node selection and handle possible snapping.
1096 */
1097 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1098 bool const snap = true)
1099 {
1100 NR::Coord best = NR_HUGE;
1101 NR::Point delta(dx, dy);
1102 NR::Point best_pt = delta;
1103 NR::Point best_abs(NR_HUGE, NR_HUGE);
1106 if (snap) {
1107 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1108 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1109 * must provide that information. */
1111 // Build a list of the unselected nodes to which the snapper should snap
1112 std::vector<NR::Point> unselected_nodes;
1113 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1114 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1115 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1116 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1117 if (!node->selected) {
1118 unselected_nodes.push_back(node->pos);
1119 }
1120 }
1121 }
1123 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1125 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1126 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1127 Inkscape::SnappedPoint s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1128 if (s.getDistance() < best) {
1129 best = s.getDistance();
1130 best_abs = s.getPoint();
1131 best_pt = best_abs - n->pos;
1132 }
1133 }
1135 if (best_abs[NR::X] < NR_HUGE) {
1136 nodepath->desktop->snapindicator->set_new_snappoint(best_abs.to_2geom());
1137 }
1138 }
1140 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1141 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1142 sp_node_moveto(n, n->pos + best_pt);
1143 }
1145 // do not update repr here so that node dragging is acceptably fast
1146 update_object(nodepath);
1147 }
1149 /**
1150 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1151 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1152 near x = 0.
1153 */
1154 double
1155 sculpt_profile (double x, double alpha, guint profile)
1156 {
1157 if (x >= 1)
1158 return 0;
1159 if (x <= 0)
1160 return 1;
1162 switch (profile) {
1163 case SCULPT_PROFILE_LINEAR:
1164 return 1 - x;
1165 case SCULPT_PROFILE_BELL:
1166 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1167 case SCULPT_PROFILE_ELLIPTIC:
1168 return sqrt(1 - x*x);
1169 }
1171 return 1;
1172 }
1174 double
1175 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1176 {
1177 // extremely primitive for now, don't have time to look for the real one
1178 double lower = NR::L2(b - a);
1179 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1180 return (lower + upper)/2;
1181 }
1183 void
1184 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1185 {
1186 n->pos = n->origin + delta;
1187 n->n.pos = n->n.origin + delta_n;
1188 n->p.pos = n->p.origin + delta_p;
1189 sp_node_adjust_handles(n);
1190 sp_node_update_handles(n, false);
1191 }
1193 /**
1194 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1195 * on how far they are from the dragged node n.
1196 */
1197 static void
1198 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1199 {
1200 g_assert (n);
1201 g_assert (nodepath);
1202 g_assert (n->subpath->nodepath == nodepath);
1204 double pressure = n->knot->pressure;
1205 if (pressure == 0)
1206 pressure = 0.5; // default
1207 pressure = CLAMP (pressure, 0.2, 0.8);
1209 // map pressure to alpha = 1/5 ... 5
1210 double alpha = 1 - 2 * fabs(pressure - 0.5);
1211 if (pressure > 0.5)
1212 alpha = 1/alpha;
1214 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1216 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1217 // Only one subpath has selected nodes:
1218 // use linear mode, where the distance from n to node being dragged is calculated along the path
1220 double n_sel_range = 0, p_sel_range = 0;
1221 guint n_nodes = 0, p_nodes = 0;
1222 guint n_sel_nodes = 0, p_sel_nodes = 0;
1224 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1225 {
1226 double n_range = 0, p_range = 0;
1227 bool n_going = true, p_going = true;
1228 Inkscape::NodePath::Node *n_node = n;
1229 Inkscape::NodePath::Node *p_node = n;
1230 do {
1231 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1232 if (n_node && n_going)
1233 n_node = n_node->n.other;
1234 if (n_node == NULL) {
1235 n_going = false;
1236 } else {
1237 n_nodes ++;
1238 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1239 if (n_node->selected) {
1240 n_sel_nodes ++;
1241 n_sel_range = n_range;
1242 }
1243 if (n_node == p_node) {
1244 n_going = false;
1245 p_going = false;
1246 }
1247 }
1248 if (p_node && p_going)
1249 p_node = p_node->p.other;
1250 if (p_node == NULL) {
1251 p_going = false;
1252 } else {
1253 p_nodes ++;
1254 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1255 if (p_node->selected) {
1256 p_sel_nodes ++;
1257 p_sel_range = p_range;
1258 }
1259 if (p_node == n_node) {
1260 n_going = false;
1261 p_going = false;
1262 }
1263 }
1264 } while (n_going || p_going);
1265 }
1267 // Second pass: actually move nodes in this subpath
1268 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1269 {
1270 double n_range = 0, p_range = 0;
1271 bool n_going = true, p_going = true;
1272 Inkscape::NodePath::Node *n_node = n;
1273 Inkscape::NodePath::Node *p_node = n;
1274 do {
1275 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1276 if (n_node && n_going)
1277 n_node = n_node->n.other;
1278 if (n_node == NULL) {
1279 n_going = false;
1280 } else {
1281 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1282 if (n_node->selected) {
1283 sp_nodepath_move_node_and_handles (n_node,
1284 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1285 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1286 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1287 }
1288 if (n_node == p_node) {
1289 n_going = false;
1290 p_going = false;
1291 }
1292 }
1293 if (p_node && p_going)
1294 p_node = p_node->p.other;
1295 if (p_node == NULL) {
1296 p_going = false;
1297 } else {
1298 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1299 if (p_node->selected) {
1300 sp_nodepath_move_node_and_handles (p_node,
1301 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1302 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1303 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1304 }
1305 if (p_node == n_node) {
1306 n_going = false;
1307 p_going = false;
1308 }
1309 }
1310 } while (n_going || p_going);
1311 }
1313 } else {
1314 // Multiple subpaths have selected nodes:
1315 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1316 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1317 // fix the pear-like shape when sculpting e.g. a ring
1319 // First pass: calculate range
1320 gdouble direct_range = 0;
1321 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1322 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1323 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1324 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1325 if (node->selected) {
1326 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1327 }
1328 }
1329 }
1331 // Second pass: actually move nodes
1332 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1333 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1334 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1335 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1336 if (node->selected) {
1337 if (direct_range > 1e-6) {
1338 sp_nodepath_move_node_and_handles (node,
1339 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1340 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1341 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1342 } else {
1343 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1344 }
1346 }
1347 }
1348 }
1349 }
1351 // do not update repr here so that node dragging is acceptably fast
1352 update_object(nodepath);
1353 }
1356 /**
1357 * Move node selection to point, adjust its and neighbouring handles,
1358 * handle possible snapping, and commit the change with possible undo.
1359 */
1360 void
1361 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1362 {
1363 if (!nodepath) return;
1365 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1367 if (dx == 0) {
1368 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1369 } else if (dy == 0) {
1370 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1371 } else {
1372 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1373 }
1374 }
1376 /**
1377 * Move node selection off screen and commit the change.
1378 */
1379 void
1380 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1381 {
1382 // borrowed from sp_selection_move_screen in selection-chemistry.c
1383 // we find out the current zoom factor and divide deltas by it
1384 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1386 gdouble zoom = desktop->current_zoom();
1387 gdouble zdx = dx / zoom;
1388 gdouble zdy = dy / zoom;
1390 if (!nodepath) return;
1392 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1394 if (dx == 0) {
1395 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1396 } else if (dy == 0) {
1397 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1398 } else {
1399 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1400 }
1401 }
1403 /**
1404 * Move selected nodes to the absolute position given
1405 */
1406 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1407 {
1408 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1409 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1410 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1411 sp_node_moveto(n, npos);
1412 }
1414 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1415 }
1417 /**
1418 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1419 */
1420 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1421 {
1422 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1423 g_return_val_if_fail(nodepath->selected, no_coord);
1425 // determine coordinate of first selected node
1426 GList *nsel = nodepath->selected;
1427 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1428 NR::Coord coord = n->pos[axis];
1429 bool coincide = true;
1431 // compare it to the coordinates of all the other selected nodes
1432 for (GList *l = nsel->next; l != NULL; l = l->next) {
1433 n = (Inkscape::NodePath::Node *) l->data;
1434 if (n->pos[axis] != coord) {
1435 coincide = false;
1436 }
1437 }
1438 if (coincide) {
1439 return coord;
1440 } else {
1441 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1442 // currently we return the coordinate of the bounding box midpoint because I don't know how
1443 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1444 return bbox.midpoint()[axis];
1445 }
1446 }
1448 /** If they don't yet exist, creates knot and line for the given side of the node */
1449 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1450 {
1451 if (!side->knot) {
1452 side->knot = sp_knot_new(desktop, _("<b>Node handle</b>: drag to shape the curve; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to lock length; with <b>Shift</b> to rotate both handles"));
1454 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1455 side->knot->setSize (7);
1456 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1457 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1458 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1459 sp_knot_update_ctrl(side->knot);
1461 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1462 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1463 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1464 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1465 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1466 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1467 }
1469 if (!side->line) {
1470 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1471 SP_TYPE_CTRLLINE, NULL);
1472 }
1473 }
1475 /**
1476 * Ensure the given handle of the node is visible/invisible, update its screen position
1477 */
1478 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1479 {
1480 g_assert(node != NULL);
1482 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1483 NRPathcode code = sp_node_path_code_from_side(node, side);
1485 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1487 if (show_handle) {
1488 if (!side->knot) { // No handle knot at all
1489 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1490 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1491 side->knot->pos = side->pos;
1492 if (side->knot->item)
1493 SP_CTRL(side->knot->item)->moveto(side->pos);
1494 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1495 sp_knot_show(side->knot);
1496 } else {
1497 if (side->knot->pos != side->pos) { // only if it's really moved
1498 if (fire_move_signals) {
1499 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1500 } else {
1501 sp_knot_moveto(side->knot, &side->pos);
1502 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1503 }
1504 }
1505 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1506 sp_knot_show(side->knot);
1507 }
1508 }
1509 sp_canvas_item_show(side->line);
1510 } else {
1511 if (side->knot) {
1512 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1513 sp_knot_hide(side->knot);
1514 }
1515 }
1516 if (side->line) {
1517 sp_canvas_item_hide(side->line);
1518 }
1519 }
1520 }
1522 /**
1523 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1524 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1525 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1526 * updated; otherwise, just move the knots silently (used in batch moves).
1527 */
1528 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1529 {
1530 g_assert(node != NULL);
1532 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1533 sp_knot_show(node->knot);
1534 }
1536 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1537 if (fire_move_signals)
1538 sp_knot_set_position(node->knot, &node->pos, 0);
1539 else
1540 sp_knot_moveto(node->knot, &node->pos);
1541 }
1543 gboolean show_handles = node->selected;
1544 if (node->p.other != NULL) {
1545 if (node->p.other->selected) show_handles = TRUE;
1546 }
1547 if (node->n.other != NULL) {
1548 if (node->n.other->selected) show_handles = TRUE;
1549 }
1551 if (node->subpath->nodepath->show_handles == false)
1552 show_handles = FALSE;
1554 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1555 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1556 }
1558 /**
1559 * Call sp_node_update_handles() for all nodes on subpath.
1560 */
1561 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1562 {
1563 g_assert(subpath != NULL);
1565 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1566 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1567 }
1568 }
1570 /**
1571 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1572 */
1573 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1574 {
1575 g_assert(nodepath != NULL);
1577 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1578 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1579 }
1580 }
1582 void
1583 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1584 {
1585 if (nodepath == NULL) return;
1587 nodepath->show_handles = show;
1588 sp_nodepath_update_handles(nodepath);
1589 }
1591 /**
1592 * Adds all selected nodes in nodepath to list.
1593 */
1594 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1595 {
1596 StlConv<Node *>::list(l, selected);
1597 /// \todo this adds a copying, rework when the selection becomes a stl list
1598 }
1600 /**
1601 * Align selected nodes on the specified axis.
1602 */
1603 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1604 {
1605 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1606 return;
1607 }
1609 if ( !nodepath->selected->next ) { // only one node selected
1610 return;
1611 }
1612 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1613 NR::Point dest(pNode->pos);
1614 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1615 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1616 if (pNode) {
1617 dest[axis] = pNode->pos[axis];
1618 sp_node_moveto(pNode, dest);
1619 }
1620 }
1622 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1623 }
1625 /// Helper struct.
1626 struct NodeSort
1627 {
1628 Inkscape::NodePath::Node *_node;
1629 NR::Coord _coord;
1630 /// \todo use vectorof pointers instead of calling copy ctor
1631 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1632 _node(node), _coord(node->pos[axis])
1633 {}
1635 };
1637 static bool operator<(NodeSort const &a, NodeSort const &b)
1638 {
1639 return (a._coord < b._coord);
1640 }
1642 /**
1643 * Distribute selected nodes on the specified axis.
1644 */
1645 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1646 {
1647 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1648 return;
1649 }
1651 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1652 return;
1653 }
1655 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1656 std::vector<NodeSort> sorted;
1657 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1658 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1659 if (pNode) {
1660 NodeSort n(pNode, axis);
1661 sorted.push_back(n);
1662 //dest[axis] = pNode->pos[axis];
1663 //sp_node_moveto(pNode, dest);
1664 }
1665 }
1666 std::sort(sorted.begin(), sorted.end());
1667 unsigned int len = sorted.size();
1668 //overall bboxes span
1669 float dist = (sorted.back()._coord -
1670 sorted.front()._coord);
1671 //new distance between each bbox
1672 float step = (dist) / (len - 1);
1673 float pos = sorted.front()._coord;
1674 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1675 it < sorted.end();
1676 it ++ )
1677 {
1678 NR::Point dest((*it)._node->pos);
1679 dest[axis] = pos;
1680 sp_node_moveto((*it)._node, dest);
1681 pos += step;
1682 }
1684 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1685 }
1688 /**
1689 * Call sp_nodepath_line_add_node() for all selected segments.
1690 */
1691 void
1692 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1693 {
1694 if (!nodepath) {
1695 return;
1696 }
1698 GList *nl = NULL;
1700 int n_added = 0;
1702 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1703 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1704 g_assert(t->selected);
1705 if (t->p.other && t->p.other->selected) {
1706 nl = g_list_prepend(nl, t);
1707 }
1708 }
1710 while (nl) {
1711 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1712 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1713 sp_nodepath_node_select(n, TRUE, FALSE);
1714 n_added ++;
1715 nl = g_list_remove(nl, t);
1716 }
1718 /** \todo fixme: adjust ? */
1719 sp_nodepath_update_handles(nodepath);
1721 if (n_added > 1) {
1722 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1723 } else if (n_added > 0) {
1724 sp_nodepath_update_repr(nodepath, _("Add node"));
1725 }
1727 sp_nodepath_update_statusbar(nodepath);
1728 }
1730 /**
1731 * Select segment nearest to point
1732 */
1733 void
1734 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1735 {
1736 if (!nodepath) {
1737 return;
1738 }
1740 sp_nodepath_ensure_livarot_path(nodepath);
1741 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1742 if (!maybe_position) {
1743 return;
1744 }
1745 Path::cut_position position = *maybe_position;
1747 //find segment to segment
1748 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1750 //fixme: this can return NULL, so check before proceeding.
1751 g_return_if_fail(e != NULL);
1753 gboolean force = FALSE;
1754 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1755 force = TRUE;
1756 }
1757 sp_nodepath_node_select(e, (gboolean) toggle, force);
1758 if (e->p.other)
1759 sp_nodepath_node_select(e->p.other, TRUE, force);
1761 sp_nodepath_update_handles(nodepath);
1763 sp_nodepath_update_statusbar(nodepath);
1764 }
1766 /**
1767 * Add a node nearest to point
1768 */
1769 void
1770 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1771 {
1772 if (!nodepath) {
1773 return;
1774 }
1776 sp_nodepath_ensure_livarot_path(nodepath);
1777 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1778 if (!maybe_position) {
1779 return;
1780 }
1781 Path::cut_position position = *maybe_position;
1783 //find segment to split
1784 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1786 //don't know why but t seems to flip for lines
1787 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1788 position.t = 1.0 - position.t;
1789 }
1790 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1791 sp_nodepath_node_select(n, FALSE, TRUE);
1793 /* fixme: adjust ? */
1794 sp_nodepath_update_handles(nodepath);
1796 sp_nodepath_update_repr(nodepath, _("Add node"));
1798 sp_nodepath_update_statusbar(nodepath);
1799 }
1801 /*
1802 * Adjusts a segment so that t moves by a certain delta for dragging
1803 * converts lines to curves
1804 *
1805 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1806 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1807 */
1808 void
1809 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1810 {
1811 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1813 //fixme: e and e->p can be NULL, so check for those before proceeding
1814 g_return_if_fail(e != NULL);
1815 g_return_if_fail(&e->p != NULL);
1817 /* feel good is an arbitrary parameter that distributes the delta between handles
1818 * if t of the drag point is less than 1/6 distance form the endpoint only
1819 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1820 */
1821 double feel_good;
1822 if (t <= 1.0 / 6.0)
1823 feel_good = 0;
1824 else if (t <= 0.5)
1825 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1826 else if (t <= 5.0 / 6.0)
1827 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1828 else
1829 feel_good = 1;
1831 //if we're dragging a line convert it to a curve
1832 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1833 sp_nodepath_set_line_type(e, NR_CURVETO);
1834 }
1836 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1837 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1838 e->p.other->n.pos += offsetcoord0;
1839 e->p.pos += offsetcoord1;
1841 // adjust handles of adjacent nodes where necessary
1842 sp_node_adjust_handle(e,1);
1843 sp_node_adjust_handle(e->p.other,-1);
1845 sp_nodepath_update_handles(e->subpath->nodepath);
1847 update_object(e->subpath->nodepath);
1849 sp_nodepath_update_statusbar(e->subpath->nodepath);
1850 }
1853 /**
1854 * Call sp_nodepath_break() for all selected segments.
1855 */
1856 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1857 {
1858 if (!nodepath) return;
1860 GList *temp = NULL;
1861 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1862 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1863 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1864 if (nn == NULL) continue; // no break, no new node
1865 temp = g_list_prepend(temp, nn);
1866 }
1868 if (temp) {
1869 sp_nodepath_deselect(nodepath);
1870 }
1871 for (GList *l = temp; l != NULL; l = l->next) {
1872 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1873 }
1875 sp_nodepath_update_handles(nodepath);
1877 sp_nodepath_update_repr(nodepath, _("Break path"));
1878 }
1880 /**
1881 * Duplicate the selected node(s).
1882 */
1883 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
1884 {
1885 if (!nodepath) {
1886 return;
1887 }
1889 GList *temp = NULL;
1890 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1891 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1892 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1893 if (nn == NULL) continue; // could not duplicate
1894 temp = g_list_prepend(temp, nn);
1895 }
1897 if (temp) {
1898 sp_nodepath_deselect(nodepath);
1899 }
1900 for (GList *l = temp; l != NULL; l = l->next) {
1901 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1902 }
1904 sp_nodepath_update_handles(nodepath);
1906 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1907 }
1909 /**
1910 * Internal function to join two nodes by merging them into one.
1911 */
1912 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
1913 {
1914 /* a and b are endpoints */
1916 NR::Point c;
1917 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1918 c = a->pos;
1919 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1920 c = b->pos;
1921 } else {
1922 c = (a->pos + b->pos) / 2;
1923 }
1925 if (a->subpath == b->subpath) {
1926 Inkscape::NodePath::SubPath *sp = a->subpath;
1927 sp_nodepath_subpath_close(sp);
1928 sp_node_moveto (sp->first, c);
1930 sp_nodepath_update_handles(sp->nodepath);
1931 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1932 return;
1933 }
1935 /* a and b are separate subpaths */
1936 Inkscape::NodePath::SubPath *sa = a->subpath;
1937 Inkscape::NodePath::SubPath *sb = b->subpath;
1938 NR::Point p;
1939 Inkscape::NodePath::Node *n;
1940 NRPathcode code;
1941 if (a == sa->first) {
1942 p = sa->first->n.pos;
1943 code = (NRPathcode)sa->first->n.other->code;
1944 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1945 n = sa->last;
1946 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1947 n = n->p.other;
1948 while (n) {
1949 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1950 n = n->p.other;
1951 if (n == sa->first) n = NULL;
1952 }
1953 sp_nodepath_subpath_destroy(sa);
1954 sa = t;
1955 } else if (a == sa->last) {
1956 p = sa->last->p.pos;
1957 code = (NRPathcode)sa->last->code;
1958 sp_nodepath_node_destroy(sa->last);
1959 } else {
1960 code = NR_END;
1961 g_assert_not_reached();
1962 }
1964 if (b == sb->first) {
1965 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1966 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1967 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1968 }
1969 } else if (b == sb->last) {
1970 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1971 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1972 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1973 }
1974 } else {
1975 g_assert_not_reached();
1976 }
1977 /* and now destroy sb */
1979 sp_nodepath_subpath_destroy(sb);
1981 sp_nodepath_update_handles(sa->nodepath);
1983 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1985 sp_nodepath_update_statusbar(nodepath);
1986 }
1988 /**
1989 * Internal function to join two nodes by adding a segment between them.
1990 */
1991 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
1992 {
1993 if (a->subpath == b->subpath) {
1994 Inkscape::NodePath::SubPath *sp = a->subpath;
1996 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1997 sp->closed = TRUE;
1999 sp->first->p.other = sp->last;
2000 sp->last->n.other = sp->first;
2002 sp_node_handle_mirror_p_to_n(sp->last);
2003 sp_node_handle_mirror_n_to_p(sp->first);
2005 sp->first->code = sp->last->code;
2006 sp->first = sp->last;
2008 sp_nodepath_update_handles(sp->nodepath);
2010 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2012 return;
2013 }
2015 /* a and b are separate subpaths */
2016 Inkscape::NodePath::SubPath *sa = a->subpath;
2017 Inkscape::NodePath::SubPath *sb = b->subpath;
2019 Inkscape::NodePath::Node *n;
2020 NR::Point p;
2021 NRPathcode code;
2022 if (a == sa->first) {
2023 code = (NRPathcode) sa->first->n.other->code;
2024 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2025 n = sa->last;
2026 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2027 for (n = n->p.other; n != NULL; n = n->p.other) {
2028 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2029 }
2030 sp_nodepath_subpath_destroy(sa);
2031 sa = t;
2032 } else if (a == sa->last) {
2033 code = (NRPathcode)sa->last->code;
2034 } else {
2035 code = NR_END;
2036 g_assert_not_reached();
2037 }
2039 if (b == sb->first) {
2040 n = sb->first;
2041 sp_node_handle_mirror_p_to_n(sa->last);
2042 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2043 sp_node_handle_mirror_n_to_p(sa->last);
2044 for (n = n->n.other; n != NULL; n = n->n.other) {
2045 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2046 }
2047 } else if (b == sb->last) {
2048 n = sb->last;
2049 sp_node_handle_mirror_p_to_n(sa->last);
2050 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2051 sp_node_handle_mirror_n_to_p(sa->last);
2052 for (n = n->p.other; n != NULL; n = n->p.other) {
2053 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2054 }
2055 } else {
2056 g_assert_not_reached();
2057 }
2058 /* and now destroy sb */
2060 sp_nodepath_subpath_destroy(sb);
2062 sp_nodepath_update_handles(sa->nodepath);
2064 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2065 }
2067 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2069 /**
2070 * Internal function to handle joining two nodes.
2071 */
2072 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2073 {
2074 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2076 if (g_list_length(nodepath->selected) != 2) {
2077 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2078 return;
2079 }
2081 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2082 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2084 g_assert(a != b);
2085 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2086 // someone tried to join an orphan node (i.e. a single-node subpath).
2087 // this is not worth an error message, just fail silently.
2088 return;
2089 }
2091 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2092 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2093 return;
2094 }
2096 switch(mode) {
2097 case NODE_JOIN_ENDPOINTS:
2098 do_node_selected_join(nodepath, a, b);
2099 break;
2100 case NODE_JOIN_SEGMENT:
2101 do_node_selected_join_segment(nodepath, a, b);
2102 break;
2103 }
2104 }
2106 /**
2107 * Join two nodes by merging them into one.
2108 */
2109 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2110 {
2111 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2112 }
2114 /**
2115 * Join two nodes by adding a segment between them.
2116 */
2117 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2118 {
2119 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2120 }
2122 /**
2123 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2124 */
2125 void sp_node_delete_preserve(GList *nodes_to_delete)
2126 {
2127 GSList *nodepaths = NULL;
2129 while (nodes_to_delete) {
2130 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2131 Inkscape::NodePath::SubPath *sp = node->subpath;
2132 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2133 Inkscape::NodePath::Node *sample_cursor = NULL;
2134 Inkscape::NodePath::Node *sample_end = NULL;
2135 Inkscape::NodePath::Node *delete_cursor = node;
2136 bool just_delete = false;
2138 //find the start of this contiguous selection
2139 //move left to the first node that is not selected
2140 //or the start of the non-closed path
2141 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2142 delete_cursor = curr;
2143 }
2145 //just delete at the beginning of an open path
2146 if (!delete_cursor->p.other) {
2147 sample_cursor = delete_cursor;
2148 just_delete = true;
2149 } else {
2150 sample_cursor = delete_cursor->p.other;
2151 }
2153 //calculate points for each segment
2154 int rate = 5;
2155 float period = 1.0 / rate;
2156 std::vector<NR::Point> data;
2157 if (!just_delete) {
2158 data.push_back(sample_cursor->pos);
2159 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2160 //just delete at the end of an open path
2161 if (!sp->closed && curr == sp->last) {
2162 just_delete = true;
2163 break;
2164 }
2166 //sample points on the contiguous selected segment
2167 NR::Point *bez;
2168 bez = new NR::Point [4];
2169 bez[0] = curr->pos;
2170 bez[1] = curr->n.pos;
2171 bez[2] = curr->n.other->p.pos;
2172 bez[3] = curr->n.other->pos;
2173 for (int i=1; i<rate; i++) {
2174 gdouble t = i * period;
2175 NR::Point p = bezier_pt(3, bez, t);
2176 data.push_back(p);
2177 }
2178 data.push_back(curr->n.other->pos);
2180 sample_end = curr->n.other;
2181 //break if we've come full circle or hit the end of the selection
2182 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2183 break;
2184 }
2185 }
2186 }
2188 if (!just_delete) {
2189 //calculate the best fitting single segment and adjust the endpoints
2190 NR::Point *adata;
2191 adata = new NR::Point [data.size()];
2192 copy(data.begin(), data.end(), adata);
2194 NR::Point *bez;
2195 bez = new NR::Point [4];
2196 //would decreasing error create a better fitting approximation?
2197 gdouble error = 1.0;
2198 gint ret;
2199 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2201 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2202 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2203 //the resulting nodes behave as expected.
2204 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2205 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2207 //adjust endpoints
2208 sample_cursor->n.pos = bez[1];
2209 sample_end->p.pos = bez[2];
2210 }
2212 //destroy this contiguous selection
2213 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2214 Inkscape::NodePath::Node *temp = delete_cursor;
2215 if (delete_cursor->n.other == delete_cursor) {
2216 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2217 delete_cursor = NULL;
2218 } else {
2219 delete_cursor = delete_cursor->n.other;
2220 }
2221 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2222 sp_nodepath_node_destroy(temp);
2223 }
2225 sp_nodepath_update_handles(nodepath);
2227 if (!g_slist_find(nodepaths, nodepath))
2228 nodepaths = g_slist_prepend (nodepaths, nodepath);
2229 }
2231 for (GSList *i = nodepaths; i; i = i->next) {
2232 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2233 // different nodepaths will give us one undo event per nodepath
2234 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2236 // if the entire nodepath is removed, delete the selected object.
2237 if (nodepath->subpaths == NULL ||
2238 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2239 //at least 2
2240 sp_nodepath_get_node_count(nodepath) < 2) {
2241 SPDocument *document = sp_desktop_document (nodepath->desktop);
2242 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2243 //delete this nodepath's object, not the entire selection! (though at this time, this
2244 //does not matter)
2245 sp_selection_delete();
2246 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2247 _("Delete nodes"));
2248 } else {
2249 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2250 sp_nodepath_update_statusbar(nodepath);
2251 }
2252 }
2254 g_slist_free (nodepaths);
2255 }
2257 /**
2258 * Delete one or more selected nodes.
2259 */
2260 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2261 {
2262 if (!nodepath) return;
2263 if (!nodepath->selected) return;
2265 /** \todo fixme: do it the right way */
2266 while (nodepath->selected) {
2267 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2268 sp_nodepath_node_destroy(node);
2269 }
2272 //clean up the nodepath (such as for trivial subpaths)
2273 sp_nodepath_cleanup(nodepath);
2275 sp_nodepath_update_handles(nodepath);
2277 // if the entire nodepath is removed, delete the selected object.
2278 if (nodepath->subpaths == NULL ||
2279 sp_nodepath_get_node_count(nodepath) < 2) {
2280 SPDocument *document = sp_desktop_document (nodepath->desktop);
2281 sp_selection_delete();
2282 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2283 _("Delete nodes"));
2284 return;
2285 }
2287 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2289 sp_nodepath_update_statusbar(nodepath);
2290 }
2292 /**
2293 * Delete one or more segments between two selected nodes.
2294 * This is the code for 'split'.
2295 */
2296 void
2297 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2298 {
2299 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2300 Inkscape::NodePath::Node *curr, *next; //Iterators
2302 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2304 if (g_list_length(nodepath->selected) != 2) {
2305 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2306 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2307 return;
2308 }
2310 //Selected nodes, not inclusive
2311 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2312 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2314 if ( ( a==b) || //same node
2315 (a->subpath != b->subpath ) || //not the same path
2316 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2317 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2318 {
2319 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2320 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2321 return;
2322 }
2324 //###########################################
2325 //# BEGIN EDITS
2326 //###########################################
2327 //##################################
2328 //# CLOSED PATH
2329 //##################################
2330 if (a->subpath->closed) {
2333 gboolean reversed = FALSE;
2335 //Since we can go in a circle, we need to find the shorter distance.
2336 // a->b or b->a
2337 start = end = NULL;
2338 int distance = 0;
2339 int minDistance = 0;
2340 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2341 if (curr==b) {
2342 //printf("a to b:%d\n", distance);
2343 start = a;//go from a to b
2344 end = b;
2345 minDistance = distance;
2346 //printf("A to B :\n");
2347 break;
2348 }
2349 distance++;
2350 }
2352 //try again, the other direction
2353 distance = 0;
2354 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2355 if (curr==a) {
2356 //printf("b to a:%d\n", distance);
2357 if (distance < minDistance) {
2358 start = b; //we go from b to a
2359 end = a;
2360 reversed = TRUE;
2361 //printf("B to A\n");
2362 }
2363 break;
2364 }
2365 distance++;
2366 }
2369 //Copy everything from 'end' to 'start' to a new subpath
2370 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2371 for (curr=end ; curr ; curr=curr->n.other) {
2372 NRPathcode code = (NRPathcode) curr->code;
2373 if (curr == end)
2374 code = NR_MOVETO;
2375 sp_nodepath_node_new(t, NULL,
2376 (Inkscape::NodePath::NodeType)curr->type, code,
2377 &curr->p.pos, &curr->pos, &curr->n.pos);
2378 if (curr == start)
2379 break;
2380 }
2381 sp_nodepath_subpath_destroy(a->subpath);
2384 }
2388 //##################################
2389 //# OPEN PATH
2390 //##################################
2391 else {
2393 //We need to get the direction of the list between A and B
2394 //Can we walk from a to b?
2395 start = end = NULL;
2396 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2397 if (curr==b) {
2398 start = a; //did it! we go from a to b
2399 end = b;
2400 //printf("A to B\n");
2401 break;
2402 }
2403 }
2404 if (!start) {//didn't work? let's try the other direction
2405 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2406 if (curr==a) {
2407 start = b; //did it! we go from b to a
2408 end = a;
2409 //printf("B to A\n");
2410 break;
2411 }
2412 }
2413 }
2414 if (!start) {
2415 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2416 _("Cannot find path between nodes."));
2417 return;
2418 }
2422 //Copy everything after 'end' to a new subpath
2423 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2424 for (curr=end ; curr ; curr=curr->n.other) {
2425 NRPathcode code = (NRPathcode) curr->code;
2426 if (curr == end)
2427 code = NR_MOVETO;
2428 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2429 &curr->p.pos, &curr->pos, &curr->n.pos);
2430 }
2432 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2433 for (curr = start->n.other ; curr ; curr=next) {
2434 next = curr->n.other;
2435 sp_nodepath_node_destroy(curr);
2436 }
2438 }
2439 //###########################################
2440 //# END EDITS
2441 //###########################################
2443 //clean up the nodepath (such as for trivial subpaths)
2444 sp_nodepath_cleanup(nodepath);
2446 sp_nodepath_update_handles(nodepath);
2448 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2450 sp_nodepath_update_statusbar(nodepath);
2451 }
2453 /**
2454 * Call sp_nodepath_set_line() for all selected segments.
2455 */
2456 void
2457 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2458 {
2459 if (nodepath == NULL) return;
2461 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2462 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2463 g_assert(n->selected);
2464 if (n->p.other && n->p.other->selected) {
2465 sp_nodepath_set_line_type(n, code);
2466 }
2467 }
2469 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2470 }
2472 /**
2473 * Call sp_nodepath_convert_node_type() for all selected nodes.
2474 */
2475 void
2476 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2477 {
2478 if (nodepath == NULL) return;
2480 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2482 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2483 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2484 }
2486 sp_nodepath_update_repr(nodepath, _("Change node type"));
2487 }
2489 /**
2490 * Change select status of node, update its own and neighbour handles.
2491 */
2492 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2493 {
2494 node->selected = selected;
2496 if (selected) {
2497 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2498 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2499 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2500 sp_knot_update_ctrl(node->knot);
2501 } else {
2502 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2503 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2504 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2505 sp_knot_update_ctrl(node->knot);
2506 }
2508 sp_node_update_handles(node);
2509 if (node->n.other) sp_node_update_handles(node->n.other);
2510 if (node->p.other) sp_node_update_handles(node->p.other);
2511 }
2513 /**
2514 \brief Select a node
2515 \param node The node to select
2516 \param incremental If true, add to selection, otherwise deselect others
2517 \param override If true, always select this node, otherwise toggle selected status
2518 */
2519 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2520 {
2521 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2523 if (incremental) {
2524 if (override) {
2525 if (!g_list_find(nodepath->selected, node)) {
2526 nodepath->selected = g_list_prepend(nodepath->selected, node);
2527 }
2528 sp_node_set_selected(node, TRUE);
2529 } else { // toggle
2530 if (node->selected) {
2531 g_assert(g_list_find(nodepath->selected, node));
2532 nodepath->selected = g_list_remove(nodepath->selected, node);
2533 } else {
2534 g_assert(!g_list_find(nodepath->selected, node));
2535 nodepath->selected = g_list_prepend(nodepath->selected, node);
2536 }
2537 sp_node_set_selected(node, !node->selected);
2538 }
2539 } else {
2540 sp_nodepath_deselect(nodepath);
2541 nodepath->selected = g_list_prepend(nodepath->selected, node);
2542 sp_node_set_selected(node, TRUE);
2543 }
2545 sp_nodepath_update_statusbar(nodepath);
2546 }
2549 /**
2550 \brief Deselect all nodes in the nodepath
2551 */
2552 void
2553 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2554 {
2555 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2557 while (nodepath->selected) {
2558 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2559 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2560 }
2561 sp_nodepath_update_statusbar(nodepath);
2562 }
2564 /**
2565 \brief Select or invert selection of all nodes in the nodepath
2566 */
2567 void
2568 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2569 {
2570 if (!nodepath) return;
2572 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2573 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2574 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2575 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2576 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2577 }
2578 }
2579 }
2581 /**
2582 * If nothing selected, does the same as sp_nodepath_select_all();
2583 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2584 * (i.e., similar to "select all in layer", with the "selected" subpaths
2585 * being treated as "layers" in the path).
2586 */
2587 void
2588 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2589 {
2590 if (!nodepath) return;
2592 if (g_list_length (nodepath->selected) == 0) {
2593 sp_nodepath_select_all (nodepath, invert);
2594 return;
2595 }
2597 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2598 GSList *subpaths = NULL;
2600 for (GList *l = copy; l != NULL; l = l->next) {
2601 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2602 Inkscape::NodePath::SubPath *subpath = n->subpath;
2603 if (!g_slist_find (subpaths, subpath))
2604 subpaths = g_slist_prepend (subpaths, subpath);
2605 }
2607 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2608 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2609 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2610 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2611 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2612 }
2613 }
2615 g_slist_free (subpaths);
2616 g_list_free (copy);
2617 }
2619 /**
2620 * \brief Select the node after the last selected; if none is selected,
2621 * select the first within path.
2622 */
2623 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2624 {
2625 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2627 Inkscape::NodePath::Node *last = NULL;
2628 if (nodepath->selected) {
2629 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2630 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2631 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2632 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2633 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2634 if (node->selected) {
2635 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2636 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2637 if (spl->next) { // there's a next subpath
2638 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2639 last = subpath_next->first;
2640 } else if (spl->prev) { // there's a previous subpath
2641 last = NULL; // to be set later to the first node of first subpath
2642 } else {
2643 last = node->n.other;
2644 }
2645 } else {
2646 last = node->n.other;
2647 }
2648 } else {
2649 if (node->n.other) {
2650 last = node->n.other;
2651 } else {
2652 if (spl->next) { // there's a next subpath
2653 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2654 last = subpath_next->first;
2655 } else if (spl->prev) { // there's a previous subpath
2656 last = NULL; // to be set later to the first node of first subpath
2657 } else {
2658 last = (Inkscape::NodePath::Node *) subpath->first;
2659 }
2660 }
2661 }
2662 }
2663 }
2664 }
2665 sp_nodepath_deselect(nodepath);
2666 }
2668 if (last) { // there's at least one more node after selected
2669 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2670 } else { // no more nodes, select the first one in first subpath
2671 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2672 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2673 }
2674 }
2676 /**
2677 * \brief Select the node before the first selected; if none is selected,
2678 * select the last within path
2679 */
2680 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2681 {
2682 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2684 Inkscape::NodePath::Node *last = NULL;
2685 if (nodepath->selected) {
2686 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2687 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2688 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2689 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2690 if (node->selected) {
2691 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2692 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2693 if (spl->prev) { // there's a prev subpath
2694 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2695 last = subpath_prev->last;
2696 } else if (spl->next) { // there's a next subpath
2697 last = NULL; // to be set later to the last node of last subpath
2698 } else {
2699 last = node->p.other;
2700 }
2701 } else {
2702 last = node->p.other;
2703 }
2704 } else {
2705 if (node->p.other) {
2706 last = node->p.other;
2707 } else {
2708 if (spl->prev) { // there's a prev subpath
2709 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2710 last = subpath_prev->last;
2711 } else if (spl->next) { // there's a next subpath
2712 last = NULL; // to be set later to the last node of last subpath
2713 } else {
2714 last = (Inkscape::NodePath::Node *) subpath->last;
2715 }
2716 }
2717 }
2718 }
2719 }
2720 }
2721 sp_nodepath_deselect(nodepath);
2722 }
2724 if (last) { // there's at least one more node before selected
2725 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2726 } else { // no more nodes, select the last one in last subpath
2727 GList *spl = g_list_last(nodepath->subpaths);
2728 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2729 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2730 }
2731 }
2733 /**
2734 * \brief Select all nodes that are within the rectangle.
2735 */
2736 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2737 {
2738 if (!incremental) {
2739 sp_nodepath_deselect(nodepath);
2740 }
2742 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2743 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2744 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2745 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2747 if (b.contains(node->pos)) {
2748 sp_nodepath_node_select(node, TRUE, TRUE);
2749 }
2750 }
2751 }
2752 }
2755 void
2756 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2757 {
2758 g_assert (n);
2759 g_assert (nodepath);
2760 g_assert (n->subpath->nodepath == nodepath);
2762 if (g_list_length (nodepath->selected) == 0) {
2763 if (grow > 0) {
2764 sp_nodepath_node_select(n, TRUE, TRUE);
2765 }
2766 return;
2767 }
2769 if (g_list_length (nodepath->selected) == 1) {
2770 if (grow < 0) {
2771 sp_nodepath_deselect (nodepath);
2772 return;
2773 }
2774 }
2776 double n_sel_range = 0, p_sel_range = 0;
2777 Inkscape::NodePath::Node *farthest_n_node = n;
2778 Inkscape::NodePath::Node *farthest_p_node = n;
2780 // Calculate ranges
2781 {
2782 double n_range = 0, p_range = 0;
2783 bool n_going = true, p_going = true;
2784 Inkscape::NodePath::Node *n_node = n;
2785 Inkscape::NodePath::Node *p_node = n;
2786 do {
2787 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2788 if (n_node && n_going)
2789 n_node = n_node->n.other;
2790 if (n_node == NULL) {
2791 n_going = false;
2792 } else {
2793 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2794 if (n_node->selected) {
2795 n_sel_range = n_range;
2796 farthest_n_node = n_node;
2797 }
2798 if (n_node == p_node) {
2799 n_going = false;
2800 p_going = false;
2801 }
2802 }
2803 if (p_node && p_going)
2804 p_node = p_node->p.other;
2805 if (p_node == NULL) {
2806 p_going = false;
2807 } else {
2808 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2809 if (p_node->selected) {
2810 p_sel_range = p_range;
2811 farthest_p_node = p_node;
2812 }
2813 if (p_node == n_node) {
2814 n_going = false;
2815 p_going = false;
2816 }
2817 }
2818 } while (n_going || p_going);
2819 }
2821 if (grow > 0) {
2822 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2823 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2824 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2825 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2826 }
2827 } else {
2828 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2829 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2830 } else if (farthest_p_node && farthest_p_node->selected) {
2831 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2832 }
2833 }
2834 }
2836 void
2837 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2838 {
2839 g_assert (n);
2840 g_assert (nodepath);
2841 g_assert (n->subpath->nodepath == nodepath);
2843 if (g_list_length (nodepath->selected) == 0) {
2844 if (grow > 0) {
2845 sp_nodepath_node_select(n, TRUE, TRUE);
2846 }
2847 return;
2848 }
2850 if (g_list_length (nodepath->selected) == 1) {
2851 if (grow < 0) {
2852 sp_nodepath_deselect (nodepath);
2853 return;
2854 }
2855 }
2857 Inkscape::NodePath::Node *farthest_selected = NULL;
2858 double farthest_dist = 0;
2860 Inkscape::NodePath::Node *closest_unselected = NULL;
2861 double closest_dist = NR_HUGE;
2863 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2864 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2865 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2866 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2867 if (node == n)
2868 continue;
2869 if (node->selected) {
2870 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2871 farthest_dist = NR::L2(node->pos - n->pos);
2872 farthest_selected = node;
2873 }
2874 } else {
2875 if (NR::L2(node->pos - n->pos) < closest_dist) {
2876 closest_dist = NR::L2(node->pos - n->pos);
2877 closest_unselected = node;
2878 }
2879 }
2880 }
2881 }
2883 if (grow > 0) {
2884 if (closest_unselected) {
2885 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2886 }
2887 } else {
2888 if (farthest_selected) {
2889 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2890 }
2891 }
2892 }
2895 /**
2896 \brief Saves all nodes' and handles' current positions in their origin members
2897 */
2898 void
2899 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2900 {
2901 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2902 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2903 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2904 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2905 n->origin = n->pos;
2906 n->p.origin = n->p.pos;
2907 n->n.origin = n->n.pos;
2908 }
2909 }
2910 }
2912 /**
2913 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2914 */
2915 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2916 {
2917 if (!nodepath->selected) {
2918 return NULL;
2919 }
2921 GList *r = NULL;
2922 guint i = 0;
2923 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2924 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2925 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2926 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2927 i++;
2928 if (node->selected) {
2929 r = g_list_append(r, GINT_TO_POINTER(i));
2930 }
2931 }
2932 }
2933 return r;
2934 }
2936 /**
2937 \brief Restores selection by selecting nodes whose positions are in the list
2938 */
2939 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2940 {
2941 sp_nodepath_deselect(nodepath);
2943 guint i = 0;
2944 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2945 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2946 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2947 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2948 i++;
2949 if (g_list_find(r, GINT_TO_POINTER(i))) {
2950 sp_nodepath_node_select(node, TRUE, TRUE);
2951 }
2952 }
2953 }
2955 }
2957 /**
2958 \brief Adjusts handle according to node type and line code.
2959 */
2960 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2961 {
2962 double len, otherlen, linelen;
2964 g_assert(node);
2966 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2967 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2969 /** \todo fixme: */
2970 if (me->other == NULL) return;
2971 if (other->other == NULL) return;
2973 /* I have line */
2975 NRPathcode mecode, ocode;
2976 if (which_adjust == 1) {
2977 mecode = (NRPathcode)me->other->code;
2978 ocode = (NRPathcode)node->code;
2979 } else {
2980 mecode = (NRPathcode)node->code;
2981 ocode = (NRPathcode)other->other->code;
2982 }
2984 if (mecode == NR_LINETO) return;
2986 /* I am curve */
2988 if (other->other == NULL) return;
2990 /* Other has line */
2992 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2994 NR::Point delta;
2995 if (ocode == NR_LINETO) {
2996 /* other is lineto, we are either smooth or symm */
2997 Inkscape::NodePath::Node *othernode = other->other;
2998 len = NR::L2(me->pos - node->pos);
2999 delta = node->pos - othernode->pos;
3000 linelen = NR::L2(delta);
3001 if (linelen < 1e-18)
3002 return;
3003 me->pos = node->pos + (len / linelen)*delta;
3004 return;
3005 }
3007 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3009 me->pos = 2 * node->pos - other->pos;
3010 return;
3011 }
3013 /* We are smooth */
3015 len = NR::L2(me->pos - node->pos);
3016 delta = other->pos - node->pos;
3017 otherlen = NR::L2(delta);
3018 if (otherlen < 1e-18) return;
3020 me->pos = node->pos - (len / otherlen) * delta;
3021 }
3023 /**
3024 \brief Adjusts both handles according to node type and line code
3025 */
3026 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3027 {
3028 g_assert(node);
3030 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3032 /* we are either smooth or symm */
3034 if (node->p.other == NULL) return;
3036 if (node->n.other == NULL) return;
3038 if (node->code == NR_LINETO) {
3039 if (node->n.other->code == NR_LINETO) return;
3040 sp_node_adjust_handle(node, 1);
3041 return;
3042 }
3044 if (node->n.other->code == NR_LINETO) {
3045 if (node->code == NR_LINETO) return;
3046 sp_node_adjust_handle(node, -1);
3047 return;
3048 }
3050 /* both are curves */
3051 NR::Point const delta( node->n.pos - node->p.pos );
3053 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3054 node->p.pos = node->pos - delta / 2;
3055 node->n.pos = node->pos + delta / 2;
3056 return;
3057 }
3059 /* We are smooth */
3060 double plen = NR::L2(node->p.pos - node->pos);
3061 if (plen < 1e-18) return;
3062 double nlen = NR::L2(node->n.pos - node->pos);
3063 if (nlen < 1e-18) return;
3064 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3065 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3066 }
3068 /**
3069 * Node event callback.
3070 */
3071 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3072 {
3073 gboolean ret = FALSE;
3074 switch (event->type) {
3075 case GDK_ENTER_NOTIFY:
3076 Inkscape::NodePath::Path::active_node = n;
3077 break;
3078 case GDK_LEAVE_NOTIFY:
3079 Inkscape::NodePath::Path::active_node = NULL;
3080 break;
3081 case GDK_SCROLL:
3082 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3083 switch (event->scroll.direction) {
3084 case GDK_SCROLL_UP:
3085 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3086 break;
3087 case GDK_SCROLL_DOWN:
3088 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3089 break;
3090 default:
3091 break;
3092 }
3093 ret = TRUE;
3094 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3095 switch (event->scroll.direction) {
3096 case GDK_SCROLL_UP:
3097 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3098 break;
3099 case GDK_SCROLL_DOWN:
3100 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3101 break;
3102 default:
3103 break;
3104 }
3105 ret = TRUE;
3106 }
3107 break;
3108 case GDK_KEY_PRESS:
3109 switch (get_group0_keyval (&event->key)) {
3110 case GDK_space:
3111 if (event->key.state & GDK_BUTTON1_MASK) {
3112 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3113 stamp_repr(nodepath);
3114 ret = TRUE;
3115 }
3116 break;
3117 case GDK_Page_Up:
3118 if (event->key.state & GDK_CONTROL_MASK) {
3119 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3120 } else {
3121 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3122 }
3123 break;
3124 case GDK_Page_Down:
3125 if (event->key.state & GDK_CONTROL_MASK) {
3126 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3127 } else {
3128 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3129 }
3130 break;
3131 default:
3132 break;
3133 }
3134 break;
3135 default:
3136 break;
3137 }
3139 return ret;
3140 }
3142 /**
3143 * Handle keypress on node; directly called.
3144 */
3145 gboolean node_key(GdkEvent *event)
3146 {
3147 Inkscape::NodePath::Path *np;
3149 // there is no way to verify nodes so set active_node to nil when deleting!!
3150 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3152 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3153 gint ret = FALSE;
3154 switch (get_group0_keyval (&event->key)) {
3155 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3156 case GDK_BackSpace:
3157 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3158 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3159 sp_nodepath_update_repr(np, _("Delete node"));
3160 Inkscape::NodePath::Path::active_node = NULL;
3161 ret = TRUE;
3162 break;
3163 case GDK_c:
3164 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3165 ret = TRUE;
3166 break;
3167 case GDK_s:
3168 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3169 ret = TRUE;
3170 break;
3171 case GDK_y:
3172 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3173 ret = TRUE;
3174 break;
3175 case GDK_b:
3176 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3177 ret = TRUE;
3178 break;
3179 }
3180 return ret;
3181 }
3182 return FALSE;
3183 }
3185 /**
3186 * Mouseclick on node callback.
3187 */
3188 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3189 {
3190 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3192 if (state & GDK_CONTROL_MASK) {
3193 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3195 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3196 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3197 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3198 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3199 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3200 } else {
3201 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3202 }
3203 sp_nodepath_update_repr(nodepath, _("Change node type"));
3204 sp_nodepath_update_statusbar(nodepath);
3206 } else { //ctrl+alt+click: delete node
3207 GList *node_to_delete = NULL;
3208 node_to_delete = g_list_append(node_to_delete, n);
3209 sp_node_delete_preserve(node_to_delete);
3210 }
3212 } else {
3213 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3214 }
3215 }
3217 /**
3218 * Mouse grabbed node callback.
3219 */
3220 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3221 {
3222 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3224 if (!n->selected) {
3225 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3226 }
3228 n->is_dragging = true;
3229 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3231 sp_nodepath_remember_origins (n->subpath->nodepath);
3232 }
3234 /**
3235 * Mouse ungrabbed node callback.
3236 */
3237 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3238 {
3239 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3241 n->dragging_out = NULL;
3242 n->is_dragging = false;
3243 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3245 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3246 }
3248 /**
3249 * The point on a line, given by its angle, closest to the given point.
3250 * \param p A point.
3251 * \param a Angle of the line; it is assumed to go through coordinate origin.
3252 * \param closest Pointer to the point struct where the result is stored.
3253 * \todo FIXME: use dot product perhaps?
3254 */
3255 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3256 {
3257 if (a == HUGE_VAL) { // vertical
3258 *closest = NR::Point(0, (*p)[NR::Y]);
3259 } else {
3260 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3261 (*closest)[NR::Y] = a * (*closest)[NR::X];
3262 }
3263 }
3265 /**
3266 * Distance from the point to a line given by its angle.
3267 * \param p A point.
3268 * \param a Angle of the line; it is assumed to go through coordinate origin.
3269 */
3270 static double point_line_distance(NR::Point *p, double a)
3271 {
3272 NR::Point c;
3273 point_line_closest(p, a, &c);
3274 return sqrt(((*p)[NR::X] - c[NR::X])*((*p)[NR::X] - c[NR::X]) + ((*p)[NR::Y] - c[NR::Y])*((*p)[NR::Y] - c[NR::Y]));
3275 }
3277 /**
3278 * Callback for node "request" signal.
3279 * \todo fixme: This goes to "moved" event? (lauris)
3280 */
3281 static gboolean
3282 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3283 {
3284 double yn, xn, yp, xp;
3285 double an, ap, na, pa;
3286 double d_an, d_ap, d_na, d_pa;
3287 gboolean collinear = FALSE;
3288 NR::Point c;
3289 NR::Point pr;
3291 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3293 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3295 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3296 if ( (!n->subpath->nodepath->straight_path) &&
3297 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3298 || n->dragging_out ) )
3299 {
3300 NR::Point mouse = (*p);
3302 if (!n->dragging_out) {
3303 // This is the first drag-out event; find out which handle to drag out
3304 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3305 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3307 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3308 return FALSE;
3310 Inkscape::NodePath::NodeSide *opposite;
3311 if (appr_p > appr_n) { // closer to p
3312 n->dragging_out = &n->p;
3313 opposite = &n->n;
3314 n->code = NR_CURVETO;
3315 } else if (appr_p < appr_n) { // closer to n
3316 n->dragging_out = &n->n;
3317 opposite = &n->p;
3318 n->n.other->code = NR_CURVETO;
3319 } else { // p and n nodes are the same
3320 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3321 n->dragging_out = &n->p;
3322 opposite = &n->n;
3323 n->code = NR_CURVETO;
3324 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3325 n->dragging_out = &n->n;
3326 opposite = &n->p;
3327 n->n.other->code = NR_CURVETO;
3328 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3329 double appr_other_n = (n->n.other ? NR::L2(n->n.other->n.pos - n->pos) - NR::L2(n->n.other->n.pos - (*p)) : -HUGE_VAL);
3330 double appr_other_p = (n->n.other ? NR::L2(n->n.other->p.pos - n->pos) - NR::L2(n->n.other->p.pos - (*p)) : -HUGE_VAL);
3331 if (appr_other_p > appr_other_n) { // closer to other's p handle
3332 n->dragging_out = &n->n;
3333 opposite = &n->p;
3334 n->n.other->code = NR_CURVETO;
3335 } else { // closer to other's n handle
3336 n->dragging_out = &n->p;
3337 opposite = &n->n;
3338 n->code = NR_CURVETO;
3339 }
3340 }
3341 }
3343 // if there's another handle, make sure the one we drag out starts parallel to it
3344 if (opposite->pos != n->pos) {
3345 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3346 }
3348 // knots might not be created yet!
3349 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3350 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3351 }
3353 // pass this on to the handle-moved callback
3354 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3355 sp_node_update_handles(n);
3356 return TRUE;
3357 }
3359 if (state & GDK_CONTROL_MASK) { // constrained motion
3361 // calculate relative distances of handles
3362 // n handle:
3363 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3364 xn = n->n.pos[NR::X] - n->pos[NR::X];
3365 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3366 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3367 if (n->n.other) { // if there is the next point
3368 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3369 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3370 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3371 }
3372 }
3373 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3374 if (yn < 0) { xn = -xn; yn = -yn; }
3376 // p handle:
3377 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3378 xp = n->p.pos[NR::X] - n->pos[NR::X];
3379 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3380 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3381 if (n->p.other) {
3382 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3383 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3384 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3385 }
3386 }
3387 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3388 if (yp < 0) { xp = -xp; yp = -yp; }
3390 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3391 // sliding on handles, only if at least one of the handles is non-vertical
3392 // (otherwise it's the same as ctrl+drag anyway)
3394 // calculate angles of the handles
3395 if (xn == 0) {
3396 if (yn == 0) { // no handle, consider it the continuation of the other one
3397 an = 0;
3398 collinear = TRUE;
3399 }
3400 else an = 0; // vertical; set the angle to horizontal
3401 } else an = yn/xn;
3403 if (xp == 0) {
3404 if (yp == 0) { // no handle, consider it the continuation of the other one
3405 ap = an;
3406 }
3407 else ap = 0; // vertical; set the angle to horizontal
3408 } else ap = yp/xp;
3410 if (collinear) an = ap;
3412 // angles of the perpendiculars; HUGE_VAL means vertical
3413 if (an == 0) na = HUGE_VAL; else na = -1/an;
3414 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3416 // mouse point relative to the node's original pos
3417 pr = (*p) - n->origin;
3419 // distances to the four lines (two handles and two perpendiculars)
3420 d_an = point_line_distance(&pr, an);
3421 d_na = point_line_distance(&pr, na);
3422 d_ap = point_line_distance(&pr, ap);
3423 d_pa = point_line_distance(&pr, pa);
3425 // find out which line is the closest, save its closest point in c
3426 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3427 point_line_closest(&pr, an, &c);
3428 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3429 point_line_closest(&pr, ap, &c);
3430 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3431 point_line_closest(&pr, na, &c);
3432 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3433 point_line_closest(&pr, pa, &c);
3434 }
3436 // move the node to the closest point
3437 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3438 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3439 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3441 } else { // constraining to hor/vert
3443 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3444 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3445 } else { // snap to vert
3446 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3447 }
3448 }
3449 } else { // move freely
3450 if (n->is_dragging) {
3451 if (state & GDK_MOD1_MASK) { // sculpt
3452 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3453 } else {
3454 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3455 (*p)[NR::X] - n->pos[NR::X],
3456 (*p)[NR::Y] - n->pos[NR::Y],
3457 (state & GDK_SHIFT_MASK) == 0);
3458 }
3459 }
3460 }
3462 n->subpath->nodepath->desktop->scroll_to_point(p);
3464 return TRUE;
3465 }
3467 /**
3468 * Node handle clicked callback.
3469 */
3470 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3471 {
3472 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3474 if (state & GDK_CONTROL_MASK) { // "delete" handle
3475 if (n->p.knot == knot) {
3476 n->p.pos = n->pos;
3477 } else if (n->n.knot == knot) {
3478 n->n.pos = n->pos;
3479 }
3480 sp_node_update_handles(n);
3481 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3482 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3483 sp_nodepath_update_statusbar(nodepath);
3485 } else { // just select or add to selection, depending in Shift
3486 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3487 }
3488 }
3490 /**
3491 * Node handle grabbed callback.
3492 */
3493 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3494 {
3495 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3497 if (!n->selected) {
3498 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3499 }
3501 // remember the origin point of the handle
3502 if (n->p.knot == knot) {
3503 n->p.origin_radial = n->p.pos - n->pos;
3504 } else if (n->n.knot == knot) {
3505 n->n.origin_radial = n->n.pos - n->pos;
3506 } else {
3507 g_assert_not_reached();
3508 }
3510 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3511 }
3513 /**
3514 * Node handle ungrabbed callback.
3515 */
3516 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3517 {
3518 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3520 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3521 if (n->p.knot == knot) {
3522 n->p.origin_radial.a = 0;
3523 sp_knot_set_position(knot, &n->p.pos, state);
3524 } else if (n->n.knot == knot) {
3525 n->n.origin_radial.a = 0;
3526 sp_knot_set_position(knot, &n->n.pos, state);
3527 } else {
3528 g_assert_not_reached();
3529 }
3531 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3532 }
3534 /**
3535 * Node handle "request" signal callback.
3536 */
3537 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3538 {
3539 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3541 Inkscape::NodePath::NodeSide *me, *opposite;
3542 gint which;
3543 if (n->p.knot == knot) {
3544 me = &n->p;
3545 opposite = &n->n;
3546 which = -1;
3547 } else if (n->n.knot == knot) {
3548 me = &n->n;
3549 opposite = &n->p;
3550 which = 1;
3551 } else {
3552 me = opposite = NULL;
3553 which = 0;
3554 g_assert_not_reached();
3555 }
3557 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3559 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3560 Inkscape::SnappedPoint s ;
3561 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3562 /* We are smooth node adjacent with line */
3563 NR::Point const delta = *p - n->pos;
3564 NR::Coord const len = NR::L2(delta);
3565 Inkscape::NodePath::Node *othernode = opposite->other;
3566 NR::Point const ndelta = n->pos - othernode->pos;
3567 NR::Coord const linelen = NR::L2(ndelta);
3568 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3569 NR::Coord const scal = dot(delta, ndelta) / linelen;
3570 (*p) = n->pos + (scal / linelen) * ndelta;
3571 }
3572 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), n->subpath->nodepath->item);
3573 } else {
3574 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, n->subpath->nodepath->item);
3575 }
3576 *p = s.getPoint();
3577 if (s.getDistance() < NR_HUGE) {
3578 n->subpath->nodepath->desktop->snapindicator->set_new_snappoint((*p).to_2geom());
3579 }
3581 sp_node_adjust_handle(n, -which);
3583 return FALSE;
3584 }
3586 /**
3587 * Node handle moved callback.
3588 */
3589 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3590 {
3591 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3593 Inkscape::NodePath::NodeSide *me;
3594 Inkscape::NodePath::NodeSide *other;
3595 if (n->p.knot == knot) {
3596 me = &n->p;
3597 other = &n->n;
3598 } else if (n->n.knot == knot) {
3599 me = &n->n;
3600 other = &n->p;
3601 } else {
3602 me = NULL;
3603 other = NULL;
3604 g_assert_not_reached();
3605 }
3607 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3608 Radial rme(me->pos - n->pos);
3609 Radial rother(other->pos - n->pos);
3610 Radial rnew(*p - n->pos);
3612 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3613 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3614 /* 0 interpreted as "no snapping". */
3616 // The closest PI/snaps angle, starting from zero.
3617 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3618 if (me->origin_radial.a == HUGE_VAL) {
3619 // ortho doesn't exist: original handle was zero length.
3620 rnew.a = a_snapped;
3621 } else {
3622 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3623 * its opposite and perpendiculars). */
3624 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3626 // Snap to the closest.
3627 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3628 ? a_snapped
3629 : a_ortho );
3630 }
3631 }
3633 if (state & GDK_MOD1_MASK) {
3634 // lock handle length
3635 rnew.r = me->origin_radial.r;
3636 }
3638 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3639 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3640 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3641 rother.a += rnew.a - rme.a;
3642 other->pos = NR::Point(rother) + n->pos;
3643 if (other->knot) {
3644 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3645 sp_knot_moveto(other->knot, &other->pos);
3646 }
3647 }
3649 me->pos = NR::Point(rnew) + n->pos;
3650 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3652 // move knot, but without emitting the signal:
3653 // we cannot emit a "moved" signal because we're now processing it
3654 sp_knot_moveto(me->knot, &(me->pos));
3656 update_object(n->subpath->nodepath);
3658 /* status text */
3659 SPDesktop *desktop = n->subpath->nodepath->desktop;
3660 if (!desktop) return;
3661 SPEventContext *ec = desktop->event_context;
3662 if (!ec) return;
3663 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3664 if (!mc) return;
3666 double degrees = 180 / M_PI * rnew.a;
3667 if (degrees > 180) degrees -= 360;
3668 if (degrees < -180) degrees += 360;
3669 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3670 degrees = angle_to_compass (degrees);
3672 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3674 mc->setF(Inkscape::NORMAL_MESSAGE,
3675 _("<b>Node handle</b>: angle %0.2f°, length %s; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to lock length; with <b>Shift</b> to rotate both handles"), degrees, length->str);
3677 g_string_free(length, TRUE);
3678 }
3680 /**
3681 * Node handle event callback.
3682 */
3683 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3684 {
3685 gboolean ret = FALSE;
3686 switch (event->type) {
3687 case GDK_KEY_PRESS:
3688 switch (get_group0_keyval (&event->key)) {
3689 case GDK_space:
3690 if (event->key.state & GDK_BUTTON1_MASK) {
3691 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3692 stamp_repr(nodepath);
3693 ret = TRUE;
3694 }
3695 break;
3696 default:
3697 break;
3698 }
3699 break;
3700 case GDK_ENTER_NOTIFY:
3701 // we use an experimentally determined threshold that seems to work fine
3702 if (NR::L2(n->pos - knot->pos) < 0.75)
3703 Inkscape::NodePath::Path::active_node = n;
3704 break;
3705 case GDK_LEAVE_NOTIFY:
3706 // we use an experimentally determined threshold that seems to work fine
3707 if (NR::L2(n->pos - knot->pos) < 0.75)
3708 Inkscape::NodePath::Path::active_node = NULL;
3709 break;
3710 default:
3711 break;
3712 }
3714 return ret;
3715 }
3717 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3718 Radial &rme, Radial &rother, gboolean const both)
3719 {
3720 rme.a += angle;
3721 if ( both
3722 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3723 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3724 {
3725 rother.a += angle;
3726 }
3727 }
3729 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3730 Radial &rme, Radial &rother, gboolean const both)
3731 {
3732 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3734 gdouble r;
3735 if ( both
3736 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3737 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3738 {
3739 r = MAX(rme.r, rother.r);
3740 } else {
3741 r = rme.r;
3742 }
3744 gdouble const weird_angle = atan2(norm_angle, r);
3745 /* Bulia says norm_angle is just the visible distance that the
3746 * object's end must travel on the screen. Left as 'angle' for want of
3747 * a better name.*/
3749 rme.a += weird_angle;
3750 if ( both
3751 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3752 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3753 {
3754 rother.a += weird_angle;
3755 }
3756 }
3758 /**
3759 * Rotate one node.
3760 */
3761 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3762 {
3763 Inkscape::NodePath::NodeSide *me, *other;
3764 bool both = false;
3766 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3767 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3769 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3770 me = &(n->p);
3771 other = &(n->n);
3772 } else if (!n->p.other) {
3773 me = &(n->n);
3774 other = &(n->p);
3775 } else {
3776 if (which > 0) { // right handle
3777 if (xn > xp) {
3778 me = &(n->n);
3779 other = &(n->p);
3780 } else {
3781 me = &(n->p);
3782 other = &(n->n);
3783 }
3784 } else if (which < 0){ // left handle
3785 if (xn <= xp) {
3786 me = &(n->n);
3787 other = &(n->p);
3788 } else {
3789 me = &(n->p);
3790 other = &(n->n);
3791 }
3792 } else { // both handles
3793 me = &(n->n);
3794 other = &(n->p);
3795 both = true;
3796 }
3797 }
3799 Radial rme(me->pos - n->pos);
3800 Radial rother(other->pos - n->pos);
3802 if (screen) {
3803 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3804 } else {
3805 node_rotate_one_internal (*n, angle, rme, rother, both);
3806 }
3808 me->pos = n->pos + NR::Point(rme);
3810 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3811 other->pos = n->pos + NR::Point(rother);
3812 }
3814 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3815 // so here we just move all the knots without emitting move signals, for speed
3816 sp_node_update_handles(n, false);
3817 }
3819 /**
3820 * Rotate selected nodes.
3821 */
3822 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3823 {
3824 if (!nodepath || !nodepath->selected) return;
3826 if (g_list_length(nodepath->selected) == 1) {
3827 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3828 node_rotate_one (n, angle, which, screen);
3829 } else {
3830 // rotate as an object:
3832 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3833 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3834 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3835 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3836 box.expandTo (n->pos); // contain all selected nodes
3837 }
3839 gdouble rot;
3840 if (screen) {
3841 gdouble const zoom = nodepath->desktop->current_zoom();
3842 gdouble const zmove = angle / zoom;
3843 gdouble const r = NR::L2(box.max() - box.midpoint());
3844 rot = atan2(zmove, r);
3845 } else {
3846 rot = angle;
3847 }
3849 NR::Point rot_center;
3850 if (Inkscape::NodePath::Path::active_node == NULL)
3851 rot_center = box.midpoint();
3852 else
3853 rot_center = Inkscape::NodePath::Path::active_node->pos;
3855 NR::Matrix t =
3856 NR::Matrix (NR::translate(-rot_center)) *
3857 NR::Matrix (NR::rotate(rot)) *
3858 NR::Matrix (NR::translate(rot_center));
3860 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3861 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3862 n->pos *= t;
3863 n->n.pos *= t;
3864 n->p.pos *= t;
3865 sp_node_update_handles(n, false);
3866 }
3867 }
3869 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3870 }
3872 /**
3873 * Scale one node.
3874 */
3875 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3876 {
3877 bool both = false;
3878 Inkscape::NodePath::NodeSide *me, *other;
3880 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3881 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3883 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3884 me = &(n->p);
3885 other = &(n->n);
3886 n->code = NR_CURVETO;
3887 } else if (!n->p.other) {
3888 me = &(n->n);
3889 other = &(n->p);
3890 if (n->n.other)
3891 n->n.other->code = NR_CURVETO;
3892 } else {
3893 if (which > 0) { // right handle
3894 if (xn > xp) {
3895 me = &(n->n);
3896 other = &(n->p);
3897 if (n->n.other)
3898 n->n.other->code = NR_CURVETO;
3899 } else {
3900 me = &(n->p);
3901 other = &(n->n);
3902 n->code = NR_CURVETO;
3903 }
3904 } else if (which < 0){ // left handle
3905 if (xn <= xp) {
3906 me = &(n->n);
3907 other = &(n->p);
3908 if (n->n.other)
3909 n->n.other->code = NR_CURVETO;
3910 } else {
3911 me = &(n->p);
3912 other = &(n->n);
3913 n->code = NR_CURVETO;
3914 }
3915 } else { // both handles
3916 me = &(n->n);
3917 other = &(n->p);
3918 both = true;
3919 n->code = NR_CURVETO;
3920 if (n->n.other)
3921 n->n.other->code = NR_CURVETO;
3922 }
3923 }
3925 Radial rme(me->pos - n->pos);
3926 Radial rother(other->pos - n->pos);
3928 rme.r += grow;
3929 if (rme.r < 0) rme.r = 0;
3930 if (rme.a == HUGE_VAL) {
3931 if (me->other) { // if direction is unknown, initialize it towards the next node
3932 Radial rme_next(me->other->pos - n->pos);
3933 rme.a = rme_next.a;
3934 } else { // if there's no next, initialize to 0
3935 rme.a = 0;
3936 }
3937 }
3938 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3939 rother.r += grow;
3940 if (rother.r < 0) rother.r = 0;
3941 if (rother.a == HUGE_VAL) {
3942 rother.a = rme.a + M_PI;
3943 }
3944 }
3946 me->pos = n->pos + NR::Point(rme);
3948 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3949 other->pos = n->pos + NR::Point(rother);
3950 }
3952 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3953 // so here we just move all the knots without emitting move signals, for speed
3954 sp_node_update_handles(n, false);
3955 }
3957 /**
3958 * Scale selected nodes.
3959 */
3960 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3961 {
3962 if (!nodepath || !nodepath->selected) return;
3964 if (g_list_length(nodepath->selected) == 1) {
3965 // scale handles of the single selected node
3966 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3967 node_scale_one (n, grow, which);
3968 } else {
3969 // scale nodes as an "object":
3971 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3972 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3973 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3974 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3975 box.expandTo (n->pos); // contain all selected nodes
3976 }
3978 double scale = (box.maxExtent() + grow)/box.maxExtent();
3980 NR::Point scale_center;
3981 if (Inkscape::NodePath::Path::active_node == NULL)
3982 scale_center = box.midpoint();
3983 else
3984 scale_center = Inkscape::NodePath::Path::active_node->pos;
3986 NR::Matrix t =
3987 NR::Matrix (NR::translate(-scale_center)) *
3988 NR::Matrix (NR::scale(scale, scale)) *
3989 NR::Matrix (NR::translate(scale_center));
3991 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3992 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3993 n->pos *= t;
3994 n->n.pos *= t;
3995 n->p.pos *= t;
3996 sp_node_update_handles(n, false);
3997 }
3998 }
4000 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4001 }
4003 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4004 {
4005 if (!nodepath) return;
4006 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4007 }
4009 /**
4010 * Flip selected nodes horizontally/vertically.
4011 */
4012 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4013 {
4014 if (!nodepath || !nodepath->selected) return;
4016 if (g_list_length(nodepath->selected) == 1 && !center) {
4017 // flip handles of the single selected node
4018 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4019 double temp = n->p.pos[axis];
4020 n->p.pos[axis] = n->n.pos[axis];
4021 n->n.pos[axis] = temp;
4022 sp_node_update_handles(n, false);
4023 } else {
4024 // scale nodes as an "object":
4026 NR::Rect box = sp_node_selected_bbox (nodepath);
4027 if (!center) {
4028 center = box.midpoint();
4029 }
4030 NR::Matrix t =
4031 NR::Matrix (NR::translate(- *center)) *
4032 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4033 NR::Matrix (NR::translate(*center));
4035 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4036 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4037 n->pos *= t;
4038 n->n.pos *= t;
4039 n->p.pos *= t;
4040 sp_node_update_handles(n, false);
4041 }
4042 }
4044 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4045 }
4047 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4048 {
4049 g_assert (nodepath->selected);
4051 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4052 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4053 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4054 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4055 box.expandTo (n->pos); // contain all selected nodes
4056 }
4057 return box;
4058 }
4060 //-----------------------------------------------
4061 /**
4062 * Return new subpath under given nodepath.
4063 */
4064 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4065 {
4066 g_assert(nodepath);
4067 g_assert(nodepath->desktop);
4069 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4071 s->nodepath = nodepath;
4072 s->closed = FALSE;
4073 s->nodes = NULL;
4074 s->first = NULL;
4075 s->last = NULL;
4077 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4078 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4079 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4081 return s;
4082 }
4084 /**
4085 * Destroy nodes in subpath, then subpath itself.
4086 */
4087 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4088 {
4089 g_assert(subpath);
4090 g_assert(subpath->nodepath);
4091 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4093 while (subpath->nodes) {
4094 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4095 }
4097 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4099 g_free(subpath);
4100 }
4102 /**
4103 * Link head to tail in subpath.
4104 */
4105 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4106 {
4107 g_assert(!sp->closed);
4108 g_assert(sp->last != sp->first);
4109 g_assert(sp->first->code == NR_MOVETO);
4111 sp->closed = TRUE;
4113 //Link the head to the tail
4114 sp->first->p.other = sp->last;
4115 sp->last->n.other = sp->first;
4116 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4117 sp->first = sp->last;
4119 //Remove the extra end node
4120 sp_nodepath_node_destroy(sp->last->n.other);
4121 }
4123 /**
4124 * Open closed (loopy) subpath at node.
4125 */
4126 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4127 {
4128 g_assert(sp->closed);
4129 g_assert(n->subpath == sp);
4130 g_assert(sp->first == sp->last);
4132 /* We create new startpoint, current node will become last one */
4134 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4135 &n->pos, &n->pos, &n->n.pos);
4138 sp->closed = FALSE;
4140 //Unlink to make a head and tail
4141 sp->first = new_path;
4142 sp->last = n;
4143 n->n.other = NULL;
4144 new_path->p.other = NULL;
4145 }
4147 /**
4148 * Return new node in subpath with given properties.
4149 * \param pos Position of node.
4150 * \param ppos Handle position in previous direction
4151 * \param npos Handle position in previous direction
4152 */
4153 Inkscape::NodePath::Node *
4154 sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp, Inkscape::NodePath::Node *next, Inkscape::NodePath::NodeType type, NRPathcode code, NR::Point *ppos, NR::Point *pos, NR::Point *npos)
4155 {
4156 g_assert(sp);
4157 g_assert(sp->nodepath);
4158 g_assert(sp->nodepath->desktop);
4160 if (nodechunk == NULL)
4161 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4163 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4165 n->subpath = sp;
4167 if (type != Inkscape::NodePath::NODE_NONE) {
4168 // use the type from sodipodi:nodetypes
4169 n->type = type;
4170 } else {
4171 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4172 // points are (almost) collinear
4173 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4174 // endnode, or a node with a retracted handle
4175 n->type = Inkscape::NodePath::NODE_CUSP;
4176 } else {
4177 n->type = Inkscape::NodePath::NODE_SMOOTH;
4178 }
4179 } else {
4180 n->type = Inkscape::NodePath::NODE_CUSP;
4181 }
4182 }
4184 n->code = code;
4185 n->selected = FALSE;
4186 n->pos = *pos;
4187 n->p.pos = *ppos;
4188 n->n.pos = *npos;
4190 n->dragging_out = NULL;
4192 Inkscape::NodePath::Node *prev;
4193 if (next) {
4194 //g_assert(g_list_find(sp->nodes, next));
4195 prev = next->p.other;
4196 } else {
4197 prev = sp->last;
4198 }
4200 if (prev)
4201 prev->n.other = n;
4202 else
4203 sp->first = n;
4205 if (next)
4206 next->p.other = n;
4207 else
4208 sp->last = n;
4210 n->p.other = prev;
4211 n->n.other = next;
4213 n->knot = sp_knot_new(sp->nodepath->desktop, _("<b>Node</b>: drag to edit the path; with <b>Ctrl</b> to snap to horizontal/vertical; with <b>Ctrl+Alt</b> to snap to handles' directions"));
4214 sp_knot_set_position(n->knot, pos, 0);
4216 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4217 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4218 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4219 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4220 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4221 sp_knot_update_ctrl(n->knot);
4223 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4224 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4225 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4226 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4227 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4228 sp_knot_show(n->knot);
4230 // We only create handle knots and lines on demand
4231 n->p.knot = NULL;
4232 n->p.line = NULL;
4233 n->n.knot = NULL;
4234 n->n.line = NULL;
4236 sp->nodes = g_list_prepend(sp->nodes, n);
4238 return n;
4239 }
4241 /**
4242 * Destroy node and its knots, link neighbors in subpath.
4243 */
4244 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4245 {
4246 g_assert(node);
4247 g_assert(node->subpath);
4248 g_assert(SP_IS_KNOT(node->knot));
4250 Inkscape::NodePath::SubPath *sp = node->subpath;
4252 if (node->selected) { // first, deselect
4253 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4254 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4255 }
4257 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4259 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4260 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4261 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4262 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4263 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4264 g_object_unref(G_OBJECT(node->knot));
4266 if (node->p.knot) {
4267 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4268 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4269 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4270 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4271 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4272 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4273 g_object_unref(G_OBJECT(node->p.knot));
4274 node->p.knot = NULL;
4275 }
4277 if (node->n.knot) {
4278 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4279 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4280 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4281 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4282 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4283 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4284 g_object_unref(G_OBJECT(node->n.knot));
4285 node->n.knot = NULL;
4286 }
4288 if (node->p.line)
4289 gtk_object_destroy(GTK_OBJECT(node->p.line));
4290 if (node->n.line)
4291 gtk_object_destroy(GTK_OBJECT(node->n.line));
4293 if (sp->nodes) { // there are others nodes on the subpath
4294 if (sp->closed) {
4295 if (sp->first == node) {
4296 g_assert(sp->last == node);
4297 sp->first = node->n.other;
4298 sp->last = sp->first;
4299 }
4300 node->p.other->n.other = node->n.other;
4301 node->n.other->p.other = node->p.other;
4302 } else {
4303 if (sp->first == node) {
4304 sp->first = node->n.other;
4305 sp->first->code = NR_MOVETO;
4306 }
4307 if (sp->last == node) sp->last = node->p.other;
4308 if (node->p.other) node->p.other->n.other = node->n.other;
4309 if (node->n.other) node->n.other->p.other = node->p.other;
4310 }
4311 } else { // this was the last node on subpath
4312 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4313 }
4315 g_mem_chunk_free(nodechunk, node);
4316 }
4318 /**
4319 * Returns one of the node's two sides.
4320 * \param which Indicates which side.
4321 * \return Pointer to previous node side if which==-1, next if which==1.
4322 */
4323 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4324 {
4325 g_assert(node);
4327 switch (which) {
4328 case -1:
4329 return &node->p;
4330 case 1:
4331 return &node->n;
4332 default:
4333 break;
4334 }
4336 g_assert_not_reached();
4338 return NULL;
4339 }
4341 /**
4342 * Return the other side of the node, given one of its sides.
4343 */
4344 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4345 {
4346 g_assert(node);
4348 if (me == &node->p) return &node->n;
4349 if (me == &node->n) return &node->p;
4351 g_assert_not_reached();
4353 return NULL;
4354 }
4356 /**
4357 * Return NRPathcode on the given side of the node.
4358 */
4359 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4360 {
4361 g_assert(node);
4363 if (me == &node->p) {
4364 if (node->p.other) return (NRPathcode)node->code;
4365 return NR_MOVETO;
4366 }
4368 if (me == &node->n) {
4369 if (node->n.other) return (NRPathcode)node->n.other->code;
4370 return NR_MOVETO;
4371 }
4373 g_assert_not_reached();
4375 return NR_END;
4376 }
4378 /**
4379 * Return node with the given index
4380 */
4381 Inkscape::NodePath::Node *
4382 sp_nodepath_get_node_by_index(int index)
4383 {
4384 Inkscape::NodePath::Node *e = NULL;
4386 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4387 if (!nodepath) {
4388 return e;
4389 }
4391 //find segment
4392 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4394 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4395 int n = g_list_length(sp->nodes);
4396 if (sp->closed) {
4397 n++;
4398 }
4400 //if the piece belongs to this subpath grab it
4401 //otherwise move onto the next subpath
4402 if (index < n) {
4403 e = sp->first;
4404 for (int i = 0; i < index; ++i) {
4405 e = e->n.other;
4406 }
4407 break;
4408 } else {
4409 if (sp->closed) {
4410 index -= (n+1);
4411 } else {
4412 index -= n;
4413 }
4414 }
4415 }
4417 return e;
4418 }
4420 /**
4421 * Returns plain text meaning of node type.
4422 */
4423 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4424 {
4425 unsigned retracted = 0;
4426 bool endnode = false;
4428 for (int which = -1; which <= 1; which += 2) {
4429 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4430 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4431 retracted ++;
4432 if (!side->other)
4433 endnode = true;
4434 }
4436 if (retracted == 0) {
4437 if (endnode) {
4438 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4439 return _("end node");
4440 } else {
4441 switch (node->type) {
4442 case Inkscape::NodePath::NODE_CUSP:
4443 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4444 return _("cusp");
4445 case Inkscape::NodePath::NODE_SMOOTH:
4446 // TRANSLATORS: "smooth" is an adjective here
4447 return _("smooth");
4448 case Inkscape::NodePath::NODE_SYMM:
4449 return _("symmetric");
4450 }
4451 }
4452 } else if (retracted == 1) {
4453 if (endnode) {
4454 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4455 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4456 } else {
4457 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4458 }
4459 } else {
4460 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4461 }
4463 return NULL;
4464 }
4466 /**
4467 * Handles content of statusbar as long as node tool is active.
4468 */
4469 void
4470 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4471 {
4472 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>< ></b> to scale, <b>[ ]</b> to rotate");
4473 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4475 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4476 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4477 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4478 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4480 SPDesktop *desktop = NULL;
4481 if (nodepath) {
4482 desktop = nodepath->desktop;
4483 } else {
4484 desktop = SP_ACTIVE_DESKTOP;
4485 }
4487 SPEventContext *ec = desktop->event_context;
4488 if (!ec) return;
4489 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4490 if (!mc) return;
4492 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4494 if (selected_nodes == 0) {
4495 Inkscape::Selection *sel = desktop->selection;
4496 if (!sel || sel->isEmpty()) {
4497 mc->setF(Inkscape::NORMAL_MESSAGE,
4498 _("Select a single object to edit its nodes or handles."));
4499 } else {
4500 if (nodepath) {
4501 mc->setF(Inkscape::NORMAL_MESSAGE,
4502 ngettext("<b>0</b> out of <b>%i</b> node selected. <b>Click</b>, <b>Shift+click</b>, or <b>drag around</b> nodes to select.",
4503 "<b>0</b> out of <b>%i</b> nodes selected. <b>Click</b>, <b>Shift+click</b>, or <b>drag around</b> nodes to select.",
4504 total_nodes),
4505 total_nodes);
4506 } else {
4507 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4508 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4509 } else {
4510 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4511 }
4512 }
4513 }
4514 } else if (nodepath && selected_nodes == 1) {
4515 mc->setF(Inkscape::NORMAL_MESSAGE,
4516 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4517 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4518 total_nodes),
4519 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4520 } else {
4521 if (selected_subpaths > 1) {
4522 mc->setF(Inkscape::NORMAL_MESSAGE,
4523 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4524 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4525 total_nodes),
4526 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4527 } else {
4528 mc->setF(Inkscape::NORMAL_MESSAGE,
4529 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4530 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4531 total_nodes),
4532 selected_nodes, total_nodes, when_selected);
4533 }
4534 }
4535 }
4537 /*
4538 * returns a *copy* of the curve of that object.
4539 */
4540 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4541 if (!object)
4542 return NULL;
4544 SPCurve *curve = NULL;
4545 if (SP_IS_PATH(object)) {
4546 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4547 curve = sp_curve_copy(curve_new);
4548 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4549 const gchar *svgd = object->repr->attribute(key);
4550 if (svgd) {
4551 NArtBpath *bpath = sp_svg_read_path(svgd);
4552 SPCurve *curve_new = sp_curve_new_from_bpath(bpath);
4553 if (curve_new) {
4554 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4555 } else {
4556 g_free(bpath);
4557 }
4558 }
4559 }
4561 return curve;
4562 }
4564 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4565 if (!np || !np->object || !curve)
4566 return;
4568 if (SP_IS_PATH(np->object)) {
4569 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4570 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4571 } else {
4572 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4573 }
4574 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4575 // FIXME: this writing to string and then reading from string is bound to be slow.
4576 // create a method to convert from curve directly to 2geom...
4577 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
4578 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4579 g_free(svgpath);
4581 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4582 }
4583 }
4585 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4586 np->show_helperpath = show;
4588 if (show) {
4589 SPCurve *helper_curve = sp_curve_copy(np->curve);
4590 sp_curve_transform(helper_curve, np->i2d );
4591 if (!np->helper_path) {
4592 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4593 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4594 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4595 sp_canvas_item_show(np->helper_path);
4596 } else {
4597 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4598 }
4599 sp_curve_unref(helper_curve);
4600 } else {
4601 if (np->helper_path) {
4602 GtkObject *temp = np->helper_path;
4603 np->helper_path = NULL;
4604 gtk_object_destroy(temp);
4605 }
4606 }
4607 }
4609 /* this function does not work yet */
4610 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4611 np->straight_path = true;
4612 np->show_handles = false;
4613 g_message("add code to make the path straight.");
4614 // do sp_nodepath_convert_node_type on all nodes?
4615 // search for this text !!! "Make selected segments lines"
4616 }
4619 /*
4620 Local Variables:
4621 mode:c++
4622 c-file-style:"stroustrup"
4623 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4624 indent-tabs-mode:nil
4625 fill-column:99
4626 End:
4627 */
4628 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :