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, gchar const *t);
99 static gchar *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 NArtBpath const *bpath = curve->get_bpath();
199 gint length = curve->get_length();
200 if (length == 0) {
201 curve->unref();
202 return NULL; // prevent crash for one-node paths
203 }
205 //Create new nodepath
206 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
207 if (!np) {
208 curve->unref();
209 return NULL;
210 }
212 // Set defaults
213 np->desktop = desktop;
214 np->object = object;
215 np->subpaths = NULL;
216 np->selected = NULL;
217 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
218 np->livarot_path = NULL;
219 np->local_change = 0;
220 np->show_handles = show_handles;
221 np->helper_path = NULL;
222 np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
223 np->helperpath_width = 1.0;
224 np->curve = curve->copy();
225 np->show_helperpath = (prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1);
226 if (SP_IS_LPE_ITEM(object)) {
227 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
228 if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
229 np->show_helperpath = true;
230 }
231 }
232 np->straight_path = false;
233 if (IS_LIVEPATHEFFECT(object) && item) {
234 np->item = item;
235 } else {
236 np->item = SP_ITEM(object);
237 }
239 // we need to update item's transform from the repr here,
240 // because they may be out of sync when we respond
241 // to a change in repr by regenerating nodepath --bb
242 sp_object_read_attr(SP_OBJECT(np->item), "transform");
244 np->i2d = from_2geom(sp_item_i2d_affine(np->item));
245 np->d2i = np->i2d.inverse();
247 np->repr = repr;
248 if (repr_key_in) { // apparantly the object is an LPEObject
249 np->repr_key = g_strdup(repr_key_in);
250 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
251 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
252 if (lpeparam) {
253 lpeparam->param_setup_nodepath(np);
254 }
255 } else {
256 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
257 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
258 np->repr_key = g_strdup("inkscape:original-d");
260 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
261 if (lpe) {
262 lpe->setup_nodepath(np);
263 }
264 } else {
265 np->repr_key = g_strdup("d");
266 }
267 }
269 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
270 gchar *typestr = parse_nodetypes(nodetypes, length);
272 // create the subpath(s) from the 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 g_free(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 * \todo Fixme: t should be a proper type, rather than gchar
456 */
457 static NArtBpath const * subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath const *b, gchar const *t)
458 {
459 NR::Point ppos, pos, npos;
461 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
463 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
464 bool const closed = (b->code == NR_MOVETO);
466 pos = NR::Point(b->x3, b->y3) * np->i2d;
467 if (b[1].code == NR_CURVETO) {
468 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
469 } else {
470 npos = pos;
471 }
472 Inkscape::NodePath::Node *n;
473 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
474 g_assert(sp->first == n);
475 g_assert(sp->last == n);
477 b++;
478 t++;
479 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
480 pos = NR::Point(b->x3, b->y3) * np->i2d;
481 if (b->code == NR_CURVETO) {
482 ppos = NR::Point(b->x2, b->y2) * np->i2d;
483 } else {
484 ppos = pos;
485 }
486 if (b[1].code == NR_CURVETO) {
487 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
488 } else {
489 npos = pos;
490 }
491 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
492 b++;
493 t++;
494 }
496 if (closed) sp_nodepath_subpath_close(sp);
498 return b;
499 }
501 /**
502 * Convert from sodipodi:nodetypes to new style type string.
503 */
504 static gchar *parse_nodetypes(gchar const *types, gint length)
505 {
506 g_assert(length > 0);
508 gchar *typestr = g_new(gchar, 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 while (node->n.other) { // copy the remaining nodes into the new subpath
891 Inkscape::NodePath::Node *n = node->n.other;
892 Inkscape::NodePath::Node *nn = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
893 if (n->selected) {
894 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
895 }
896 sp_nodepath_node_destroy(n); // remove the point on the original subpath
897 }
899 return newnode;
900 }
901 }
903 /**
904 * Duplicate node and connect to neighbours.
905 */
906 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
907 {
908 g_assert(node);
909 g_assert(node->subpath);
910 g_assert(g_list_find(node->subpath->nodes, node));
912 Inkscape::NodePath::SubPath *sp = node->subpath;
914 NRPathcode code = (NRPathcode) node->code;
915 if (code == NR_MOVETO) { // if node is the endnode,
916 node->code = NR_LINETO; // new one is inserted before it, so change that to line
917 }
919 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
921 if (!node->n.other || !node->p.other) // if node is an endnode, select it
922 return node;
923 else
924 return newnode; // otherwise select the newly created node
925 }
927 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
928 {
929 node->p.pos = (node->pos + (node->pos - node->n.pos));
930 }
932 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
933 {
934 node->n.pos = (node->pos + (node->pos - node->p.pos));
935 }
937 /**
938 * Change line type at node, with side effects on neighbours.
939 */
940 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
941 {
942 g_assert(end);
943 g_assert(end->subpath);
944 g_assert(end->p.other);
946 if (end->code == static_cast< guint > ( code ) )
947 return;
949 Inkscape::NodePath::Node *start = end->p.other;
951 end->code = code;
953 if (code == NR_LINETO) {
954 if (start->code == NR_LINETO) {
955 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
956 }
957 if (end->n.other) {
958 if (end->n.other->code == NR_LINETO) {
959 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
960 }
961 }
962 } else {
963 NR::Point delta = end->pos - start->pos;
964 start->n.pos = start->pos + delta / 3;
965 end->p.pos = end->pos - delta / 3;
966 sp_node_adjust_handle(start, 1);
967 sp_node_adjust_handle(end, -1);
968 }
970 sp_node_update_handles(start);
971 sp_node_update_handles(end);
972 }
974 /**
975 * Change node type, and its handles accordingly.
976 */
977 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
978 {
979 g_assert(node);
980 g_assert(node->subpath);
982 if ((node->p.other != NULL) && (node->n.other != NULL)) {
983 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
984 type =Inkscape::NodePath::NODE_CUSP;
985 }
986 }
988 node->type = type;
990 if (node->type == Inkscape::NodePath::NODE_CUSP) {
991 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
992 node->knot->setSize (node->selected? 11 : 9);
993 sp_knot_update_ctrl(node->knot);
994 } else {
995 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
996 node->knot->setSize (node->selected? 9 : 7);
997 sp_knot_update_ctrl(node->knot);
998 }
1000 // if one of handles is mouseovered, preserve its position
1001 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1002 sp_node_adjust_handle(node, 1);
1003 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1004 sp_node_adjust_handle(node, -1);
1005 } else {
1006 sp_node_adjust_handles(node);
1007 }
1009 sp_node_update_handles(node);
1011 sp_nodepath_update_statusbar(node->subpath->nodepath);
1013 return node;
1014 }
1016 bool
1017 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1018 {
1019 Inkscape::NodePath::Node *othernode = side->other;
1020 if (!othernode)
1021 return false;
1022 NRPathcode const code = sp_node_path_code_from_side(node, side);
1023 if (code == NR_LINETO)
1024 return true;
1025 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1026 if (&node->p == side) {
1027 other_to_me = &othernode->n;
1028 } else if (&node->n == side) {
1029 other_to_me = &othernode->p;
1030 }
1031 if (!other_to_me)
1032 return false;
1033 bool is_line =
1034 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1035 NR::L2(node->pos - side->pos) < 1e-6);
1036 return is_line;
1037 }
1039 /**
1040 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1041 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1042 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1043 * If already cusp and set to cusp, retracts handles.
1044 */
1045 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1046 {
1047 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1049 /*
1050 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1052 if (two_handles) {
1053 // do nothing, adjust_handles called via set_node_type will line them up
1054 } else if (one_handle) {
1055 if (opposite_to_handle_is_line) {
1056 if (lined_up) {
1057 // already half-smooth; pull opposite handle too making it fully smooth
1058 } else {
1059 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1060 }
1061 } else {
1062 // pull opposite handle in line with the existing one
1063 }
1064 } else if (no_handles) {
1065 if (both_segments_are_lines OR both_segments_are_curves) {
1066 //pull both handles
1067 } else {
1068 // pull the handle opposite to line segment, making node half-smooth
1069 }
1070 }
1071 */
1072 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1073 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1074 bool p_is_line = sp_node_side_is_line(node, &node->p);
1075 bool n_is_line = sp_node_side_is_line(node, &node->n);
1077 if (p_has_handle && n_has_handle) {
1078 // do nothing, adjust_handles will line them up
1079 } else if (p_has_handle || n_has_handle) {
1080 if (p_has_handle && n_is_line) {
1081 Radial line (node->n.other->pos - node->pos);
1082 Radial handle (node->pos - node->p.pos);
1083 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1084 // already half-smooth; pull opposite handle too making it fully smooth
1085 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1086 } else {
1087 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1088 }
1089 } else if (n_has_handle && p_is_line) {
1090 Radial line (node->p.other->pos - node->pos);
1091 Radial handle (node->pos - node->n.pos);
1092 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1093 // already half-smooth; pull opposite handle too making it fully smooth
1094 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1095 } else {
1096 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1097 }
1098 } else if (p_has_handle && node->n.other) {
1099 // pull n handle
1100 node->n.other->code = NR_CURVETO;
1101 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1102 NR::L2(node->p.pos - node->pos) :
1103 NR::L2(node->n.other->pos - node->pos) / 3;
1104 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1105 } else if (n_has_handle && node->p.other) {
1106 // pull p handle
1107 node->code = NR_CURVETO;
1108 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1109 NR::L2(node->n.pos - node->pos) :
1110 NR::L2(node->p.other->pos - node->pos) / 3;
1111 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1112 }
1113 } else if (!p_has_handle && !n_has_handle) {
1114 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1115 // no handles, but both segments are either lnes or curves:
1116 //pull both handles
1118 // convert both to curves:
1119 node->code = NR_CURVETO;
1120 node->n.other->code = NR_CURVETO;
1122 NR::Point leg_prev = node->pos - node->p.other->pos;
1123 NR::Point leg_next = node->pos - node->n.other->pos;
1125 double norm_leg_prev = L2(leg_prev);
1126 double norm_leg_next = L2(leg_next);
1128 NR::Point delta;
1129 if (norm_leg_next > 0.0) {
1130 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1131 (&delta)->normalize();
1132 }
1134 if (type == Inkscape::NodePath::NODE_SYMM) {
1135 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1136 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1137 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1138 } else {
1139 // length of handle is proportional to distance to adjacent node
1140 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1141 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1142 }
1144 } else {
1145 // pull the handle opposite to line segment, making it half-smooth
1146 if (p_is_line && node->n.other) {
1147 if (type != Inkscape::NodePath::NODE_SYMM) {
1148 // pull n handle
1149 node->n.other->code = NR_CURVETO;
1150 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1151 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1152 }
1153 } else if (n_is_line && node->p.other) {
1154 if (type != Inkscape::NodePath::NODE_SYMM) {
1155 // pull p handle
1156 node->code = NR_CURVETO;
1157 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1158 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1159 }
1160 }
1161 }
1162 }
1163 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1164 // cusping a cusp: retract nodes
1165 node->p.pos = node->pos;
1166 node->n.pos = node->pos;
1167 }
1169 sp_nodepath_set_node_type (node, type);
1170 }
1172 /**
1173 * Move node to point, and adjust its and neighbouring handles.
1174 */
1175 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1176 {
1177 NR::Point delta = p - node->pos;
1178 node->pos = p;
1180 node->p.pos += delta;
1181 node->n.pos += delta;
1183 Inkscape::NodePath::Node *node_p = NULL;
1184 Inkscape::NodePath::Node *node_n = NULL;
1186 if (node->p.other) {
1187 if (node->code == NR_LINETO) {
1188 sp_node_adjust_handle(node, 1);
1189 sp_node_adjust_handle(node->p.other, -1);
1190 node_p = node->p.other;
1191 }
1192 }
1193 if (node->n.other) {
1194 if (node->n.other->code == NR_LINETO) {
1195 sp_node_adjust_handle(node, -1);
1196 sp_node_adjust_handle(node->n.other, 1);
1197 node_n = node->n.other;
1198 }
1199 }
1201 // this function is only called from batch movers that will update display at the end
1202 // themselves, so here we just move all the knots without emitting move signals, for speed
1203 sp_node_update_handles(node, false);
1204 if (node_n) {
1205 sp_node_update_handles(node_n, false);
1206 }
1207 if (node_p) {
1208 sp_node_update_handles(node_p, false);
1209 }
1210 }
1212 /**
1213 * Call sp_node_moveto() for node selection and handle possible snapping.
1214 */
1215 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1216 bool const snap, bool constrained = false,
1217 Inkscape::Snapper::ConstraintLine const &constraint = NR::Point())
1218 {
1219 NR::Coord best = NR_HUGE;
1220 NR::Point delta(dx, dy);
1221 NR::Point best_pt = delta;
1222 Inkscape::SnappedPoint best_abs;
1224 if (snap) {
1225 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1226 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1227 * must provide that information. */
1229 // Build a list of the unselected nodes to which the snapper should snap
1230 std::vector<NR::Point> unselected_nodes;
1231 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1232 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1233 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1234 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1235 if (!node->selected) {
1236 unselected_nodes.push_back(node->pos);
1237 }
1238 }
1239 }
1241 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1243 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1244 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1245 m.setup(NULL, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1246 Inkscape::SnappedPoint s;
1247 if (constrained) {
1248 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1249 dedicated_constraint.setPoint(n->pos);
1250 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, dedicated_constraint);
1251 } else {
1252 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1253 }
1254 if (s.getSnapped() && (s.getDistance() < best)) {
1255 best = s.getDistance();
1256 best_abs = s;
1257 best_pt = s.getPoint() - n->pos;
1258 }
1259 }
1261 if (best_abs.getSnapped()) {
1262 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1263 } else {
1264 nodepath->desktop->snapindicator->remove_snappoint();
1265 }
1266 }
1268 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1269 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1270 sp_node_moveto(n, n->pos + best_pt);
1271 }
1273 // do not update repr here so that node dragging is acceptably fast
1274 update_object(nodepath);
1275 }
1277 /**
1278 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1279 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1280 near x = 0.
1281 */
1282 double
1283 sculpt_profile (double x, double alpha, guint profile)
1284 {
1285 if (x >= 1)
1286 return 0;
1287 if (x <= 0)
1288 return 1;
1290 switch (profile) {
1291 case SCULPT_PROFILE_LINEAR:
1292 return 1 - x;
1293 case SCULPT_PROFILE_BELL:
1294 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1295 case SCULPT_PROFILE_ELLIPTIC:
1296 return sqrt(1 - x*x);
1297 }
1299 return 1;
1300 }
1302 double
1303 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1304 {
1305 // extremely primitive for now, don't have time to look for the real one
1306 double lower = NR::L2(b - a);
1307 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1308 return (lower + upper)/2;
1309 }
1311 void
1312 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1313 {
1314 n->pos = n->origin + delta;
1315 n->n.pos = n->n.origin + delta_n;
1316 n->p.pos = n->p.origin + delta_p;
1317 sp_node_adjust_handles(n);
1318 sp_node_update_handles(n, false);
1319 }
1321 /**
1322 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1323 * on how far they are from the dragged node n.
1324 */
1325 static void
1326 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1327 {
1328 g_assert (n);
1329 g_assert (nodepath);
1330 g_assert (n->subpath->nodepath == nodepath);
1332 double pressure = n->knot->pressure;
1333 if (pressure == 0)
1334 pressure = 0.5; // default
1335 pressure = CLAMP (pressure, 0.2, 0.8);
1337 // map pressure to alpha = 1/5 ... 5
1338 double alpha = 1 - 2 * fabs(pressure - 0.5);
1339 if (pressure > 0.5)
1340 alpha = 1/alpha;
1342 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1344 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1345 // Only one subpath has selected nodes:
1346 // use linear mode, where the distance from n to node being dragged is calculated along the path
1348 double n_sel_range = 0, p_sel_range = 0;
1349 guint n_nodes = 0, p_nodes = 0;
1350 guint n_sel_nodes = 0, p_sel_nodes = 0;
1352 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1353 {
1354 double n_range = 0, p_range = 0;
1355 bool n_going = true, p_going = true;
1356 Inkscape::NodePath::Node *n_node = n;
1357 Inkscape::NodePath::Node *p_node = n;
1358 do {
1359 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1360 if (n_node && n_going)
1361 n_node = n_node->n.other;
1362 if (n_node == NULL) {
1363 n_going = false;
1364 } else {
1365 n_nodes ++;
1366 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1367 if (n_node->selected) {
1368 n_sel_nodes ++;
1369 n_sel_range = n_range;
1370 }
1371 if (n_node == p_node) {
1372 n_going = false;
1373 p_going = false;
1374 }
1375 }
1376 if (p_node && p_going)
1377 p_node = p_node->p.other;
1378 if (p_node == NULL) {
1379 p_going = false;
1380 } else {
1381 p_nodes ++;
1382 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1383 if (p_node->selected) {
1384 p_sel_nodes ++;
1385 p_sel_range = p_range;
1386 }
1387 if (p_node == n_node) {
1388 n_going = false;
1389 p_going = false;
1390 }
1391 }
1392 } while (n_going || p_going);
1393 }
1395 // Second pass: actually move nodes in this subpath
1396 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1397 {
1398 double n_range = 0, p_range = 0;
1399 bool n_going = true, p_going = true;
1400 Inkscape::NodePath::Node *n_node = n;
1401 Inkscape::NodePath::Node *p_node = n;
1402 do {
1403 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1404 if (n_node && n_going)
1405 n_node = n_node->n.other;
1406 if (n_node == NULL) {
1407 n_going = false;
1408 } else {
1409 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1410 if (n_node->selected) {
1411 sp_nodepath_move_node_and_handles (n_node,
1412 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1413 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1414 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1415 }
1416 if (n_node == p_node) {
1417 n_going = false;
1418 p_going = false;
1419 }
1420 }
1421 if (p_node && p_going)
1422 p_node = p_node->p.other;
1423 if (p_node == NULL) {
1424 p_going = false;
1425 } else {
1426 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1427 if (p_node->selected) {
1428 sp_nodepath_move_node_and_handles (p_node,
1429 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1430 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1431 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1432 }
1433 if (p_node == n_node) {
1434 n_going = false;
1435 p_going = false;
1436 }
1437 }
1438 } while (n_going || p_going);
1439 }
1441 } else {
1442 // Multiple subpaths have selected nodes:
1443 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1444 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1445 // fix the pear-like shape when sculpting e.g. a ring
1447 // First pass: calculate range
1448 gdouble direct_range = 0;
1449 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1450 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1451 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1452 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1453 if (node->selected) {
1454 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1455 }
1456 }
1457 }
1459 // Second pass: actually move nodes
1460 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1461 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1462 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1463 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1464 if (node->selected) {
1465 if (direct_range > 1e-6) {
1466 sp_nodepath_move_node_and_handles (node,
1467 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1468 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1469 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1470 } else {
1471 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1472 }
1474 }
1475 }
1476 }
1477 }
1479 // do not update repr here so that node dragging is acceptably fast
1480 update_object(nodepath);
1481 }
1484 /**
1485 * Move node selection to point, adjust its and neighbouring handles,
1486 * handle possible snapping, and commit the change with possible undo.
1487 */
1488 void
1489 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1490 {
1491 if (!nodepath) return;
1493 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1495 if (dx == 0) {
1496 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1497 } else if (dy == 0) {
1498 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1499 } else {
1500 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1501 }
1502 }
1504 /**
1505 * Move node selection off screen and commit the change.
1506 */
1507 void
1508 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1509 {
1510 // borrowed from sp_selection_move_screen in selection-chemistry.c
1511 // we find out the current zoom factor and divide deltas by it
1512 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1514 gdouble zoom = desktop->current_zoom();
1515 gdouble zdx = dx / zoom;
1516 gdouble zdy = dy / zoom;
1518 if (!nodepath) return;
1520 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1522 if (dx == 0) {
1523 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1524 } else if (dy == 0) {
1525 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1526 } else {
1527 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1528 }
1529 }
1531 /**
1532 * Move selected nodes to the absolute position given
1533 */
1534 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1535 {
1536 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1537 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1538 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1539 sp_node_moveto(n, npos);
1540 }
1542 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1543 }
1545 /**
1546 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1547 */
1548 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1549 {
1550 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1551 g_return_val_if_fail(nodepath->selected, no_coord);
1553 // determine coordinate of first selected node
1554 GList *nsel = nodepath->selected;
1555 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1556 NR::Coord coord = n->pos[axis];
1557 bool coincide = true;
1559 // compare it to the coordinates of all the other selected nodes
1560 for (GList *l = nsel->next; l != NULL; l = l->next) {
1561 n = (Inkscape::NodePath::Node *) l->data;
1562 if (n->pos[axis] != coord) {
1563 coincide = false;
1564 }
1565 }
1566 if (coincide) {
1567 return coord;
1568 } else {
1569 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1570 // currently we return the coordinate of the bounding box midpoint because I don't know how
1571 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1572 return bbox.midpoint()[axis];
1573 }
1574 }
1576 /** If they don't yet exist, creates knot and line for the given side of the node */
1577 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1578 {
1579 if (!side->knot) {
1580 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"));
1582 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1583 side->knot->setSize (7);
1584 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1585 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1586 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1587 sp_knot_update_ctrl(side->knot);
1589 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1590 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1591 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1592 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1593 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1594 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1595 }
1597 if (!side->line) {
1598 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1599 SP_TYPE_CTRLLINE, NULL);
1600 }
1601 }
1603 /**
1604 * Ensure the given handle of the node is visible/invisible, update its screen position
1605 */
1606 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1607 {
1608 g_assert(node != NULL);
1610 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1611 NRPathcode code = sp_node_path_code_from_side(node, side);
1613 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1615 if (show_handle) {
1616 if (!side->knot) { // No handle knot at all
1617 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1618 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1619 side->knot->pos = side->pos;
1620 if (side->knot->item)
1621 SP_CTRL(side->knot->item)->moveto(side->pos);
1622 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1623 sp_knot_show(side->knot);
1624 } else {
1625 if (side->knot->pos != side->pos) { // only if it's really moved
1626 if (fire_move_signals) {
1627 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1628 } else {
1629 sp_knot_moveto(side->knot, &side->pos);
1630 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1631 }
1632 }
1633 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1634 sp_knot_show(side->knot);
1635 }
1636 }
1637 sp_canvas_item_show(side->line);
1638 } else {
1639 if (side->knot) {
1640 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1641 sp_knot_hide(side->knot);
1642 }
1643 }
1644 if (side->line) {
1645 sp_canvas_item_hide(side->line);
1646 }
1647 }
1648 }
1650 /**
1651 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1652 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1653 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1654 * updated; otherwise, just move the knots silently (used in batch moves).
1655 */
1656 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1657 {
1658 g_assert(node != NULL);
1660 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1661 sp_knot_show(node->knot);
1662 }
1664 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1665 if (fire_move_signals)
1666 sp_knot_set_position(node->knot, &node->pos, 0);
1667 else
1668 sp_knot_moveto(node->knot, &node->pos);
1669 }
1671 gboolean show_handles = node->selected;
1672 if (node->p.other != NULL) {
1673 if (node->p.other->selected) show_handles = TRUE;
1674 }
1675 if (node->n.other != NULL) {
1676 if (node->n.other->selected) show_handles = TRUE;
1677 }
1679 if (node->subpath->nodepath->show_handles == false)
1680 show_handles = FALSE;
1682 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1683 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1684 }
1686 /**
1687 * Call sp_node_update_handles() for all nodes on subpath.
1688 */
1689 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1690 {
1691 g_assert(subpath != NULL);
1693 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1694 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1695 }
1696 }
1698 /**
1699 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1700 */
1701 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1702 {
1703 g_assert(nodepath != NULL);
1705 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1706 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1707 }
1708 }
1710 void
1711 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1712 {
1713 if (nodepath == NULL) return;
1715 nodepath->show_handles = show;
1716 sp_nodepath_update_handles(nodepath);
1717 }
1719 /**
1720 * Adds all selected nodes in nodepath to list.
1721 */
1722 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1723 {
1724 StlConv<Node *>::list(l, selected);
1725 /// \todo this adds a copying, rework when the selection becomes a stl list
1726 }
1728 /**
1729 * Align selected nodes on the specified axis.
1730 */
1731 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1732 {
1733 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1734 return;
1735 }
1737 if ( !nodepath->selected->next ) { // only one node selected
1738 return;
1739 }
1740 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1741 NR::Point dest(pNode->pos);
1742 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1743 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1744 if (pNode) {
1745 dest[axis] = pNode->pos[axis];
1746 sp_node_moveto(pNode, dest);
1747 }
1748 }
1750 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1751 }
1753 /// Helper struct.
1754 struct NodeSort
1755 {
1756 Inkscape::NodePath::Node *_node;
1757 NR::Coord _coord;
1758 /// \todo use vectorof pointers instead of calling copy ctor
1759 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1760 _node(node), _coord(node->pos[axis])
1761 {}
1763 };
1765 static bool operator<(NodeSort const &a, NodeSort const &b)
1766 {
1767 return (a._coord < b._coord);
1768 }
1770 /**
1771 * Distribute selected nodes on the specified axis.
1772 */
1773 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1774 {
1775 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1776 return;
1777 }
1779 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1780 return;
1781 }
1783 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1784 std::vector<NodeSort> sorted;
1785 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1786 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1787 if (pNode) {
1788 NodeSort n(pNode, axis);
1789 sorted.push_back(n);
1790 //dest[axis] = pNode->pos[axis];
1791 //sp_node_moveto(pNode, dest);
1792 }
1793 }
1794 std::sort(sorted.begin(), sorted.end());
1795 unsigned int len = sorted.size();
1796 //overall bboxes span
1797 float dist = (sorted.back()._coord -
1798 sorted.front()._coord);
1799 //new distance between each bbox
1800 float step = (dist) / (len - 1);
1801 float pos = sorted.front()._coord;
1802 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1803 it < sorted.end();
1804 it ++ )
1805 {
1806 NR::Point dest((*it)._node->pos);
1807 dest[axis] = pos;
1808 sp_node_moveto((*it)._node, dest);
1809 pos += step;
1810 }
1812 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1813 }
1816 /**
1817 * Call sp_nodepath_line_add_node() for all selected segments.
1818 */
1819 void
1820 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1821 {
1822 if (!nodepath) {
1823 return;
1824 }
1826 GList *nl = NULL;
1828 int n_added = 0;
1830 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1831 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1832 g_assert(t->selected);
1833 if (t->p.other && t->p.other->selected) {
1834 nl = g_list_prepend(nl, t);
1835 }
1836 }
1838 while (nl) {
1839 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1840 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1841 sp_nodepath_node_select(n, TRUE, FALSE);
1842 n_added ++;
1843 nl = g_list_remove(nl, t);
1844 }
1846 /** \todo fixme: adjust ? */
1847 sp_nodepath_update_handles(nodepath);
1849 if (n_added > 1) {
1850 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1851 } else if (n_added > 0) {
1852 sp_nodepath_update_repr(nodepath, _("Add node"));
1853 }
1855 sp_nodepath_update_statusbar(nodepath);
1856 }
1858 /**
1859 * Select segment nearest to point
1860 */
1861 void
1862 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1863 {
1864 if (!nodepath) {
1865 return;
1866 }
1868 sp_nodepath_ensure_livarot_path(nodepath);
1869 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1870 if (!maybe_position) {
1871 return;
1872 }
1873 Path::cut_position position = *maybe_position;
1875 //find segment to segment
1876 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1878 //fixme: this can return NULL, so check before proceeding.
1879 g_return_if_fail(e != NULL);
1881 gboolean force = FALSE;
1882 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1883 force = TRUE;
1884 }
1885 sp_nodepath_node_select(e, (gboolean) toggle, force);
1886 if (e->p.other)
1887 sp_nodepath_node_select(e->p.other, TRUE, force);
1889 sp_nodepath_update_handles(nodepath);
1891 sp_nodepath_update_statusbar(nodepath);
1892 }
1894 /**
1895 * Add a node nearest to point
1896 */
1897 void
1898 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1899 {
1900 if (!nodepath) {
1901 return;
1902 }
1904 sp_nodepath_ensure_livarot_path(nodepath);
1905 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1906 if (!maybe_position) {
1907 return;
1908 }
1909 Path::cut_position position = *maybe_position;
1911 //find segment to split
1912 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1914 //don't know why but t seems to flip for lines
1915 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1916 position.t = 1.0 - position.t;
1917 }
1918 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1919 sp_nodepath_node_select(n, FALSE, TRUE);
1921 /* fixme: adjust ? */
1922 sp_nodepath_update_handles(nodepath);
1924 sp_nodepath_update_repr(nodepath, _("Add node"));
1926 sp_nodepath_update_statusbar(nodepath);
1927 }
1929 /*
1930 * Adjusts a segment so that t moves by a certain delta for dragging
1931 * converts lines to curves
1932 *
1933 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1934 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1935 */
1936 void
1937 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1938 {
1939 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1941 //fixme: e and e->p can be NULL, so check for those before proceeding
1942 g_return_if_fail(e != NULL);
1943 g_return_if_fail(&e->p != NULL);
1945 /* feel good is an arbitrary parameter that distributes the delta between handles
1946 * if t of the drag point is less than 1/6 distance form the endpoint only
1947 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1948 */
1949 double feel_good;
1950 if (t <= 1.0 / 6.0)
1951 feel_good = 0;
1952 else if (t <= 0.5)
1953 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1954 else if (t <= 5.0 / 6.0)
1955 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1956 else
1957 feel_good = 1;
1959 //if we're dragging a line convert it to a curve
1960 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1961 sp_nodepath_set_line_type(e, NR_CURVETO);
1962 }
1964 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1965 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1966 e->p.other->n.pos += offsetcoord0;
1967 e->p.pos += offsetcoord1;
1969 // adjust handles of adjacent nodes where necessary
1970 sp_node_adjust_handle(e,1);
1971 sp_node_adjust_handle(e->p.other,-1);
1973 sp_nodepath_update_handles(e->subpath->nodepath);
1975 update_object(e->subpath->nodepath);
1977 sp_nodepath_update_statusbar(e->subpath->nodepath);
1978 }
1981 /**
1982 * Call sp_nodepath_break() for all selected segments.
1983 */
1984 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1985 {
1986 if (!nodepath) return;
1988 GList *temp = NULL;
1989 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1990 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1991 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1992 if (nn == NULL) continue; // no break, no new node
1993 temp = g_list_prepend(temp, nn);
1994 }
1996 if (temp) {
1997 sp_nodepath_deselect(nodepath);
1998 }
1999 for (GList *l = temp; l != NULL; l = l->next) {
2000 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2001 }
2003 sp_nodepath_update_handles(nodepath);
2005 sp_nodepath_update_repr(nodepath, _("Break path"));
2006 }
2008 /**
2009 * Duplicate the selected node(s).
2010 */
2011 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2012 {
2013 if (!nodepath) {
2014 return;
2015 }
2017 GList *temp = NULL;
2018 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2019 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2020 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2021 if (nn == NULL) continue; // could not duplicate
2022 temp = g_list_prepend(temp, nn);
2023 }
2025 if (temp) {
2026 sp_nodepath_deselect(nodepath);
2027 }
2028 for (GList *l = temp; l != NULL; l = l->next) {
2029 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2030 }
2032 sp_nodepath_update_handles(nodepath);
2034 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2035 }
2037 /**
2038 * Internal function to join two nodes by merging them into one.
2039 */
2040 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2041 {
2042 /* a and b are endpoints */
2044 // if one of the two nodes is mouseovered, fix its position
2045 NR::Point c;
2046 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2047 c = a->pos;
2048 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2049 c = b->pos;
2050 } else {
2051 // otherwise, move joined node to the midpoint
2052 c = (a->pos + b->pos) / 2;
2053 }
2055 if (a->subpath == b->subpath) {
2056 Inkscape::NodePath::SubPath *sp = a->subpath;
2057 sp_nodepath_subpath_close(sp);
2058 sp_node_moveto (sp->first, c);
2060 sp_nodepath_update_handles(sp->nodepath);
2061 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2062 return;
2063 }
2065 /* a and b are separate subpaths */
2066 Inkscape::NodePath::SubPath *sa = a->subpath;
2067 Inkscape::NodePath::SubPath *sb = b->subpath;
2068 NR::Point p;
2069 Inkscape::NodePath::Node *n;
2070 NRPathcode code;
2071 if (a == sa->first) {
2072 // we will now reverse sa, so that a is its last node, not first, and drop that node
2073 p = sa->first->n.pos;
2074 code = (NRPathcode)sa->first->n.other->code;
2075 // create new subpath
2076 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2077 // create a first moveto node on it
2078 n = sa->last;
2079 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2080 n = n->p.other;
2081 if (n == sa->first) n = NULL;
2082 while (n) {
2083 // copy the rest of the nodes from sa to t, going backwards
2084 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2085 n = n->p.other;
2086 if (n == sa->first) n = NULL;
2087 }
2088 // replace sa with t
2089 sp_nodepath_subpath_destroy(sa);
2090 sa = t;
2091 } else if (a == sa->last) {
2092 // a is already last, just drop it
2093 p = sa->last->p.pos;
2094 code = (NRPathcode)sa->last->code;
2095 sp_nodepath_node_destroy(sa->last);
2096 } else {
2097 code = NR_END;
2098 g_assert_not_reached();
2099 }
2101 if (b == sb->first) {
2102 // copy all nodes from b to a, forward
2103 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2104 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2105 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2106 }
2107 } else if (b == sb->last) {
2108 // copy all nodes from b to a, backward
2109 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2110 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2111 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2112 }
2113 } else {
2114 g_assert_not_reached();
2115 }
2116 /* and now destroy sb */
2118 sp_nodepath_subpath_destroy(sb);
2120 sp_nodepath_update_handles(sa->nodepath);
2122 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2124 sp_nodepath_update_statusbar(nodepath);
2125 }
2127 /**
2128 * Internal function to join two nodes by adding a segment between them.
2129 */
2130 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2131 {
2132 if (a->subpath == b->subpath) {
2133 Inkscape::NodePath::SubPath *sp = a->subpath;
2135 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2136 sp->closed = TRUE;
2138 sp->first->p.other = sp->last;
2139 sp->last->n.other = sp->first;
2141 sp_node_handle_mirror_p_to_n(sp->last);
2142 sp_node_handle_mirror_n_to_p(sp->first);
2144 sp->first->code = sp->last->code;
2145 sp->first = sp->last;
2147 sp_nodepath_update_handles(sp->nodepath);
2149 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2151 return;
2152 }
2154 /* a and b are separate subpaths */
2155 Inkscape::NodePath::SubPath *sa = a->subpath;
2156 Inkscape::NodePath::SubPath *sb = b->subpath;
2158 Inkscape::NodePath::Node *n;
2159 NR::Point p;
2160 NRPathcode code;
2161 if (a == sa->first) {
2162 code = (NRPathcode) sa->first->n.other->code;
2163 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2164 n = sa->last;
2165 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2166 for (n = n->p.other; n != NULL; n = n->p.other) {
2167 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2168 }
2169 sp_nodepath_subpath_destroy(sa);
2170 sa = t;
2171 } else if (a == sa->last) {
2172 code = (NRPathcode)sa->last->code;
2173 } else {
2174 code = NR_END;
2175 g_assert_not_reached();
2176 }
2178 if (b == sb->first) {
2179 n = sb->first;
2180 sp_node_handle_mirror_p_to_n(sa->last);
2181 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2182 sp_node_handle_mirror_n_to_p(sa->last);
2183 for (n = n->n.other; n != NULL; n = n->n.other) {
2184 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2185 }
2186 } else if (b == sb->last) {
2187 n = sb->last;
2188 sp_node_handle_mirror_p_to_n(sa->last);
2189 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2190 sp_node_handle_mirror_n_to_p(sa->last);
2191 for (n = n->p.other; n != NULL; n = n->p.other) {
2192 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2193 }
2194 } else {
2195 g_assert_not_reached();
2196 }
2197 /* and now destroy sb */
2199 sp_nodepath_subpath_destroy(sb);
2201 sp_nodepath_update_handles(sa->nodepath);
2203 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2204 }
2206 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2208 /**
2209 * Internal function to handle joining two nodes.
2210 */
2211 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2212 {
2213 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2215 if (g_list_length(nodepath->selected) != 2) {
2216 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2217 return;
2218 }
2220 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2221 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2223 g_assert(a != b);
2224 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2225 // someone tried to join an orphan node (i.e. a single-node subpath).
2226 // this is not worth an error message, just fail silently.
2227 return;
2228 }
2230 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2231 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2232 return;
2233 }
2235 switch(mode) {
2236 case NODE_JOIN_ENDPOINTS:
2237 do_node_selected_join(nodepath, a, b);
2238 break;
2239 case NODE_JOIN_SEGMENT:
2240 do_node_selected_join_segment(nodepath, a, b);
2241 break;
2242 }
2243 }
2245 /**
2246 * Join two nodes by merging them into one.
2247 */
2248 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2249 {
2250 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2251 }
2253 /**
2254 * Join two nodes by adding a segment between them.
2255 */
2256 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2257 {
2258 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2259 }
2261 /**
2262 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2263 */
2264 void sp_node_delete_preserve(GList *nodes_to_delete)
2265 {
2266 GSList *nodepaths = NULL;
2268 while (nodes_to_delete) {
2269 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2270 Inkscape::NodePath::SubPath *sp = node->subpath;
2271 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2272 Inkscape::NodePath::Node *sample_cursor = NULL;
2273 Inkscape::NodePath::Node *sample_end = NULL;
2274 Inkscape::NodePath::Node *delete_cursor = node;
2275 bool just_delete = false;
2277 //find the start of this contiguous selection
2278 //move left to the first node that is not selected
2279 //or the start of the non-closed path
2280 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2281 delete_cursor = curr;
2282 }
2284 //just delete at the beginning of an open path
2285 if (!delete_cursor->p.other) {
2286 sample_cursor = delete_cursor;
2287 just_delete = true;
2288 } else {
2289 sample_cursor = delete_cursor->p.other;
2290 }
2292 //calculate points for each segment
2293 int rate = 5;
2294 float period = 1.0 / rate;
2295 std::vector<NR::Point> data;
2296 if (!just_delete) {
2297 data.push_back(sample_cursor->pos);
2298 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2299 //just delete at the end of an open path
2300 if (!sp->closed && curr == sp->last) {
2301 just_delete = true;
2302 break;
2303 }
2305 //sample points on the contiguous selected segment
2306 NR::Point *bez;
2307 bez = new NR::Point [4];
2308 bez[0] = curr->pos;
2309 bez[1] = curr->n.pos;
2310 bez[2] = curr->n.other->p.pos;
2311 bez[3] = curr->n.other->pos;
2312 for (int i=1; i<rate; i++) {
2313 gdouble t = i * period;
2314 NR::Point p = bezier_pt(3, bez, t);
2315 data.push_back(p);
2316 }
2317 data.push_back(curr->n.other->pos);
2319 sample_end = curr->n.other;
2320 //break if we've come full circle or hit the end of the selection
2321 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2322 break;
2323 }
2324 }
2325 }
2327 if (!just_delete) {
2328 //calculate the best fitting single segment and adjust the endpoints
2329 NR::Point *adata;
2330 adata = new NR::Point [data.size()];
2331 copy(data.begin(), data.end(), adata);
2333 NR::Point *bez;
2334 bez = new NR::Point [4];
2335 //would decreasing error create a better fitting approximation?
2336 gdouble error = 1.0;
2337 gint ret;
2338 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2340 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2341 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2342 //the resulting nodes behave as expected.
2343 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2344 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2345 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2346 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2348 //adjust endpoints
2349 sample_cursor->n.pos = bez[1];
2350 sample_end->p.pos = bez[2];
2351 }
2353 //destroy this contiguous selection
2354 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2355 Inkscape::NodePath::Node *temp = delete_cursor;
2356 if (delete_cursor->n.other == delete_cursor) {
2357 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2358 delete_cursor = NULL;
2359 } else {
2360 delete_cursor = delete_cursor->n.other;
2361 }
2362 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2363 sp_nodepath_node_destroy(temp);
2364 }
2366 sp_nodepath_update_handles(nodepath);
2368 if (!g_slist_find(nodepaths, nodepath))
2369 nodepaths = g_slist_prepend (nodepaths, nodepath);
2370 }
2372 for (GSList *i = nodepaths; i; i = i->next) {
2373 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2374 // different nodepaths will give us one undo event per nodepath
2375 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2377 // if the entire nodepath is removed, delete the selected object.
2378 if (nodepath->subpaths == NULL ||
2379 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2380 //at least 2
2381 sp_nodepath_get_node_count(nodepath) < 2) {
2382 SPDocument *document = sp_desktop_document (nodepath->desktop);
2383 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2384 //delete this nodepath's object, not the entire selection! (though at this time, this
2385 //does not matter)
2386 sp_selection_delete();
2387 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2388 _("Delete nodes"));
2389 } else {
2390 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2391 sp_nodepath_update_statusbar(nodepath);
2392 }
2393 }
2395 g_slist_free (nodepaths);
2396 }
2398 /**
2399 * Delete one or more selected nodes.
2400 */
2401 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2402 {
2403 if (!nodepath) return;
2404 if (!nodepath->selected) return;
2406 /** \todo fixme: do it the right way */
2407 while (nodepath->selected) {
2408 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2409 sp_nodepath_node_destroy(node);
2410 }
2413 //clean up the nodepath (such as for trivial subpaths)
2414 sp_nodepath_cleanup(nodepath);
2416 sp_nodepath_update_handles(nodepath);
2418 // if the entire nodepath is removed, delete the selected object.
2419 if (nodepath->subpaths == NULL ||
2420 sp_nodepath_get_node_count(nodepath) < 2) {
2421 SPDocument *document = sp_desktop_document (nodepath->desktop);
2422 sp_selection_delete();
2423 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2424 _("Delete nodes"));
2425 return;
2426 }
2428 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2430 sp_nodepath_update_statusbar(nodepath);
2431 }
2433 /**
2434 * Delete one or more segments between two selected nodes.
2435 * This is the code for 'split'.
2436 */
2437 void
2438 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2439 {
2440 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2441 Inkscape::NodePath::Node *curr, *next; //Iterators
2443 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2445 if (g_list_length(nodepath->selected) != 2) {
2446 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2447 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2448 return;
2449 }
2451 //Selected nodes, not inclusive
2452 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2453 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2455 if ( ( a==b) || //same node
2456 (a->subpath != b->subpath ) || //not the same path
2457 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2458 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2459 {
2460 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2461 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2462 return;
2463 }
2465 //###########################################
2466 //# BEGIN EDITS
2467 //###########################################
2468 //##################################
2469 //# CLOSED PATH
2470 //##################################
2471 if (a->subpath->closed) {
2474 gboolean reversed = FALSE;
2476 //Since we can go in a circle, we need to find the shorter distance.
2477 // a->b or b->a
2478 start = end = NULL;
2479 int distance = 0;
2480 int minDistance = 0;
2481 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2482 if (curr==b) {
2483 //printf("a to b:%d\n", distance);
2484 start = a;//go from a to b
2485 end = b;
2486 minDistance = distance;
2487 //printf("A to B :\n");
2488 break;
2489 }
2490 distance++;
2491 }
2493 //try again, the other direction
2494 distance = 0;
2495 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2496 if (curr==a) {
2497 //printf("b to a:%d\n", distance);
2498 if (distance < minDistance) {
2499 start = b; //we go from b to a
2500 end = a;
2501 reversed = TRUE;
2502 //printf("B to A\n");
2503 }
2504 break;
2505 }
2506 distance++;
2507 }
2510 //Copy everything from 'end' to 'start' to a new subpath
2511 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2512 for (curr=end ; curr ; curr=curr->n.other) {
2513 NRPathcode code = (NRPathcode) curr->code;
2514 if (curr == end)
2515 code = NR_MOVETO;
2516 sp_nodepath_node_new(t, NULL,
2517 (Inkscape::NodePath::NodeType)curr->type, code,
2518 &curr->p.pos, &curr->pos, &curr->n.pos);
2519 if (curr == start)
2520 break;
2521 }
2522 sp_nodepath_subpath_destroy(a->subpath);
2525 }
2529 //##################################
2530 //# OPEN PATH
2531 //##################################
2532 else {
2534 //We need to get the direction of the list between A and B
2535 //Can we walk from a to b?
2536 start = end = NULL;
2537 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2538 if (curr==b) {
2539 start = a; //did it! we go from a to b
2540 end = b;
2541 //printf("A to B\n");
2542 break;
2543 }
2544 }
2545 if (!start) {//didn't work? let's try the other direction
2546 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2547 if (curr==a) {
2548 start = b; //did it! we go from b to a
2549 end = a;
2550 //printf("B to A\n");
2551 break;
2552 }
2553 }
2554 }
2555 if (!start) {
2556 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2557 _("Cannot find path between nodes."));
2558 return;
2559 }
2563 //Copy everything after 'end' to a new subpath
2564 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2565 for (curr=end ; curr ; curr=curr->n.other) {
2566 NRPathcode code = (NRPathcode) curr->code;
2567 if (curr == end)
2568 code = NR_MOVETO;
2569 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2570 &curr->p.pos, &curr->pos, &curr->n.pos);
2571 }
2573 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2574 for (curr = start->n.other ; curr ; curr=next) {
2575 next = curr->n.other;
2576 sp_nodepath_node_destroy(curr);
2577 }
2579 }
2580 //###########################################
2581 //# END EDITS
2582 //###########################################
2584 //clean up the nodepath (such as for trivial subpaths)
2585 sp_nodepath_cleanup(nodepath);
2587 sp_nodepath_update_handles(nodepath);
2589 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2591 sp_nodepath_update_statusbar(nodepath);
2592 }
2594 /**
2595 * Call sp_nodepath_set_line() for all selected segments.
2596 */
2597 void
2598 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2599 {
2600 if (nodepath == NULL) return;
2602 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2603 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2604 g_assert(n->selected);
2605 if (n->p.other && n->p.other->selected) {
2606 sp_nodepath_set_line_type(n, code);
2607 }
2608 }
2610 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2611 }
2613 /**
2614 * Call sp_nodepath_convert_node_type() for all selected nodes.
2615 */
2616 void
2617 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2618 {
2619 if (nodepath == NULL) return;
2621 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2623 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2624 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2625 }
2627 sp_nodepath_update_repr(nodepath, _("Change node type"));
2628 }
2630 /**
2631 * Change select status of node, update its own and neighbour handles.
2632 */
2633 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2634 {
2635 node->selected = selected;
2637 if (selected) {
2638 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2639 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2640 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2641 sp_knot_update_ctrl(node->knot);
2642 } else {
2643 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2644 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2645 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2646 sp_knot_update_ctrl(node->knot);
2647 }
2649 sp_node_update_handles(node);
2650 if (node->n.other) sp_node_update_handles(node->n.other);
2651 if (node->p.other) sp_node_update_handles(node->p.other);
2652 }
2654 /**
2655 \brief Select a node
2656 \param node The node to select
2657 \param incremental If true, add to selection, otherwise deselect others
2658 \param override If true, always select this node, otherwise toggle selected status
2659 */
2660 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2661 {
2662 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2664 if (incremental) {
2665 if (override) {
2666 if (!g_list_find(nodepath->selected, node)) {
2667 nodepath->selected = g_list_prepend(nodepath->selected, node);
2668 }
2669 sp_node_set_selected(node, TRUE);
2670 } else { // toggle
2671 if (node->selected) {
2672 g_assert(g_list_find(nodepath->selected, node));
2673 nodepath->selected = g_list_remove(nodepath->selected, node);
2674 } else {
2675 g_assert(!g_list_find(nodepath->selected, node));
2676 nodepath->selected = g_list_prepend(nodepath->selected, node);
2677 }
2678 sp_node_set_selected(node, !node->selected);
2679 }
2680 } else {
2681 sp_nodepath_deselect(nodepath);
2682 nodepath->selected = g_list_prepend(nodepath->selected, node);
2683 sp_node_set_selected(node, TRUE);
2684 }
2686 sp_nodepath_update_statusbar(nodepath);
2687 }
2690 /**
2691 \brief Deselect all nodes in the nodepath
2692 */
2693 void
2694 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2695 {
2696 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2698 while (nodepath->selected) {
2699 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2700 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2701 }
2702 sp_nodepath_update_statusbar(nodepath);
2703 }
2705 /**
2706 \brief Select or invert selection of all nodes in the nodepath
2707 */
2708 void
2709 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2710 {
2711 if (!nodepath) return;
2713 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2714 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2715 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2716 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2717 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2718 }
2719 }
2720 }
2722 /**
2723 * If nothing selected, does the same as sp_nodepath_select_all();
2724 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2725 * (i.e., similar to "select all in layer", with the "selected" subpaths
2726 * being treated as "layers" in the path).
2727 */
2728 void
2729 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2730 {
2731 if (!nodepath) return;
2733 if (g_list_length (nodepath->selected) == 0) {
2734 sp_nodepath_select_all (nodepath, invert);
2735 return;
2736 }
2738 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2739 GSList *subpaths = NULL;
2741 for (GList *l = copy; l != NULL; l = l->next) {
2742 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2743 Inkscape::NodePath::SubPath *subpath = n->subpath;
2744 if (!g_slist_find (subpaths, subpath))
2745 subpaths = g_slist_prepend (subpaths, subpath);
2746 }
2748 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2749 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2750 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2751 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2752 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2753 }
2754 }
2756 g_slist_free (subpaths);
2757 g_list_free (copy);
2758 }
2760 /**
2761 * \brief Select the node after the last selected; if none is selected,
2762 * select the first within path.
2763 */
2764 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2765 {
2766 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2768 Inkscape::NodePath::Node *last = NULL;
2769 if (nodepath->selected) {
2770 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2771 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2772 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2773 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2774 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2775 if (node->selected) {
2776 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2777 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2778 if (spl->next) { // there's a next subpath
2779 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2780 last = subpath_next->first;
2781 } else if (spl->prev) { // there's a previous subpath
2782 last = NULL; // to be set later to the first node of first subpath
2783 } else {
2784 last = node->n.other;
2785 }
2786 } else {
2787 last = node->n.other;
2788 }
2789 } else {
2790 if (node->n.other) {
2791 last = node->n.other;
2792 } else {
2793 if (spl->next) { // there's a next subpath
2794 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2795 last = subpath_next->first;
2796 } else if (spl->prev) { // there's a previous subpath
2797 last = NULL; // to be set later to the first node of first subpath
2798 } else {
2799 last = (Inkscape::NodePath::Node *) subpath->first;
2800 }
2801 }
2802 }
2803 }
2804 }
2805 }
2806 sp_nodepath_deselect(nodepath);
2807 }
2809 if (last) { // there's at least one more node after selected
2810 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2811 } else { // no more nodes, select the first one in first subpath
2812 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2813 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2814 }
2815 }
2817 /**
2818 * \brief Select the node before the first selected; if none is selected,
2819 * select the last within path
2820 */
2821 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2822 {
2823 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2825 Inkscape::NodePath::Node *last = NULL;
2826 if (nodepath->selected) {
2827 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2828 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2829 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2830 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2831 if (node->selected) {
2832 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2833 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2834 if (spl->prev) { // there's a prev subpath
2835 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2836 last = subpath_prev->last;
2837 } else if (spl->next) { // there's a next subpath
2838 last = NULL; // to be set later to the last node of last subpath
2839 } else {
2840 last = node->p.other;
2841 }
2842 } else {
2843 last = node->p.other;
2844 }
2845 } else {
2846 if (node->p.other) {
2847 last = node->p.other;
2848 } else {
2849 if (spl->prev) { // there's a prev subpath
2850 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2851 last = subpath_prev->last;
2852 } else if (spl->next) { // there's a next subpath
2853 last = NULL; // to be set later to the last node of last subpath
2854 } else {
2855 last = (Inkscape::NodePath::Node *) subpath->last;
2856 }
2857 }
2858 }
2859 }
2860 }
2861 }
2862 sp_nodepath_deselect(nodepath);
2863 }
2865 if (last) { // there's at least one more node before selected
2866 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2867 } else { // no more nodes, select the last one in last subpath
2868 GList *spl = g_list_last(nodepath->subpaths);
2869 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2870 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2871 }
2872 }
2874 /**
2875 * \brief Select all nodes that are within the rectangle.
2876 */
2877 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2878 {
2879 if (!incremental) {
2880 sp_nodepath_deselect(nodepath);
2881 }
2883 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2884 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2885 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2886 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2888 if (b.contains(node->pos)) {
2889 sp_nodepath_node_select(node, TRUE, TRUE);
2890 }
2891 }
2892 }
2893 }
2896 void
2897 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2898 {
2899 g_assert (n);
2900 g_assert (nodepath);
2901 g_assert (n->subpath->nodepath == nodepath);
2903 if (g_list_length (nodepath->selected) == 0) {
2904 if (grow > 0) {
2905 sp_nodepath_node_select(n, TRUE, TRUE);
2906 }
2907 return;
2908 }
2910 if (g_list_length (nodepath->selected) == 1) {
2911 if (grow < 0) {
2912 sp_nodepath_deselect (nodepath);
2913 return;
2914 }
2915 }
2917 double n_sel_range = 0, p_sel_range = 0;
2918 Inkscape::NodePath::Node *farthest_n_node = n;
2919 Inkscape::NodePath::Node *farthest_p_node = n;
2921 // Calculate ranges
2922 {
2923 double n_range = 0, p_range = 0;
2924 bool n_going = true, p_going = true;
2925 Inkscape::NodePath::Node *n_node = n;
2926 Inkscape::NodePath::Node *p_node = n;
2927 do {
2928 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2929 if (n_node && n_going)
2930 n_node = n_node->n.other;
2931 if (n_node == NULL) {
2932 n_going = false;
2933 } else {
2934 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2935 if (n_node->selected) {
2936 n_sel_range = n_range;
2937 farthest_n_node = n_node;
2938 }
2939 if (n_node == p_node) {
2940 n_going = false;
2941 p_going = false;
2942 }
2943 }
2944 if (p_node && p_going)
2945 p_node = p_node->p.other;
2946 if (p_node == NULL) {
2947 p_going = false;
2948 } else {
2949 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2950 if (p_node->selected) {
2951 p_sel_range = p_range;
2952 farthest_p_node = p_node;
2953 }
2954 if (p_node == n_node) {
2955 n_going = false;
2956 p_going = false;
2957 }
2958 }
2959 } while (n_going || p_going);
2960 }
2962 if (grow > 0) {
2963 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2964 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2965 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2966 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2967 }
2968 } else {
2969 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2970 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2971 } else if (farthest_p_node && farthest_p_node->selected) {
2972 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2973 }
2974 }
2975 }
2977 void
2978 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2979 {
2980 g_assert (n);
2981 g_assert (nodepath);
2982 g_assert (n->subpath->nodepath == nodepath);
2984 if (g_list_length (nodepath->selected) == 0) {
2985 if (grow > 0) {
2986 sp_nodepath_node_select(n, TRUE, TRUE);
2987 }
2988 return;
2989 }
2991 if (g_list_length (nodepath->selected) == 1) {
2992 if (grow < 0) {
2993 sp_nodepath_deselect (nodepath);
2994 return;
2995 }
2996 }
2998 Inkscape::NodePath::Node *farthest_selected = NULL;
2999 double farthest_dist = 0;
3001 Inkscape::NodePath::Node *closest_unselected = NULL;
3002 double closest_dist = NR_HUGE;
3004 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3005 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3006 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3007 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3008 if (node == n)
3009 continue;
3010 if (node->selected) {
3011 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3012 farthest_dist = NR::L2(node->pos - n->pos);
3013 farthest_selected = node;
3014 }
3015 } else {
3016 if (NR::L2(node->pos - n->pos) < closest_dist) {
3017 closest_dist = NR::L2(node->pos - n->pos);
3018 closest_unselected = node;
3019 }
3020 }
3021 }
3022 }
3024 if (grow > 0) {
3025 if (closest_unselected) {
3026 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3027 }
3028 } else {
3029 if (farthest_selected) {
3030 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3031 }
3032 }
3033 }
3036 /**
3037 \brief Saves all nodes' and handles' current positions in their origin members
3038 */
3039 void
3040 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3041 {
3042 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3043 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3044 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3045 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3046 n->origin = n->pos;
3047 n->p.origin = n->p.pos;
3048 n->n.origin = n->n.pos;
3049 }
3050 }
3051 }
3053 /**
3054 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3055 */
3056 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3057 {
3058 if (!nodepath->selected) {
3059 return NULL;
3060 }
3062 GList *r = NULL;
3063 guint i = 0;
3064 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3065 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3066 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3067 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3068 i++;
3069 if (node->selected) {
3070 r = g_list_append(r, GINT_TO_POINTER(i));
3071 }
3072 }
3073 }
3074 return r;
3075 }
3077 /**
3078 \brief Restores selection by selecting nodes whose positions are in the list
3079 */
3080 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3081 {
3082 sp_nodepath_deselect(nodepath);
3084 guint i = 0;
3085 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3086 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3087 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3088 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3089 i++;
3090 if (g_list_find(r, GINT_TO_POINTER(i))) {
3091 sp_nodepath_node_select(node, TRUE, TRUE);
3092 }
3093 }
3094 }
3095 }
3098 /**
3099 \brief Adjusts handle according to node type and line code.
3100 */
3101 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3102 {
3103 g_assert(node);
3105 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3106 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3108 // nothing to do if we are an end node
3109 if (me->other == NULL) return;
3110 if (other->other == NULL) return;
3112 // nothing to do if we are a cusp node
3113 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3115 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3116 NRPathcode mecode;
3117 if (which_adjust == 1) {
3118 mecode = (NRPathcode)me->other->code;
3119 } else {
3120 mecode = (NRPathcode)node->code;
3121 }
3122 if (mecode == NR_LINETO) return;
3124 if (sp_node_side_is_line(node, other)) {
3125 // other is a line, and we are either smooth or symm
3126 Inkscape::NodePath::Node *othernode = other->other;
3127 double len = NR::L2(me->pos - node->pos);
3128 NR::Point delta = node->pos - othernode->pos;
3129 double linelen = NR::L2(delta);
3130 if (linelen < 1e-18)
3131 return;
3132 me->pos = node->pos + (len / linelen)*delta;
3133 return;
3134 }
3136 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3137 // symmetrize
3138 me->pos = 2 * node->pos - other->pos;
3139 return;
3140 } else {
3141 // smoothify
3142 double len = NR::L2(me->pos - node->pos);
3143 NR::Point delta = other->pos - node->pos;
3144 double otherlen = NR::L2(delta);
3145 if (otherlen < 1e-18) return;
3146 me->pos = node->pos - (len / otherlen) * delta;
3147 }
3148 }
3150 /**
3151 \brief Adjusts both handles according to node type and line code
3152 */
3153 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3154 {
3155 g_assert(node);
3157 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3159 /* we are either smooth or symm */
3161 if (node->p.other == NULL) return;
3162 if (node->n.other == NULL) return;
3164 if (sp_node_side_is_line(node, &node->p)) {
3165 sp_node_adjust_handle(node, 1);
3166 return;
3167 }
3169 if (sp_node_side_is_line(node, &node->n)) {
3170 sp_node_adjust_handle(node, -1);
3171 return;
3172 }
3174 /* both are curves */
3175 NR::Point const delta( node->n.pos - node->p.pos );
3177 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3178 node->p.pos = node->pos - delta / 2;
3179 node->n.pos = node->pos + delta / 2;
3180 return;
3181 }
3183 /* We are smooth */
3184 double plen = NR::L2(node->p.pos - node->pos);
3185 if (plen < 1e-18) return;
3186 double nlen = NR::L2(node->n.pos - node->pos);
3187 if (nlen < 1e-18) return;
3188 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3189 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3190 }
3192 /**
3193 * Node event callback.
3194 */
3195 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3196 {
3197 gboolean ret = FALSE;
3198 switch (event->type) {
3199 case GDK_ENTER_NOTIFY:
3200 Inkscape::NodePath::Path::active_node = n;
3201 break;
3202 case GDK_LEAVE_NOTIFY:
3203 Inkscape::NodePath::Path::active_node = NULL;
3204 break;
3205 case GDK_SCROLL:
3206 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3207 switch (event->scroll.direction) {
3208 case GDK_SCROLL_UP:
3209 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3210 break;
3211 case GDK_SCROLL_DOWN:
3212 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3213 break;
3214 default:
3215 break;
3216 }
3217 ret = TRUE;
3218 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3219 switch (event->scroll.direction) {
3220 case GDK_SCROLL_UP:
3221 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3222 break;
3223 case GDK_SCROLL_DOWN:
3224 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3225 break;
3226 default:
3227 break;
3228 }
3229 ret = TRUE;
3230 }
3231 break;
3232 case GDK_KEY_PRESS:
3233 switch (get_group0_keyval (&event->key)) {
3234 case GDK_space:
3235 if (event->key.state & GDK_BUTTON1_MASK) {
3236 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3237 stamp_repr(nodepath);
3238 ret = TRUE;
3239 }
3240 break;
3241 case GDK_Page_Up:
3242 if (event->key.state & GDK_CONTROL_MASK) {
3243 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3244 } else {
3245 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3246 }
3247 break;
3248 case GDK_Page_Down:
3249 if (event->key.state & GDK_CONTROL_MASK) {
3250 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3251 } else {
3252 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3253 }
3254 break;
3255 default:
3256 break;
3257 }
3258 break;
3259 default:
3260 break;
3261 }
3263 return ret;
3264 }
3266 /**
3267 * Handle keypress on node; directly called.
3268 */
3269 gboolean node_key(GdkEvent *event)
3270 {
3271 Inkscape::NodePath::Path *np;
3273 // there is no way to verify nodes so set active_node to nil when deleting!!
3274 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3276 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3277 gint ret = FALSE;
3278 switch (get_group0_keyval (&event->key)) {
3279 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3280 case GDK_BackSpace:
3281 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3282 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3283 sp_nodepath_update_repr(np, _("Delete node"));
3284 Inkscape::NodePath::Path::active_node = NULL;
3285 ret = TRUE;
3286 break;
3287 case GDK_c:
3288 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3289 ret = TRUE;
3290 break;
3291 case GDK_s:
3292 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3293 ret = TRUE;
3294 break;
3295 case GDK_y:
3296 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3297 ret = TRUE;
3298 break;
3299 case GDK_b:
3300 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3301 ret = TRUE;
3302 break;
3303 }
3304 return ret;
3305 }
3306 return FALSE;
3307 }
3309 /**
3310 * Mouseclick on node callback.
3311 */
3312 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3313 {
3314 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3316 if (state & GDK_CONTROL_MASK) {
3317 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3319 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3320 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3321 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3322 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3323 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3324 } else {
3325 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3326 }
3327 sp_nodepath_update_repr(nodepath, _("Change node type"));
3328 sp_nodepath_update_statusbar(nodepath);
3330 } else { //ctrl+alt+click: delete node
3331 GList *node_to_delete = NULL;
3332 node_to_delete = g_list_append(node_to_delete, n);
3333 sp_node_delete_preserve(node_to_delete);
3334 }
3336 } else {
3337 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3338 }
3339 }
3341 /**
3342 * Mouse grabbed node callback.
3343 */
3344 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3345 {
3346 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3348 if (!n->selected) {
3349 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3350 }
3352 n->is_dragging = true;
3353 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3355 sp_nodepath_remember_origins (n->subpath->nodepath);
3356 }
3358 /**
3359 * Mouse ungrabbed node callback.
3360 */
3361 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3362 {
3363 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3365 n->dragging_out = NULL;
3366 n->is_dragging = false;
3367 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3369 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3370 }
3372 /**
3373 * The point on a line, given by its angle, closest to the given point.
3374 * \param p A point.
3375 * \param a Angle of the line; it is assumed to go through coordinate origin.
3376 * \param closest Pointer to the point struct where the result is stored.
3377 * \todo FIXME: use dot product perhaps?
3378 */
3379 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3380 {
3381 if (a == HUGE_VAL) { // vertical
3382 *closest = NR::Point(0, (*p)[NR::Y]);
3383 } else {
3384 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3385 (*closest)[NR::Y] = a * (*closest)[NR::X];
3386 }
3387 }
3389 /**
3390 * Distance from the point to a line given by its angle.
3391 * \param p A point.
3392 * \param a Angle of the line; it is assumed to go through coordinate origin.
3393 */
3394 static double point_line_distance(NR::Point *p, double a)
3395 {
3396 NR::Point c;
3397 point_line_closest(p, a, &c);
3398 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]));
3399 }
3401 /**
3402 * Callback for node "request" signal.
3403 * \todo fixme: This goes to "moved" event? (lauris)
3404 */
3405 static gboolean
3406 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3407 {
3408 double yn, xn, yp, xp;
3409 double an, ap, na, pa;
3410 double d_an, d_ap, d_na, d_pa;
3411 gboolean collinear = FALSE;
3412 NR::Point c;
3413 NR::Point pr;
3415 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3417 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3419 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3420 if ( (!n->subpath->nodepath->straight_path) &&
3421 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3422 || n->dragging_out ) )
3423 {
3424 NR::Point mouse = (*p);
3426 if (!n->dragging_out) {
3427 // This is the first drag-out event; find out which handle to drag out
3428 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3429 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3431 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3432 return FALSE;
3434 Inkscape::NodePath::NodeSide *opposite;
3435 if (appr_p > appr_n) { // closer to p
3436 n->dragging_out = &n->p;
3437 opposite = &n->n;
3438 n->code = NR_CURVETO;
3439 } else if (appr_p < appr_n) { // closer to n
3440 n->dragging_out = &n->n;
3441 opposite = &n->p;
3442 n->n.other->code = NR_CURVETO;
3443 } else { // p and n nodes are the same
3444 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3445 n->dragging_out = &n->p;
3446 opposite = &n->n;
3447 n->code = NR_CURVETO;
3448 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3449 n->dragging_out = &n->n;
3450 opposite = &n->p;
3451 n->n.other->code = NR_CURVETO;
3452 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3453 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);
3454 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);
3455 if (appr_other_p > appr_other_n) { // closer to other's p handle
3456 n->dragging_out = &n->n;
3457 opposite = &n->p;
3458 n->n.other->code = NR_CURVETO;
3459 } else { // closer to other's n handle
3460 n->dragging_out = &n->p;
3461 opposite = &n->n;
3462 n->code = NR_CURVETO;
3463 }
3464 }
3465 }
3467 // if there's another handle, make sure the one we drag out starts parallel to it
3468 if (opposite->pos != n->pos) {
3469 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3470 }
3472 // knots might not be created yet!
3473 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3474 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3475 }
3477 // pass this on to the handle-moved callback
3478 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3479 sp_node_update_handles(n);
3480 return TRUE;
3481 }
3483 if (state & GDK_CONTROL_MASK) { // constrained motion
3485 // calculate relative distances of handles
3486 // n handle:
3487 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3488 xn = n->n.pos[NR::X] - n->pos[NR::X];
3489 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3490 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3491 if (n->n.other) { // if there is the next point
3492 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3493 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3494 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3495 }
3496 }
3497 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3498 if (yn < 0) { xn = -xn; yn = -yn; }
3500 // p handle:
3501 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3502 xp = n->p.pos[NR::X] - n->pos[NR::X];
3503 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3504 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3505 if (n->p.other) {
3506 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3507 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3508 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3509 }
3510 }
3511 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3512 if (yp < 0) { xp = -xp; yp = -yp; }
3514 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3515 // sliding on handles, only if at least one of the handles is non-vertical
3516 // (otherwise it's the same as ctrl+drag anyway)
3518 // calculate angles of the handles
3519 if (xn == 0) {
3520 if (yn == 0) { // no handle, consider it the continuation of the other one
3521 an = 0;
3522 collinear = TRUE;
3523 }
3524 else an = 0; // vertical; set the angle to horizontal
3525 } else an = yn/xn;
3527 if (xp == 0) {
3528 if (yp == 0) { // no handle, consider it the continuation of the other one
3529 ap = an;
3530 }
3531 else ap = 0; // vertical; set the angle to horizontal
3532 } else ap = yp/xp;
3534 if (collinear) an = ap;
3536 // angles of the perpendiculars; HUGE_VAL means vertical
3537 if (an == 0) na = HUGE_VAL; else na = -1/an;
3538 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3540 // mouse point relative to the node's original pos
3541 pr = (*p) - n->origin;
3543 // distances to the four lines (two handles and two perpendiculars)
3544 d_an = point_line_distance(&pr, an);
3545 d_na = point_line_distance(&pr, na);
3546 d_ap = point_line_distance(&pr, ap);
3547 d_pa = point_line_distance(&pr, pa);
3549 // find out which line is the closest, save its closest point in c
3550 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3551 point_line_closest(&pr, an, &c);
3552 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3553 point_line_closest(&pr, ap, &c);
3554 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3555 point_line_closest(&pr, na, &c);
3556 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3557 point_line_closest(&pr, pa, &c);
3558 }
3560 // move the node to the closest point
3561 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3562 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3563 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3564 true);
3566 } else { // constraining to hor/vert
3568 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3569 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3570 (*p)[NR::X] - n->pos[NR::X],
3571 n->origin[NR::Y] - n->pos[NR::Y],
3572 true,
3573 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3574 } else { // snap to vert
3575 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3576 n->origin[NR::X] - n->pos[NR::X],
3577 (*p)[NR::Y] - n->pos[NR::Y],
3578 true,
3579 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3580 }
3581 }
3582 } else { // move freely
3583 if (n->is_dragging) {
3584 if (state & GDK_MOD1_MASK) { // sculpt
3585 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3586 } else {
3587 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3588 (*p)[NR::X] - n->pos[NR::X],
3589 (*p)[NR::Y] - n->pos[NR::Y],
3590 (state & GDK_SHIFT_MASK) == 0);
3591 }
3592 }
3593 }
3595 n->subpath->nodepath->desktop->scroll_to_point(p);
3597 return TRUE;
3598 }
3600 /**
3601 * Node handle clicked callback.
3602 */
3603 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3604 {
3605 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3607 if (state & GDK_CONTROL_MASK) { // "delete" handle
3608 if (n->p.knot == knot) {
3609 n->p.pos = n->pos;
3610 } else if (n->n.knot == knot) {
3611 n->n.pos = n->pos;
3612 }
3613 sp_node_update_handles(n);
3614 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3615 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3616 sp_nodepath_update_statusbar(nodepath);
3618 } else { // just select or add to selection, depending in Shift
3619 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3620 }
3621 }
3623 /**
3624 * Node handle grabbed callback.
3625 */
3626 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3627 {
3628 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3630 if (!n->selected) {
3631 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3632 }
3634 // remember the origin point of the handle
3635 if (n->p.knot == knot) {
3636 n->p.origin_radial = n->p.pos - n->pos;
3637 } else if (n->n.knot == knot) {
3638 n->n.origin_radial = n->n.pos - n->pos;
3639 } else {
3640 g_assert_not_reached();
3641 }
3643 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3644 }
3646 /**
3647 * Node handle ungrabbed callback.
3648 */
3649 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3650 {
3651 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3653 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3654 if (n->p.knot == knot) {
3655 n->p.origin_radial.a = 0;
3656 sp_knot_set_position(knot, &n->p.pos, state);
3657 } else if (n->n.knot == knot) {
3658 n->n.origin_radial.a = 0;
3659 sp_knot_set_position(knot, &n->n.pos, state);
3660 } else {
3661 g_assert_not_reached();
3662 }
3664 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3665 }
3667 /**
3668 * Node handle "request" signal callback.
3669 */
3670 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3671 {
3672 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3674 Inkscape::NodePath::NodeSide *me, *opposite;
3675 gint which;
3676 if (n->p.knot == knot) {
3677 me = &n->p;
3678 opposite = &n->n;
3679 which = -1;
3680 } else if (n->n.knot == knot) {
3681 me = &n->n;
3682 opposite = &n->p;
3683 which = 1;
3684 } else {
3685 me = opposite = NULL;
3686 which = 0;
3687 g_assert_not_reached();
3688 }
3690 SPDesktop *desktop = n->subpath->nodepath->desktop;
3691 SnapManager &m = desktop->namedview->snap_manager;
3692 m.setup(desktop, n->subpath->nodepath->item);
3693 Inkscape::SnappedPoint s ;
3695 Inkscape::NodePath::Node *othernode = opposite->other;
3696 if (othernode) {
3697 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3698 /* We are smooth node adjacent with line */
3699 NR::Point const delta = *p - n->pos;
3700 NR::Coord const len = NR::L2(delta);
3701 Inkscape::NodePath::Node *othernode = opposite->other;
3702 NR::Point const ndelta = n->pos - othernode->pos;
3703 NR::Coord const linelen = NR::L2(ndelta);
3704 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3705 NR::Coord const scal = dot(delta, ndelta) / linelen;
3706 (*p) = n->pos + (scal / linelen) * ndelta;
3707 }
3708 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3709 } else {
3710 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3711 }
3712 } else {
3713 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3714 }
3716 s.getPoint(*p);
3718 sp_node_adjust_handle(n, -which);
3720 return FALSE;
3721 }
3723 /**
3724 * Node handle moved callback.
3725 */
3726 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3727 {
3728 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3730 Inkscape::NodePath::NodeSide *me;
3731 Inkscape::NodePath::NodeSide *other;
3732 if (n->p.knot == knot) {
3733 me = &n->p;
3734 other = &n->n;
3735 } else if (n->n.knot == knot) {
3736 me = &n->n;
3737 other = &n->p;
3738 } else {
3739 me = NULL;
3740 other = NULL;
3741 g_assert_not_reached();
3742 }
3744 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3745 Radial rme(me->pos - n->pos);
3746 Radial rother(other->pos - n->pos);
3747 Radial rnew(*p - n->pos);
3749 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3750 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3751 /* 0 interpreted as "no snapping". */
3753 // 1. Snap to the closest PI/snaps angle, starting from zero.
3754 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3756 // 2. Snap to the original angle, its opposite and perpendiculars
3757 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3758 /* The closest PI/2 angle, starting from original angle */
3759 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3761 // Snap to the closest.
3762 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3763 ? a_snapped
3764 : a_ortho );
3765 }
3767 // 3. Snap to the angle of the opposite line, if any
3768 Inkscape::NodePath::Node *othernode = other->other;
3769 if (othernode) {
3770 NR::Point other_to_snap(0,0);
3771 if (sp_node_side_is_line(n, other)) {
3772 other_to_snap = othernode->pos - n->pos;
3773 } else {
3774 other_to_snap = other->pos - n->pos;
3775 }
3776 if (NR::L2(other_to_snap) > 1e-3) {
3777 Radial rother_to_snap(other_to_snap);
3778 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3779 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3781 // Snap to the closest.
3782 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3783 ? a_snapped
3784 : a_oppo );
3785 }
3786 }
3788 rnew.a = a_snapped;
3789 }
3791 if (state & GDK_MOD1_MASK) {
3792 // lock handle length
3793 rnew.r = me->origin_radial.r;
3794 }
3796 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3797 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3798 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3799 rother.a += rnew.a - rme.a;
3800 other->pos = NR::Point(rother) + n->pos;
3801 if (other->knot) {
3802 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3803 sp_knot_moveto(other->knot, &other->pos);
3804 }
3805 }
3807 me->pos = NR::Point(rnew) + n->pos;
3808 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3810 // move knot, but without emitting the signal:
3811 // we cannot emit a "moved" signal because we're now processing it
3812 sp_knot_moveto(me->knot, &(me->pos));
3814 update_object(n->subpath->nodepath);
3816 /* status text */
3817 SPDesktop *desktop = n->subpath->nodepath->desktop;
3818 if (!desktop) return;
3819 SPEventContext *ec = desktop->event_context;
3820 if (!ec) return;
3821 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3822 if (!mc) return;
3824 double degrees = 180 / M_PI * rnew.a;
3825 if (degrees > 180) degrees -= 360;
3826 if (degrees < -180) degrees += 360;
3827 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3828 degrees = angle_to_compass (degrees);
3830 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3832 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3833 _("<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);
3835 g_string_free(length, TRUE);
3836 }
3838 /**
3839 * Node handle event callback.
3840 */
3841 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3842 {
3843 gboolean ret = FALSE;
3844 switch (event->type) {
3845 case GDK_KEY_PRESS:
3846 switch (get_group0_keyval (&event->key)) {
3847 case GDK_space:
3848 if (event->key.state & GDK_BUTTON1_MASK) {
3849 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3850 stamp_repr(nodepath);
3851 ret = TRUE;
3852 }
3853 break;
3854 default:
3855 break;
3856 }
3857 break;
3858 case GDK_ENTER_NOTIFY:
3859 // we use an experimentally determined threshold that seems to work fine
3860 if (NR::L2(n->pos - knot->pos) < 0.75)
3861 Inkscape::NodePath::Path::active_node = n;
3862 break;
3863 case GDK_LEAVE_NOTIFY:
3864 // we use an experimentally determined threshold that seems to work fine
3865 if (NR::L2(n->pos - knot->pos) < 0.75)
3866 Inkscape::NodePath::Path::active_node = NULL;
3867 break;
3868 default:
3869 break;
3870 }
3872 return ret;
3873 }
3875 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3876 Radial &rme, Radial &rother, gboolean const both)
3877 {
3878 rme.a += angle;
3879 if ( both
3880 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3881 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3882 {
3883 rother.a += angle;
3884 }
3885 }
3887 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3888 Radial &rme, Radial &rother, gboolean const both)
3889 {
3890 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3892 gdouble r;
3893 if ( both
3894 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3895 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3896 {
3897 r = MAX(rme.r, rother.r);
3898 } else {
3899 r = rme.r;
3900 }
3902 gdouble const weird_angle = atan2(norm_angle, r);
3903 /* Bulia says norm_angle is just the visible distance that the
3904 * object's end must travel on the screen. Left as 'angle' for want of
3905 * a better name.*/
3907 rme.a += weird_angle;
3908 if ( both
3909 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3910 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3911 {
3912 rother.a += weird_angle;
3913 }
3914 }
3916 /**
3917 * Rotate one node.
3918 */
3919 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3920 {
3921 Inkscape::NodePath::NodeSide *me, *other;
3922 bool both = false;
3924 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3925 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3927 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3928 me = &(n->p);
3929 other = &(n->n);
3930 } else if (!n->p.other) {
3931 me = &(n->n);
3932 other = &(n->p);
3933 } else {
3934 if (which > 0) { // right handle
3935 if (xn > xp) {
3936 me = &(n->n);
3937 other = &(n->p);
3938 } else {
3939 me = &(n->p);
3940 other = &(n->n);
3941 }
3942 } else if (which < 0){ // left 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 { // both handles
3951 me = &(n->n);
3952 other = &(n->p);
3953 both = true;
3954 }
3955 }
3957 Radial rme(me->pos - n->pos);
3958 Radial rother(other->pos - n->pos);
3960 if (screen) {
3961 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3962 } else {
3963 node_rotate_one_internal (*n, angle, rme, rother, both);
3964 }
3966 me->pos = n->pos + NR::Point(rme);
3968 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3969 other->pos = n->pos + NR::Point(rother);
3970 }
3972 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3973 // so here we just move all the knots without emitting move signals, for speed
3974 sp_node_update_handles(n, false);
3975 }
3977 /**
3978 * Rotate selected nodes.
3979 */
3980 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3981 {
3982 if (!nodepath || !nodepath->selected) return;
3984 if (g_list_length(nodepath->selected) == 1) {
3985 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3986 node_rotate_one (n, angle, which, screen);
3987 } else {
3988 // rotate as an object:
3990 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3991 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3992 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3993 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3994 box.expandTo (n->pos); // contain all selected nodes
3995 }
3997 gdouble rot;
3998 if (screen) {
3999 gdouble const zoom = nodepath->desktop->current_zoom();
4000 gdouble const zmove = angle / zoom;
4001 gdouble const r = NR::L2(box.max() - box.midpoint());
4002 rot = atan2(zmove, r);
4003 } else {
4004 rot = angle;
4005 }
4007 NR::Point rot_center;
4008 if (Inkscape::NodePath::Path::active_node == NULL)
4009 rot_center = box.midpoint();
4010 else
4011 rot_center = Inkscape::NodePath::Path::active_node->pos;
4013 NR::Matrix t =
4014 NR::Matrix (NR::translate(-rot_center)) *
4015 NR::Matrix (NR::rotate(rot)) *
4016 NR::Matrix (NR::translate(rot_center));
4018 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4019 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4020 n->pos *= t;
4021 n->n.pos *= t;
4022 n->p.pos *= t;
4023 sp_node_update_handles(n, false);
4024 }
4025 }
4027 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4028 }
4030 /**
4031 * Scale one node.
4032 */
4033 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4034 {
4035 bool both = false;
4036 Inkscape::NodePath::NodeSide *me, *other;
4038 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4039 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4041 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4042 me = &(n->p);
4043 other = &(n->n);
4044 n->code = NR_CURVETO;
4045 } else if (!n->p.other) {
4046 me = &(n->n);
4047 other = &(n->p);
4048 if (n->n.other)
4049 n->n.other->code = NR_CURVETO;
4050 } else {
4051 if (which > 0) { // right handle
4052 if (xn > xp) {
4053 me = &(n->n);
4054 other = &(n->p);
4055 if (n->n.other)
4056 n->n.other->code = NR_CURVETO;
4057 } else {
4058 me = &(n->p);
4059 other = &(n->n);
4060 n->code = NR_CURVETO;
4061 }
4062 } else if (which < 0){ // left handle
4063 if (xn <= xp) {
4064 me = &(n->n);
4065 other = &(n->p);
4066 if (n->n.other)
4067 n->n.other->code = NR_CURVETO;
4068 } else {
4069 me = &(n->p);
4070 other = &(n->n);
4071 n->code = NR_CURVETO;
4072 }
4073 } else { // both handles
4074 me = &(n->n);
4075 other = &(n->p);
4076 both = true;
4077 n->code = NR_CURVETO;
4078 if (n->n.other)
4079 n->n.other->code = NR_CURVETO;
4080 }
4081 }
4083 Radial rme(me->pos - n->pos);
4084 Radial rother(other->pos - n->pos);
4086 rme.r += grow;
4087 if (rme.r < 0) rme.r = 0;
4088 if (rme.a == HUGE_VAL) {
4089 if (me->other) { // if direction is unknown, initialize it towards the next node
4090 Radial rme_next(me->other->pos - n->pos);
4091 rme.a = rme_next.a;
4092 } else { // if there's no next, initialize to 0
4093 rme.a = 0;
4094 }
4095 }
4096 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4097 rother.r += grow;
4098 if (rother.r < 0) rother.r = 0;
4099 if (rother.a == HUGE_VAL) {
4100 rother.a = rme.a + M_PI;
4101 }
4102 }
4104 me->pos = n->pos + NR::Point(rme);
4106 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4107 other->pos = n->pos + NR::Point(rother);
4108 }
4110 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4111 // so here we just move all the knots without emitting move signals, for speed
4112 sp_node_update_handles(n, false);
4113 }
4115 /**
4116 * Scale selected nodes.
4117 */
4118 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4119 {
4120 if (!nodepath || !nodepath->selected) return;
4122 if (g_list_length(nodepath->selected) == 1) {
4123 // scale handles of the single selected node
4124 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4125 node_scale_one (n, grow, which);
4126 } else {
4127 // scale nodes as an "object":
4129 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4130 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4131 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4132 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4133 box.expandTo (n->pos); // contain all selected nodes
4134 }
4136 double scale = (box.maxExtent() + grow)/box.maxExtent();
4138 NR::Point scale_center;
4139 if (Inkscape::NodePath::Path::active_node == NULL)
4140 scale_center = box.midpoint();
4141 else
4142 scale_center = Inkscape::NodePath::Path::active_node->pos;
4144 NR::Matrix t =
4145 NR::Matrix (NR::translate(-scale_center)) *
4146 NR::Matrix (NR::scale(scale, scale)) *
4147 NR::Matrix (NR::translate(scale_center));
4149 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4150 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4151 n->pos *= t;
4152 n->n.pos *= t;
4153 n->p.pos *= t;
4154 sp_node_update_handles(n, false);
4155 }
4156 }
4158 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4159 }
4161 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4162 {
4163 if (!nodepath) return;
4164 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4165 }
4167 /**
4168 * Flip selected nodes horizontally/vertically.
4169 */
4170 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4171 {
4172 if (!nodepath || !nodepath->selected) return;
4174 if (g_list_length(nodepath->selected) == 1 && !center) {
4175 // flip handles of the single selected node
4176 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4177 double temp = n->p.pos[axis];
4178 n->p.pos[axis] = n->n.pos[axis];
4179 n->n.pos[axis] = temp;
4180 sp_node_update_handles(n, false);
4181 } else {
4182 // scale nodes as an "object":
4184 NR::Rect box = sp_node_selected_bbox (nodepath);
4185 if (!center) {
4186 center = box.midpoint();
4187 }
4188 NR::Matrix t =
4189 NR::Matrix (NR::translate(- *center)) *
4190 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4191 NR::Matrix (NR::translate(*center));
4193 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4194 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4195 n->pos *= t;
4196 n->n.pos *= t;
4197 n->p.pos *= t;
4198 sp_node_update_handles(n, false);
4199 }
4200 }
4202 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4203 }
4205 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4206 {
4207 g_assert (nodepath->selected);
4209 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4210 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4211 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4212 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4213 box.expandTo (n->pos); // contain all selected nodes
4214 }
4215 return box;
4216 }
4218 //-----------------------------------------------
4219 /**
4220 * Return new subpath under given nodepath.
4221 */
4222 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4223 {
4224 g_assert(nodepath);
4225 g_assert(nodepath->desktop);
4227 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4229 s->nodepath = nodepath;
4230 s->closed = FALSE;
4231 s->nodes = NULL;
4232 s->first = NULL;
4233 s->last = NULL;
4235 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4236 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4237 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4239 return s;
4240 }
4242 /**
4243 * Destroy nodes in subpath, then subpath itself.
4244 */
4245 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4246 {
4247 g_assert(subpath);
4248 g_assert(subpath->nodepath);
4249 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4251 while (subpath->nodes) {
4252 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4253 }
4255 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4257 g_free(subpath);
4258 }
4260 /**
4261 * Link head to tail in subpath.
4262 */
4263 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4264 {
4265 g_assert(!sp->closed);
4266 g_assert(sp->last != sp->first);
4267 g_assert(sp->first->code == NR_MOVETO);
4269 sp->closed = TRUE;
4271 //Link the head to the tail
4272 sp->first->p.other = sp->last;
4273 sp->last->n.other = sp->first;
4274 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4275 sp->first = sp->last;
4277 //Remove the extra end node
4278 sp_nodepath_node_destroy(sp->last->n.other);
4279 }
4281 /**
4282 * Open closed (loopy) subpath at node.
4283 */
4284 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4285 {
4286 g_assert(sp->closed);
4287 g_assert(n->subpath == sp);
4288 g_assert(sp->first == sp->last);
4290 /* We create new startpoint, current node will become last one */
4292 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4293 &n->pos, &n->pos, &n->n.pos);
4296 sp->closed = FALSE;
4298 //Unlink to make a head and tail
4299 sp->first = new_path;
4300 sp->last = n;
4301 n->n.other = NULL;
4302 new_path->p.other = NULL;
4303 }
4305 /**
4306 * Return new node in subpath with given properties.
4307 * \param pos Position of node.
4308 * \param ppos Handle position in previous direction
4309 * \param npos Handle position in previous direction
4310 */
4311 Inkscape::NodePath::Node *
4312 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)
4313 {
4314 g_assert(sp);
4315 g_assert(sp->nodepath);
4316 g_assert(sp->nodepath->desktop);
4318 if (nodechunk == NULL)
4319 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4321 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4323 n->subpath = sp;
4325 if (type != Inkscape::NodePath::NODE_NONE) {
4326 // use the type from sodipodi:nodetypes
4327 n->type = type;
4328 } else {
4329 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4330 // points are (almost) collinear
4331 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4332 // endnode, or a node with a retracted handle
4333 n->type = Inkscape::NodePath::NODE_CUSP;
4334 } else {
4335 n->type = Inkscape::NodePath::NODE_SMOOTH;
4336 }
4337 } else {
4338 n->type = Inkscape::NodePath::NODE_CUSP;
4339 }
4340 }
4342 n->code = code;
4343 n->selected = FALSE;
4344 n->pos = *pos;
4345 n->p.pos = *ppos;
4346 n->n.pos = *npos;
4348 n->dragging_out = NULL;
4350 Inkscape::NodePath::Node *prev;
4351 if (next) {
4352 //g_assert(g_list_find(sp->nodes, next));
4353 prev = next->p.other;
4354 } else {
4355 prev = sp->last;
4356 }
4358 if (prev)
4359 prev->n.other = n;
4360 else
4361 sp->first = n;
4363 if (next)
4364 next->p.other = n;
4365 else
4366 sp->last = n;
4368 n->p.other = prev;
4369 n->n.other = next;
4371 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"));
4372 sp_knot_set_position(n->knot, pos, 0);
4374 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4375 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4376 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4377 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4378 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4379 sp_knot_update_ctrl(n->knot);
4381 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4382 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4383 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4384 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4385 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4386 sp_knot_show(n->knot);
4388 // We only create handle knots and lines on demand
4389 n->p.knot = NULL;
4390 n->p.line = NULL;
4391 n->n.knot = NULL;
4392 n->n.line = NULL;
4394 sp->nodes = g_list_prepend(sp->nodes, n);
4396 return n;
4397 }
4399 /**
4400 * Destroy node and its knots, link neighbors in subpath.
4401 */
4402 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4403 {
4404 g_assert(node);
4405 g_assert(node->subpath);
4406 g_assert(SP_IS_KNOT(node->knot));
4408 Inkscape::NodePath::SubPath *sp = node->subpath;
4410 if (node->selected) { // first, deselect
4411 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4412 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4413 }
4415 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4417 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4418 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4419 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4420 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4421 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4422 g_object_unref(G_OBJECT(node->knot));
4424 if (node->p.knot) {
4425 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4426 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4427 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4428 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4429 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4430 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4431 g_object_unref(G_OBJECT(node->p.knot));
4432 node->p.knot = NULL;
4433 }
4435 if (node->n.knot) {
4436 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4437 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4438 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4439 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4440 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4441 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4442 g_object_unref(G_OBJECT(node->n.knot));
4443 node->n.knot = NULL;
4444 }
4446 if (node->p.line)
4447 gtk_object_destroy(GTK_OBJECT(node->p.line));
4448 if (node->n.line)
4449 gtk_object_destroy(GTK_OBJECT(node->n.line));
4451 if (sp->nodes) { // there are others nodes on the subpath
4452 if (sp->closed) {
4453 if (sp->first == node) {
4454 g_assert(sp->last == node);
4455 sp->first = node->n.other;
4456 sp->last = sp->first;
4457 }
4458 node->p.other->n.other = node->n.other;
4459 node->n.other->p.other = node->p.other;
4460 } else {
4461 if (sp->first == node) {
4462 sp->first = node->n.other;
4463 sp->first->code = NR_MOVETO;
4464 }
4465 if (sp->last == node) sp->last = node->p.other;
4466 if (node->p.other) node->p.other->n.other = node->n.other;
4467 if (node->n.other) node->n.other->p.other = node->p.other;
4468 }
4469 } else { // this was the last node on subpath
4470 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4471 }
4473 g_mem_chunk_free(nodechunk, node);
4474 }
4476 /**
4477 * Returns one of the node's two sides.
4478 * \param which Indicates which side.
4479 * \return Pointer to previous node side if which==-1, next if which==1.
4480 */
4481 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4482 {
4483 g_assert(node);
4485 switch (which) {
4486 case -1:
4487 return &node->p;
4488 case 1:
4489 return &node->n;
4490 default:
4491 break;
4492 }
4494 g_assert_not_reached();
4496 return NULL;
4497 }
4499 /**
4500 * Return the other side of the node, given one of its sides.
4501 */
4502 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4503 {
4504 g_assert(node);
4506 if (me == &node->p) return &node->n;
4507 if (me == &node->n) return &node->p;
4509 g_assert_not_reached();
4511 return NULL;
4512 }
4514 /**
4515 * Return NRPathcode on the given side of the node.
4516 */
4517 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4518 {
4519 g_assert(node);
4521 if (me == &node->p) {
4522 if (node->p.other) return (NRPathcode)node->code;
4523 return NR_MOVETO;
4524 }
4526 if (me == &node->n) {
4527 if (node->n.other) return (NRPathcode)node->n.other->code;
4528 return NR_MOVETO;
4529 }
4531 g_assert_not_reached();
4533 return NR_END;
4534 }
4536 /**
4537 * Return node with the given index
4538 */
4539 Inkscape::NodePath::Node *
4540 sp_nodepath_get_node_by_index(int index)
4541 {
4542 Inkscape::NodePath::Node *e = NULL;
4544 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4545 if (!nodepath) {
4546 return e;
4547 }
4549 //find segment
4550 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4552 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4553 int n = g_list_length(sp->nodes);
4554 if (sp->closed) {
4555 n++;
4556 }
4558 //if the piece belongs to this subpath grab it
4559 //otherwise move onto the next subpath
4560 if (index < n) {
4561 e = sp->first;
4562 for (int i = 0; i < index; ++i) {
4563 e = e->n.other;
4564 }
4565 break;
4566 } else {
4567 if (sp->closed) {
4568 index -= (n+1);
4569 } else {
4570 index -= n;
4571 }
4572 }
4573 }
4575 return e;
4576 }
4578 /**
4579 * Returns plain text meaning of node type.
4580 */
4581 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4582 {
4583 unsigned retracted = 0;
4584 bool endnode = false;
4586 for (int which = -1; which <= 1; which += 2) {
4587 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4588 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4589 retracted ++;
4590 if (!side->other)
4591 endnode = true;
4592 }
4594 if (retracted == 0) {
4595 if (endnode) {
4596 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4597 return _("end node");
4598 } else {
4599 switch (node->type) {
4600 case Inkscape::NodePath::NODE_CUSP:
4601 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4602 return _("cusp");
4603 case Inkscape::NodePath::NODE_SMOOTH:
4604 // TRANSLATORS: "smooth" is an adjective here
4605 return _("smooth");
4606 case Inkscape::NodePath::NODE_SYMM:
4607 return _("symmetric");
4608 }
4609 }
4610 } else if (retracted == 1) {
4611 if (endnode) {
4612 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4613 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4614 } else {
4615 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4616 }
4617 } else {
4618 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4619 }
4621 return NULL;
4622 }
4624 /**
4625 * Handles content of statusbar as long as node tool is active.
4626 */
4627 void
4628 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4629 {
4630 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");
4631 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4633 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4634 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4635 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4636 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4638 SPDesktop *desktop = NULL;
4639 if (nodepath) {
4640 desktop = nodepath->desktop;
4641 } else {
4642 desktop = SP_ACTIVE_DESKTOP;
4643 }
4645 SPEventContext *ec = desktop->event_context;
4646 if (!ec) return;
4647 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4648 if (!mc) return;
4650 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4652 if (selected_nodes == 0) {
4653 Inkscape::Selection *sel = desktop->selection;
4654 if (!sel || sel->isEmpty()) {
4655 mc->setF(Inkscape::NORMAL_MESSAGE,
4656 _("Select a single object to edit its nodes or handles."));
4657 } else {
4658 if (nodepath) {
4659 mc->setF(Inkscape::NORMAL_MESSAGE,
4660 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.",
4661 "<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.",
4662 total_nodes),
4663 total_nodes);
4664 } else {
4665 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4666 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4667 } else {
4668 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4669 }
4670 }
4671 }
4672 } else if (nodepath && selected_nodes == 1) {
4673 mc->setF(Inkscape::NORMAL_MESSAGE,
4674 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4675 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4676 total_nodes),
4677 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4678 } else {
4679 if (selected_subpaths > 1) {
4680 mc->setF(Inkscape::NORMAL_MESSAGE,
4681 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4682 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4683 total_nodes),
4684 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4685 } else {
4686 mc->setF(Inkscape::NORMAL_MESSAGE,
4687 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4688 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4689 total_nodes),
4690 selected_nodes, total_nodes, when_selected);
4691 }
4692 }
4693 }
4695 /*
4696 * returns a *copy* of the curve of that object.
4697 */
4698 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4699 if (!object)
4700 return NULL;
4702 SPCurve *curve = NULL;
4703 if (SP_IS_PATH(object)) {
4704 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4705 curve = curve_new->copy();
4706 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4707 const gchar *svgd = object->repr->attribute(key);
4708 if (svgd) {
4709 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4710 SPCurve *curve_new = new SPCurve(pv);
4711 if (curve_new) {
4712 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4713 }
4714 }
4715 }
4717 return curve;
4718 }
4720 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4721 if (!np || !np->object || !curve)
4722 return;
4724 if (SP_IS_PATH(np->object)) {
4725 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4726 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4727 } else {
4728 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4729 }
4730 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4731 // FIXME: this writing to string and then reading from string is bound to be slow.
4732 // create a method to convert from curve directly to 2geom...
4733 gchar *svgpath = sp_svg_write_path( np->curve->get_pathvector() );
4734 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4735 g_free(svgpath);
4737 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4738 }
4739 }
4741 SPCanvasItem *
4742 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4743 SPCurve *flash_curve = curve->copy();
4744 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4745 flash_curve->transform(i2d);
4746 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4747 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4748 // unless we also flash the nodes...
4749 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4750 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4751 sp_canvas_item_show(canvasitem);
4752 flash_curve->unref();
4753 return canvasitem;
4754 }
4756 SPCanvasItem *
4757 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4758 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4759 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4760 }
4762 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4763 np->show_helperpath = show;
4765 if (show) {
4766 SPCurve *helper_curve = np->curve->copy();
4767 helper_curve->transform(np->i2d );
4768 if (!np->helper_path) {
4769 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4770 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);
4771 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4772 sp_canvas_item_move_to_z(np->helper_path, 0);
4773 sp_canvas_item_show(np->helper_path);
4774 } else {
4775 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4776 }
4777 helper_curve->unref();
4778 } else {
4779 if (np->helper_path) {
4780 GtkObject *temp = np->helper_path;
4781 np->helper_path = NULL;
4782 gtk_object_destroy(temp);
4783 }
4784 }
4785 }
4787 /* sp_nodepath_make_straight_path:
4788 * Prevents user from curving the path by dragging a segment or activating handles etc.
4789 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4790 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4791 */
4792 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4793 np->straight_path = true;
4794 np->show_handles = false;
4795 g_message("add code to make the path straight.");
4796 // do sp_nodepath_convert_node_type on all nodes?
4797 // coding tip: search for this text : "Make selected segments lines"
4798 }
4801 /*
4802 Local Variables:
4803 mode:c++
4804 c-file-style:"stroustrup"
4805 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4806 indent-tabs-mode:nil
4807 fill-column:99
4808 End:
4809 */
4810 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :