56c38d7343ad939fbf573f59ce0ce9c7453c0903
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/effect.h"
55 #include "live_effects/parameter/parameter.h"
56 #include "util/mathfns.h"
57 #include "display/snap-indicator.h"
58 #include "snapped-point.h"
60 class NR::Matrix;
62 /// \todo
63 /// evil evil evil. FIXME: conflict of two different Path classes!
64 /// There is a conflict in the namespace between two classes named Path.
65 /// #include "sp-flowtext.h"
66 /// #include "sp-flowregion.h"
68 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
69 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
70 GType sp_flowregion_get_type (void);
71 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
72 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
73 GType sp_flowtext_get_type (void);
74 // end evil workaround
76 #include "helper/stlport.h"
79 /// \todo fixme: Implement these via preferences */
81 #define NODE_FILL 0xbfbfbf00
82 #define NODE_STROKE 0x000000ff
83 #define NODE_FILL_HI 0xff000000
84 #define NODE_STROKE_HI 0x000000ff
85 #define NODE_FILL_SEL 0x0000ffff
86 #define NODE_STROKE_SEL 0x000000ff
87 #define NODE_FILL_SEL_HI 0xff000000
88 #define NODE_STROKE_SEL_HI 0x000000ff
89 #define KNOT_FILL 0xffffffff
90 #define KNOT_STROKE 0x000000ff
91 #define KNOT_FILL_HI 0xff000000
92 #define KNOT_STROKE_HI 0x000000ff
94 static GMemChunk *nodechunk = NULL;
96 /* Creation from object */
98 static NArtBpath const * subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath const *b, Inkscape::NodePath::NodeType const *t);
99 static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, gint length);
101 /* Object updating */
103 static void stamp_repr(Inkscape::NodePath::Path *np);
104 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
105 static gchar *create_typestr(Inkscape::NodePath::Path *np);
107 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
109 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
111 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
113 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
115 /* Adjust handle placement, if the node or the other handle is moved */
116 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
117 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
119 /* Node event callbacks */
120 static void node_clicked(SPKnot *knot, guint state, gpointer data);
121 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
122 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
123 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
125 /* Handle event callbacks */
126 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
127 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
128 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
129 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
130 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
131 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
133 /* Constructors and destructors */
135 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
136 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
137 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
138 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
139 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
140 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
141 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
143 /* Helpers */
145 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
146 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
147 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
149 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
150 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
152 // active_node indicates mouseover node
153 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
155 static void sp_nodepath_draw_helper_curve(Inkscape::NodePath::Path *np, SPDesktop *desktop) {
156 // Draw helper curve
157 if (np->show_helperpath) {
158 SPCurve *helper_curve = np->curve->copy();
159 helper_curve->transform(np->i2d );
160 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
161 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);
162 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
163 sp_canvas_item_move_to_z(np->helper_path, 0);
164 sp_canvas_item_show(np->helper_path);
165 helper_curve->unref();
166 }
167 }
169 /**
170 * \brief Creates new nodepath from item
171 */
172 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
173 {
174 Inkscape::XML::Node *repr = object->repr;
176 /** \todo
177 * FIXME: remove this. We don't want to edit paths inside flowtext.
178 * Instead we will build our flowtext with cloned paths, so that the
179 * real paths are outside the flowtext and thus editable as usual.
180 */
181 if (SP_IS_FLOWTEXT(object)) {
182 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
183 if SP_IS_FLOWREGION(child) {
184 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
185 if (grandchild && SP_IS_PATH(grandchild)) {
186 object = SP_ITEM(grandchild);
187 break;
188 }
189 }
190 }
191 }
193 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
195 if (curve == NULL)
196 return NULL;
198 gint length = curve->get_length();
199 if (length == 0) {
200 curve->unref();
201 return NULL; // prevent crash for one-node paths
202 }
204 //Create new nodepath
205 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
206 if (!np) {
207 curve->unref();
208 return NULL;
209 }
211 // Set defaults
212 np->desktop = desktop;
213 np->object = object;
214 np->subpaths = NULL;
215 np->selected = NULL;
216 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
217 np->livarot_path = NULL;
218 np->local_change = 0;
219 np->show_handles = show_handles;
220 np->helper_path = NULL;
221 np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
222 np->helperpath_width = 1.0;
223 np->curve = curve->copy();
224 np->show_helperpath = (prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1);
225 if (SP_IS_LPE_ITEM(object)) {
226 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
227 if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
228 np->show_helperpath = true;
229 }
230 }
231 np->straight_path = false;
232 if (IS_LIVEPATHEFFECT(object) && item) {
233 np->item = item;
234 } else {
235 np->item = SP_ITEM(object);
236 }
238 // we need to update item's transform from the repr here,
239 // because they may be out of sync when we respond
240 // to a change in repr by regenerating nodepath --bb
241 sp_object_read_attr(SP_OBJECT(np->item), "transform");
243 np->i2d = from_2geom(sp_item_i2d_affine(np->item));
244 np->d2i = np->i2d.inverse();
246 np->repr = repr;
247 if (repr_key_in) { // apparantly the object is an LPEObject
248 np->repr_key = g_strdup(repr_key_in);
249 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
250 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
251 if (lpeparam) {
252 lpeparam->param_setup_nodepath(np);
253 }
254 } else {
255 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
256 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
257 np->repr_key = g_strdup("inkscape:original-d");
259 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
260 if (lpe) {
261 lpe->setup_nodepath(np);
262 }
263 } else {
264 np->repr_key = g_strdup("d");
265 }
266 }
268 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
269 Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
271 // create the subpath(s) from the bpath
272 NArtBpath const *bpath = curve->get_bpath();
273 NArtBpath const *b = bpath;
274 while (b->code != NR_END) {
275 b = subpath_from_bpath(np, b, typestr + (b - bpath));
276 }
278 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
279 np->subpaths = g_list_reverse(np->subpaths);
281 delete[] typestr;
282 curve->unref();
284 // create the livarot representation from the same item
285 sp_nodepath_ensure_livarot_path(np);
287 sp_nodepath_draw_helper_curve(np, desktop);
289 return np;
290 }
292 /**
293 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
294 */
295 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
297 if (!np) //soft fail, like delete
298 return;
300 while (np->subpaths) {
301 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
302 }
304 //Inform the ShapeEditor that made me, if any, that I am gone.
305 if (np->shape_editor)
306 np->shape_editor->nodepath_destroyed();
308 g_assert(!np->selected);
310 if (np->livarot_path) {
311 delete np->livarot_path;
312 np->livarot_path = NULL;
313 }
315 if (np->helper_path) {
316 GtkObject *temp = np->helper_path;
317 np->helper_path = NULL;
318 gtk_object_destroy(temp);
319 }
320 if (np->curve) {
321 np->curve->unref();
322 np->curve = NULL;
323 }
325 if (np->repr_key) {
326 g_free(np->repr_key);
327 np->repr_key = NULL;
328 }
329 if (np->repr_nodetypes_key) {
330 g_free(np->repr_nodetypes_key);
331 np->repr_nodetypes_key = NULL;
332 }
334 np->desktop = NULL;
336 g_free(np);
337 }
340 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
341 {
342 if (np && np->livarot_path == NULL) {
343 SPCurve *curve = create_curve(np);
344 np->livarot_path = new Path;
345 np->livarot_path->LoadPathVector(curve->get_pathvector());
347 if (np->livarot_path)
348 np->livarot_path->ConvertWithBackData(0.01);
350 curve->unref();
351 }
352 }
355 /**
356 * Return the node count of a given NodeSubPath.
357 */
358 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
359 {
360 if (!subpath)
361 return 0;
362 gint nodeCount = g_list_length(subpath->nodes);
363 return nodeCount;
364 }
366 /**
367 * Return the node count of a given NodePath.
368 */
369 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
370 {
371 if (!np)
372 return 0;
373 gint nodeCount = 0;
374 for (GList *item = np->subpaths ; item ; item=item->next) {
375 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
376 nodeCount += g_list_length(subpath->nodes);
377 }
378 return nodeCount;
379 }
381 /**
382 * Return the subpath count of a given NodePath.
383 */
384 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
385 {
386 if (!np)
387 return 0;
388 return g_list_length (np->subpaths);
389 }
391 /**
392 * Return the selected node count of a given NodePath.
393 */
394 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
395 {
396 if (!np)
397 return 0;
398 return g_list_length (np->selected);
399 }
401 /**
402 * Return the number of subpaths where nodes are selected in a given NodePath.
403 */
404 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
405 {
406 if (!np)
407 return 0;
408 if (!np->selected)
409 return 0;
410 if (!np->selected->next)
411 return 1;
412 gint count = 0;
413 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
414 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
415 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
416 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
417 if (node->selected) {
418 count ++;
419 break;
420 }
421 }
422 }
423 return count;
424 }
426 /**
427 * Clean up a nodepath after editing.
428 *
429 * Currently we are deleting trivial subpaths.
430 */
431 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
432 {
433 GList *badSubPaths = NULL;
435 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
436 for (GList *l = nodepath->subpaths; l ; l=l->next) {
437 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
438 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
439 badSubPaths = g_list_append(badSubPaths, sp);
440 }
442 //Delete them. This second step is because sp_nodepath_subpath_destroy()
443 //also removes the subpath from nodepath->subpaths
444 for (GList *l = badSubPaths; l ; l=l->next) {
445 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
446 sp_nodepath_subpath_destroy(sp);
447 }
449 g_list_free(badSubPaths);
450 }
452 /**
453 * Create new nodepath from b, make it subpath of np.
454 * \param t The node type.
455 */
456 static NArtBpath const * subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath const *b, Inkscape::NodePath::NodeType const *t)
457 {
458 NR::Point ppos, pos, npos;
460 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
462 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
463 bool const closed = (b->code == NR_MOVETO);
465 pos = NR::Point(b->x3, b->y3) * np->i2d;
466 if (b[1].code == NR_CURVETO) {
467 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
468 } else {
469 npos = pos;
470 }
471 Inkscape::NodePath::Node *n;
472 n = sp_nodepath_node_new(sp, NULL, *t, NR_MOVETO, &pos, &pos, &npos);
473 g_assert(sp->first == n);
474 g_assert(sp->last == n);
476 b++;
477 t++;
478 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
479 pos = NR::Point(b->x3, b->y3) * np->i2d;
480 if (b->code == NR_CURVETO) {
481 ppos = NR::Point(b->x2, b->y2) * np->i2d;
482 } else {
483 ppos = pos;
484 }
485 if (b[1].code == NR_CURVETO) {
486 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
487 } else {
488 npos = pos;
489 }
490 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
491 b++;
492 t++;
493 }
495 if (closed) sp_nodepath_subpath_close(sp);
497 return b;
498 }
500 /**
501 * Convert from sodipodi:nodetypes to new style type array.
502 */
503 static
504 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, gint length)
505 {
506 g_assert(length > 0);
508 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
510 gint pos = 0;
512 if (types) {
513 for (gint i = 0; types[i] && ( i < length ); i++) {
514 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
515 if (types[i] != '\0') {
516 switch (types[i]) {
517 case 's':
518 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
519 break;
520 case 'z':
521 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
522 break;
523 case 'c':
524 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
525 break;
526 default:
527 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
528 break;
529 }
530 }
531 }
532 }
534 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
536 return typestr;
537 }
539 /**
540 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
541 * updated but repr is not (for speed). Used during curve and node drag.
542 */
543 static void update_object(Inkscape::NodePath::Path *np)
544 {
545 g_assert(np);
547 np->curve->unref();
548 np->curve = create_curve(np);
550 sp_nodepath_set_curve(np, np->curve);
552 if (np->show_helperpath) {
553 SPCurve * helper_curve = np->curve->copy();
554 helper_curve->transform(np->i2d );
555 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
556 helper_curve->unref();
557 }
558 }
560 /**
561 * Update XML path node with data from path object.
562 */
563 static void update_repr_internal(Inkscape::NodePath::Path *np)
564 {
565 g_assert(np);
567 Inkscape::XML::Node *repr = np->object->repr;
569 np->curve->unref();
570 np->curve = create_curve(np);
572 gchar *typestr = create_typestr(np);
573 gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
575 // determine if path has an effect applied and write to correct "d" attribute.
576 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
577 np->local_change++;
578 repr->setAttribute(np->repr_key, svgpath);
579 }
581 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
582 np->local_change++;
583 repr->setAttribute(np->repr_nodetypes_key, typestr);
584 }
586 g_free(svgpath);
587 g_free(typestr);
589 if (np->show_helperpath) {
590 SPCurve * helper_curve = np->curve->copy();
591 helper_curve->transform(np->i2d );
592 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
593 helper_curve->unref();
594 }
595 }
597 /**
598 * Update XML path node with data from path object, commit changes forever.
599 */
600 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
601 {
602 //fixme: np can be NULL, so check before proceeding
603 g_return_if_fail(np != NULL);
605 if (np->livarot_path) {
606 delete np->livarot_path;
607 np->livarot_path = NULL;
608 }
610 update_repr_internal(np);
611 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
613 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
614 annotation);
615 }
617 /**
618 * Update XML path node with data from path object, commit changes with undo.
619 */
620 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
621 {
622 if (np->livarot_path) {
623 delete np->livarot_path;
624 np->livarot_path = NULL;
625 }
627 update_repr_internal(np);
628 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
629 annotation);
630 }
632 /**
633 * Make duplicate of path, replace corresponding XML node in tree, commit.
634 */
635 static void stamp_repr(Inkscape::NodePath::Path *np)
636 {
637 g_assert(np);
639 Inkscape::XML::Node *old_repr = np->object->repr;
640 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
642 // remember the position of the item
643 gint pos = old_repr->position();
644 // remember parent
645 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
647 SPCurve *curve = create_curve(np);
648 gchar *typestr = create_typestr(np);
650 gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
652 new_repr->setAttribute(np->repr_key, svgpath);
653 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
655 // add the new repr to the parent
656 parent->appendChild(new_repr);
657 // move to the saved position
658 new_repr->setPosition(pos > 0 ? pos : 0);
660 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
661 _("Stamp"));
663 Inkscape::GC::release(new_repr);
664 g_free(svgpath);
665 g_free(typestr);
666 curve->unref();
667 }
669 /**
670 * Create curve from path.
671 */
672 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
673 {
674 SPCurve *curve = new SPCurve();
676 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
677 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
678 curve->moveto(sp->first->pos * np->d2i);
679 Inkscape::NodePath::Node *n = sp->first->n.other;
680 while (n) {
681 NR::Point const end_pt = n->pos * np->d2i;
682 switch (n->code) {
683 case NR_LINETO:
684 curve->lineto(end_pt);
685 break;
686 case NR_CURVETO:
687 curve->curveto(n->p.other->n.pos * np->d2i,
688 n->p.pos * np->d2i,
689 end_pt);
690 break;
691 default:
692 g_assert_not_reached();
693 break;
694 }
695 if (n != sp->last) {
696 n = n->n.other;
697 } else {
698 n = NULL;
699 }
700 }
701 if (sp->closed) {
702 curve->closepath();
703 }
704 }
706 return curve;
707 }
709 /**
710 * Convert path type string to sodipodi:nodetypes style.
711 */
712 static gchar *create_typestr(Inkscape::NodePath::Path *np)
713 {
714 gchar *typestr = g_new(gchar, 32);
715 gint len = 32;
716 gint pos = 0;
718 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
719 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
721 if (pos >= len) {
722 typestr = g_renew(gchar, typestr, len + 32);
723 len += 32;
724 }
726 typestr[pos++] = 'c';
728 Inkscape::NodePath::Node *n;
729 n = sp->first->n.other;
730 while (n) {
731 gchar code;
733 switch (n->type) {
734 case Inkscape::NodePath::NODE_CUSP:
735 code = 'c';
736 break;
737 case Inkscape::NodePath::NODE_SMOOTH:
738 code = 's';
739 break;
740 case Inkscape::NodePath::NODE_SYMM:
741 code = 'z';
742 break;
743 default:
744 g_assert_not_reached();
745 code = '\0';
746 break;
747 }
749 if (pos >= len) {
750 typestr = g_renew(gchar, typestr, len + 32);
751 len += 32;
752 }
754 typestr[pos++] = code;
756 if (n != sp->last) {
757 n = n->n.other;
758 } else {
759 n = NULL;
760 }
761 }
762 }
764 if (pos >= len) {
765 typestr = g_renew(gchar, typestr, len + 1);
766 len += 1;
767 }
769 typestr[pos++] = '\0';
771 return typestr;
772 }
774 /**
775 * Returns current path in context. // later eliminate this function at all!
776 */
777 static Inkscape::NodePath::Path *sp_nodepath_current()
778 {
779 if (!SP_ACTIVE_DESKTOP) {
780 return NULL;
781 }
783 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
785 if (!SP_IS_NODE_CONTEXT(event_context)) {
786 return NULL;
787 }
789 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
790 }
794 /**
795 \brief Fills node and handle positions for three nodes, splitting line
796 marked by end at distance t.
797 */
798 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
799 {
800 g_assert(new_path != NULL);
801 g_assert(end != NULL);
803 g_assert(end->p.other == new_path);
804 Inkscape::NodePath::Node *start = new_path->p.other;
805 g_assert(start);
807 if (end->code == NR_LINETO) {
808 new_path->type =Inkscape::NodePath::NODE_CUSP;
809 new_path->code = NR_LINETO;
810 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
811 } else {
812 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
813 new_path->code = NR_CURVETO;
814 gdouble s = 1 - t;
815 for (int dim = 0; dim < 2; dim++) {
816 NR::Coord const f000 = start->pos[dim];
817 NR::Coord const f001 = start->n.pos[dim];
818 NR::Coord const f011 = end->p.pos[dim];
819 NR::Coord const f111 = end->pos[dim];
820 NR::Coord const f00t = s * f000 + t * f001;
821 NR::Coord const f01t = s * f001 + t * f011;
822 NR::Coord const f11t = s * f011 + t * f111;
823 NR::Coord const f0tt = s * f00t + t * f01t;
824 NR::Coord const f1tt = s * f01t + t * f11t;
825 NR::Coord const fttt = s * f0tt + t * f1tt;
826 start->n.pos[dim] = f00t;
827 new_path->p.pos[dim] = f0tt;
828 new_path->pos[dim] = fttt;
829 new_path->n.pos[dim] = f1tt;
830 end->p.pos[dim] = f11t;
831 }
832 }
833 }
835 /**
836 * Adds new node on direct line between two nodes, activates handles of all
837 * three nodes.
838 */
839 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
840 {
841 g_assert(end);
842 g_assert(end->subpath);
843 g_assert(g_list_find(end->subpath->nodes, end));
845 Inkscape::NodePath::Node *start = end->p.other;
846 g_assert( start->n.other == end );
847 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
848 end,
849 (NRPathcode)end->code == NR_LINETO?
850 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
851 (NRPathcode)end->code,
852 &start->pos, &start->pos, &start->n.pos);
853 sp_nodepath_line_midpoint(newnode, end, t);
855 sp_node_adjust_handles(start);
856 sp_node_update_handles(start);
857 sp_node_update_handles(newnode);
858 sp_node_adjust_handles(end);
859 sp_node_update_handles(end);
861 return newnode;
862 }
864 /**
865 \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
866 */
867 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
868 {
869 g_assert(node);
870 g_assert(node->subpath);
871 g_assert(g_list_find(node->subpath->nodes, node));
873 Inkscape::NodePath::SubPath *sp = node->subpath;
874 Inkscape::NodePath::Path *np = sp->nodepath;
876 if (sp->closed) {
877 sp_nodepath_subpath_open(sp, node);
878 return sp->first;
879 } else {
880 // no break for end nodes
881 if (node == sp->first) return NULL;
882 if (node == sp->last ) return NULL;
884 // create a new subpath
885 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
887 // duplicate the break node as start of the new subpath
888 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
890 // attach rest of curve to new node
891 g_assert(node->n.other);
892 newnode->n.other = node->n.other; node->n.other = NULL;
893 newnode->n.other->p.other = newnode;
894 newsubpath->last = sp->last;
895 sp->last = node;
896 node = newnode;
897 while (node->n.other) {
898 node = node->n.other;
899 node->subpath = newsubpath;
900 sp->nodes = g_list_remove(sp->nodes, node);
901 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
902 }
905 return newnode;
906 }
907 }
909 /**
910 * Duplicate node and connect to neighbours.
911 */
912 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
913 {
914 g_assert(node);
915 g_assert(node->subpath);
916 g_assert(g_list_find(node->subpath->nodes, node));
918 Inkscape::NodePath::SubPath *sp = node->subpath;
920 NRPathcode code = (NRPathcode) node->code;
921 if (code == NR_MOVETO) { // if node is the endnode,
922 node->code = NR_LINETO; // new one is inserted before it, so change that to line
923 }
925 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
927 if (!node->n.other || !node->p.other) // if node is an endnode, select it
928 return node;
929 else
930 return newnode; // otherwise select the newly created node
931 }
933 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
934 {
935 node->p.pos = (node->pos + (node->pos - node->n.pos));
936 }
938 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
939 {
940 node->n.pos = (node->pos + (node->pos - node->p.pos));
941 }
943 /**
944 * Change line type at node, with side effects on neighbours.
945 */
946 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
947 {
948 g_assert(end);
949 g_assert(end->subpath);
950 g_assert(end->p.other);
952 if (end->code == static_cast< guint > ( code ) )
953 return;
955 Inkscape::NodePath::Node *start = end->p.other;
957 end->code = code;
959 if (code == NR_LINETO) {
960 if (start->code == NR_LINETO) {
961 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
962 }
963 if (end->n.other) {
964 if (end->n.other->code == NR_LINETO) {
965 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
966 }
967 }
968 } else {
969 NR::Point delta = end->pos - start->pos;
970 start->n.pos = start->pos + delta / 3;
971 end->p.pos = end->pos - delta / 3;
972 sp_node_adjust_handle(start, 1);
973 sp_node_adjust_handle(end, -1);
974 }
976 sp_node_update_handles(start);
977 sp_node_update_handles(end);
978 }
980 /**
981 * Change node type, and its handles accordingly.
982 */
983 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
984 {
985 g_assert(node);
986 g_assert(node->subpath);
988 if ((node->p.other != NULL) && (node->n.other != NULL)) {
989 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
990 type =Inkscape::NodePath::NODE_CUSP;
991 }
992 }
994 node->type = type;
996 if (node->type == Inkscape::NodePath::NODE_CUSP) {
997 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
998 node->knot->setSize (node->selected? 11 : 9);
999 sp_knot_update_ctrl(node->knot);
1000 } else {
1001 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1002 node->knot->setSize (node->selected? 9 : 7);
1003 sp_knot_update_ctrl(node->knot);
1004 }
1006 // if one of handles is mouseovered, preserve its position
1007 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1008 sp_node_adjust_handle(node, 1);
1009 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1010 sp_node_adjust_handle(node, -1);
1011 } else {
1012 sp_node_adjust_handles(node);
1013 }
1015 sp_node_update_handles(node);
1017 sp_nodepath_update_statusbar(node->subpath->nodepath);
1019 return node;
1020 }
1022 bool
1023 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1024 {
1025 Inkscape::NodePath::Node *othernode = side->other;
1026 if (!othernode)
1027 return false;
1028 NRPathcode const code = sp_node_path_code_from_side(node, side);
1029 if (code == NR_LINETO)
1030 return true;
1031 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1032 if (&node->p == side) {
1033 other_to_me = &othernode->n;
1034 } else if (&node->n == side) {
1035 other_to_me = &othernode->p;
1036 }
1037 if (!other_to_me)
1038 return false;
1039 bool is_line =
1040 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1041 NR::L2(node->pos - side->pos) < 1e-6);
1042 return is_line;
1043 }
1045 /**
1046 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1047 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1048 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1049 * If already cusp and set to cusp, retracts handles.
1050 */
1051 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1052 {
1053 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1055 /*
1056 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1058 if (two_handles) {
1059 // do nothing, adjust_handles called via set_node_type will line them up
1060 } else if (one_handle) {
1061 if (opposite_to_handle_is_line) {
1062 if (lined_up) {
1063 // already half-smooth; pull opposite handle too making it fully smooth
1064 } else {
1065 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1066 }
1067 } else {
1068 // pull opposite handle in line with the existing one
1069 }
1070 } else if (no_handles) {
1071 if (both_segments_are_lines OR both_segments_are_curves) {
1072 //pull both handles
1073 } else {
1074 // pull the handle opposite to line segment, making node half-smooth
1075 }
1076 }
1077 */
1078 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1079 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1080 bool p_is_line = sp_node_side_is_line(node, &node->p);
1081 bool n_is_line = sp_node_side_is_line(node, &node->n);
1083 if (p_has_handle && n_has_handle) {
1084 // do nothing, adjust_handles will line them up
1085 } else if (p_has_handle || n_has_handle) {
1086 if (p_has_handle && n_is_line) {
1087 Radial line (node->n.other->pos - node->pos);
1088 Radial handle (node->pos - node->p.pos);
1089 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1090 // already half-smooth; pull opposite handle too making it fully smooth
1091 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1092 } else {
1093 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1094 }
1095 } else if (n_has_handle && p_is_line) {
1096 Radial line (node->p.other->pos - node->pos);
1097 Radial handle (node->pos - node->n.pos);
1098 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1099 // already half-smooth; pull opposite handle too making it fully smooth
1100 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1101 } else {
1102 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1103 }
1104 } else if (p_has_handle && node->n.other) {
1105 // pull n handle
1106 node->n.other->code = NR_CURVETO;
1107 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1108 NR::L2(node->p.pos - node->pos) :
1109 NR::L2(node->n.other->pos - node->pos) / 3;
1110 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1111 } else if (n_has_handle && node->p.other) {
1112 // pull p handle
1113 node->code = NR_CURVETO;
1114 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1115 NR::L2(node->n.pos - node->pos) :
1116 NR::L2(node->p.other->pos - node->pos) / 3;
1117 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1118 }
1119 } else if (!p_has_handle && !n_has_handle) {
1120 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1121 // no handles, but both segments are either lnes or curves:
1122 //pull both handles
1124 // convert both to curves:
1125 node->code = NR_CURVETO;
1126 node->n.other->code = NR_CURVETO;
1128 NR::Point leg_prev = node->pos - node->p.other->pos;
1129 NR::Point leg_next = node->pos - node->n.other->pos;
1131 double norm_leg_prev = L2(leg_prev);
1132 double norm_leg_next = L2(leg_next);
1134 NR::Point delta;
1135 if (norm_leg_next > 0.0) {
1136 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1137 (&delta)->normalize();
1138 }
1140 if (type == Inkscape::NodePath::NODE_SYMM) {
1141 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1142 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1143 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1144 } else {
1145 // length of handle is proportional to distance to adjacent node
1146 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1147 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1148 }
1150 } else {
1151 // pull the handle opposite to line segment, making it half-smooth
1152 if (p_is_line && node->n.other) {
1153 if (type != Inkscape::NodePath::NODE_SYMM) {
1154 // pull n handle
1155 node->n.other->code = NR_CURVETO;
1156 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1157 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1158 }
1159 } else if (n_is_line && node->p.other) {
1160 if (type != Inkscape::NodePath::NODE_SYMM) {
1161 // pull p handle
1162 node->code = NR_CURVETO;
1163 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1164 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1165 }
1166 }
1167 }
1168 }
1169 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1170 // cusping a cusp: retract nodes
1171 node->p.pos = node->pos;
1172 node->n.pos = node->pos;
1173 }
1175 sp_nodepath_set_node_type (node, type);
1176 }
1178 /**
1179 * Move node to point, and adjust its and neighbouring handles.
1180 */
1181 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1182 {
1183 NR::Point delta = p - node->pos;
1184 node->pos = p;
1186 node->p.pos += delta;
1187 node->n.pos += delta;
1189 Inkscape::NodePath::Node *node_p = NULL;
1190 Inkscape::NodePath::Node *node_n = NULL;
1192 if (node->p.other) {
1193 if (node->code == NR_LINETO) {
1194 sp_node_adjust_handle(node, 1);
1195 sp_node_adjust_handle(node->p.other, -1);
1196 node_p = node->p.other;
1197 }
1198 }
1199 if (node->n.other) {
1200 if (node->n.other->code == NR_LINETO) {
1201 sp_node_adjust_handle(node, -1);
1202 sp_node_adjust_handle(node->n.other, 1);
1203 node_n = node->n.other;
1204 }
1205 }
1207 // this function is only called from batch movers that will update display at the end
1208 // themselves, so here we just move all the knots without emitting move signals, for speed
1209 sp_node_update_handles(node, false);
1210 if (node_n) {
1211 sp_node_update_handles(node_n, false);
1212 }
1213 if (node_p) {
1214 sp_node_update_handles(node_p, false);
1215 }
1216 }
1218 /**
1219 * Call sp_node_moveto() for node selection and handle possible snapping.
1220 */
1221 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1222 bool const snap, bool constrained = false,
1223 Inkscape::Snapper::ConstraintLine const &constraint = NR::Point())
1224 {
1225 NR::Coord best = NR_HUGE;
1226 NR::Point delta(dx, dy);
1227 NR::Point best_pt = delta;
1228 Inkscape::SnappedPoint best_abs;
1230 if (snap) {
1231 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1232 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1233 * must provide that information. */
1235 // Build a list of the unselected nodes to which the snapper should snap
1236 std::vector<NR::Point> unselected_nodes;
1237 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1238 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1239 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1240 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1241 if (!node->selected) {
1242 unselected_nodes.push_back(node->pos);
1243 }
1244 }
1245 }
1247 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1249 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1250 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1251 m.setup(NULL, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1252 Inkscape::SnappedPoint s;
1253 if (constrained) {
1254 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1255 dedicated_constraint.setPoint(n->pos);
1256 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, dedicated_constraint);
1257 } else {
1258 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1259 }
1260 if (s.getSnapped() && (s.getDistance() < best)) {
1261 best = s.getDistance();
1262 best_abs = s;
1263 best_pt = s.getPoint() - n->pos;
1264 }
1265 }
1267 if (best_abs.getSnapped()) {
1268 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1269 } else {
1270 nodepath->desktop->snapindicator->remove_snappoint();
1271 }
1272 }
1274 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1275 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1276 sp_node_moveto(n, n->pos + best_pt);
1277 }
1279 // do not update repr here so that node dragging is acceptably fast
1280 update_object(nodepath);
1281 }
1283 /**
1284 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1285 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1286 near x = 0.
1287 */
1288 double
1289 sculpt_profile (double x, double alpha, guint profile)
1290 {
1291 if (x >= 1)
1292 return 0;
1293 if (x <= 0)
1294 return 1;
1296 switch (profile) {
1297 case SCULPT_PROFILE_LINEAR:
1298 return 1 - x;
1299 case SCULPT_PROFILE_BELL:
1300 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1301 case SCULPT_PROFILE_ELLIPTIC:
1302 return sqrt(1 - x*x);
1303 }
1305 return 1;
1306 }
1308 double
1309 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1310 {
1311 // extremely primitive for now, don't have time to look for the real one
1312 double lower = NR::L2(b - a);
1313 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1314 return (lower + upper)/2;
1315 }
1317 void
1318 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1319 {
1320 n->pos = n->origin + delta;
1321 n->n.pos = n->n.origin + delta_n;
1322 n->p.pos = n->p.origin + delta_p;
1323 sp_node_adjust_handles(n);
1324 sp_node_update_handles(n, false);
1325 }
1327 /**
1328 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1329 * on how far they are from the dragged node n.
1330 */
1331 static void
1332 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1333 {
1334 g_assert (n);
1335 g_assert (nodepath);
1336 g_assert (n->subpath->nodepath == nodepath);
1338 double pressure = n->knot->pressure;
1339 if (pressure == 0)
1340 pressure = 0.5; // default
1341 pressure = CLAMP (pressure, 0.2, 0.8);
1343 // map pressure to alpha = 1/5 ... 5
1344 double alpha = 1 - 2 * fabs(pressure - 0.5);
1345 if (pressure > 0.5)
1346 alpha = 1/alpha;
1348 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1350 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1351 // Only one subpath has selected nodes:
1352 // use linear mode, where the distance from n to node being dragged is calculated along the path
1354 double n_sel_range = 0, p_sel_range = 0;
1355 guint n_nodes = 0, p_nodes = 0;
1356 guint n_sel_nodes = 0, p_sel_nodes = 0;
1358 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1359 {
1360 double n_range = 0, p_range = 0;
1361 bool n_going = true, p_going = true;
1362 Inkscape::NodePath::Node *n_node = n;
1363 Inkscape::NodePath::Node *p_node = n;
1364 do {
1365 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1366 if (n_node && n_going)
1367 n_node = n_node->n.other;
1368 if (n_node == NULL) {
1369 n_going = false;
1370 } else {
1371 n_nodes ++;
1372 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1373 if (n_node->selected) {
1374 n_sel_nodes ++;
1375 n_sel_range = n_range;
1376 }
1377 if (n_node == p_node) {
1378 n_going = false;
1379 p_going = false;
1380 }
1381 }
1382 if (p_node && p_going)
1383 p_node = p_node->p.other;
1384 if (p_node == NULL) {
1385 p_going = false;
1386 } else {
1387 p_nodes ++;
1388 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1389 if (p_node->selected) {
1390 p_sel_nodes ++;
1391 p_sel_range = p_range;
1392 }
1393 if (p_node == n_node) {
1394 n_going = false;
1395 p_going = false;
1396 }
1397 }
1398 } while (n_going || p_going);
1399 }
1401 // Second pass: actually move nodes in this subpath
1402 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1403 {
1404 double n_range = 0, p_range = 0;
1405 bool n_going = true, p_going = true;
1406 Inkscape::NodePath::Node *n_node = n;
1407 Inkscape::NodePath::Node *p_node = n;
1408 do {
1409 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1410 if (n_node && n_going)
1411 n_node = n_node->n.other;
1412 if (n_node == NULL) {
1413 n_going = false;
1414 } else {
1415 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1416 if (n_node->selected) {
1417 sp_nodepath_move_node_and_handles (n_node,
1418 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1419 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1420 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1421 }
1422 if (n_node == p_node) {
1423 n_going = false;
1424 p_going = false;
1425 }
1426 }
1427 if (p_node && p_going)
1428 p_node = p_node->p.other;
1429 if (p_node == NULL) {
1430 p_going = false;
1431 } else {
1432 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1433 if (p_node->selected) {
1434 sp_nodepath_move_node_and_handles (p_node,
1435 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1436 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1437 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1438 }
1439 if (p_node == n_node) {
1440 n_going = false;
1441 p_going = false;
1442 }
1443 }
1444 } while (n_going || p_going);
1445 }
1447 } else {
1448 // Multiple subpaths have selected nodes:
1449 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1450 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1451 // fix the pear-like shape when sculpting e.g. a ring
1453 // First pass: calculate range
1454 gdouble direct_range = 0;
1455 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1456 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1457 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1458 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1459 if (node->selected) {
1460 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1461 }
1462 }
1463 }
1465 // Second pass: actually move nodes
1466 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1467 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1468 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1469 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1470 if (node->selected) {
1471 if (direct_range > 1e-6) {
1472 sp_nodepath_move_node_and_handles (node,
1473 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1474 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1475 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1476 } else {
1477 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1478 }
1480 }
1481 }
1482 }
1483 }
1485 // do not update repr here so that node dragging is acceptably fast
1486 update_object(nodepath);
1487 }
1490 /**
1491 * Move node selection to point, adjust its and neighbouring handles,
1492 * handle possible snapping, and commit the change with possible undo.
1493 */
1494 void
1495 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1496 {
1497 if (!nodepath) return;
1499 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1501 if (dx == 0) {
1502 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1503 } else if (dy == 0) {
1504 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1505 } else {
1506 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1507 }
1508 }
1510 /**
1511 * Move node selection off screen and commit the change.
1512 */
1513 void
1514 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1515 {
1516 // borrowed from sp_selection_move_screen in selection-chemistry.c
1517 // we find out the current zoom factor and divide deltas by it
1518 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1520 gdouble zoom = desktop->current_zoom();
1521 gdouble zdx = dx / zoom;
1522 gdouble zdy = dy / zoom;
1524 if (!nodepath) return;
1526 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1528 if (dx == 0) {
1529 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1530 } else if (dy == 0) {
1531 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1532 } else {
1533 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1534 }
1535 }
1537 /**
1538 * Move selected nodes to the absolute position given
1539 */
1540 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1541 {
1542 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1543 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1544 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1545 sp_node_moveto(n, npos);
1546 }
1548 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1549 }
1551 /**
1552 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1553 */
1554 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1555 {
1556 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1557 g_return_val_if_fail(nodepath->selected, no_coord);
1559 // determine coordinate of first selected node
1560 GList *nsel = nodepath->selected;
1561 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1562 NR::Coord coord = n->pos[axis];
1563 bool coincide = true;
1565 // compare it to the coordinates of all the other selected nodes
1566 for (GList *l = nsel->next; l != NULL; l = l->next) {
1567 n = (Inkscape::NodePath::Node *) l->data;
1568 if (n->pos[axis] != coord) {
1569 coincide = false;
1570 }
1571 }
1572 if (coincide) {
1573 return coord;
1574 } else {
1575 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1576 // currently we return the coordinate of the bounding box midpoint because I don't know how
1577 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1578 return bbox.midpoint()[axis];
1579 }
1580 }
1582 /** If they don't yet exist, creates knot and line for the given side of the node */
1583 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1584 {
1585 if (!side->knot) {
1586 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"));
1588 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1589 side->knot->setSize (7);
1590 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1591 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1592 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1593 sp_knot_update_ctrl(side->knot);
1595 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1596 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1597 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1598 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1599 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1600 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1601 }
1603 if (!side->line) {
1604 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1605 SP_TYPE_CTRLLINE, NULL);
1606 }
1607 }
1609 /**
1610 * Ensure the given handle of the node is visible/invisible, update its screen position
1611 */
1612 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1613 {
1614 g_assert(node != NULL);
1616 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1617 NRPathcode code = sp_node_path_code_from_side(node, side);
1619 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1621 if (show_handle) {
1622 if (!side->knot) { // No handle knot at all
1623 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1624 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1625 side->knot->pos = side->pos;
1626 if (side->knot->item)
1627 SP_CTRL(side->knot->item)->moveto(side->pos);
1628 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1629 sp_knot_show(side->knot);
1630 } else {
1631 if (side->knot->pos != side->pos) { // only if it's really moved
1632 if (fire_move_signals) {
1633 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1634 } else {
1635 sp_knot_moveto(side->knot, &side->pos);
1636 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1637 }
1638 }
1639 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1640 sp_knot_show(side->knot);
1641 }
1642 }
1643 sp_canvas_item_show(side->line);
1644 } else {
1645 if (side->knot) {
1646 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1647 sp_knot_hide(side->knot);
1648 }
1649 }
1650 if (side->line) {
1651 sp_canvas_item_hide(side->line);
1652 }
1653 }
1654 }
1656 /**
1657 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1658 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1659 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1660 * updated; otherwise, just move the knots silently (used in batch moves).
1661 */
1662 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1663 {
1664 g_assert(node != NULL);
1666 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1667 sp_knot_show(node->knot);
1668 }
1670 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1671 if (fire_move_signals)
1672 sp_knot_set_position(node->knot, &node->pos, 0);
1673 else
1674 sp_knot_moveto(node->knot, &node->pos);
1675 }
1677 gboolean show_handles = node->selected;
1678 if (node->p.other != NULL) {
1679 if (node->p.other->selected) show_handles = TRUE;
1680 }
1681 if (node->n.other != NULL) {
1682 if (node->n.other->selected) show_handles = TRUE;
1683 }
1685 if (node->subpath->nodepath->show_handles == false)
1686 show_handles = FALSE;
1688 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1689 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1690 }
1692 /**
1693 * Call sp_node_update_handles() for all nodes on subpath.
1694 */
1695 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1696 {
1697 g_assert(subpath != NULL);
1699 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1700 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1701 }
1702 }
1704 /**
1705 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1706 */
1707 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1708 {
1709 g_assert(nodepath != NULL);
1711 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1712 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1713 }
1714 }
1716 void
1717 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1718 {
1719 if (nodepath == NULL) return;
1721 nodepath->show_handles = show;
1722 sp_nodepath_update_handles(nodepath);
1723 }
1725 /**
1726 * Adds all selected nodes in nodepath to list.
1727 */
1728 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1729 {
1730 StlConv<Node *>::list(l, selected);
1731 /// \todo this adds a copying, rework when the selection becomes a stl list
1732 }
1734 /**
1735 * Align selected nodes on the specified axis.
1736 */
1737 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1738 {
1739 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1740 return;
1741 }
1743 if ( !nodepath->selected->next ) { // only one node selected
1744 return;
1745 }
1746 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1747 NR::Point dest(pNode->pos);
1748 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1749 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1750 if (pNode) {
1751 dest[axis] = pNode->pos[axis];
1752 sp_node_moveto(pNode, dest);
1753 }
1754 }
1756 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1757 }
1759 /// Helper struct.
1760 struct NodeSort
1761 {
1762 Inkscape::NodePath::Node *_node;
1763 NR::Coord _coord;
1764 /// \todo use vectorof pointers instead of calling copy ctor
1765 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1766 _node(node), _coord(node->pos[axis])
1767 {}
1769 };
1771 static bool operator<(NodeSort const &a, NodeSort const &b)
1772 {
1773 return (a._coord < b._coord);
1774 }
1776 /**
1777 * Distribute selected nodes on the specified axis.
1778 */
1779 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1780 {
1781 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1782 return;
1783 }
1785 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1786 return;
1787 }
1789 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1790 std::vector<NodeSort> sorted;
1791 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1792 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1793 if (pNode) {
1794 NodeSort n(pNode, axis);
1795 sorted.push_back(n);
1796 //dest[axis] = pNode->pos[axis];
1797 //sp_node_moveto(pNode, dest);
1798 }
1799 }
1800 std::sort(sorted.begin(), sorted.end());
1801 unsigned int len = sorted.size();
1802 //overall bboxes span
1803 float dist = (sorted.back()._coord -
1804 sorted.front()._coord);
1805 //new distance between each bbox
1806 float step = (dist) / (len - 1);
1807 float pos = sorted.front()._coord;
1808 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1809 it < sorted.end();
1810 it ++ )
1811 {
1812 NR::Point dest((*it)._node->pos);
1813 dest[axis] = pos;
1814 sp_node_moveto((*it)._node, dest);
1815 pos += step;
1816 }
1818 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1819 }
1822 /**
1823 * Call sp_nodepath_line_add_node() for all selected segments.
1824 */
1825 void
1826 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1827 {
1828 if (!nodepath) {
1829 return;
1830 }
1832 GList *nl = NULL;
1834 int n_added = 0;
1836 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1837 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1838 g_assert(t->selected);
1839 if (t->p.other && t->p.other->selected) {
1840 nl = g_list_prepend(nl, t);
1841 }
1842 }
1844 while (nl) {
1845 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1846 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1847 sp_nodepath_node_select(n, TRUE, FALSE);
1848 n_added ++;
1849 nl = g_list_remove(nl, t);
1850 }
1852 /** \todo fixme: adjust ? */
1853 sp_nodepath_update_handles(nodepath);
1855 if (n_added > 1) {
1856 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1857 } else if (n_added > 0) {
1858 sp_nodepath_update_repr(nodepath, _("Add node"));
1859 }
1861 sp_nodepath_update_statusbar(nodepath);
1862 }
1864 /**
1865 * Select segment nearest to point
1866 */
1867 void
1868 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1869 {
1870 if (!nodepath) {
1871 return;
1872 }
1874 sp_nodepath_ensure_livarot_path(nodepath);
1875 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1876 if (!maybe_position) {
1877 return;
1878 }
1879 Path::cut_position position = *maybe_position;
1881 //find segment to segment
1882 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1884 //fixme: this can return NULL, so check before proceeding.
1885 g_return_if_fail(e != NULL);
1887 gboolean force = FALSE;
1888 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1889 force = TRUE;
1890 }
1891 sp_nodepath_node_select(e, (gboolean) toggle, force);
1892 if (e->p.other)
1893 sp_nodepath_node_select(e->p.other, TRUE, force);
1895 sp_nodepath_update_handles(nodepath);
1897 sp_nodepath_update_statusbar(nodepath);
1898 }
1900 /**
1901 * Add a node nearest to point
1902 */
1903 void
1904 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1905 {
1906 if (!nodepath) {
1907 return;
1908 }
1910 sp_nodepath_ensure_livarot_path(nodepath);
1911 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1912 if (!maybe_position) {
1913 return;
1914 }
1915 Path::cut_position position = *maybe_position;
1917 //find segment to split
1918 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1920 //don't know why but t seems to flip for lines
1921 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1922 position.t = 1.0 - position.t;
1923 }
1924 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1925 sp_nodepath_node_select(n, FALSE, TRUE);
1927 /* fixme: adjust ? */
1928 sp_nodepath_update_handles(nodepath);
1930 sp_nodepath_update_repr(nodepath, _("Add node"));
1932 sp_nodepath_update_statusbar(nodepath);
1933 }
1935 /*
1936 * Adjusts a segment so that t moves by a certain delta for dragging
1937 * converts lines to curves
1938 *
1939 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1940 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1941 */
1942 void
1943 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1944 {
1945 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1947 //fixme: e and e->p can be NULL, so check for those before proceeding
1948 g_return_if_fail(e != NULL);
1949 g_return_if_fail(&e->p != NULL);
1951 /* feel good is an arbitrary parameter that distributes the delta between handles
1952 * if t of the drag point is less than 1/6 distance form the endpoint only
1953 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1954 */
1955 double feel_good;
1956 if (t <= 1.0 / 6.0)
1957 feel_good = 0;
1958 else if (t <= 0.5)
1959 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1960 else if (t <= 5.0 / 6.0)
1961 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1962 else
1963 feel_good = 1;
1965 //if we're dragging a line convert it to a curve
1966 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1967 sp_nodepath_set_line_type(e, NR_CURVETO);
1968 }
1970 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1971 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1972 e->p.other->n.pos += offsetcoord0;
1973 e->p.pos += offsetcoord1;
1975 // adjust handles of adjacent nodes where necessary
1976 sp_node_adjust_handle(e,1);
1977 sp_node_adjust_handle(e->p.other,-1);
1979 sp_nodepath_update_handles(e->subpath->nodepath);
1981 update_object(e->subpath->nodepath);
1983 sp_nodepath_update_statusbar(e->subpath->nodepath);
1984 }
1987 /**
1988 * Call sp_nodepath_break() for all selected segments.
1989 */
1990 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1991 {
1992 if (!nodepath) return;
1994 GList *tempin = g_list_copy(nodepath->selected);
1995 GList *temp = NULL;
1996 for (GList *l = tempin; l != NULL; l = l->next) {
1997 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1998 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1999 if (nn == NULL) continue; // no break, no new node
2000 temp = g_list_prepend(temp, nn);
2001 }
2002 g_list_free(tempin);
2004 if (temp) {
2005 sp_nodepath_deselect(nodepath);
2006 }
2007 for (GList *l = temp; l != NULL; l = l->next) {
2008 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2009 }
2011 sp_nodepath_update_handles(nodepath);
2013 sp_nodepath_update_repr(nodepath, _("Break path"));
2014 }
2016 /**
2017 * Duplicate the selected node(s).
2018 */
2019 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2020 {
2021 if (!nodepath) {
2022 return;
2023 }
2025 GList *temp = NULL;
2026 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2027 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2028 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2029 if (nn == NULL) continue; // could not duplicate
2030 temp = g_list_prepend(temp, nn);
2031 }
2033 if (temp) {
2034 sp_nodepath_deselect(nodepath);
2035 }
2036 for (GList *l = temp; l != NULL; l = l->next) {
2037 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2038 }
2040 sp_nodepath_update_handles(nodepath);
2042 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2043 }
2045 /**
2046 * Internal function to join two nodes by merging them into one.
2047 */
2048 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2049 {
2050 /* a and b are endpoints */
2052 // if one of the two nodes is mouseovered, fix its position
2053 NR::Point c;
2054 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2055 c = a->pos;
2056 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2057 c = b->pos;
2058 } else {
2059 // otherwise, move joined node to the midpoint
2060 c = (a->pos + b->pos) / 2;
2061 }
2063 if (a->subpath == b->subpath) {
2064 Inkscape::NodePath::SubPath *sp = a->subpath;
2065 sp_nodepath_subpath_close(sp);
2066 sp_node_moveto (sp->first, c);
2068 sp_nodepath_update_handles(sp->nodepath);
2069 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2070 return;
2071 }
2073 /* a and b are separate subpaths */
2074 Inkscape::NodePath::SubPath *sa = a->subpath;
2075 Inkscape::NodePath::SubPath *sb = b->subpath;
2076 NR::Point p;
2077 Inkscape::NodePath::Node *n;
2078 NRPathcode code;
2079 if (a == sa->first) {
2080 // we will now reverse sa, so that a is its last node, not first, and drop that node
2081 p = sa->first->n.pos;
2082 code = (NRPathcode)sa->first->n.other->code;
2083 // create new subpath
2084 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2085 // create a first moveto node on it
2086 n = sa->last;
2087 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2088 n = n->p.other;
2089 if (n == sa->first) n = NULL;
2090 while (n) {
2091 // copy the rest of the nodes from sa to t, going backwards
2092 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2093 n = n->p.other;
2094 if (n == sa->first) n = NULL;
2095 }
2096 // replace sa with t
2097 sp_nodepath_subpath_destroy(sa);
2098 sa = t;
2099 } else if (a == sa->last) {
2100 // a is already last, just drop it
2101 p = sa->last->p.pos;
2102 code = (NRPathcode)sa->last->code;
2103 sp_nodepath_node_destroy(sa->last);
2104 } else {
2105 code = NR_END;
2106 g_assert_not_reached();
2107 }
2109 if (b == sb->first) {
2110 // copy all nodes from b to a, forward
2111 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2112 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2113 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2114 }
2115 } else if (b == sb->last) {
2116 // copy all nodes from b to a, backward
2117 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2118 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2119 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2120 }
2121 } else {
2122 g_assert_not_reached();
2123 }
2124 /* and now destroy sb */
2126 sp_nodepath_subpath_destroy(sb);
2128 sp_nodepath_update_handles(sa->nodepath);
2130 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2132 sp_nodepath_update_statusbar(nodepath);
2133 }
2135 /**
2136 * Internal function to join two nodes by adding a segment between them.
2137 */
2138 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2139 {
2140 if (a->subpath == b->subpath) {
2141 Inkscape::NodePath::SubPath *sp = a->subpath;
2143 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2144 sp->closed = TRUE;
2146 sp->first->p.other = sp->last;
2147 sp->last->n.other = sp->first;
2149 sp_node_handle_mirror_p_to_n(sp->last);
2150 sp_node_handle_mirror_n_to_p(sp->first);
2152 sp->first->code = sp->last->code;
2153 sp->first = sp->last;
2155 sp_nodepath_update_handles(sp->nodepath);
2157 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2159 return;
2160 }
2162 /* a and b are separate subpaths */
2163 Inkscape::NodePath::SubPath *sa = a->subpath;
2164 Inkscape::NodePath::SubPath *sb = b->subpath;
2166 Inkscape::NodePath::Node *n;
2167 NR::Point p;
2168 NRPathcode code;
2169 if (a == sa->first) {
2170 code = (NRPathcode) sa->first->n.other->code;
2171 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2172 n = sa->last;
2173 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2174 for (n = n->p.other; n != NULL; n = n->p.other) {
2175 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2176 }
2177 sp_nodepath_subpath_destroy(sa);
2178 sa = t;
2179 } else if (a == sa->last) {
2180 code = (NRPathcode)sa->last->code;
2181 } else {
2182 code = NR_END;
2183 g_assert_not_reached();
2184 }
2186 if (b == sb->first) {
2187 n = sb->first;
2188 sp_node_handle_mirror_p_to_n(sa->last);
2189 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2190 sp_node_handle_mirror_n_to_p(sa->last);
2191 for (n = n->n.other; n != NULL; n = n->n.other) {
2192 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2193 }
2194 } else if (b == sb->last) {
2195 n = sb->last;
2196 sp_node_handle_mirror_p_to_n(sa->last);
2197 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2198 sp_node_handle_mirror_n_to_p(sa->last);
2199 for (n = n->p.other; n != NULL; n = n->p.other) {
2200 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2201 }
2202 } else {
2203 g_assert_not_reached();
2204 }
2205 /* and now destroy sb */
2207 sp_nodepath_subpath_destroy(sb);
2209 sp_nodepath_update_handles(sa->nodepath);
2211 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2212 }
2214 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2216 /**
2217 * Internal function to handle joining two nodes.
2218 */
2219 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2220 {
2221 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2223 if (g_list_length(nodepath->selected) != 2) {
2224 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2225 return;
2226 }
2228 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2229 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2231 g_assert(a != b);
2232 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2233 // someone tried to join an orphan node (i.e. a single-node subpath).
2234 // this is not worth an error message, just fail silently.
2235 return;
2236 }
2238 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2239 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2240 return;
2241 }
2243 switch(mode) {
2244 case NODE_JOIN_ENDPOINTS:
2245 do_node_selected_join(nodepath, a, b);
2246 break;
2247 case NODE_JOIN_SEGMENT:
2248 do_node_selected_join_segment(nodepath, a, b);
2249 break;
2250 }
2251 }
2253 /**
2254 * Join two nodes by merging them into one.
2255 */
2256 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2257 {
2258 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2259 }
2261 /**
2262 * Join two nodes by adding a segment between them.
2263 */
2264 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2265 {
2266 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2267 }
2269 /**
2270 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2271 */
2272 void sp_node_delete_preserve(GList *nodes_to_delete)
2273 {
2274 GSList *nodepaths = NULL;
2276 while (nodes_to_delete) {
2277 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2278 Inkscape::NodePath::SubPath *sp = node->subpath;
2279 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2280 Inkscape::NodePath::Node *sample_cursor = NULL;
2281 Inkscape::NodePath::Node *sample_end = NULL;
2282 Inkscape::NodePath::Node *delete_cursor = node;
2283 bool just_delete = false;
2285 //find the start of this contiguous selection
2286 //move left to the first node that is not selected
2287 //or the start of the non-closed path
2288 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2289 delete_cursor = curr;
2290 }
2292 //just delete at the beginning of an open path
2293 if (!delete_cursor->p.other) {
2294 sample_cursor = delete_cursor;
2295 just_delete = true;
2296 } else {
2297 sample_cursor = delete_cursor->p.other;
2298 }
2300 //calculate points for each segment
2301 int rate = 5;
2302 float period = 1.0 / rate;
2303 std::vector<NR::Point> data;
2304 if (!just_delete) {
2305 data.push_back(sample_cursor->pos);
2306 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2307 //just delete at the end of an open path
2308 if (!sp->closed && curr == sp->last) {
2309 just_delete = true;
2310 break;
2311 }
2313 //sample points on the contiguous selected segment
2314 NR::Point *bez;
2315 bez = new NR::Point [4];
2316 bez[0] = curr->pos;
2317 bez[1] = curr->n.pos;
2318 bez[2] = curr->n.other->p.pos;
2319 bez[3] = curr->n.other->pos;
2320 for (int i=1; i<rate; i++) {
2321 gdouble t = i * period;
2322 NR::Point p = bezier_pt(3, bez, t);
2323 data.push_back(p);
2324 }
2325 data.push_back(curr->n.other->pos);
2327 sample_end = curr->n.other;
2328 //break if we've come full circle or hit the end of the selection
2329 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2330 break;
2331 }
2332 }
2333 }
2335 if (!just_delete) {
2336 //calculate the best fitting single segment and adjust the endpoints
2337 NR::Point *adata;
2338 adata = new NR::Point [data.size()];
2339 copy(data.begin(), data.end(), adata);
2341 NR::Point *bez;
2342 bez = new NR::Point [4];
2343 //would decreasing error create a better fitting approximation?
2344 gdouble error = 1.0;
2345 gint ret;
2346 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2348 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2349 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2350 //the resulting nodes behave as expected.
2351 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2352 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2353 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2354 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2356 //adjust endpoints
2357 sample_cursor->n.pos = bez[1];
2358 sample_end->p.pos = bez[2];
2359 }
2361 //destroy this contiguous selection
2362 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2363 Inkscape::NodePath::Node *temp = delete_cursor;
2364 if (delete_cursor->n.other == delete_cursor) {
2365 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2366 delete_cursor = NULL;
2367 } else {
2368 delete_cursor = delete_cursor->n.other;
2369 }
2370 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2371 sp_nodepath_node_destroy(temp);
2372 }
2374 sp_nodepath_update_handles(nodepath);
2376 if (!g_slist_find(nodepaths, nodepath))
2377 nodepaths = g_slist_prepend (nodepaths, nodepath);
2378 }
2380 for (GSList *i = nodepaths; i; i = i->next) {
2381 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2382 // different nodepaths will give us one undo event per nodepath
2383 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2385 // if the entire nodepath is removed, delete the selected object.
2386 if (nodepath->subpaths == NULL ||
2387 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2388 //at least 2
2389 sp_nodepath_get_node_count(nodepath) < 2) {
2390 SPDocument *document = sp_desktop_document (nodepath->desktop);
2391 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2392 //delete this nodepath's object, not the entire selection! (though at this time, this
2393 //does not matter)
2394 sp_selection_delete();
2395 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2396 _("Delete nodes"));
2397 } else {
2398 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2399 sp_nodepath_update_statusbar(nodepath);
2400 }
2401 }
2403 g_slist_free (nodepaths);
2404 }
2406 /**
2407 * Delete one or more selected nodes.
2408 */
2409 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2410 {
2411 if (!nodepath) return;
2412 if (!nodepath->selected) return;
2414 /** \todo fixme: do it the right way */
2415 while (nodepath->selected) {
2416 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2417 sp_nodepath_node_destroy(node);
2418 }
2421 //clean up the nodepath (such as for trivial subpaths)
2422 sp_nodepath_cleanup(nodepath);
2424 sp_nodepath_update_handles(nodepath);
2426 // if the entire nodepath is removed, delete the selected object.
2427 if (nodepath->subpaths == NULL ||
2428 sp_nodepath_get_node_count(nodepath) < 2) {
2429 SPDocument *document = sp_desktop_document (nodepath->desktop);
2430 sp_selection_delete();
2431 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2432 _("Delete nodes"));
2433 return;
2434 }
2436 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2438 sp_nodepath_update_statusbar(nodepath);
2439 }
2441 /**
2442 * Delete one or more segments between two selected nodes.
2443 * This is the code for 'split'.
2444 */
2445 void
2446 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2447 {
2448 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2449 Inkscape::NodePath::Node *curr, *next; //Iterators
2451 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2453 if (g_list_length(nodepath->selected) != 2) {
2454 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2455 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2456 return;
2457 }
2459 //Selected nodes, not inclusive
2460 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2461 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2463 if ( ( a==b) || //same node
2464 (a->subpath != b->subpath ) || //not the same path
2465 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2466 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2467 {
2468 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2469 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2470 return;
2471 }
2473 //###########################################
2474 //# BEGIN EDITS
2475 //###########################################
2476 //##################################
2477 //# CLOSED PATH
2478 //##################################
2479 if (a->subpath->closed) {
2482 gboolean reversed = FALSE;
2484 //Since we can go in a circle, we need to find the shorter distance.
2485 // a->b or b->a
2486 start = end = NULL;
2487 int distance = 0;
2488 int minDistance = 0;
2489 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2490 if (curr==b) {
2491 //printf("a to b:%d\n", distance);
2492 start = a;//go from a to b
2493 end = b;
2494 minDistance = distance;
2495 //printf("A to B :\n");
2496 break;
2497 }
2498 distance++;
2499 }
2501 //try again, the other direction
2502 distance = 0;
2503 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2504 if (curr==a) {
2505 //printf("b to a:%d\n", distance);
2506 if (distance < minDistance) {
2507 start = b; //we go from b to a
2508 end = a;
2509 reversed = TRUE;
2510 //printf("B to A\n");
2511 }
2512 break;
2513 }
2514 distance++;
2515 }
2518 //Copy everything from 'end' to 'start' to a new subpath
2519 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2520 for (curr=end ; curr ; curr=curr->n.other) {
2521 NRPathcode code = (NRPathcode) curr->code;
2522 if (curr == end)
2523 code = NR_MOVETO;
2524 sp_nodepath_node_new(t, NULL,
2525 (Inkscape::NodePath::NodeType)curr->type, code,
2526 &curr->p.pos, &curr->pos, &curr->n.pos);
2527 if (curr == start)
2528 break;
2529 }
2530 sp_nodepath_subpath_destroy(a->subpath);
2533 }
2537 //##################################
2538 //# OPEN PATH
2539 //##################################
2540 else {
2542 //We need to get the direction of the list between A and B
2543 //Can we walk from a to b?
2544 start = end = NULL;
2545 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2546 if (curr==b) {
2547 start = a; //did it! we go from a to b
2548 end = b;
2549 //printf("A to B\n");
2550 break;
2551 }
2552 }
2553 if (!start) {//didn't work? let's try the other direction
2554 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2555 if (curr==a) {
2556 start = b; //did it! we go from b to a
2557 end = a;
2558 //printf("B to A\n");
2559 break;
2560 }
2561 }
2562 }
2563 if (!start) {
2564 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2565 _("Cannot find path between nodes."));
2566 return;
2567 }
2571 //Copy everything after 'end' to a new subpath
2572 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2573 for (curr=end ; curr ; curr=curr->n.other) {
2574 NRPathcode code = (NRPathcode) curr->code;
2575 if (curr == end)
2576 code = NR_MOVETO;
2577 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2578 &curr->p.pos, &curr->pos, &curr->n.pos);
2579 }
2581 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2582 for (curr = start->n.other ; curr ; curr=next) {
2583 next = curr->n.other;
2584 sp_nodepath_node_destroy(curr);
2585 }
2587 }
2588 //###########################################
2589 //# END EDITS
2590 //###########################################
2592 //clean up the nodepath (such as for trivial subpaths)
2593 sp_nodepath_cleanup(nodepath);
2595 sp_nodepath_update_handles(nodepath);
2597 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2599 sp_nodepath_update_statusbar(nodepath);
2600 }
2602 /**
2603 * Call sp_nodepath_set_line() for all selected segments.
2604 */
2605 void
2606 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2607 {
2608 if (nodepath == NULL) return;
2610 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2611 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2612 g_assert(n->selected);
2613 if (n->p.other && n->p.other->selected) {
2614 sp_nodepath_set_line_type(n, code);
2615 }
2616 }
2618 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2619 }
2621 /**
2622 * Call sp_nodepath_convert_node_type() for all selected nodes.
2623 */
2624 void
2625 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2626 {
2627 if (nodepath == NULL) return;
2629 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2631 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2632 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2633 }
2635 sp_nodepath_update_repr(nodepath, _("Change node type"));
2636 }
2638 /**
2639 * Change select status of node, update its own and neighbour handles.
2640 */
2641 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2642 {
2643 node->selected = selected;
2645 if (selected) {
2646 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2647 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2648 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2649 sp_knot_update_ctrl(node->knot);
2650 } else {
2651 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2652 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2653 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2654 sp_knot_update_ctrl(node->knot);
2655 }
2657 sp_node_update_handles(node);
2658 if (node->n.other) sp_node_update_handles(node->n.other);
2659 if (node->p.other) sp_node_update_handles(node->p.other);
2660 }
2662 /**
2663 \brief Select a node
2664 \param node The node to select
2665 \param incremental If true, add to selection, otherwise deselect others
2666 \param override If true, always select this node, otherwise toggle selected status
2667 */
2668 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2669 {
2670 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2672 if (incremental) {
2673 if (override) {
2674 if (!g_list_find(nodepath->selected, node)) {
2675 nodepath->selected = g_list_prepend(nodepath->selected, node);
2676 }
2677 sp_node_set_selected(node, TRUE);
2678 } else { // toggle
2679 if (node->selected) {
2680 g_assert(g_list_find(nodepath->selected, node));
2681 nodepath->selected = g_list_remove(nodepath->selected, node);
2682 } else {
2683 g_assert(!g_list_find(nodepath->selected, node));
2684 nodepath->selected = g_list_prepend(nodepath->selected, node);
2685 }
2686 sp_node_set_selected(node, !node->selected);
2687 }
2688 } else {
2689 sp_nodepath_deselect(nodepath);
2690 nodepath->selected = g_list_prepend(nodepath->selected, node);
2691 sp_node_set_selected(node, TRUE);
2692 }
2694 sp_nodepath_update_statusbar(nodepath);
2695 }
2698 /**
2699 \brief Deselect all nodes in the nodepath
2700 */
2701 void
2702 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2703 {
2704 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2706 while (nodepath->selected) {
2707 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2708 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2709 }
2710 sp_nodepath_update_statusbar(nodepath);
2711 }
2713 /**
2714 \brief Select or invert selection of all nodes in the nodepath
2715 */
2716 void
2717 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2718 {
2719 if (!nodepath) return;
2721 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2722 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2723 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2724 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2725 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2726 }
2727 }
2728 }
2730 /**
2731 * If nothing selected, does the same as sp_nodepath_select_all();
2732 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2733 * (i.e., similar to "select all in layer", with the "selected" subpaths
2734 * being treated as "layers" in the path).
2735 */
2736 void
2737 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2738 {
2739 if (!nodepath) return;
2741 if (g_list_length (nodepath->selected) == 0) {
2742 sp_nodepath_select_all (nodepath, invert);
2743 return;
2744 }
2746 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2747 GSList *subpaths = NULL;
2749 for (GList *l = copy; l != NULL; l = l->next) {
2750 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2751 Inkscape::NodePath::SubPath *subpath = n->subpath;
2752 if (!g_slist_find (subpaths, subpath))
2753 subpaths = g_slist_prepend (subpaths, subpath);
2754 }
2756 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2757 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2758 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2759 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2760 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2761 }
2762 }
2764 g_slist_free (subpaths);
2765 g_list_free (copy);
2766 }
2768 /**
2769 * \brief Select the node after the last selected; if none is selected,
2770 * select the first within path.
2771 */
2772 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2773 {
2774 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2776 Inkscape::NodePath::Node *last = NULL;
2777 if (nodepath->selected) {
2778 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2779 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2780 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2781 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2782 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2783 if (node->selected) {
2784 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2785 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2786 if (spl->next) { // there's a next subpath
2787 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2788 last = subpath_next->first;
2789 } else if (spl->prev) { // there's a previous subpath
2790 last = NULL; // to be set later to the first node of first subpath
2791 } else {
2792 last = node->n.other;
2793 }
2794 } else {
2795 last = node->n.other;
2796 }
2797 } else {
2798 if (node->n.other) {
2799 last = node->n.other;
2800 } else {
2801 if (spl->next) { // there's a next subpath
2802 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2803 last = subpath_next->first;
2804 } else if (spl->prev) { // there's a previous subpath
2805 last = NULL; // to be set later to the first node of first subpath
2806 } else {
2807 last = (Inkscape::NodePath::Node *) subpath->first;
2808 }
2809 }
2810 }
2811 }
2812 }
2813 }
2814 sp_nodepath_deselect(nodepath);
2815 }
2817 if (last) { // there's at least one more node after selected
2818 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2819 } else { // no more nodes, select the first one in first subpath
2820 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2821 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2822 }
2823 }
2825 /**
2826 * \brief Select the node before the first selected; if none is selected,
2827 * select the last within path
2828 */
2829 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2830 {
2831 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2833 Inkscape::NodePath::Node *last = NULL;
2834 if (nodepath->selected) {
2835 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2836 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2837 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2838 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2839 if (node->selected) {
2840 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2841 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2842 if (spl->prev) { // there's a prev subpath
2843 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2844 last = subpath_prev->last;
2845 } else if (spl->next) { // there's a next subpath
2846 last = NULL; // to be set later to the last node of last subpath
2847 } else {
2848 last = node->p.other;
2849 }
2850 } else {
2851 last = node->p.other;
2852 }
2853 } else {
2854 if (node->p.other) {
2855 last = node->p.other;
2856 } else {
2857 if (spl->prev) { // there's a prev subpath
2858 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2859 last = subpath_prev->last;
2860 } else if (spl->next) { // there's a next subpath
2861 last = NULL; // to be set later to the last node of last subpath
2862 } else {
2863 last = (Inkscape::NodePath::Node *) subpath->last;
2864 }
2865 }
2866 }
2867 }
2868 }
2869 }
2870 sp_nodepath_deselect(nodepath);
2871 }
2873 if (last) { // there's at least one more node before selected
2874 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2875 } else { // no more nodes, select the last one in last subpath
2876 GList *spl = g_list_last(nodepath->subpaths);
2877 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2878 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2879 }
2880 }
2882 /**
2883 * \brief Select all nodes that are within the rectangle.
2884 */
2885 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2886 {
2887 if (!incremental) {
2888 sp_nodepath_deselect(nodepath);
2889 }
2891 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2892 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2893 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2894 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2896 if (b.contains(node->pos)) {
2897 sp_nodepath_node_select(node, TRUE, TRUE);
2898 }
2899 }
2900 }
2901 }
2904 void
2905 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2906 {
2907 g_assert (n);
2908 g_assert (nodepath);
2909 g_assert (n->subpath->nodepath == nodepath);
2911 if (g_list_length (nodepath->selected) == 0) {
2912 if (grow > 0) {
2913 sp_nodepath_node_select(n, TRUE, TRUE);
2914 }
2915 return;
2916 }
2918 if (g_list_length (nodepath->selected) == 1) {
2919 if (grow < 0) {
2920 sp_nodepath_deselect (nodepath);
2921 return;
2922 }
2923 }
2925 double n_sel_range = 0, p_sel_range = 0;
2926 Inkscape::NodePath::Node *farthest_n_node = n;
2927 Inkscape::NodePath::Node *farthest_p_node = n;
2929 // Calculate ranges
2930 {
2931 double n_range = 0, p_range = 0;
2932 bool n_going = true, p_going = true;
2933 Inkscape::NodePath::Node *n_node = n;
2934 Inkscape::NodePath::Node *p_node = n;
2935 do {
2936 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2937 if (n_node && n_going)
2938 n_node = n_node->n.other;
2939 if (n_node == NULL) {
2940 n_going = false;
2941 } else {
2942 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2943 if (n_node->selected) {
2944 n_sel_range = n_range;
2945 farthest_n_node = n_node;
2946 }
2947 if (n_node == p_node) {
2948 n_going = false;
2949 p_going = false;
2950 }
2951 }
2952 if (p_node && p_going)
2953 p_node = p_node->p.other;
2954 if (p_node == NULL) {
2955 p_going = false;
2956 } else {
2957 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2958 if (p_node->selected) {
2959 p_sel_range = p_range;
2960 farthest_p_node = p_node;
2961 }
2962 if (p_node == n_node) {
2963 n_going = false;
2964 p_going = false;
2965 }
2966 }
2967 } while (n_going || p_going);
2968 }
2970 if (grow > 0) {
2971 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2972 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2973 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2974 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2975 }
2976 } else {
2977 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2978 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2979 } else if (farthest_p_node && farthest_p_node->selected) {
2980 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2981 }
2982 }
2983 }
2985 void
2986 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2987 {
2988 g_assert (n);
2989 g_assert (nodepath);
2990 g_assert (n->subpath->nodepath == nodepath);
2992 if (g_list_length (nodepath->selected) == 0) {
2993 if (grow > 0) {
2994 sp_nodepath_node_select(n, TRUE, TRUE);
2995 }
2996 return;
2997 }
2999 if (g_list_length (nodepath->selected) == 1) {
3000 if (grow < 0) {
3001 sp_nodepath_deselect (nodepath);
3002 return;
3003 }
3004 }
3006 Inkscape::NodePath::Node *farthest_selected = NULL;
3007 double farthest_dist = 0;
3009 Inkscape::NodePath::Node *closest_unselected = NULL;
3010 double closest_dist = NR_HUGE;
3012 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3013 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3014 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3015 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3016 if (node == n)
3017 continue;
3018 if (node->selected) {
3019 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3020 farthest_dist = NR::L2(node->pos - n->pos);
3021 farthest_selected = node;
3022 }
3023 } else {
3024 if (NR::L2(node->pos - n->pos) < closest_dist) {
3025 closest_dist = NR::L2(node->pos - n->pos);
3026 closest_unselected = node;
3027 }
3028 }
3029 }
3030 }
3032 if (grow > 0) {
3033 if (closest_unselected) {
3034 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3035 }
3036 } else {
3037 if (farthest_selected) {
3038 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3039 }
3040 }
3041 }
3044 /**
3045 \brief Saves all nodes' and handles' current positions in their origin members
3046 */
3047 void
3048 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3049 {
3050 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3051 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3052 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3053 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3054 n->origin = n->pos;
3055 n->p.origin = n->p.pos;
3056 n->n.origin = n->n.pos;
3057 }
3058 }
3059 }
3061 /**
3062 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3063 */
3064 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3065 {
3066 if (!nodepath->selected) {
3067 return NULL;
3068 }
3070 GList *r = NULL;
3071 guint i = 0;
3072 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3073 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3074 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3075 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3076 i++;
3077 if (node->selected) {
3078 r = g_list_append(r, GINT_TO_POINTER(i));
3079 }
3080 }
3081 }
3082 return r;
3083 }
3085 /**
3086 \brief Restores selection by selecting nodes whose positions are in the list
3087 */
3088 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3089 {
3090 sp_nodepath_deselect(nodepath);
3092 guint i = 0;
3093 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3094 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3095 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3096 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3097 i++;
3098 if (g_list_find(r, GINT_TO_POINTER(i))) {
3099 sp_nodepath_node_select(node, TRUE, TRUE);
3100 }
3101 }
3102 }
3103 }
3106 /**
3107 \brief Adjusts handle according to node type and line code.
3108 */
3109 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3110 {
3111 g_assert(node);
3113 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3114 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3116 // nothing to do if we are an end node
3117 if (me->other == NULL) return;
3118 if (other->other == NULL) return;
3120 // nothing to do if we are a cusp node
3121 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3123 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3124 NRPathcode mecode;
3125 if (which_adjust == 1) {
3126 mecode = (NRPathcode)me->other->code;
3127 } else {
3128 mecode = (NRPathcode)node->code;
3129 }
3130 if (mecode == NR_LINETO) return;
3132 if (sp_node_side_is_line(node, other)) {
3133 // other is a line, and we are either smooth or symm
3134 Inkscape::NodePath::Node *othernode = other->other;
3135 double len = NR::L2(me->pos - node->pos);
3136 NR::Point delta = node->pos - othernode->pos;
3137 double linelen = NR::L2(delta);
3138 if (linelen < 1e-18)
3139 return;
3140 me->pos = node->pos + (len / linelen)*delta;
3141 return;
3142 }
3144 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3145 // symmetrize
3146 me->pos = 2 * node->pos - other->pos;
3147 return;
3148 } else {
3149 // smoothify
3150 double len = NR::L2(me->pos - node->pos);
3151 NR::Point delta = other->pos - node->pos;
3152 double otherlen = NR::L2(delta);
3153 if (otherlen < 1e-18) return;
3154 me->pos = node->pos - (len / otherlen) * delta;
3155 }
3156 }
3158 /**
3159 \brief Adjusts both handles according to node type and line code
3160 */
3161 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3162 {
3163 g_assert(node);
3165 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3167 /* we are either smooth or symm */
3169 if (node->p.other == NULL) return;
3170 if (node->n.other == NULL) return;
3172 if (sp_node_side_is_line(node, &node->p)) {
3173 sp_node_adjust_handle(node, 1);
3174 return;
3175 }
3177 if (sp_node_side_is_line(node, &node->n)) {
3178 sp_node_adjust_handle(node, -1);
3179 return;
3180 }
3182 /* both are curves */
3183 NR::Point const delta( node->n.pos - node->p.pos );
3185 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3186 node->p.pos = node->pos - delta / 2;
3187 node->n.pos = node->pos + delta / 2;
3188 return;
3189 }
3191 /* We are smooth */
3192 double plen = NR::L2(node->p.pos - node->pos);
3193 if (plen < 1e-18) return;
3194 double nlen = NR::L2(node->n.pos - node->pos);
3195 if (nlen < 1e-18) return;
3196 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3197 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3198 }
3200 /**
3201 * Node event callback.
3202 */
3203 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3204 {
3205 gboolean ret = FALSE;
3206 switch (event->type) {
3207 case GDK_ENTER_NOTIFY:
3208 Inkscape::NodePath::Path::active_node = n;
3209 break;
3210 case GDK_LEAVE_NOTIFY:
3211 Inkscape::NodePath::Path::active_node = NULL;
3212 break;
3213 case GDK_SCROLL:
3214 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3215 switch (event->scroll.direction) {
3216 case GDK_SCROLL_UP:
3217 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3218 break;
3219 case GDK_SCROLL_DOWN:
3220 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3221 break;
3222 default:
3223 break;
3224 }
3225 ret = TRUE;
3226 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3227 switch (event->scroll.direction) {
3228 case GDK_SCROLL_UP:
3229 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3230 break;
3231 case GDK_SCROLL_DOWN:
3232 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3233 break;
3234 default:
3235 break;
3236 }
3237 ret = TRUE;
3238 }
3239 break;
3240 case GDK_KEY_PRESS:
3241 switch (get_group0_keyval (&event->key)) {
3242 case GDK_space:
3243 if (event->key.state & GDK_BUTTON1_MASK) {
3244 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3245 stamp_repr(nodepath);
3246 ret = TRUE;
3247 }
3248 break;
3249 case GDK_Page_Up:
3250 if (event->key.state & GDK_CONTROL_MASK) {
3251 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3252 } else {
3253 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3254 }
3255 break;
3256 case GDK_Page_Down:
3257 if (event->key.state & GDK_CONTROL_MASK) {
3258 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3259 } else {
3260 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3261 }
3262 break;
3263 default:
3264 break;
3265 }
3266 break;
3267 default:
3268 break;
3269 }
3271 return ret;
3272 }
3274 /**
3275 * Handle keypress on node; directly called.
3276 */
3277 gboolean node_key(GdkEvent *event)
3278 {
3279 Inkscape::NodePath::Path *np;
3281 // there is no way to verify nodes so set active_node to nil when deleting!!
3282 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3284 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3285 gint ret = FALSE;
3286 switch (get_group0_keyval (&event->key)) {
3287 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3288 case GDK_BackSpace:
3289 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3290 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3291 sp_nodepath_update_repr(np, _("Delete node"));
3292 Inkscape::NodePath::Path::active_node = NULL;
3293 ret = TRUE;
3294 break;
3295 case GDK_c:
3296 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3297 ret = TRUE;
3298 break;
3299 case GDK_s:
3300 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3301 ret = TRUE;
3302 break;
3303 case GDK_y:
3304 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3305 ret = TRUE;
3306 break;
3307 case GDK_b:
3308 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3309 ret = TRUE;
3310 break;
3311 }
3312 return ret;
3313 }
3314 return FALSE;
3315 }
3317 /**
3318 * Mouseclick on node callback.
3319 */
3320 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3321 {
3322 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3324 if (state & GDK_CONTROL_MASK) {
3325 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3327 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3328 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3329 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3330 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3331 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3332 } else {
3333 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3334 }
3335 sp_nodepath_update_repr(nodepath, _("Change node type"));
3336 sp_nodepath_update_statusbar(nodepath);
3338 } else { //ctrl+alt+click: delete node
3339 GList *node_to_delete = NULL;
3340 node_to_delete = g_list_append(node_to_delete, n);
3341 sp_node_delete_preserve(node_to_delete);
3342 }
3344 } else {
3345 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3346 }
3347 }
3349 /**
3350 * Mouse grabbed node callback.
3351 */
3352 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3353 {
3354 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3356 if (!n->selected) {
3357 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3358 }
3360 n->is_dragging = true;
3361 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3363 sp_nodepath_remember_origins (n->subpath->nodepath);
3364 }
3366 /**
3367 * Mouse ungrabbed node callback.
3368 */
3369 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3370 {
3371 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3373 n->dragging_out = NULL;
3374 n->is_dragging = false;
3375 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3377 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3378 }
3380 /**
3381 * The point on a line, given by its angle, closest to the given point.
3382 * \param p A point.
3383 * \param a Angle of the line; it is assumed to go through coordinate origin.
3384 * \param closest Pointer to the point struct where the result is stored.
3385 * \todo FIXME: use dot product perhaps?
3386 */
3387 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3388 {
3389 if (a == HUGE_VAL) { // vertical
3390 *closest = NR::Point(0, (*p)[NR::Y]);
3391 } else {
3392 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3393 (*closest)[NR::Y] = a * (*closest)[NR::X];
3394 }
3395 }
3397 /**
3398 * Distance from the point to a line given by its angle.
3399 * \param p A point.
3400 * \param a Angle of the line; it is assumed to go through coordinate origin.
3401 */
3402 static double point_line_distance(NR::Point *p, double a)
3403 {
3404 NR::Point c;
3405 point_line_closest(p, a, &c);
3406 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]));
3407 }
3409 /**
3410 * Callback for node "request" signal.
3411 * \todo fixme: This goes to "moved" event? (lauris)
3412 */
3413 static gboolean
3414 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3415 {
3416 double yn, xn, yp, xp;
3417 double an, ap, na, pa;
3418 double d_an, d_ap, d_na, d_pa;
3419 gboolean collinear = FALSE;
3420 NR::Point c;
3421 NR::Point pr;
3423 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3425 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3427 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3428 if ( (!n->subpath->nodepath->straight_path) &&
3429 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3430 || n->dragging_out ) )
3431 {
3432 NR::Point mouse = (*p);
3434 if (!n->dragging_out) {
3435 // This is the first drag-out event; find out which handle to drag out
3436 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3437 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3439 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3440 return FALSE;
3442 Inkscape::NodePath::NodeSide *opposite;
3443 if (appr_p > appr_n) { // closer to p
3444 n->dragging_out = &n->p;
3445 opposite = &n->n;
3446 n->code = NR_CURVETO;
3447 } else if (appr_p < appr_n) { // closer to n
3448 n->dragging_out = &n->n;
3449 opposite = &n->p;
3450 n->n.other->code = NR_CURVETO;
3451 } else { // p and n nodes are the same
3452 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3453 n->dragging_out = &n->p;
3454 opposite = &n->n;
3455 n->code = NR_CURVETO;
3456 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3457 n->dragging_out = &n->n;
3458 opposite = &n->p;
3459 n->n.other->code = NR_CURVETO;
3460 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3461 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);
3462 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);
3463 if (appr_other_p > appr_other_n) { // closer to other's p handle
3464 n->dragging_out = &n->n;
3465 opposite = &n->p;
3466 n->n.other->code = NR_CURVETO;
3467 } else { // closer to other's n handle
3468 n->dragging_out = &n->p;
3469 opposite = &n->n;
3470 n->code = NR_CURVETO;
3471 }
3472 }
3473 }
3475 // if there's another handle, make sure the one we drag out starts parallel to it
3476 if (opposite->pos != n->pos) {
3477 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3478 }
3480 // knots might not be created yet!
3481 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3482 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3483 }
3485 // pass this on to the handle-moved callback
3486 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3487 sp_node_update_handles(n);
3488 return TRUE;
3489 }
3491 if (state & GDK_CONTROL_MASK) { // constrained motion
3493 // calculate relative distances of handles
3494 // n handle:
3495 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3496 xn = n->n.pos[NR::X] - n->pos[NR::X];
3497 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3498 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3499 if (n->n.other) { // if there is the next point
3500 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3501 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3502 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3503 }
3504 }
3505 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3506 if (yn < 0) { xn = -xn; yn = -yn; }
3508 // p handle:
3509 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3510 xp = n->p.pos[NR::X] - n->pos[NR::X];
3511 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3512 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3513 if (n->p.other) {
3514 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3515 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3516 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3517 }
3518 }
3519 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3520 if (yp < 0) { xp = -xp; yp = -yp; }
3522 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3523 // sliding on handles, only if at least one of the handles is non-vertical
3524 // (otherwise it's the same as ctrl+drag anyway)
3526 // calculate angles of the handles
3527 if (xn == 0) {
3528 if (yn == 0) { // no handle, consider it the continuation of the other one
3529 an = 0;
3530 collinear = TRUE;
3531 }
3532 else an = 0; // vertical; set the angle to horizontal
3533 } else an = yn/xn;
3535 if (xp == 0) {
3536 if (yp == 0) { // no handle, consider it the continuation of the other one
3537 ap = an;
3538 }
3539 else ap = 0; // vertical; set the angle to horizontal
3540 } else ap = yp/xp;
3542 if (collinear) an = ap;
3544 // angles of the perpendiculars; HUGE_VAL means vertical
3545 if (an == 0) na = HUGE_VAL; else na = -1/an;
3546 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3548 // mouse point relative to the node's original pos
3549 pr = (*p) - n->origin;
3551 // distances to the four lines (two handles and two perpendiculars)
3552 d_an = point_line_distance(&pr, an);
3553 d_na = point_line_distance(&pr, na);
3554 d_ap = point_line_distance(&pr, ap);
3555 d_pa = point_line_distance(&pr, pa);
3557 // find out which line is the closest, save its closest point in c
3558 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3559 point_line_closest(&pr, an, &c);
3560 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3561 point_line_closest(&pr, ap, &c);
3562 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3563 point_line_closest(&pr, na, &c);
3564 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3565 point_line_closest(&pr, pa, &c);
3566 }
3568 // move the node to the closest point
3569 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3570 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3571 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3572 true);
3574 } else { // constraining to hor/vert
3576 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3577 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3578 (*p)[NR::X] - n->pos[NR::X],
3579 n->origin[NR::Y] - n->pos[NR::Y],
3580 true,
3581 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3582 } else { // snap to vert
3583 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3584 n->origin[NR::X] - n->pos[NR::X],
3585 (*p)[NR::Y] - n->pos[NR::Y],
3586 true,
3587 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3588 }
3589 }
3590 } else { // move freely
3591 if (n->is_dragging) {
3592 if (state & GDK_MOD1_MASK) { // sculpt
3593 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3594 } else {
3595 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3596 (*p)[NR::X] - n->pos[NR::X],
3597 (*p)[NR::Y] - n->pos[NR::Y],
3598 (state & GDK_SHIFT_MASK) == 0);
3599 }
3600 }
3601 }
3603 n->subpath->nodepath->desktop->scroll_to_point(p);
3605 return TRUE;
3606 }
3608 /**
3609 * Node handle clicked callback.
3610 */
3611 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3612 {
3613 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3615 if (state & GDK_CONTROL_MASK) { // "delete" handle
3616 if (n->p.knot == knot) {
3617 n->p.pos = n->pos;
3618 } else if (n->n.knot == knot) {
3619 n->n.pos = n->pos;
3620 }
3621 sp_node_update_handles(n);
3622 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3623 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3624 sp_nodepath_update_statusbar(nodepath);
3626 } else { // just select or add to selection, depending in Shift
3627 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3628 }
3629 }
3631 /**
3632 * Node handle grabbed callback.
3633 */
3634 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3635 {
3636 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3638 if (!n->selected) {
3639 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3640 }
3642 // remember the origin point of the handle
3643 if (n->p.knot == knot) {
3644 n->p.origin_radial = n->p.pos - n->pos;
3645 } else if (n->n.knot == knot) {
3646 n->n.origin_radial = n->n.pos - n->pos;
3647 } else {
3648 g_assert_not_reached();
3649 }
3651 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3652 }
3654 /**
3655 * Node handle ungrabbed callback.
3656 */
3657 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3658 {
3659 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3661 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3662 if (n->p.knot == knot) {
3663 n->p.origin_radial.a = 0;
3664 sp_knot_set_position(knot, &n->p.pos, state);
3665 } else if (n->n.knot == knot) {
3666 n->n.origin_radial.a = 0;
3667 sp_knot_set_position(knot, &n->n.pos, state);
3668 } else {
3669 g_assert_not_reached();
3670 }
3672 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3673 }
3675 /**
3676 * Node handle "request" signal callback.
3677 */
3678 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3679 {
3680 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3682 Inkscape::NodePath::NodeSide *me, *opposite;
3683 gint which;
3684 if (n->p.knot == knot) {
3685 me = &n->p;
3686 opposite = &n->n;
3687 which = -1;
3688 } else if (n->n.knot == knot) {
3689 me = &n->n;
3690 opposite = &n->p;
3691 which = 1;
3692 } else {
3693 me = opposite = NULL;
3694 which = 0;
3695 g_assert_not_reached();
3696 }
3698 SPDesktop *desktop = n->subpath->nodepath->desktop;
3699 SnapManager &m = desktop->namedview->snap_manager;
3700 m.setup(desktop, n->subpath->nodepath->item);
3701 Inkscape::SnappedPoint s ;
3703 Inkscape::NodePath::Node *othernode = opposite->other;
3704 if (othernode) {
3705 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3706 /* We are smooth node adjacent with line */
3707 NR::Point const delta = *p - n->pos;
3708 NR::Coord const len = NR::L2(delta);
3709 Inkscape::NodePath::Node *othernode = opposite->other;
3710 NR::Point const ndelta = n->pos - othernode->pos;
3711 NR::Coord const linelen = NR::L2(ndelta);
3712 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3713 NR::Coord const scal = dot(delta, ndelta) / linelen;
3714 (*p) = n->pos + (scal / linelen) * ndelta;
3715 }
3716 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3717 } else {
3718 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3719 }
3720 } else {
3721 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3722 }
3724 s.getPoint(*p);
3726 sp_node_adjust_handle(n, -which);
3728 return FALSE;
3729 }
3731 /**
3732 * Node handle moved callback.
3733 */
3734 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3735 {
3736 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3738 Inkscape::NodePath::NodeSide *me;
3739 Inkscape::NodePath::NodeSide *other;
3740 if (n->p.knot == knot) {
3741 me = &n->p;
3742 other = &n->n;
3743 } else if (n->n.knot == knot) {
3744 me = &n->n;
3745 other = &n->p;
3746 } else {
3747 me = NULL;
3748 other = NULL;
3749 g_assert_not_reached();
3750 }
3752 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3753 Radial rme(me->pos - n->pos);
3754 Radial rother(other->pos - n->pos);
3755 Radial rnew(*p - n->pos);
3757 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3758 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3759 /* 0 interpreted as "no snapping". */
3761 // 1. Snap to the closest PI/snaps angle, starting from zero.
3762 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3764 // 2. Snap to the original angle, its opposite and perpendiculars
3765 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3766 /* The closest PI/2 angle, starting from original angle */
3767 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3769 // Snap to the closest.
3770 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3771 ? a_snapped
3772 : a_ortho );
3773 }
3775 // 3. Snap to the angle of the opposite line, if any
3776 Inkscape::NodePath::Node *othernode = other->other;
3777 if (othernode) {
3778 NR::Point other_to_snap(0,0);
3779 if (sp_node_side_is_line(n, other)) {
3780 other_to_snap = othernode->pos - n->pos;
3781 } else {
3782 other_to_snap = other->pos - n->pos;
3783 }
3784 if (NR::L2(other_to_snap) > 1e-3) {
3785 Radial rother_to_snap(other_to_snap);
3786 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3787 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3789 // Snap to the closest.
3790 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3791 ? a_snapped
3792 : a_oppo );
3793 }
3794 }
3796 rnew.a = a_snapped;
3797 }
3799 if (state & GDK_MOD1_MASK) {
3800 // lock handle length
3801 rnew.r = me->origin_radial.r;
3802 }
3804 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3805 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3806 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3807 rother.a += rnew.a - rme.a;
3808 other->pos = NR::Point(rother) + n->pos;
3809 if (other->knot) {
3810 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3811 sp_knot_moveto(other->knot, &other->pos);
3812 }
3813 }
3815 me->pos = NR::Point(rnew) + n->pos;
3816 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3818 // move knot, but without emitting the signal:
3819 // we cannot emit a "moved" signal because we're now processing it
3820 sp_knot_moveto(me->knot, &(me->pos));
3822 update_object(n->subpath->nodepath);
3824 /* status text */
3825 SPDesktop *desktop = n->subpath->nodepath->desktop;
3826 if (!desktop) return;
3827 SPEventContext *ec = desktop->event_context;
3828 if (!ec) return;
3829 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3830 if (!mc) return;
3832 double degrees = 180 / M_PI * rnew.a;
3833 if (degrees > 180) degrees -= 360;
3834 if (degrees < -180) degrees += 360;
3835 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3836 degrees = angle_to_compass (degrees);
3838 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3840 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3841 _("<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);
3843 g_string_free(length, TRUE);
3844 }
3846 /**
3847 * Node handle event callback.
3848 */
3849 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3850 {
3851 gboolean ret = FALSE;
3852 switch (event->type) {
3853 case GDK_KEY_PRESS:
3854 switch (get_group0_keyval (&event->key)) {
3855 case GDK_space:
3856 if (event->key.state & GDK_BUTTON1_MASK) {
3857 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3858 stamp_repr(nodepath);
3859 ret = TRUE;
3860 }
3861 break;
3862 default:
3863 break;
3864 }
3865 break;
3866 case GDK_ENTER_NOTIFY:
3867 // we use an experimentally determined threshold that seems to work fine
3868 if (NR::L2(n->pos - knot->pos) < 0.75)
3869 Inkscape::NodePath::Path::active_node = n;
3870 break;
3871 case GDK_LEAVE_NOTIFY:
3872 // we use an experimentally determined threshold that seems to work fine
3873 if (NR::L2(n->pos - knot->pos) < 0.75)
3874 Inkscape::NodePath::Path::active_node = NULL;
3875 break;
3876 default:
3877 break;
3878 }
3880 return ret;
3881 }
3883 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3884 Radial &rme, Radial &rother, gboolean const both)
3885 {
3886 rme.a += angle;
3887 if ( both
3888 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3889 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3890 {
3891 rother.a += angle;
3892 }
3893 }
3895 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3896 Radial &rme, Radial &rother, gboolean const both)
3897 {
3898 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3900 gdouble r;
3901 if ( both
3902 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3903 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3904 {
3905 r = MAX(rme.r, rother.r);
3906 } else {
3907 r = rme.r;
3908 }
3910 gdouble const weird_angle = atan2(norm_angle, r);
3911 /* Bulia says norm_angle is just the visible distance that the
3912 * object's end must travel on the screen. Left as 'angle' for want of
3913 * a better name.*/
3915 rme.a += weird_angle;
3916 if ( both
3917 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3918 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3919 {
3920 rother.a += weird_angle;
3921 }
3922 }
3924 /**
3925 * Rotate one node.
3926 */
3927 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3928 {
3929 Inkscape::NodePath::NodeSide *me, *other;
3930 bool both = false;
3932 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3933 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3935 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3936 me = &(n->p);
3937 other = &(n->n);
3938 } else if (!n->p.other) {
3939 me = &(n->n);
3940 other = &(n->p);
3941 } else {
3942 if (which > 0) { // right handle
3943 if (xn > xp) {
3944 me = &(n->n);
3945 other = &(n->p);
3946 } else {
3947 me = &(n->p);
3948 other = &(n->n);
3949 }
3950 } else if (which < 0){ // left handle
3951 if (xn <= xp) {
3952 me = &(n->n);
3953 other = &(n->p);
3954 } else {
3955 me = &(n->p);
3956 other = &(n->n);
3957 }
3958 } else { // both handles
3959 me = &(n->n);
3960 other = &(n->p);
3961 both = true;
3962 }
3963 }
3965 Radial rme(me->pos - n->pos);
3966 Radial rother(other->pos - n->pos);
3968 if (screen) {
3969 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3970 } else {
3971 node_rotate_one_internal (*n, angle, rme, rother, both);
3972 }
3974 me->pos = n->pos + NR::Point(rme);
3976 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3977 other->pos = n->pos + NR::Point(rother);
3978 }
3980 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3981 // so here we just move all the knots without emitting move signals, for speed
3982 sp_node_update_handles(n, false);
3983 }
3985 /**
3986 * Rotate selected nodes.
3987 */
3988 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3989 {
3990 if (!nodepath || !nodepath->selected) return;
3992 if (g_list_length(nodepath->selected) == 1) {
3993 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3994 node_rotate_one (n, angle, which, screen);
3995 } else {
3996 // rotate as an object:
3998 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3999 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4000 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4001 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4002 box.expandTo (n->pos); // contain all selected nodes
4003 }
4005 gdouble rot;
4006 if (screen) {
4007 gdouble const zoom = nodepath->desktop->current_zoom();
4008 gdouble const zmove = angle / zoom;
4009 gdouble const r = NR::L2(box.max() - box.midpoint());
4010 rot = atan2(zmove, r);
4011 } else {
4012 rot = angle;
4013 }
4015 NR::Point rot_center;
4016 if (Inkscape::NodePath::Path::active_node == NULL)
4017 rot_center = box.midpoint();
4018 else
4019 rot_center = Inkscape::NodePath::Path::active_node->pos;
4021 NR::Matrix t =
4022 NR::Matrix (NR::translate(-rot_center)) *
4023 NR::Matrix (NR::rotate(rot)) *
4024 NR::Matrix (NR::translate(rot_center));
4026 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4027 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4028 n->pos *= t;
4029 n->n.pos *= t;
4030 n->p.pos *= t;
4031 sp_node_update_handles(n, false);
4032 }
4033 }
4035 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4036 }
4038 /**
4039 * Scale one node.
4040 */
4041 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4042 {
4043 bool both = false;
4044 Inkscape::NodePath::NodeSide *me, *other;
4046 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4047 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4049 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4050 me = &(n->p);
4051 other = &(n->n);
4052 n->code = NR_CURVETO;
4053 } else if (!n->p.other) {
4054 me = &(n->n);
4055 other = &(n->p);
4056 if (n->n.other)
4057 n->n.other->code = NR_CURVETO;
4058 } else {
4059 if (which > 0) { // right handle
4060 if (xn > xp) {
4061 me = &(n->n);
4062 other = &(n->p);
4063 if (n->n.other)
4064 n->n.other->code = NR_CURVETO;
4065 } else {
4066 me = &(n->p);
4067 other = &(n->n);
4068 n->code = NR_CURVETO;
4069 }
4070 } else if (which < 0){ // left handle
4071 if (xn <= xp) {
4072 me = &(n->n);
4073 other = &(n->p);
4074 if (n->n.other)
4075 n->n.other->code = NR_CURVETO;
4076 } else {
4077 me = &(n->p);
4078 other = &(n->n);
4079 n->code = NR_CURVETO;
4080 }
4081 } else { // both handles
4082 me = &(n->n);
4083 other = &(n->p);
4084 both = true;
4085 n->code = NR_CURVETO;
4086 if (n->n.other)
4087 n->n.other->code = NR_CURVETO;
4088 }
4089 }
4091 Radial rme(me->pos - n->pos);
4092 Radial rother(other->pos - n->pos);
4094 rme.r += grow;
4095 if (rme.r < 0) rme.r = 0;
4096 if (rme.a == HUGE_VAL) {
4097 if (me->other) { // if direction is unknown, initialize it towards the next node
4098 Radial rme_next(me->other->pos - n->pos);
4099 rme.a = rme_next.a;
4100 } else { // if there's no next, initialize to 0
4101 rme.a = 0;
4102 }
4103 }
4104 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4105 rother.r += grow;
4106 if (rother.r < 0) rother.r = 0;
4107 if (rother.a == HUGE_VAL) {
4108 rother.a = rme.a + M_PI;
4109 }
4110 }
4112 me->pos = n->pos + NR::Point(rme);
4114 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4115 other->pos = n->pos + NR::Point(rother);
4116 }
4118 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4119 // so here we just move all the knots without emitting move signals, for speed
4120 sp_node_update_handles(n, false);
4121 }
4123 /**
4124 * Scale selected nodes.
4125 */
4126 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4127 {
4128 if (!nodepath || !nodepath->selected) return;
4130 if (g_list_length(nodepath->selected) == 1) {
4131 // scale handles of the single selected node
4132 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4133 node_scale_one (n, grow, which);
4134 } else {
4135 // scale nodes as an "object":
4137 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4138 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4139 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4140 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4141 box.expandTo (n->pos); // contain all selected nodes
4142 }
4144 double scale = (box.maxExtent() + grow)/box.maxExtent();
4146 NR::Point scale_center;
4147 if (Inkscape::NodePath::Path::active_node == NULL)
4148 scale_center = box.midpoint();
4149 else
4150 scale_center = Inkscape::NodePath::Path::active_node->pos;
4152 NR::Matrix t =
4153 NR::Matrix (NR::translate(-scale_center)) *
4154 NR::Matrix (NR::scale(scale, scale)) *
4155 NR::Matrix (NR::translate(scale_center));
4157 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4158 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4159 n->pos *= t;
4160 n->n.pos *= t;
4161 n->p.pos *= t;
4162 sp_node_update_handles(n, false);
4163 }
4164 }
4166 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4167 }
4169 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4170 {
4171 if (!nodepath) return;
4172 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4173 }
4175 /**
4176 * Flip selected nodes horizontally/vertically.
4177 */
4178 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4179 {
4180 if (!nodepath || !nodepath->selected) return;
4182 if (g_list_length(nodepath->selected) == 1 && !center) {
4183 // flip handles of the single selected node
4184 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4185 double temp = n->p.pos[axis];
4186 n->p.pos[axis] = n->n.pos[axis];
4187 n->n.pos[axis] = temp;
4188 sp_node_update_handles(n, false);
4189 } else {
4190 // scale nodes as an "object":
4192 NR::Rect box = sp_node_selected_bbox (nodepath);
4193 if (!center) {
4194 center = box.midpoint();
4195 }
4196 NR::Matrix t =
4197 NR::Matrix (NR::translate(- *center)) *
4198 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4199 NR::Matrix (NR::translate(*center));
4201 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4202 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4203 n->pos *= t;
4204 n->n.pos *= t;
4205 n->p.pos *= t;
4206 sp_node_update_handles(n, false);
4207 }
4208 }
4210 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4211 }
4213 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4214 {
4215 g_assert (nodepath->selected);
4217 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4218 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4219 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4220 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4221 box.expandTo (n->pos); // contain all selected nodes
4222 }
4223 return box;
4224 }
4226 //-----------------------------------------------
4227 /**
4228 * Return new subpath under given nodepath.
4229 */
4230 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4231 {
4232 g_assert(nodepath);
4233 g_assert(nodepath->desktop);
4235 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4237 s->nodepath = nodepath;
4238 s->closed = FALSE;
4239 s->nodes = NULL;
4240 s->first = NULL;
4241 s->last = NULL;
4243 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4244 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4245 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4247 return s;
4248 }
4250 /**
4251 * Destroy nodes in subpath, then subpath itself.
4252 */
4253 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4254 {
4255 g_assert(subpath);
4256 g_assert(subpath->nodepath);
4257 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4259 while (subpath->nodes) {
4260 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4261 }
4263 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4265 g_free(subpath);
4266 }
4268 /**
4269 * Link head to tail in subpath.
4270 */
4271 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4272 {
4273 g_assert(!sp->closed);
4274 g_assert(sp->last != sp->first);
4275 g_assert(sp->first->code == NR_MOVETO);
4277 sp->closed = TRUE;
4279 //Link the head to the tail
4280 sp->first->p.other = sp->last;
4281 sp->last->n.other = sp->first;
4282 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4283 sp->first = sp->last;
4285 //Remove the extra end node
4286 sp_nodepath_node_destroy(sp->last->n.other);
4287 }
4289 /**
4290 * Open closed (loopy) subpath at node.
4291 */
4292 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4293 {
4294 g_assert(sp->closed);
4295 g_assert(n->subpath == sp);
4296 g_assert(sp->first == sp->last);
4298 /* We create new startpoint, current node will become last one */
4300 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4301 &n->pos, &n->pos, &n->n.pos);
4304 sp->closed = FALSE;
4306 //Unlink to make a head and tail
4307 sp->first = new_path;
4308 sp->last = n;
4309 n->n.other = NULL;
4310 new_path->p.other = NULL;
4311 }
4313 /**
4314 * Return new node in subpath with given properties.
4315 * \param pos Position of node.
4316 * \param ppos Handle position in previous direction
4317 * \param npos Handle position in previous direction
4318 */
4319 Inkscape::NodePath::Node *
4320 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)
4321 {
4322 g_assert(sp);
4323 g_assert(sp->nodepath);
4324 g_assert(sp->nodepath->desktop);
4326 if (nodechunk == NULL)
4327 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4329 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4331 n->subpath = sp;
4333 if (type != Inkscape::NodePath::NODE_NONE) {
4334 // use the type from sodipodi:nodetypes
4335 n->type = type;
4336 } else {
4337 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4338 // points are (almost) collinear
4339 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4340 // endnode, or a node with a retracted handle
4341 n->type = Inkscape::NodePath::NODE_CUSP;
4342 } else {
4343 n->type = Inkscape::NodePath::NODE_SMOOTH;
4344 }
4345 } else {
4346 n->type = Inkscape::NodePath::NODE_CUSP;
4347 }
4348 }
4350 n->code = code;
4351 n->selected = FALSE;
4352 n->pos = *pos;
4353 n->p.pos = *ppos;
4354 n->n.pos = *npos;
4356 n->dragging_out = NULL;
4358 Inkscape::NodePath::Node *prev;
4359 if (next) {
4360 //g_assert(g_list_find(sp->nodes, next));
4361 prev = next->p.other;
4362 } else {
4363 prev = sp->last;
4364 }
4366 if (prev)
4367 prev->n.other = n;
4368 else
4369 sp->first = n;
4371 if (next)
4372 next->p.other = n;
4373 else
4374 sp->last = n;
4376 n->p.other = prev;
4377 n->n.other = next;
4379 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"));
4380 sp_knot_set_position(n->knot, pos, 0);
4382 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4383 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4384 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4385 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4386 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4387 sp_knot_update_ctrl(n->knot);
4389 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4390 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4391 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4392 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4393 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4394 sp_knot_show(n->knot);
4396 // We only create handle knots and lines on demand
4397 n->p.knot = NULL;
4398 n->p.line = NULL;
4399 n->n.knot = NULL;
4400 n->n.line = NULL;
4402 sp->nodes = g_list_prepend(sp->nodes, n);
4404 return n;
4405 }
4407 /**
4408 * Destroy node and its knots, link neighbors in subpath.
4409 */
4410 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4411 {
4412 g_assert(node);
4413 g_assert(node->subpath);
4414 g_assert(SP_IS_KNOT(node->knot));
4416 Inkscape::NodePath::SubPath *sp = node->subpath;
4418 if (node->selected) { // first, deselect
4419 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4420 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4421 }
4423 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4425 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4426 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4427 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4428 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4429 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4430 g_object_unref(G_OBJECT(node->knot));
4432 if (node->p.knot) {
4433 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4434 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4435 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4436 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4437 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4438 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4439 g_object_unref(G_OBJECT(node->p.knot));
4440 node->p.knot = NULL;
4441 }
4443 if (node->n.knot) {
4444 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4445 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4446 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4447 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4448 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4449 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4450 g_object_unref(G_OBJECT(node->n.knot));
4451 node->n.knot = NULL;
4452 }
4454 if (node->p.line)
4455 gtk_object_destroy(GTK_OBJECT(node->p.line));
4456 if (node->n.line)
4457 gtk_object_destroy(GTK_OBJECT(node->n.line));
4459 if (sp->nodes) { // there are others nodes on the subpath
4460 if (sp->closed) {
4461 if (sp->first == node) {
4462 g_assert(sp->last == node);
4463 sp->first = node->n.other;
4464 sp->last = sp->first;
4465 }
4466 node->p.other->n.other = node->n.other;
4467 node->n.other->p.other = node->p.other;
4468 } else {
4469 if (sp->first == node) {
4470 sp->first = node->n.other;
4471 sp->first->code = NR_MOVETO;
4472 }
4473 if (sp->last == node) sp->last = node->p.other;
4474 if (node->p.other) node->p.other->n.other = node->n.other;
4475 if (node->n.other) node->n.other->p.other = node->p.other;
4476 }
4477 } else { // this was the last node on subpath
4478 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4479 }
4481 g_mem_chunk_free(nodechunk, node);
4482 }
4484 /**
4485 * Returns one of the node's two sides.
4486 * \param which Indicates which side.
4487 * \return Pointer to previous node side if which==-1, next if which==1.
4488 */
4489 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4490 {
4491 g_assert(node);
4493 switch (which) {
4494 case -1:
4495 return &node->p;
4496 case 1:
4497 return &node->n;
4498 default:
4499 break;
4500 }
4502 g_assert_not_reached();
4504 return NULL;
4505 }
4507 /**
4508 * Return the other side of the node, given one of its sides.
4509 */
4510 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4511 {
4512 g_assert(node);
4514 if (me == &node->p) return &node->n;
4515 if (me == &node->n) return &node->p;
4517 g_assert_not_reached();
4519 return NULL;
4520 }
4522 /**
4523 * Return NRPathcode on the given side of the node.
4524 */
4525 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4526 {
4527 g_assert(node);
4529 if (me == &node->p) {
4530 if (node->p.other) return (NRPathcode)node->code;
4531 return NR_MOVETO;
4532 }
4534 if (me == &node->n) {
4535 if (node->n.other) return (NRPathcode)node->n.other->code;
4536 return NR_MOVETO;
4537 }
4539 g_assert_not_reached();
4541 return NR_END;
4542 }
4544 /**
4545 * Return node with the given index
4546 */
4547 Inkscape::NodePath::Node *
4548 sp_nodepath_get_node_by_index(int index)
4549 {
4550 Inkscape::NodePath::Node *e = NULL;
4552 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4553 if (!nodepath) {
4554 return e;
4555 }
4557 //find segment
4558 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4560 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4561 int n = g_list_length(sp->nodes);
4562 if (sp->closed) {
4563 n++;
4564 }
4566 //if the piece belongs to this subpath grab it
4567 //otherwise move onto the next subpath
4568 if (index < n) {
4569 e = sp->first;
4570 for (int i = 0; i < index; ++i) {
4571 e = e->n.other;
4572 }
4573 break;
4574 } else {
4575 if (sp->closed) {
4576 index -= (n+1);
4577 } else {
4578 index -= n;
4579 }
4580 }
4581 }
4583 return e;
4584 }
4586 /**
4587 * Returns plain text meaning of node type.
4588 */
4589 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4590 {
4591 unsigned retracted = 0;
4592 bool endnode = false;
4594 for (int which = -1; which <= 1; which += 2) {
4595 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4596 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4597 retracted ++;
4598 if (!side->other)
4599 endnode = true;
4600 }
4602 if (retracted == 0) {
4603 if (endnode) {
4604 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4605 return _("end node");
4606 } else {
4607 switch (node->type) {
4608 case Inkscape::NodePath::NODE_CUSP:
4609 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4610 return _("cusp");
4611 case Inkscape::NodePath::NODE_SMOOTH:
4612 // TRANSLATORS: "smooth" is an adjective here
4613 return _("smooth");
4614 case Inkscape::NodePath::NODE_SYMM:
4615 return _("symmetric");
4616 }
4617 }
4618 } else if (retracted == 1) {
4619 if (endnode) {
4620 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4621 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4622 } else {
4623 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4624 }
4625 } else {
4626 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4627 }
4629 return NULL;
4630 }
4632 /**
4633 * Handles content of statusbar as long as node tool is active.
4634 */
4635 void
4636 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4637 {
4638 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");
4639 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4641 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4642 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4643 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4644 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4646 SPDesktop *desktop = NULL;
4647 if (nodepath) {
4648 desktop = nodepath->desktop;
4649 } else {
4650 desktop = SP_ACTIVE_DESKTOP;
4651 }
4653 SPEventContext *ec = desktop->event_context;
4654 if (!ec) return;
4655 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4656 if (!mc) return;
4658 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4660 if (selected_nodes == 0) {
4661 Inkscape::Selection *sel = desktop->selection;
4662 if (!sel || sel->isEmpty()) {
4663 mc->setF(Inkscape::NORMAL_MESSAGE,
4664 _("Select a single object to edit its nodes or handles."));
4665 } else {
4666 if (nodepath) {
4667 mc->setF(Inkscape::NORMAL_MESSAGE,
4668 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.",
4669 "<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.",
4670 total_nodes),
4671 total_nodes);
4672 } else {
4673 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4674 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4675 } else {
4676 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4677 }
4678 }
4679 }
4680 } else if (nodepath && selected_nodes == 1) {
4681 mc->setF(Inkscape::NORMAL_MESSAGE,
4682 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4683 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4684 total_nodes),
4685 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4686 } else {
4687 if (selected_subpaths > 1) {
4688 mc->setF(Inkscape::NORMAL_MESSAGE,
4689 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4690 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4691 total_nodes),
4692 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4693 } else {
4694 mc->setF(Inkscape::NORMAL_MESSAGE,
4695 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4696 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4697 total_nodes),
4698 selected_nodes, total_nodes, when_selected);
4699 }
4700 }
4701 }
4703 /*
4704 * returns a *copy* of the curve of that object.
4705 */
4706 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4707 if (!object)
4708 return NULL;
4710 SPCurve *curve = NULL;
4711 if (SP_IS_PATH(object)) {
4712 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4713 curve = curve_new->copy();
4714 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4715 const gchar *svgd = object->repr->attribute(key);
4716 if (svgd) {
4717 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4718 SPCurve *curve_new = new SPCurve(pv);
4719 if (curve_new) {
4720 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4721 }
4722 }
4723 }
4725 return curve;
4726 }
4728 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4729 if (!np || !np->object || !curve)
4730 return;
4732 if (SP_IS_PATH(np->object)) {
4733 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4734 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4735 } else {
4736 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4737 }
4738 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4739 // FIXME: this writing to string and then reading from string is bound to be slow.
4740 // create a method to convert from curve directly to 2geom...
4741 gchar *svgpath = sp_svg_write_path( np->curve->get_pathvector() );
4742 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4743 g_free(svgpath);
4745 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4746 }
4747 }
4749 SPCanvasItem *
4750 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4751 SPCurve *flash_curve = curve->copy();
4752 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4753 flash_curve->transform(i2d);
4754 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4755 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4756 // unless we also flash the nodes...
4757 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4758 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4759 sp_canvas_item_show(canvasitem);
4760 flash_curve->unref();
4761 return canvasitem;
4762 }
4764 SPCanvasItem *
4765 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4766 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4767 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4768 }
4770 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4771 np->show_helperpath = show;
4773 if (show) {
4774 SPCurve *helper_curve = np->curve->copy();
4775 helper_curve->transform(np->i2d );
4776 if (!np->helper_path) {
4777 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4778 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);
4779 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4780 sp_canvas_item_move_to_z(np->helper_path, 0);
4781 sp_canvas_item_show(np->helper_path);
4782 } else {
4783 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4784 }
4785 helper_curve->unref();
4786 } else {
4787 if (np->helper_path) {
4788 GtkObject *temp = np->helper_path;
4789 np->helper_path = NULL;
4790 gtk_object_destroy(temp);
4791 }
4792 }
4793 }
4795 /* sp_nodepath_make_straight_path:
4796 * Prevents user from curving the path by dragging a segment or activating handles etc.
4797 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4798 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4799 */
4800 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4801 np->straight_path = true;
4802 np->show_handles = false;
4803 g_message("add code to make the path straight.");
4804 // do sp_nodepath_convert_node_type on all nodes?
4805 // coding tip: search for this text : "Make selected segments lines"
4806 }
4809 /*
4810 Local Variables:
4811 mode:c++
4812 c-file-style:"stroustrup"
4813 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4814 indent-tabs-mode:nil
4815 fill-column:99
4816 End:
4817 */
4818 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :