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