249721cfaba134849578428dc4ef98e323d1b823
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 /**
156 * \brief Creates new nodepath from item
157 */
158 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
159 {
160 Inkscape::XML::Node *repr = object->repr;
162 /** \todo
163 * FIXME: remove this. We don't want to edit paths inside flowtext.
164 * Instead we will build our flowtext with cloned paths, so that the
165 * real paths are outside the flowtext and thus editable as usual.
166 */
167 if (SP_IS_FLOWTEXT(object)) {
168 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
169 if SP_IS_FLOWREGION(child) {
170 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
171 if (grandchild && SP_IS_PATH(grandchild)) {
172 object = SP_ITEM(grandchild);
173 break;
174 }
175 }
176 }
177 }
179 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
181 if (curve == NULL)
182 return NULL;
184 NArtBpath const *bpath = curve->get_bpath();
185 gint length = curve->get_length();
186 if (length == 0) {
187 curve->unref();
188 return NULL; // prevent crash for one-node paths
189 }
191 //Create new nodepath
192 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
193 if (!np) {
194 curve->unref();
195 return NULL;
196 }
198 // Set defaults
199 np->desktop = desktop;
200 np->object = object;
201 np->subpaths = NULL;
202 np->selected = NULL;
203 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
204 np->livarot_path = NULL;
205 np->local_change = 0;
206 np->show_handles = show_handles;
207 np->helper_path = NULL;
208 np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
209 np->helperpath_width = 1.0;
210 np->curve = curve->copy();
211 np->show_helperpath = prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1;
212 np->straight_path = false;
213 if (IS_LIVEPATHEFFECT(object) && item) {
214 np->item = item;
215 } else {
216 np->item = SP_ITEM(object);
217 }
219 // we need to update item's transform from the repr here,
220 // because they may be out of sync when we respond
221 // to a change in repr by regenerating nodepath --bb
222 sp_object_read_attr(SP_OBJECT(np->item), "transform");
224 np->i2d = sp_item_i2d_affine(np->item);
225 np->d2i = np->i2d.inverse();
227 np->repr = repr;
228 if (repr_key_in) { // apparantly the object is an LPEObject
229 np->repr_key = g_strdup(repr_key_in);
230 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
231 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
232 if (lpeparam) {
233 lpeparam->param_setup_nodepath(np);
234 }
235 } else {
236 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
237 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
238 np->repr_key = g_strdup("inkscape:original-d");
240 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
241 if (lpe) {
242 lpe->setup_nodepath(np);
243 }
244 } else {
245 np->repr_key = g_strdup("d");
246 }
247 }
249 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
250 gchar *typestr = parse_nodetypes(nodetypes, length);
252 // create the subpath(s) from the bpath
253 NArtBpath const *b = bpath;
254 while (b->code != NR_END) {
255 b = subpath_from_bpath(np, b, typestr + (b - bpath));
256 }
258 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
259 np->subpaths = g_list_reverse(np->subpaths);
261 g_free(typestr);
262 curve->unref();
264 // create the livarot representation from the same item
265 sp_nodepath_ensure_livarot_path(np);
267 // Draw helper curve
268 if (np->show_helperpath) {
269 SPCurve *helper_curve = np->curve->copy();
270 helper_curve->transform(np->i2d );
271 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
272 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);
273 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
274 sp_canvas_item_move_to_z(np->helper_path, 0);
275 sp_canvas_item_show(np->helper_path);
276 helper_curve->unref();
277 }
279 return np;
280 }
282 /**
283 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
284 */
285 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
287 if (!np) //soft fail, like delete
288 return;
290 while (np->subpaths) {
291 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
292 }
294 //Inform the ShapeEditor that made me, if any, that I am gone.
295 if (np->shape_editor)
296 np->shape_editor->nodepath_destroyed();
298 g_assert(!np->selected);
300 if (np->livarot_path) {
301 delete np->livarot_path;
302 np->livarot_path = NULL;
303 }
305 if (np->helper_path) {
306 GtkObject *temp = np->helper_path;
307 np->helper_path = NULL;
308 gtk_object_destroy(temp);
309 }
310 if (np->curve) {
311 np->curve->unref();
312 np->curve = NULL;
313 }
315 if (np->repr_key) {
316 g_free(np->repr_key);
317 np->repr_key = NULL;
318 }
319 if (np->repr_nodetypes_key) {
320 g_free(np->repr_nodetypes_key);
321 np->repr_nodetypes_key = NULL;
322 }
324 np->desktop = NULL;
326 g_free(np);
327 }
330 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
331 {
332 if (np && np->livarot_path == NULL) {
333 SPCurve *curve = create_curve(np);
334 NArtBpath const *bpath = SP_CURVE_BPATH(curve);
335 np->livarot_path = bpath_to_Path(bpath);
337 if (np->livarot_path)
338 np->livarot_path->ConvertWithBackData(0.01);
340 curve->unref();
341 }
342 }
345 /**
346 * Return the node count of a given NodeSubPath.
347 */
348 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
349 {
350 if (!subpath)
351 return 0;
352 gint nodeCount = g_list_length(subpath->nodes);
353 return nodeCount;
354 }
356 /**
357 * Return the node count of a given NodePath.
358 */
359 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
360 {
361 if (!np)
362 return 0;
363 gint nodeCount = 0;
364 for (GList *item = np->subpaths ; item ; item=item->next) {
365 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
366 nodeCount += g_list_length(subpath->nodes);
367 }
368 return nodeCount;
369 }
371 /**
372 * Return the subpath count of a given NodePath.
373 */
374 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
375 {
376 if (!np)
377 return 0;
378 return g_list_length (np->subpaths);
379 }
381 /**
382 * Return the selected node count of a given NodePath.
383 */
384 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
385 {
386 if (!np)
387 return 0;
388 return g_list_length (np->selected);
389 }
391 /**
392 * Return the number of subpaths where nodes are selected in a given NodePath.
393 */
394 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
395 {
396 if (!np)
397 return 0;
398 if (!np->selected)
399 return 0;
400 if (!np->selected->next)
401 return 1;
402 gint count = 0;
403 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
404 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
405 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
406 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
407 if (node->selected) {
408 count ++;
409 break;
410 }
411 }
412 }
413 return count;
414 }
416 /**
417 * Clean up a nodepath after editing.
418 *
419 * Currently we are deleting trivial subpaths.
420 */
421 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
422 {
423 GList *badSubPaths = NULL;
425 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
426 for (GList *l = nodepath->subpaths; l ; l=l->next) {
427 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
428 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
429 badSubPaths = g_list_append(badSubPaths, sp);
430 }
432 //Delete them. This second step is because sp_nodepath_subpath_destroy()
433 //also removes the subpath from nodepath->subpaths
434 for (GList *l = badSubPaths; l ; l=l->next) {
435 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
436 sp_nodepath_subpath_destroy(sp);
437 }
439 g_list_free(badSubPaths);
440 }
442 /**
443 * Create new nodepath from b, make it subpath of np.
444 * \param t The node type.
445 * \todo Fixme: t should be a proper type, rather than gchar
446 */
447 static NArtBpath const * subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath const *b, gchar const *t)
448 {
449 NR::Point ppos, pos, npos;
451 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
453 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
454 bool const closed = (b->code == NR_MOVETO);
456 pos = NR::Point(b->x3, b->y3) * np->i2d;
457 if (b[1].code == NR_CURVETO) {
458 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
459 } else {
460 npos = pos;
461 }
462 Inkscape::NodePath::Node *n;
463 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
464 g_assert(sp->first == n);
465 g_assert(sp->last == n);
467 b++;
468 t++;
469 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
470 pos = NR::Point(b->x3, b->y3) * np->i2d;
471 if (b->code == NR_CURVETO) {
472 ppos = NR::Point(b->x2, b->y2) * np->i2d;
473 } else {
474 ppos = pos;
475 }
476 if (b[1].code == NR_CURVETO) {
477 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
478 } else {
479 npos = pos;
480 }
481 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
482 b++;
483 t++;
484 }
486 if (closed) sp_nodepath_subpath_close(sp);
488 return b;
489 }
491 /**
492 * Convert from sodipodi:nodetypes to new style type string.
493 */
494 static gchar *parse_nodetypes(gchar const *types, gint length)
495 {
496 g_assert(length > 0);
498 gchar *typestr = g_new(gchar, length + 1);
500 gint pos = 0;
502 if (types) {
503 for (gint i = 0; types[i] && ( i < length ); i++) {
504 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
505 if (types[i] != '\0') {
506 switch (types[i]) {
507 case 's':
508 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
509 break;
510 case 'z':
511 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
512 break;
513 case 'c':
514 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
515 break;
516 default:
517 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
518 break;
519 }
520 }
521 }
522 }
524 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
526 return typestr;
527 }
529 /**
530 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
531 * updated but repr is not (for speed). Used during curve and node drag.
532 */
533 static void update_object(Inkscape::NodePath::Path *np)
534 {
535 g_assert(np);
537 np->curve->unref();
538 np->curve = create_curve(np);
540 sp_nodepath_set_curve(np, np->curve);
542 if (np->show_helperpath) {
543 SPCurve * helper_curve = np->curve->copy();
544 helper_curve->transform(np->i2d );
545 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
546 helper_curve->unref();
547 }
548 }
550 /**
551 * Update XML path node with data from path object.
552 */
553 static void update_repr_internal(Inkscape::NodePath::Path *np)
554 {
555 g_assert(np);
557 Inkscape::XML::Node *repr = np->object->repr;
559 np->curve->unref();
560 np->curve = create_curve(np);
562 gchar *typestr = create_typestr(np);
563 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
565 // determine if path has an effect applied and write to correct "d" attribute.
566 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
567 np->local_change++;
568 repr->setAttribute(np->repr_key, svgpath);
569 }
571 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
572 np->local_change++;
573 repr->setAttribute(np->repr_nodetypes_key, typestr);
574 }
576 g_free(svgpath);
577 g_free(typestr);
579 if (np->show_helperpath) {
580 SPCurve * helper_curve = np->curve->copy();
581 helper_curve->transform(np->i2d );
582 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
583 helper_curve->unref();
584 }
585 }
587 /**
588 * Update XML path node with data from path object, commit changes forever.
589 */
590 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
591 {
592 //fixme: np can be NULL, so check before proceeding
593 g_return_if_fail(np != NULL);
595 if (np->livarot_path) {
596 delete np->livarot_path;
597 np->livarot_path = NULL;
598 }
600 update_repr_internal(np);
601 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
603 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
604 annotation);
605 }
607 /**
608 * Update XML path node with data from path object, commit changes with undo.
609 */
610 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
611 {
612 if (np->livarot_path) {
613 delete np->livarot_path;
614 np->livarot_path = NULL;
615 }
617 update_repr_internal(np);
618 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
619 annotation);
620 }
622 /**
623 * Make duplicate of path, replace corresponding XML node in tree, commit.
624 */
625 static void stamp_repr(Inkscape::NodePath::Path *np)
626 {
627 g_assert(np);
629 Inkscape::XML::Node *old_repr = np->object->repr;
630 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
632 // remember the position of the item
633 gint pos = old_repr->position();
634 // remember parent
635 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
637 SPCurve *curve = create_curve(np);
638 gchar *typestr = create_typestr(np);
640 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
642 new_repr->setAttribute(np->repr_key, svgpath);
643 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
645 // add the new repr to the parent
646 parent->appendChild(new_repr);
647 // move to the saved position
648 new_repr->setPosition(pos > 0 ? pos : 0);
650 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
651 _("Stamp"));
653 Inkscape::GC::release(new_repr);
654 g_free(svgpath);
655 g_free(typestr);
656 curve->unref();
657 }
659 /**
660 * Create curve from path.
661 */
662 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
663 {
664 SPCurve *curve = new SPCurve();
666 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
667 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
668 curve->moveto(sp->first->pos * np->d2i);
669 Inkscape::NodePath::Node *n = sp->first->n.other;
670 while (n) {
671 NR::Point const end_pt = n->pos * np->d2i;
672 switch (n->code) {
673 case NR_LINETO:
674 curve->lineto(end_pt);
675 break;
676 case NR_CURVETO:
677 curve->curveto(n->p.other->n.pos * np->d2i,
678 n->p.pos * np->d2i,
679 end_pt);
680 break;
681 default:
682 g_assert_not_reached();
683 break;
684 }
685 if (n != sp->last) {
686 n = n->n.other;
687 } else {
688 n = NULL;
689 }
690 }
691 if (sp->closed) {
692 curve->closepath();
693 }
694 }
696 return curve;
697 }
699 /**
700 * Convert path type string to sodipodi:nodetypes style.
701 */
702 static gchar *create_typestr(Inkscape::NodePath::Path *np)
703 {
704 gchar *typestr = g_new(gchar, 32);
705 gint len = 32;
706 gint pos = 0;
708 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
709 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
711 if (pos >= len) {
712 typestr = g_renew(gchar, typestr, len + 32);
713 len += 32;
714 }
716 typestr[pos++] = 'c';
718 Inkscape::NodePath::Node *n;
719 n = sp->first->n.other;
720 while (n) {
721 gchar code;
723 switch (n->type) {
724 case Inkscape::NodePath::NODE_CUSP:
725 code = 'c';
726 break;
727 case Inkscape::NodePath::NODE_SMOOTH:
728 code = 's';
729 break;
730 case Inkscape::NodePath::NODE_SYMM:
731 code = 'z';
732 break;
733 default:
734 g_assert_not_reached();
735 code = '\0';
736 break;
737 }
739 if (pos >= len) {
740 typestr = g_renew(gchar, typestr, len + 32);
741 len += 32;
742 }
744 typestr[pos++] = code;
746 if (n != sp->last) {
747 n = n->n.other;
748 } else {
749 n = NULL;
750 }
751 }
752 }
754 if (pos >= len) {
755 typestr = g_renew(gchar, typestr, len + 1);
756 len += 1;
757 }
759 typestr[pos++] = '\0';
761 return typestr;
762 }
764 /**
765 * Returns current path in context. // later eliminate this function at all!
766 */
767 static Inkscape::NodePath::Path *sp_nodepath_current()
768 {
769 if (!SP_ACTIVE_DESKTOP) {
770 return NULL;
771 }
773 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
775 if (!SP_IS_NODE_CONTEXT(event_context)) {
776 return NULL;
777 }
779 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
780 }
784 /**
785 \brief Fills node and handle positions for three nodes, splitting line
786 marked by end at distance t.
787 */
788 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
789 {
790 g_assert(new_path != NULL);
791 g_assert(end != NULL);
793 g_assert(end->p.other == new_path);
794 Inkscape::NodePath::Node *start = new_path->p.other;
795 g_assert(start);
797 if (end->code == NR_LINETO) {
798 new_path->type =Inkscape::NodePath::NODE_CUSP;
799 new_path->code = NR_LINETO;
800 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
801 } else {
802 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
803 new_path->code = NR_CURVETO;
804 gdouble s = 1 - t;
805 for (int dim = 0; dim < 2; dim++) {
806 NR::Coord const f000 = start->pos[dim];
807 NR::Coord const f001 = start->n.pos[dim];
808 NR::Coord const f011 = end->p.pos[dim];
809 NR::Coord const f111 = end->pos[dim];
810 NR::Coord const f00t = s * f000 + t * f001;
811 NR::Coord const f01t = s * f001 + t * f011;
812 NR::Coord const f11t = s * f011 + t * f111;
813 NR::Coord const f0tt = s * f00t + t * f01t;
814 NR::Coord const f1tt = s * f01t + t * f11t;
815 NR::Coord const fttt = s * f0tt + t * f1tt;
816 start->n.pos[dim] = f00t;
817 new_path->p.pos[dim] = f0tt;
818 new_path->pos[dim] = fttt;
819 new_path->n.pos[dim] = f1tt;
820 end->p.pos[dim] = f11t;
821 }
822 }
823 }
825 /**
826 * Adds new node on direct line between two nodes, activates handles of all
827 * three nodes.
828 */
829 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
830 {
831 g_assert(end);
832 g_assert(end->subpath);
833 g_assert(g_list_find(end->subpath->nodes, end));
835 Inkscape::NodePath::Node *start = end->p.other;
836 g_assert( start->n.other == end );
837 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
838 end,
839 (NRPathcode)end->code == NR_LINETO?
840 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
841 (NRPathcode)end->code,
842 &start->pos, &start->pos, &start->n.pos);
843 sp_nodepath_line_midpoint(newnode, end, t);
845 sp_node_adjust_handles(start);
846 sp_node_update_handles(start);
847 sp_node_update_handles(newnode);
848 sp_node_adjust_handles(end);
849 sp_node_update_handles(end);
851 return newnode;
852 }
854 /**
855 \brief Break the path at the node: duplicate the argument node, start a new subpath with the duplicate, and copy all nodes after the argument node to it
856 */
857 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
858 {
859 g_assert(node);
860 g_assert(node->subpath);
861 g_assert(g_list_find(node->subpath->nodes, node));
863 Inkscape::NodePath::SubPath *sp = node->subpath;
864 Inkscape::NodePath::Path *np = sp->nodepath;
866 if (sp->closed) {
867 sp_nodepath_subpath_open(sp, node);
868 return sp->first;
869 } else {
870 // no break for end nodes
871 if (node == sp->first) return NULL;
872 if (node == sp->last ) return NULL;
874 // create a new subpath
875 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
877 // duplicate the break node as start of the new subpath
878 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
880 while (node->n.other) { // copy the remaining nodes into the new subpath
881 Inkscape::NodePath::Node *n = node->n.other;
882 Inkscape::NodePath::Node *nn = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
883 if (n->selected) {
884 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
885 }
886 sp_nodepath_node_destroy(n); // remove the point on the original subpath
887 }
889 return newnode;
890 }
891 }
893 /**
894 * Duplicate node and connect to neighbours.
895 */
896 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
897 {
898 g_assert(node);
899 g_assert(node->subpath);
900 g_assert(g_list_find(node->subpath->nodes, node));
902 Inkscape::NodePath::SubPath *sp = node->subpath;
904 NRPathcode code = (NRPathcode) node->code;
905 if (code == NR_MOVETO) { // if node is the endnode,
906 node->code = NR_LINETO; // new one is inserted before it, so change that to line
907 }
909 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
911 if (!node->n.other || !node->p.other) // if node is an endnode, select it
912 return node;
913 else
914 return newnode; // otherwise select the newly created node
915 }
917 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
918 {
919 node->p.pos = (node->pos + (node->pos - node->n.pos));
920 }
922 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
923 {
924 node->n.pos = (node->pos + (node->pos - node->p.pos));
925 }
927 /**
928 * Change line type at node, with side effects on neighbours.
929 */
930 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
931 {
932 g_assert(end);
933 g_assert(end->subpath);
934 g_assert(end->p.other);
936 if (end->code == static_cast< guint > ( code ) )
937 return;
939 Inkscape::NodePath::Node *start = end->p.other;
941 end->code = code;
943 if (code == NR_LINETO) {
944 if (start->code == NR_LINETO) {
945 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
946 }
947 if (end->n.other) {
948 if (end->n.other->code == NR_LINETO) {
949 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
950 }
951 }
952 } else {
953 NR::Point delta = end->pos - start->pos;
954 start->n.pos = start->pos + delta / 3;
955 end->p.pos = end->pos - delta / 3;
956 sp_node_adjust_handle(start, 1);
957 sp_node_adjust_handle(end, -1);
958 }
960 sp_node_update_handles(start);
961 sp_node_update_handles(end);
962 }
964 /**
965 * Change node type, and its handles accordingly.
966 */
967 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
968 {
969 g_assert(node);
970 g_assert(node->subpath);
972 if ((node->p.other != NULL) && (node->n.other != NULL)) {
973 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
974 type =Inkscape::NodePath::NODE_CUSP;
975 }
976 }
978 node->type = type;
980 if (node->type == Inkscape::NodePath::NODE_CUSP) {
981 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
982 node->knot->setSize (node->selected? 11 : 9);
983 sp_knot_update_ctrl(node->knot);
984 } else {
985 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
986 node->knot->setSize (node->selected? 9 : 7);
987 sp_knot_update_ctrl(node->knot);
988 }
990 // if one of handles is mouseovered, preserve its position
991 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
992 sp_node_adjust_handle(node, 1);
993 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
994 sp_node_adjust_handle(node, -1);
995 } else {
996 sp_node_adjust_handles(node);
997 }
999 sp_node_update_handles(node);
1001 sp_nodepath_update_statusbar(node->subpath->nodepath);
1003 return node;
1004 }
1006 bool
1007 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1008 {
1009 Inkscape::NodePath::Node *othernode = side->other;
1010 if (!othernode)
1011 return false;
1012 NRPathcode const code = sp_node_path_code_from_side(node, side);
1013 if (code == NR_LINETO)
1014 return true;
1015 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1016 if (&node->p == side) {
1017 other_to_me = &othernode->n;
1018 } else if (&node->n == side) {
1019 other_to_me = &othernode->p;
1020 }
1021 if (!other_to_me)
1022 return false;
1023 bool is_line =
1024 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1025 NR::L2(node->pos - side->pos) < 1e-6);
1026 return is_line;
1027 }
1029 /**
1030 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1031 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1032 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1033 * If already cusp and set to cusp, retracts handles.
1034 */
1035 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1036 {
1037 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1039 /*
1040 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1042 if (two_handles) {
1043 // do nothing, adjust_handles called via set_node_type will line them up
1044 } else if (one_handle) {
1045 if (opposite_to_handle_is_line) {
1046 if (lined_up) {
1047 // already half-smooth; pull opposite handle too making it fully smooth
1048 } else {
1049 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1050 }
1051 } else {
1052 // pull opposite handle in line with the existing one
1053 }
1054 } else if (no_handles) {
1055 if (both_segments_are_lines OR both_segments_are_curves) {
1056 //pull both handles
1057 } else {
1058 // pull the handle opposite to line segment, making node half-smooth
1059 }
1060 }
1061 */
1062 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1063 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1064 bool p_is_line = sp_node_side_is_line(node, &node->p);
1065 bool n_is_line = sp_node_side_is_line(node, &node->n);
1067 if (p_has_handle && n_has_handle) {
1068 // do nothing, adjust_handles will line them up
1069 } else if (p_has_handle || n_has_handle) {
1070 if (p_has_handle && n_is_line) {
1071 Radial line (node->n.other->pos - node->pos);
1072 Radial handle (node->pos - node->p.pos);
1073 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1074 // already half-smooth; pull opposite handle too making it fully smooth
1075 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1076 } else {
1077 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1078 }
1079 } else if (n_has_handle && p_is_line) {
1080 Radial line (node->p.other->pos - node->pos);
1081 Radial handle (node->pos - node->n.pos);
1082 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1083 // already half-smooth; pull opposite handle too making it fully smooth
1084 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1085 } else {
1086 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1087 }
1088 } else if (p_has_handle && node->n.other) {
1089 // pull n handle
1090 node->n.other->code = NR_CURVETO;
1091 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1092 NR::L2(node->p.pos - node->pos) :
1093 NR::L2(node->n.other->pos - node->pos) / 3;
1094 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1095 } else if (n_has_handle && node->p.other) {
1096 // pull p handle
1097 node->code = NR_CURVETO;
1098 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1099 NR::L2(node->n.pos - node->pos) :
1100 NR::L2(node->p.other->pos - node->pos) / 3;
1101 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1102 }
1103 } else if (!p_has_handle && !n_has_handle) {
1104 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1105 // no handles, but both segments are either lnes or curves:
1106 //pull both handles
1108 // convert both to curves:
1109 node->code = NR_CURVETO;
1110 node->n.other->code = NR_CURVETO;
1112 NR::Point leg_prev = node->pos - node->p.other->pos;
1113 NR::Point leg_next = node->pos - node->n.other->pos;
1115 double norm_leg_prev = L2(leg_prev);
1116 double norm_leg_next = L2(leg_next);
1118 NR::Point delta;
1119 if (norm_leg_next > 0.0) {
1120 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1121 (&delta)->normalize();
1122 }
1124 if (type == Inkscape::NodePath::NODE_SYMM) {
1125 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1126 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1127 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1128 } else {
1129 // length of handle is proportional to distance to adjacent node
1130 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1131 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1132 }
1134 } else {
1135 // pull the handle opposite to line segment, making it half-smooth
1136 if (p_is_line && node->n.other) {
1137 if (type != Inkscape::NodePath::NODE_SYMM) {
1138 // pull n handle
1139 node->n.other->code = NR_CURVETO;
1140 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1141 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1142 }
1143 } else if (n_is_line && node->p.other) {
1144 if (type != Inkscape::NodePath::NODE_SYMM) {
1145 // pull p handle
1146 node->code = NR_CURVETO;
1147 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1148 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1149 }
1150 }
1151 }
1152 }
1153 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1154 // cusping a cusp: retract nodes
1155 node->p.pos = node->pos;
1156 node->n.pos = node->pos;
1157 }
1159 sp_nodepath_set_node_type (node, type);
1160 }
1162 /**
1163 * Move node to point, and adjust its and neighbouring handles.
1164 */
1165 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1166 {
1167 NR::Point delta = p - node->pos;
1168 node->pos = p;
1170 node->p.pos += delta;
1171 node->n.pos += delta;
1173 Inkscape::NodePath::Node *node_p = NULL;
1174 Inkscape::NodePath::Node *node_n = NULL;
1176 if (node->p.other) {
1177 if (node->code == NR_LINETO) {
1178 sp_node_adjust_handle(node, 1);
1179 sp_node_adjust_handle(node->p.other, -1);
1180 node_p = node->p.other;
1181 }
1182 }
1183 if (node->n.other) {
1184 if (node->n.other->code == NR_LINETO) {
1185 sp_node_adjust_handle(node, -1);
1186 sp_node_adjust_handle(node->n.other, 1);
1187 node_n = node->n.other;
1188 }
1189 }
1191 // this function is only called from batch movers that will update display at the end
1192 // themselves, so here we just move all the knots without emitting move signals, for speed
1193 sp_node_update_handles(node, false);
1194 if (node_n) {
1195 sp_node_update_handles(node_n, false);
1196 }
1197 if (node_p) {
1198 sp_node_update_handles(node_p, false);
1199 }
1200 }
1202 /**
1203 * Call sp_node_moveto() for node selection and handle possible snapping.
1204 */
1205 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1206 bool const snap, bool constrained = false,
1207 Inkscape::Snapper::ConstraintLine const &constraint = NR::Point())
1208 {
1209 NR::Coord best = NR_HUGE;
1210 NR::Point delta(dx, dy);
1211 NR::Point best_pt = delta;
1212 Inkscape::SnappedPoint best_abs;
1214 if (snap) {
1215 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1216 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1217 * must provide that information. */
1219 // Build a list of the unselected nodes to which the snapper should snap
1220 std::vector<NR::Point> unselected_nodes;
1221 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1222 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1223 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1224 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1225 if (!node->selected) {
1226 unselected_nodes.push_back(node->pos);
1227 }
1228 }
1229 }
1231 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1233 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1234 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1235 m.setup(NULL, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1236 Inkscape::SnappedPoint s;
1237 if (constrained) {
1238 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1239 dedicated_constraint.setPoint(n->pos);
1240 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, dedicated_constraint);
1241 } else {
1242 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1243 }
1244 if (s.getSnapped() && (s.getDistance() < best)) {
1245 best = s.getDistance();
1246 best_abs = s;
1247 best_pt = s.getPoint() - n->pos;
1248 }
1249 }
1251 if (best_abs.getSnapped()) {
1252 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1253 } else {
1254 nodepath->desktop->snapindicator->remove_snappoint();
1255 }
1256 }
1258 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1259 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1260 sp_node_moveto(n, n->pos + best_pt);
1261 }
1263 // do not update repr here so that node dragging is acceptably fast
1264 update_object(nodepath);
1265 }
1267 /**
1268 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1269 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1270 near x = 0.
1271 */
1272 double
1273 sculpt_profile (double x, double alpha, guint profile)
1274 {
1275 if (x >= 1)
1276 return 0;
1277 if (x <= 0)
1278 return 1;
1280 switch (profile) {
1281 case SCULPT_PROFILE_LINEAR:
1282 return 1 - x;
1283 case SCULPT_PROFILE_BELL:
1284 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1285 case SCULPT_PROFILE_ELLIPTIC:
1286 return sqrt(1 - x*x);
1287 }
1289 return 1;
1290 }
1292 double
1293 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1294 {
1295 // extremely primitive for now, don't have time to look for the real one
1296 double lower = NR::L2(b - a);
1297 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1298 return (lower + upper)/2;
1299 }
1301 void
1302 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1303 {
1304 n->pos = n->origin + delta;
1305 n->n.pos = n->n.origin + delta_n;
1306 n->p.pos = n->p.origin + delta_p;
1307 sp_node_adjust_handles(n);
1308 sp_node_update_handles(n, false);
1309 }
1311 /**
1312 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1313 * on how far they are from the dragged node n.
1314 */
1315 static void
1316 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1317 {
1318 g_assert (n);
1319 g_assert (nodepath);
1320 g_assert (n->subpath->nodepath == nodepath);
1322 double pressure = n->knot->pressure;
1323 if (pressure == 0)
1324 pressure = 0.5; // default
1325 pressure = CLAMP (pressure, 0.2, 0.8);
1327 // map pressure to alpha = 1/5 ... 5
1328 double alpha = 1 - 2 * fabs(pressure - 0.5);
1329 if (pressure > 0.5)
1330 alpha = 1/alpha;
1332 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1334 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1335 // Only one subpath has selected nodes:
1336 // use linear mode, where the distance from n to node being dragged is calculated along the path
1338 double n_sel_range = 0, p_sel_range = 0;
1339 guint n_nodes = 0, p_nodes = 0;
1340 guint n_sel_nodes = 0, p_sel_nodes = 0;
1342 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1343 {
1344 double n_range = 0, p_range = 0;
1345 bool n_going = true, p_going = true;
1346 Inkscape::NodePath::Node *n_node = n;
1347 Inkscape::NodePath::Node *p_node = n;
1348 do {
1349 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1350 if (n_node && n_going)
1351 n_node = n_node->n.other;
1352 if (n_node == NULL) {
1353 n_going = false;
1354 } else {
1355 n_nodes ++;
1356 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1357 if (n_node->selected) {
1358 n_sel_nodes ++;
1359 n_sel_range = n_range;
1360 }
1361 if (n_node == p_node) {
1362 n_going = false;
1363 p_going = false;
1364 }
1365 }
1366 if (p_node && p_going)
1367 p_node = p_node->p.other;
1368 if (p_node == NULL) {
1369 p_going = false;
1370 } else {
1371 p_nodes ++;
1372 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1373 if (p_node->selected) {
1374 p_sel_nodes ++;
1375 p_sel_range = p_range;
1376 }
1377 if (p_node == n_node) {
1378 n_going = false;
1379 p_going = false;
1380 }
1381 }
1382 } while (n_going || p_going);
1383 }
1385 // Second pass: actually move nodes in this subpath
1386 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1387 {
1388 double n_range = 0, p_range = 0;
1389 bool n_going = true, p_going = true;
1390 Inkscape::NodePath::Node *n_node = n;
1391 Inkscape::NodePath::Node *p_node = n;
1392 do {
1393 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1394 if (n_node && n_going)
1395 n_node = n_node->n.other;
1396 if (n_node == NULL) {
1397 n_going = false;
1398 } else {
1399 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1400 if (n_node->selected) {
1401 sp_nodepath_move_node_and_handles (n_node,
1402 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1403 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1404 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1405 }
1406 if (n_node == p_node) {
1407 n_going = false;
1408 p_going = false;
1409 }
1410 }
1411 if (p_node && p_going)
1412 p_node = p_node->p.other;
1413 if (p_node == NULL) {
1414 p_going = false;
1415 } else {
1416 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1417 if (p_node->selected) {
1418 sp_nodepath_move_node_and_handles (p_node,
1419 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1420 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1421 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1422 }
1423 if (p_node == n_node) {
1424 n_going = false;
1425 p_going = false;
1426 }
1427 }
1428 } while (n_going || p_going);
1429 }
1431 } else {
1432 // Multiple subpaths have selected nodes:
1433 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1434 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1435 // fix the pear-like shape when sculpting e.g. a ring
1437 // First pass: calculate range
1438 gdouble direct_range = 0;
1439 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1440 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1441 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1442 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1443 if (node->selected) {
1444 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1445 }
1446 }
1447 }
1449 // Second pass: actually move nodes
1450 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1451 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1452 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1453 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1454 if (node->selected) {
1455 if (direct_range > 1e-6) {
1456 sp_nodepath_move_node_and_handles (node,
1457 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1458 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1459 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1460 } else {
1461 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1462 }
1464 }
1465 }
1466 }
1467 }
1469 // do not update repr here so that node dragging is acceptably fast
1470 update_object(nodepath);
1471 }
1474 /**
1475 * Move node selection to point, adjust its and neighbouring handles,
1476 * handle possible snapping, and commit the change with possible undo.
1477 */
1478 void
1479 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1480 {
1481 if (!nodepath) return;
1483 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1485 if (dx == 0) {
1486 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1487 } else if (dy == 0) {
1488 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1489 } else {
1490 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1491 }
1492 }
1494 /**
1495 * Move node selection off screen and commit the change.
1496 */
1497 void
1498 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1499 {
1500 // borrowed from sp_selection_move_screen in selection-chemistry.c
1501 // we find out the current zoom factor and divide deltas by it
1502 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1504 gdouble zoom = desktop->current_zoom();
1505 gdouble zdx = dx / zoom;
1506 gdouble zdy = dy / zoom;
1508 if (!nodepath) return;
1510 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1512 if (dx == 0) {
1513 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1514 } else if (dy == 0) {
1515 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1516 } else {
1517 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1518 }
1519 }
1521 /**
1522 * Move selected nodes to the absolute position given
1523 */
1524 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1525 {
1526 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1527 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1528 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1529 sp_node_moveto(n, npos);
1530 }
1532 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1533 }
1535 /**
1536 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1537 */
1538 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1539 {
1540 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1541 g_return_val_if_fail(nodepath->selected, no_coord);
1543 // determine coordinate of first selected node
1544 GList *nsel = nodepath->selected;
1545 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1546 NR::Coord coord = n->pos[axis];
1547 bool coincide = true;
1549 // compare it to the coordinates of all the other selected nodes
1550 for (GList *l = nsel->next; l != NULL; l = l->next) {
1551 n = (Inkscape::NodePath::Node *) l->data;
1552 if (n->pos[axis] != coord) {
1553 coincide = false;
1554 }
1555 }
1556 if (coincide) {
1557 return coord;
1558 } else {
1559 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1560 // currently we return the coordinate of the bounding box midpoint because I don't know how
1561 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1562 return bbox.midpoint()[axis];
1563 }
1564 }
1566 /** If they don't yet exist, creates knot and line for the given side of the node */
1567 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1568 {
1569 if (!side->knot) {
1570 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"));
1572 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1573 side->knot->setSize (7);
1574 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1575 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1576 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1577 sp_knot_update_ctrl(side->knot);
1579 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1580 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1581 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1582 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1583 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1584 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1585 }
1587 if (!side->line) {
1588 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1589 SP_TYPE_CTRLLINE, NULL);
1590 }
1591 }
1593 /**
1594 * Ensure the given handle of the node is visible/invisible, update its screen position
1595 */
1596 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1597 {
1598 g_assert(node != NULL);
1600 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1601 NRPathcode code = sp_node_path_code_from_side(node, side);
1603 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1605 if (show_handle) {
1606 if (!side->knot) { // No handle knot at all
1607 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1608 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1609 side->knot->pos = side->pos;
1610 if (side->knot->item)
1611 SP_CTRL(side->knot->item)->moveto(side->pos);
1612 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1613 sp_knot_show(side->knot);
1614 } else {
1615 if (side->knot->pos != side->pos) { // only if it's really moved
1616 if (fire_move_signals) {
1617 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1618 } else {
1619 sp_knot_moveto(side->knot, &side->pos);
1620 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1621 }
1622 }
1623 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1624 sp_knot_show(side->knot);
1625 }
1626 }
1627 sp_canvas_item_show(side->line);
1628 } else {
1629 if (side->knot) {
1630 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1631 sp_knot_hide(side->knot);
1632 }
1633 }
1634 if (side->line) {
1635 sp_canvas_item_hide(side->line);
1636 }
1637 }
1638 }
1640 /**
1641 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1642 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1643 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1644 * updated; otherwise, just move the knots silently (used in batch moves).
1645 */
1646 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1647 {
1648 g_assert(node != NULL);
1650 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1651 sp_knot_show(node->knot);
1652 }
1654 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1655 if (fire_move_signals)
1656 sp_knot_set_position(node->knot, &node->pos, 0);
1657 else
1658 sp_knot_moveto(node->knot, &node->pos);
1659 }
1661 gboolean show_handles = node->selected;
1662 if (node->p.other != NULL) {
1663 if (node->p.other->selected) show_handles = TRUE;
1664 }
1665 if (node->n.other != NULL) {
1666 if (node->n.other->selected) show_handles = TRUE;
1667 }
1669 if (node->subpath->nodepath->show_handles == false)
1670 show_handles = FALSE;
1672 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1673 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1674 }
1676 /**
1677 * Call sp_node_update_handles() for all nodes on subpath.
1678 */
1679 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1680 {
1681 g_assert(subpath != NULL);
1683 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1684 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1685 }
1686 }
1688 /**
1689 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1690 */
1691 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1692 {
1693 g_assert(nodepath != NULL);
1695 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1696 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1697 }
1698 }
1700 void
1701 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1702 {
1703 if (nodepath == NULL) return;
1705 nodepath->show_handles = show;
1706 sp_nodepath_update_handles(nodepath);
1707 }
1709 /**
1710 * Adds all selected nodes in nodepath to list.
1711 */
1712 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1713 {
1714 StlConv<Node *>::list(l, selected);
1715 /// \todo this adds a copying, rework when the selection becomes a stl list
1716 }
1718 /**
1719 * Align selected nodes on the specified axis.
1720 */
1721 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1722 {
1723 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1724 return;
1725 }
1727 if ( !nodepath->selected->next ) { // only one node selected
1728 return;
1729 }
1730 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1731 NR::Point dest(pNode->pos);
1732 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1733 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1734 if (pNode) {
1735 dest[axis] = pNode->pos[axis];
1736 sp_node_moveto(pNode, dest);
1737 }
1738 }
1740 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1741 }
1743 /// Helper struct.
1744 struct NodeSort
1745 {
1746 Inkscape::NodePath::Node *_node;
1747 NR::Coord _coord;
1748 /// \todo use vectorof pointers instead of calling copy ctor
1749 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1750 _node(node), _coord(node->pos[axis])
1751 {}
1753 };
1755 static bool operator<(NodeSort const &a, NodeSort const &b)
1756 {
1757 return (a._coord < b._coord);
1758 }
1760 /**
1761 * Distribute selected nodes on the specified axis.
1762 */
1763 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1764 {
1765 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1766 return;
1767 }
1769 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1770 return;
1771 }
1773 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1774 std::vector<NodeSort> sorted;
1775 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1776 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1777 if (pNode) {
1778 NodeSort n(pNode, axis);
1779 sorted.push_back(n);
1780 //dest[axis] = pNode->pos[axis];
1781 //sp_node_moveto(pNode, dest);
1782 }
1783 }
1784 std::sort(sorted.begin(), sorted.end());
1785 unsigned int len = sorted.size();
1786 //overall bboxes span
1787 float dist = (sorted.back()._coord -
1788 sorted.front()._coord);
1789 //new distance between each bbox
1790 float step = (dist) / (len - 1);
1791 float pos = sorted.front()._coord;
1792 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1793 it < sorted.end();
1794 it ++ )
1795 {
1796 NR::Point dest((*it)._node->pos);
1797 dest[axis] = pos;
1798 sp_node_moveto((*it)._node, dest);
1799 pos += step;
1800 }
1802 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1803 }
1806 /**
1807 * Call sp_nodepath_line_add_node() for all selected segments.
1808 */
1809 void
1810 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1811 {
1812 if (!nodepath) {
1813 return;
1814 }
1816 GList *nl = NULL;
1818 int n_added = 0;
1820 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1821 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1822 g_assert(t->selected);
1823 if (t->p.other && t->p.other->selected) {
1824 nl = g_list_prepend(nl, t);
1825 }
1826 }
1828 while (nl) {
1829 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1830 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1831 sp_nodepath_node_select(n, TRUE, FALSE);
1832 n_added ++;
1833 nl = g_list_remove(nl, t);
1834 }
1836 /** \todo fixme: adjust ? */
1837 sp_nodepath_update_handles(nodepath);
1839 if (n_added > 1) {
1840 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1841 } else if (n_added > 0) {
1842 sp_nodepath_update_repr(nodepath, _("Add node"));
1843 }
1845 sp_nodepath_update_statusbar(nodepath);
1846 }
1848 /**
1849 * Select segment nearest to point
1850 */
1851 void
1852 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1853 {
1854 if (!nodepath) {
1855 return;
1856 }
1858 sp_nodepath_ensure_livarot_path(nodepath);
1859 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1860 if (!maybe_position) {
1861 return;
1862 }
1863 Path::cut_position position = *maybe_position;
1865 //find segment to segment
1866 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1868 //fixme: this can return NULL, so check before proceeding.
1869 g_return_if_fail(e != NULL);
1871 gboolean force = FALSE;
1872 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1873 force = TRUE;
1874 }
1875 sp_nodepath_node_select(e, (gboolean) toggle, force);
1876 if (e->p.other)
1877 sp_nodepath_node_select(e->p.other, TRUE, force);
1879 sp_nodepath_update_handles(nodepath);
1881 sp_nodepath_update_statusbar(nodepath);
1882 }
1884 /**
1885 * Add a node nearest to point
1886 */
1887 void
1888 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1889 {
1890 if (!nodepath) {
1891 return;
1892 }
1894 sp_nodepath_ensure_livarot_path(nodepath);
1895 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1896 if (!maybe_position) {
1897 return;
1898 }
1899 Path::cut_position position = *maybe_position;
1901 //find segment to split
1902 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1904 //don't know why but t seems to flip for lines
1905 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1906 position.t = 1.0 - position.t;
1907 }
1908 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1909 sp_nodepath_node_select(n, FALSE, TRUE);
1911 /* fixme: adjust ? */
1912 sp_nodepath_update_handles(nodepath);
1914 sp_nodepath_update_repr(nodepath, _("Add node"));
1916 sp_nodepath_update_statusbar(nodepath);
1917 }
1919 /*
1920 * Adjusts a segment so that t moves by a certain delta for dragging
1921 * converts lines to curves
1922 *
1923 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1924 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1925 */
1926 void
1927 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1928 {
1929 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1931 //fixme: e and e->p can be NULL, so check for those before proceeding
1932 g_return_if_fail(e != NULL);
1933 g_return_if_fail(&e->p != NULL);
1935 /* feel good is an arbitrary parameter that distributes the delta between handles
1936 * if t of the drag point is less than 1/6 distance form the endpoint only
1937 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1938 */
1939 double feel_good;
1940 if (t <= 1.0 / 6.0)
1941 feel_good = 0;
1942 else if (t <= 0.5)
1943 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1944 else if (t <= 5.0 / 6.0)
1945 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1946 else
1947 feel_good = 1;
1949 //if we're dragging a line convert it to a curve
1950 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1951 sp_nodepath_set_line_type(e, NR_CURVETO);
1952 }
1954 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1955 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1956 e->p.other->n.pos += offsetcoord0;
1957 e->p.pos += offsetcoord1;
1959 // adjust handles of adjacent nodes where necessary
1960 sp_node_adjust_handle(e,1);
1961 sp_node_adjust_handle(e->p.other,-1);
1963 sp_nodepath_update_handles(e->subpath->nodepath);
1965 update_object(e->subpath->nodepath);
1967 sp_nodepath_update_statusbar(e->subpath->nodepath);
1968 }
1971 /**
1972 * Call sp_nodepath_break() for all selected segments.
1973 */
1974 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1975 {
1976 if (!nodepath) return;
1978 GList *temp = NULL;
1979 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1980 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1981 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1982 if (nn == NULL) continue; // no break, no new node
1983 temp = g_list_prepend(temp, nn);
1984 }
1986 if (temp) {
1987 sp_nodepath_deselect(nodepath);
1988 }
1989 for (GList *l = temp; l != NULL; l = l->next) {
1990 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1991 }
1993 sp_nodepath_update_handles(nodepath);
1995 sp_nodepath_update_repr(nodepath, _("Break path"));
1996 }
1998 /**
1999 * Duplicate the selected node(s).
2000 */
2001 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2002 {
2003 if (!nodepath) {
2004 return;
2005 }
2007 GList *temp = NULL;
2008 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2009 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2010 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2011 if (nn == NULL) continue; // could not duplicate
2012 temp = g_list_prepend(temp, nn);
2013 }
2015 if (temp) {
2016 sp_nodepath_deselect(nodepath);
2017 }
2018 for (GList *l = temp; l != NULL; l = l->next) {
2019 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2020 }
2022 sp_nodepath_update_handles(nodepath);
2024 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2025 }
2027 /**
2028 * Internal function to join two nodes by merging them into one.
2029 */
2030 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2031 {
2032 /* a and b are endpoints */
2034 // if one of the two nodes is mouseovered, fix its position
2035 NR::Point c;
2036 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2037 c = a->pos;
2038 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2039 c = b->pos;
2040 } else {
2041 // otherwise, move joined node to the midpoint
2042 c = (a->pos + b->pos) / 2;
2043 }
2045 if (a->subpath == b->subpath) {
2046 Inkscape::NodePath::SubPath *sp = a->subpath;
2047 sp_nodepath_subpath_close(sp);
2048 sp_node_moveto (sp->first, c);
2050 sp_nodepath_update_handles(sp->nodepath);
2051 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2052 return;
2053 }
2055 /* a and b are separate subpaths */
2056 Inkscape::NodePath::SubPath *sa = a->subpath;
2057 Inkscape::NodePath::SubPath *sb = b->subpath;
2058 NR::Point p;
2059 Inkscape::NodePath::Node *n;
2060 NRPathcode code;
2061 if (a == sa->first) {
2062 // we will now reverse sa, so that a is its last node, not first, and drop that node
2063 p = sa->first->n.pos;
2064 code = (NRPathcode)sa->first->n.other->code;
2065 // create new subpath
2066 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2067 // create a first moveto node on it
2068 n = sa->last;
2069 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2070 n = n->p.other;
2071 if (n == sa->first) n = NULL;
2072 while (n) {
2073 // copy the rest of the nodes from sa to t, going backwards
2074 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2075 n = n->p.other;
2076 if (n == sa->first) n = NULL;
2077 }
2078 // replace sa with t
2079 sp_nodepath_subpath_destroy(sa);
2080 sa = t;
2081 } else if (a == sa->last) {
2082 // a is already last, just drop it
2083 p = sa->last->p.pos;
2084 code = (NRPathcode)sa->last->code;
2085 sp_nodepath_node_destroy(sa->last);
2086 } else {
2087 code = NR_END;
2088 g_assert_not_reached();
2089 }
2091 if (b == sb->first) {
2092 // copy all nodes from b to a, forward
2093 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2094 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2095 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2096 }
2097 } else if (b == sb->last) {
2098 // copy all nodes from b to a, backward
2099 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2100 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2101 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2102 }
2103 } else {
2104 g_assert_not_reached();
2105 }
2106 /* and now destroy sb */
2108 sp_nodepath_subpath_destroy(sb);
2110 sp_nodepath_update_handles(sa->nodepath);
2112 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2114 sp_nodepath_update_statusbar(nodepath);
2115 }
2117 /**
2118 * Internal function to join two nodes by adding a segment between them.
2119 */
2120 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2121 {
2122 if (a->subpath == b->subpath) {
2123 Inkscape::NodePath::SubPath *sp = a->subpath;
2125 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2126 sp->closed = TRUE;
2128 sp->first->p.other = sp->last;
2129 sp->last->n.other = sp->first;
2131 sp_node_handle_mirror_p_to_n(sp->last);
2132 sp_node_handle_mirror_n_to_p(sp->first);
2134 sp->first->code = sp->last->code;
2135 sp->first = sp->last;
2137 sp_nodepath_update_handles(sp->nodepath);
2139 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2141 return;
2142 }
2144 /* a and b are separate subpaths */
2145 Inkscape::NodePath::SubPath *sa = a->subpath;
2146 Inkscape::NodePath::SubPath *sb = b->subpath;
2148 Inkscape::NodePath::Node *n;
2149 NR::Point p;
2150 NRPathcode code;
2151 if (a == sa->first) {
2152 code = (NRPathcode) sa->first->n.other->code;
2153 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2154 n = sa->last;
2155 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2156 for (n = n->p.other; n != NULL; n = n->p.other) {
2157 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2158 }
2159 sp_nodepath_subpath_destroy(sa);
2160 sa = t;
2161 } else if (a == sa->last) {
2162 code = (NRPathcode)sa->last->code;
2163 } else {
2164 code = NR_END;
2165 g_assert_not_reached();
2166 }
2168 if (b == sb->first) {
2169 n = sb->first;
2170 sp_node_handle_mirror_p_to_n(sa->last);
2171 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2172 sp_node_handle_mirror_n_to_p(sa->last);
2173 for (n = n->n.other; n != NULL; n = n->n.other) {
2174 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2175 }
2176 } else if (b == sb->last) {
2177 n = sb->last;
2178 sp_node_handle_mirror_p_to_n(sa->last);
2179 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2180 sp_node_handle_mirror_n_to_p(sa->last);
2181 for (n = n->p.other; n != NULL; n = n->p.other) {
2182 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2183 }
2184 } else {
2185 g_assert_not_reached();
2186 }
2187 /* and now destroy sb */
2189 sp_nodepath_subpath_destroy(sb);
2191 sp_nodepath_update_handles(sa->nodepath);
2193 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2194 }
2196 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2198 /**
2199 * Internal function to handle joining two nodes.
2200 */
2201 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2202 {
2203 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2205 if (g_list_length(nodepath->selected) != 2) {
2206 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2207 return;
2208 }
2210 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2211 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2213 g_assert(a != b);
2214 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2215 // someone tried to join an orphan node (i.e. a single-node subpath).
2216 // this is not worth an error message, just fail silently.
2217 return;
2218 }
2220 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2221 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2222 return;
2223 }
2225 switch(mode) {
2226 case NODE_JOIN_ENDPOINTS:
2227 do_node_selected_join(nodepath, a, b);
2228 break;
2229 case NODE_JOIN_SEGMENT:
2230 do_node_selected_join_segment(nodepath, a, b);
2231 break;
2232 }
2233 }
2235 /**
2236 * Join two nodes by merging them into one.
2237 */
2238 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2239 {
2240 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2241 }
2243 /**
2244 * Join two nodes by adding a segment between them.
2245 */
2246 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2247 {
2248 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2249 }
2251 /**
2252 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2253 */
2254 void sp_node_delete_preserve(GList *nodes_to_delete)
2255 {
2256 GSList *nodepaths = NULL;
2258 while (nodes_to_delete) {
2259 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2260 Inkscape::NodePath::SubPath *sp = node->subpath;
2261 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2262 Inkscape::NodePath::Node *sample_cursor = NULL;
2263 Inkscape::NodePath::Node *sample_end = NULL;
2264 Inkscape::NodePath::Node *delete_cursor = node;
2265 bool just_delete = false;
2267 //find the start of this contiguous selection
2268 //move left to the first node that is not selected
2269 //or the start of the non-closed path
2270 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2271 delete_cursor = curr;
2272 }
2274 //just delete at the beginning of an open path
2275 if (!delete_cursor->p.other) {
2276 sample_cursor = delete_cursor;
2277 just_delete = true;
2278 } else {
2279 sample_cursor = delete_cursor->p.other;
2280 }
2282 //calculate points for each segment
2283 int rate = 5;
2284 float period = 1.0 / rate;
2285 std::vector<NR::Point> data;
2286 if (!just_delete) {
2287 data.push_back(sample_cursor->pos);
2288 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2289 //just delete at the end of an open path
2290 if (!sp->closed && curr == sp->last) {
2291 just_delete = true;
2292 break;
2293 }
2295 //sample points on the contiguous selected segment
2296 NR::Point *bez;
2297 bez = new NR::Point [4];
2298 bez[0] = curr->pos;
2299 bez[1] = curr->n.pos;
2300 bez[2] = curr->n.other->p.pos;
2301 bez[3] = curr->n.other->pos;
2302 for (int i=1; i<rate; i++) {
2303 gdouble t = i * period;
2304 NR::Point p = bezier_pt(3, bez, t);
2305 data.push_back(p);
2306 }
2307 data.push_back(curr->n.other->pos);
2309 sample_end = curr->n.other;
2310 //break if we've come full circle or hit the end of the selection
2311 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2312 break;
2313 }
2314 }
2315 }
2317 if (!just_delete) {
2318 //calculate the best fitting single segment and adjust the endpoints
2319 NR::Point *adata;
2320 adata = new NR::Point [data.size()];
2321 copy(data.begin(), data.end(), adata);
2323 NR::Point *bez;
2324 bez = new NR::Point [4];
2325 //would decreasing error create a better fitting approximation?
2326 gdouble error = 1.0;
2327 gint ret;
2328 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2330 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2331 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2332 //the resulting nodes behave as expected.
2333 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2334 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2335 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2336 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2338 //adjust endpoints
2339 sample_cursor->n.pos = bez[1];
2340 sample_end->p.pos = bez[2];
2341 }
2343 //destroy this contiguous selection
2344 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2345 Inkscape::NodePath::Node *temp = delete_cursor;
2346 if (delete_cursor->n.other == delete_cursor) {
2347 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2348 delete_cursor = NULL;
2349 } else {
2350 delete_cursor = delete_cursor->n.other;
2351 }
2352 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2353 sp_nodepath_node_destroy(temp);
2354 }
2356 sp_nodepath_update_handles(nodepath);
2358 if (!g_slist_find(nodepaths, nodepath))
2359 nodepaths = g_slist_prepend (nodepaths, nodepath);
2360 }
2362 for (GSList *i = nodepaths; i; i = i->next) {
2363 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2364 // different nodepaths will give us one undo event per nodepath
2365 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2367 // if the entire nodepath is removed, delete the selected object.
2368 if (nodepath->subpaths == NULL ||
2369 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2370 //at least 2
2371 sp_nodepath_get_node_count(nodepath) < 2) {
2372 SPDocument *document = sp_desktop_document (nodepath->desktop);
2373 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2374 //delete this nodepath's object, not the entire selection! (though at this time, this
2375 //does not matter)
2376 sp_selection_delete();
2377 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2378 _("Delete nodes"));
2379 } else {
2380 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2381 sp_nodepath_update_statusbar(nodepath);
2382 }
2383 }
2385 g_slist_free (nodepaths);
2386 }
2388 /**
2389 * Delete one or more selected nodes.
2390 */
2391 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2392 {
2393 if (!nodepath) return;
2394 if (!nodepath->selected) return;
2396 /** \todo fixme: do it the right way */
2397 while (nodepath->selected) {
2398 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2399 sp_nodepath_node_destroy(node);
2400 }
2403 //clean up the nodepath (such as for trivial subpaths)
2404 sp_nodepath_cleanup(nodepath);
2406 sp_nodepath_update_handles(nodepath);
2408 // if the entire nodepath is removed, delete the selected object.
2409 if (nodepath->subpaths == NULL ||
2410 sp_nodepath_get_node_count(nodepath) < 2) {
2411 SPDocument *document = sp_desktop_document (nodepath->desktop);
2412 sp_selection_delete();
2413 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2414 _("Delete nodes"));
2415 return;
2416 }
2418 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2420 sp_nodepath_update_statusbar(nodepath);
2421 }
2423 /**
2424 * Delete one or more segments between two selected nodes.
2425 * This is the code for 'split'.
2426 */
2427 void
2428 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2429 {
2430 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2431 Inkscape::NodePath::Node *curr, *next; //Iterators
2433 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2435 if (g_list_length(nodepath->selected) != 2) {
2436 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2437 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2438 return;
2439 }
2441 //Selected nodes, not inclusive
2442 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2443 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2445 if ( ( a==b) || //same node
2446 (a->subpath != b->subpath ) || //not the same path
2447 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2448 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2449 {
2450 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2451 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2452 return;
2453 }
2455 //###########################################
2456 //# BEGIN EDITS
2457 //###########################################
2458 //##################################
2459 //# CLOSED PATH
2460 //##################################
2461 if (a->subpath->closed) {
2464 gboolean reversed = FALSE;
2466 //Since we can go in a circle, we need to find the shorter distance.
2467 // a->b or b->a
2468 start = end = NULL;
2469 int distance = 0;
2470 int minDistance = 0;
2471 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2472 if (curr==b) {
2473 //printf("a to b:%d\n", distance);
2474 start = a;//go from a to b
2475 end = b;
2476 minDistance = distance;
2477 //printf("A to B :\n");
2478 break;
2479 }
2480 distance++;
2481 }
2483 //try again, the other direction
2484 distance = 0;
2485 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2486 if (curr==a) {
2487 //printf("b to a:%d\n", distance);
2488 if (distance < minDistance) {
2489 start = b; //we go from b to a
2490 end = a;
2491 reversed = TRUE;
2492 //printf("B to A\n");
2493 }
2494 break;
2495 }
2496 distance++;
2497 }
2500 //Copy everything from 'end' to 'start' to a new subpath
2501 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2502 for (curr=end ; curr ; curr=curr->n.other) {
2503 NRPathcode code = (NRPathcode) curr->code;
2504 if (curr == end)
2505 code = NR_MOVETO;
2506 sp_nodepath_node_new(t, NULL,
2507 (Inkscape::NodePath::NodeType)curr->type, code,
2508 &curr->p.pos, &curr->pos, &curr->n.pos);
2509 if (curr == start)
2510 break;
2511 }
2512 sp_nodepath_subpath_destroy(a->subpath);
2515 }
2519 //##################################
2520 //# OPEN PATH
2521 //##################################
2522 else {
2524 //We need to get the direction of the list between A and B
2525 //Can we walk from a to b?
2526 start = end = NULL;
2527 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2528 if (curr==b) {
2529 start = a; //did it! we go from a to b
2530 end = b;
2531 //printf("A to B\n");
2532 break;
2533 }
2534 }
2535 if (!start) {//didn't work? let's try the other direction
2536 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2537 if (curr==a) {
2538 start = b; //did it! we go from b to a
2539 end = a;
2540 //printf("B to A\n");
2541 break;
2542 }
2543 }
2544 }
2545 if (!start) {
2546 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2547 _("Cannot find path between nodes."));
2548 return;
2549 }
2553 //Copy everything after 'end' to a new subpath
2554 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2555 for (curr=end ; curr ; curr=curr->n.other) {
2556 NRPathcode code = (NRPathcode) curr->code;
2557 if (curr == end)
2558 code = NR_MOVETO;
2559 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2560 &curr->p.pos, &curr->pos, &curr->n.pos);
2561 }
2563 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2564 for (curr = start->n.other ; curr ; curr=next) {
2565 next = curr->n.other;
2566 sp_nodepath_node_destroy(curr);
2567 }
2569 }
2570 //###########################################
2571 //# END EDITS
2572 //###########################################
2574 //clean up the nodepath (such as for trivial subpaths)
2575 sp_nodepath_cleanup(nodepath);
2577 sp_nodepath_update_handles(nodepath);
2579 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2581 sp_nodepath_update_statusbar(nodepath);
2582 }
2584 /**
2585 * Call sp_nodepath_set_line() for all selected segments.
2586 */
2587 void
2588 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2589 {
2590 if (nodepath == NULL) return;
2592 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2593 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2594 g_assert(n->selected);
2595 if (n->p.other && n->p.other->selected) {
2596 sp_nodepath_set_line_type(n, code);
2597 }
2598 }
2600 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2601 }
2603 /**
2604 * Call sp_nodepath_convert_node_type() for all selected nodes.
2605 */
2606 void
2607 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2608 {
2609 if (nodepath == NULL) return;
2611 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2613 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2614 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2615 }
2617 sp_nodepath_update_repr(nodepath, _("Change node type"));
2618 }
2620 /**
2621 * Change select status of node, update its own and neighbour handles.
2622 */
2623 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2624 {
2625 node->selected = selected;
2627 if (selected) {
2628 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2629 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2630 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2631 sp_knot_update_ctrl(node->knot);
2632 } else {
2633 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2634 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2635 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2636 sp_knot_update_ctrl(node->knot);
2637 }
2639 sp_node_update_handles(node);
2640 if (node->n.other) sp_node_update_handles(node->n.other);
2641 if (node->p.other) sp_node_update_handles(node->p.other);
2642 }
2644 /**
2645 \brief Select a node
2646 \param node The node to select
2647 \param incremental If true, add to selection, otherwise deselect others
2648 \param override If true, always select this node, otherwise toggle selected status
2649 */
2650 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2651 {
2652 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2654 if (incremental) {
2655 if (override) {
2656 if (!g_list_find(nodepath->selected, node)) {
2657 nodepath->selected = g_list_prepend(nodepath->selected, node);
2658 }
2659 sp_node_set_selected(node, TRUE);
2660 } else { // toggle
2661 if (node->selected) {
2662 g_assert(g_list_find(nodepath->selected, node));
2663 nodepath->selected = g_list_remove(nodepath->selected, node);
2664 } else {
2665 g_assert(!g_list_find(nodepath->selected, node));
2666 nodepath->selected = g_list_prepend(nodepath->selected, node);
2667 }
2668 sp_node_set_selected(node, !node->selected);
2669 }
2670 } else {
2671 sp_nodepath_deselect(nodepath);
2672 nodepath->selected = g_list_prepend(nodepath->selected, node);
2673 sp_node_set_selected(node, TRUE);
2674 }
2676 sp_nodepath_update_statusbar(nodepath);
2677 }
2680 /**
2681 \brief Deselect all nodes in the nodepath
2682 */
2683 void
2684 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2685 {
2686 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2688 while (nodepath->selected) {
2689 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2690 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2691 }
2692 sp_nodepath_update_statusbar(nodepath);
2693 }
2695 /**
2696 \brief Select or invert selection of all nodes in the nodepath
2697 */
2698 void
2699 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2700 {
2701 if (!nodepath) return;
2703 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2704 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2705 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2706 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2707 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2708 }
2709 }
2710 }
2712 /**
2713 * If nothing selected, does the same as sp_nodepath_select_all();
2714 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2715 * (i.e., similar to "select all in layer", with the "selected" subpaths
2716 * being treated as "layers" in the path).
2717 */
2718 void
2719 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2720 {
2721 if (!nodepath) return;
2723 if (g_list_length (nodepath->selected) == 0) {
2724 sp_nodepath_select_all (nodepath, invert);
2725 return;
2726 }
2728 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2729 GSList *subpaths = NULL;
2731 for (GList *l = copy; l != NULL; l = l->next) {
2732 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2733 Inkscape::NodePath::SubPath *subpath = n->subpath;
2734 if (!g_slist_find (subpaths, subpath))
2735 subpaths = g_slist_prepend (subpaths, subpath);
2736 }
2738 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2739 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2740 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2741 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2742 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2743 }
2744 }
2746 g_slist_free (subpaths);
2747 g_list_free (copy);
2748 }
2750 /**
2751 * \brief Select the node after the last selected; if none is selected,
2752 * select the first within path.
2753 */
2754 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2755 {
2756 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2758 Inkscape::NodePath::Node *last = NULL;
2759 if (nodepath->selected) {
2760 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2761 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2762 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2763 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2764 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2765 if (node->selected) {
2766 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2767 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2768 if (spl->next) { // there's a next subpath
2769 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2770 last = subpath_next->first;
2771 } else if (spl->prev) { // there's a previous subpath
2772 last = NULL; // to be set later to the first node of first subpath
2773 } else {
2774 last = node->n.other;
2775 }
2776 } else {
2777 last = node->n.other;
2778 }
2779 } else {
2780 if (node->n.other) {
2781 last = node->n.other;
2782 } else {
2783 if (spl->next) { // there's a next subpath
2784 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2785 last = subpath_next->first;
2786 } else if (spl->prev) { // there's a previous subpath
2787 last = NULL; // to be set later to the first node of first subpath
2788 } else {
2789 last = (Inkscape::NodePath::Node *) subpath->first;
2790 }
2791 }
2792 }
2793 }
2794 }
2795 }
2796 sp_nodepath_deselect(nodepath);
2797 }
2799 if (last) { // there's at least one more node after selected
2800 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2801 } else { // no more nodes, select the first one in first subpath
2802 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2803 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2804 }
2805 }
2807 /**
2808 * \brief Select the node before the first selected; if none is selected,
2809 * select the last within path
2810 */
2811 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2812 {
2813 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2815 Inkscape::NodePath::Node *last = NULL;
2816 if (nodepath->selected) {
2817 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2818 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2819 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2820 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2821 if (node->selected) {
2822 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2823 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2824 if (spl->prev) { // there's a prev subpath
2825 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2826 last = subpath_prev->last;
2827 } else if (spl->next) { // there's a next subpath
2828 last = NULL; // to be set later to the last node of last subpath
2829 } else {
2830 last = node->p.other;
2831 }
2832 } else {
2833 last = node->p.other;
2834 }
2835 } else {
2836 if (node->p.other) {
2837 last = node->p.other;
2838 } else {
2839 if (spl->prev) { // there's a prev subpath
2840 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2841 last = subpath_prev->last;
2842 } else if (spl->next) { // there's a next subpath
2843 last = NULL; // to be set later to the last node of last subpath
2844 } else {
2845 last = (Inkscape::NodePath::Node *) subpath->last;
2846 }
2847 }
2848 }
2849 }
2850 }
2851 }
2852 sp_nodepath_deselect(nodepath);
2853 }
2855 if (last) { // there's at least one more node before selected
2856 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2857 } else { // no more nodes, select the last one in last subpath
2858 GList *spl = g_list_last(nodepath->subpaths);
2859 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2860 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2861 }
2862 }
2864 /**
2865 * \brief Select all nodes that are within the rectangle.
2866 */
2867 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2868 {
2869 if (!incremental) {
2870 sp_nodepath_deselect(nodepath);
2871 }
2873 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2874 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2875 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2876 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2878 if (b.contains(node->pos)) {
2879 sp_nodepath_node_select(node, TRUE, TRUE);
2880 }
2881 }
2882 }
2883 }
2886 void
2887 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2888 {
2889 g_assert (n);
2890 g_assert (nodepath);
2891 g_assert (n->subpath->nodepath == nodepath);
2893 if (g_list_length (nodepath->selected) == 0) {
2894 if (grow > 0) {
2895 sp_nodepath_node_select(n, TRUE, TRUE);
2896 }
2897 return;
2898 }
2900 if (g_list_length (nodepath->selected) == 1) {
2901 if (grow < 0) {
2902 sp_nodepath_deselect (nodepath);
2903 return;
2904 }
2905 }
2907 double n_sel_range = 0, p_sel_range = 0;
2908 Inkscape::NodePath::Node *farthest_n_node = n;
2909 Inkscape::NodePath::Node *farthest_p_node = n;
2911 // Calculate ranges
2912 {
2913 double n_range = 0, p_range = 0;
2914 bool n_going = true, p_going = true;
2915 Inkscape::NodePath::Node *n_node = n;
2916 Inkscape::NodePath::Node *p_node = n;
2917 do {
2918 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2919 if (n_node && n_going)
2920 n_node = n_node->n.other;
2921 if (n_node == NULL) {
2922 n_going = false;
2923 } else {
2924 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2925 if (n_node->selected) {
2926 n_sel_range = n_range;
2927 farthest_n_node = n_node;
2928 }
2929 if (n_node == p_node) {
2930 n_going = false;
2931 p_going = false;
2932 }
2933 }
2934 if (p_node && p_going)
2935 p_node = p_node->p.other;
2936 if (p_node == NULL) {
2937 p_going = false;
2938 } else {
2939 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2940 if (p_node->selected) {
2941 p_sel_range = p_range;
2942 farthest_p_node = p_node;
2943 }
2944 if (p_node == n_node) {
2945 n_going = false;
2946 p_going = false;
2947 }
2948 }
2949 } while (n_going || p_going);
2950 }
2952 if (grow > 0) {
2953 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2954 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2955 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2956 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2957 }
2958 } else {
2959 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2960 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2961 } else if (farthest_p_node && farthest_p_node->selected) {
2962 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2963 }
2964 }
2965 }
2967 void
2968 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2969 {
2970 g_assert (n);
2971 g_assert (nodepath);
2972 g_assert (n->subpath->nodepath == nodepath);
2974 if (g_list_length (nodepath->selected) == 0) {
2975 if (grow > 0) {
2976 sp_nodepath_node_select(n, TRUE, TRUE);
2977 }
2978 return;
2979 }
2981 if (g_list_length (nodepath->selected) == 1) {
2982 if (grow < 0) {
2983 sp_nodepath_deselect (nodepath);
2984 return;
2985 }
2986 }
2988 Inkscape::NodePath::Node *farthest_selected = NULL;
2989 double farthest_dist = 0;
2991 Inkscape::NodePath::Node *closest_unselected = NULL;
2992 double closest_dist = NR_HUGE;
2994 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2995 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2996 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2997 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2998 if (node == n)
2999 continue;
3000 if (node->selected) {
3001 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3002 farthest_dist = NR::L2(node->pos - n->pos);
3003 farthest_selected = node;
3004 }
3005 } else {
3006 if (NR::L2(node->pos - n->pos) < closest_dist) {
3007 closest_dist = NR::L2(node->pos - n->pos);
3008 closest_unselected = node;
3009 }
3010 }
3011 }
3012 }
3014 if (grow > 0) {
3015 if (closest_unselected) {
3016 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3017 }
3018 } else {
3019 if (farthest_selected) {
3020 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3021 }
3022 }
3023 }
3026 /**
3027 \brief Saves all nodes' and handles' current positions in their origin members
3028 */
3029 void
3030 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3031 {
3032 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3033 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3034 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3035 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3036 n->origin = n->pos;
3037 n->p.origin = n->p.pos;
3038 n->n.origin = n->n.pos;
3039 }
3040 }
3041 }
3043 /**
3044 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3045 */
3046 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3047 {
3048 if (!nodepath->selected) {
3049 return NULL;
3050 }
3052 GList *r = NULL;
3053 guint i = 0;
3054 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3055 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3056 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3057 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3058 i++;
3059 if (node->selected) {
3060 r = g_list_append(r, GINT_TO_POINTER(i));
3061 }
3062 }
3063 }
3064 return r;
3065 }
3067 /**
3068 \brief Restores selection by selecting nodes whose positions are in the list
3069 */
3070 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3071 {
3072 sp_nodepath_deselect(nodepath);
3074 guint i = 0;
3075 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3076 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3077 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3078 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3079 i++;
3080 if (g_list_find(r, GINT_TO_POINTER(i))) {
3081 sp_nodepath_node_select(node, TRUE, TRUE);
3082 }
3083 }
3084 }
3085 }
3088 /**
3089 \brief Adjusts handle according to node type and line code.
3090 */
3091 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3092 {
3093 g_assert(node);
3095 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3096 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3098 // nothing to do if we are an end node
3099 if (me->other == NULL) return;
3100 if (other->other == NULL) return;
3102 // nothing to do if we are a cusp node
3103 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3105 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3106 NRPathcode mecode;
3107 if (which_adjust == 1) {
3108 mecode = (NRPathcode)me->other->code;
3109 } else {
3110 mecode = (NRPathcode)node->code;
3111 }
3112 if (mecode == NR_LINETO) return;
3114 if (sp_node_side_is_line(node, other)) {
3115 // other is a line, and we are either smooth or symm
3116 Inkscape::NodePath::Node *othernode = other->other;
3117 double len = NR::L2(me->pos - node->pos);
3118 NR::Point delta = node->pos - othernode->pos;
3119 double linelen = NR::L2(delta);
3120 if (linelen < 1e-18)
3121 return;
3122 me->pos = node->pos + (len / linelen)*delta;
3123 return;
3124 }
3126 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3127 // symmetrize
3128 me->pos = 2 * node->pos - other->pos;
3129 return;
3130 } else {
3131 // smoothify
3132 double len = NR::L2(me->pos - node->pos);
3133 NR::Point delta = other->pos - node->pos;
3134 double otherlen = NR::L2(delta);
3135 if (otherlen < 1e-18) return;
3136 me->pos = node->pos - (len / otherlen) * delta;
3137 }
3138 }
3140 /**
3141 \brief Adjusts both handles according to node type and line code
3142 */
3143 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3144 {
3145 g_assert(node);
3147 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3149 /* we are either smooth or symm */
3151 if (node->p.other == NULL) return;
3152 if (node->n.other == NULL) return;
3154 if (sp_node_side_is_line(node, &node->p)) {
3155 sp_node_adjust_handle(node, 1);
3156 return;
3157 }
3159 if (sp_node_side_is_line(node, &node->n)) {
3160 sp_node_adjust_handle(node, -1);
3161 return;
3162 }
3164 /* both are curves */
3165 NR::Point const delta( node->n.pos - node->p.pos );
3167 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3168 node->p.pos = node->pos - delta / 2;
3169 node->n.pos = node->pos + delta / 2;
3170 return;
3171 }
3173 /* We are smooth */
3174 double plen = NR::L2(node->p.pos - node->pos);
3175 if (plen < 1e-18) return;
3176 double nlen = NR::L2(node->n.pos - node->pos);
3177 if (nlen < 1e-18) return;
3178 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3179 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3180 }
3182 /**
3183 * Node event callback.
3184 */
3185 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3186 {
3187 gboolean ret = FALSE;
3188 switch (event->type) {
3189 case GDK_ENTER_NOTIFY:
3190 Inkscape::NodePath::Path::active_node = n;
3191 break;
3192 case GDK_LEAVE_NOTIFY:
3193 Inkscape::NodePath::Path::active_node = NULL;
3194 break;
3195 case GDK_SCROLL:
3196 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3197 switch (event->scroll.direction) {
3198 case GDK_SCROLL_UP:
3199 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3200 break;
3201 case GDK_SCROLL_DOWN:
3202 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3203 break;
3204 default:
3205 break;
3206 }
3207 ret = TRUE;
3208 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3209 switch (event->scroll.direction) {
3210 case GDK_SCROLL_UP:
3211 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3212 break;
3213 case GDK_SCROLL_DOWN:
3214 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3215 break;
3216 default:
3217 break;
3218 }
3219 ret = TRUE;
3220 }
3221 break;
3222 case GDK_KEY_PRESS:
3223 switch (get_group0_keyval (&event->key)) {
3224 case GDK_space:
3225 if (event->key.state & GDK_BUTTON1_MASK) {
3226 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3227 stamp_repr(nodepath);
3228 ret = TRUE;
3229 }
3230 break;
3231 case GDK_Page_Up:
3232 if (event->key.state & GDK_CONTROL_MASK) {
3233 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3234 } else {
3235 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3236 }
3237 break;
3238 case GDK_Page_Down:
3239 if (event->key.state & GDK_CONTROL_MASK) {
3240 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3241 } else {
3242 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3243 }
3244 break;
3245 default:
3246 break;
3247 }
3248 break;
3249 default:
3250 break;
3251 }
3253 return ret;
3254 }
3256 /**
3257 * Handle keypress on node; directly called.
3258 */
3259 gboolean node_key(GdkEvent *event)
3260 {
3261 Inkscape::NodePath::Path *np;
3263 // there is no way to verify nodes so set active_node to nil when deleting!!
3264 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3266 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3267 gint ret = FALSE;
3268 switch (get_group0_keyval (&event->key)) {
3269 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3270 case GDK_BackSpace:
3271 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3272 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3273 sp_nodepath_update_repr(np, _("Delete node"));
3274 Inkscape::NodePath::Path::active_node = NULL;
3275 ret = TRUE;
3276 break;
3277 case GDK_c:
3278 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3279 ret = TRUE;
3280 break;
3281 case GDK_s:
3282 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3283 ret = TRUE;
3284 break;
3285 case GDK_y:
3286 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3287 ret = TRUE;
3288 break;
3289 case GDK_b:
3290 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3291 ret = TRUE;
3292 break;
3293 }
3294 return ret;
3295 }
3296 return FALSE;
3297 }
3299 /**
3300 * Mouseclick on node callback.
3301 */
3302 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3303 {
3304 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3306 if (state & GDK_CONTROL_MASK) {
3307 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3309 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3310 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3311 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3312 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3313 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3314 } else {
3315 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3316 }
3317 sp_nodepath_update_repr(nodepath, _("Change node type"));
3318 sp_nodepath_update_statusbar(nodepath);
3320 } else { //ctrl+alt+click: delete node
3321 GList *node_to_delete = NULL;
3322 node_to_delete = g_list_append(node_to_delete, n);
3323 sp_node_delete_preserve(node_to_delete);
3324 }
3326 } else {
3327 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3328 }
3329 }
3331 /**
3332 * Mouse grabbed node callback.
3333 */
3334 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3335 {
3336 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3338 if (!n->selected) {
3339 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3340 }
3342 n->is_dragging = true;
3343 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3345 sp_nodepath_remember_origins (n->subpath->nodepath);
3346 }
3348 /**
3349 * Mouse ungrabbed node callback.
3350 */
3351 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3352 {
3353 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3355 n->dragging_out = NULL;
3356 n->is_dragging = false;
3357 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3359 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3360 }
3362 /**
3363 * The point on a line, given by its angle, closest to the given point.
3364 * \param p A point.
3365 * \param a Angle of the line; it is assumed to go through coordinate origin.
3366 * \param closest Pointer to the point struct where the result is stored.
3367 * \todo FIXME: use dot product perhaps?
3368 */
3369 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3370 {
3371 if (a == HUGE_VAL) { // vertical
3372 *closest = NR::Point(0, (*p)[NR::Y]);
3373 } else {
3374 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3375 (*closest)[NR::Y] = a * (*closest)[NR::X];
3376 }
3377 }
3379 /**
3380 * Distance from the point to a line given by its angle.
3381 * \param p A point.
3382 * \param a Angle of the line; it is assumed to go through coordinate origin.
3383 */
3384 static double point_line_distance(NR::Point *p, double a)
3385 {
3386 NR::Point c;
3387 point_line_closest(p, a, &c);
3388 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]));
3389 }
3391 /**
3392 * Callback for node "request" signal.
3393 * \todo fixme: This goes to "moved" event? (lauris)
3394 */
3395 static gboolean
3396 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3397 {
3398 double yn, xn, yp, xp;
3399 double an, ap, na, pa;
3400 double d_an, d_ap, d_na, d_pa;
3401 gboolean collinear = FALSE;
3402 NR::Point c;
3403 NR::Point pr;
3405 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3407 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3409 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3410 if ( (!n->subpath->nodepath->straight_path) &&
3411 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3412 || n->dragging_out ) )
3413 {
3414 NR::Point mouse = (*p);
3416 if (!n->dragging_out) {
3417 // This is the first drag-out event; find out which handle to drag out
3418 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3419 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3421 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3422 return FALSE;
3424 Inkscape::NodePath::NodeSide *opposite;
3425 if (appr_p > appr_n) { // closer to p
3426 n->dragging_out = &n->p;
3427 opposite = &n->n;
3428 n->code = NR_CURVETO;
3429 } else if (appr_p < appr_n) { // closer to n
3430 n->dragging_out = &n->n;
3431 opposite = &n->p;
3432 n->n.other->code = NR_CURVETO;
3433 } else { // p and n nodes are the same
3434 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3435 n->dragging_out = &n->p;
3436 opposite = &n->n;
3437 n->code = NR_CURVETO;
3438 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3439 n->dragging_out = &n->n;
3440 opposite = &n->p;
3441 n->n.other->code = NR_CURVETO;
3442 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3443 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);
3444 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);
3445 if (appr_other_p > appr_other_n) { // closer to other's p handle
3446 n->dragging_out = &n->n;
3447 opposite = &n->p;
3448 n->n.other->code = NR_CURVETO;
3449 } else { // closer to other's n handle
3450 n->dragging_out = &n->p;
3451 opposite = &n->n;
3452 n->code = NR_CURVETO;
3453 }
3454 }
3455 }
3457 // if there's another handle, make sure the one we drag out starts parallel to it
3458 if (opposite->pos != n->pos) {
3459 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3460 }
3462 // knots might not be created yet!
3463 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3464 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3465 }
3467 // pass this on to the handle-moved callback
3468 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3469 sp_node_update_handles(n);
3470 return TRUE;
3471 }
3473 if (state & GDK_CONTROL_MASK) { // constrained motion
3475 // calculate relative distances of handles
3476 // n handle:
3477 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3478 xn = n->n.pos[NR::X] - n->pos[NR::X];
3479 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3480 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3481 if (n->n.other) { // if there is the next point
3482 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3483 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3484 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3485 }
3486 }
3487 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3488 if (yn < 0) { xn = -xn; yn = -yn; }
3490 // p handle:
3491 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3492 xp = n->p.pos[NR::X] - n->pos[NR::X];
3493 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3494 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3495 if (n->p.other) {
3496 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3497 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3498 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3499 }
3500 }
3501 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3502 if (yp < 0) { xp = -xp; yp = -yp; }
3504 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3505 // sliding on handles, only if at least one of the handles is non-vertical
3506 // (otherwise it's the same as ctrl+drag anyway)
3508 // calculate angles of the handles
3509 if (xn == 0) {
3510 if (yn == 0) { // no handle, consider it the continuation of the other one
3511 an = 0;
3512 collinear = TRUE;
3513 }
3514 else an = 0; // vertical; set the angle to horizontal
3515 } else an = yn/xn;
3517 if (xp == 0) {
3518 if (yp == 0) { // no handle, consider it the continuation of the other one
3519 ap = an;
3520 }
3521 else ap = 0; // vertical; set the angle to horizontal
3522 } else ap = yp/xp;
3524 if (collinear) an = ap;
3526 // angles of the perpendiculars; HUGE_VAL means vertical
3527 if (an == 0) na = HUGE_VAL; else na = -1/an;
3528 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3530 // mouse point relative to the node's original pos
3531 pr = (*p) - n->origin;
3533 // distances to the four lines (two handles and two perpendiculars)
3534 d_an = point_line_distance(&pr, an);
3535 d_na = point_line_distance(&pr, na);
3536 d_ap = point_line_distance(&pr, ap);
3537 d_pa = point_line_distance(&pr, pa);
3539 // find out which line is the closest, save its closest point in c
3540 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3541 point_line_closest(&pr, an, &c);
3542 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3543 point_line_closest(&pr, ap, &c);
3544 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3545 point_line_closest(&pr, na, &c);
3546 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3547 point_line_closest(&pr, pa, &c);
3548 }
3550 // move the node to the closest point
3551 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3552 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3553 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3554 true);
3556 } else { // constraining to hor/vert
3558 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3559 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3560 (*p)[NR::X] - n->pos[NR::X],
3561 n->origin[NR::Y] - n->pos[NR::Y],
3562 true,
3563 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3564 } else { // snap to vert
3565 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3566 n->origin[NR::X] - n->pos[NR::X],
3567 (*p)[NR::Y] - n->pos[NR::Y],
3568 true,
3569 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3570 }
3571 }
3572 } else { // move freely
3573 if (n->is_dragging) {
3574 if (state & GDK_MOD1_MASK) { // sculpt
3575 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3576 } else {
3577 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3578 (*p)[NR::X] - n->pos[NR::X],
3579 (*p)[NR::Y] - n->pos[NR::Y],
3580 (state & GDK_SHIFT_MASK) == 0);
3581 }
3582 }
3583 }
3585 n->subpath->nodepath->desktop->scroll_to_point(p);
3587 return TRUE;
3588 }
3590 /**
3591 * Node handle clicked callback.
3592 */
3593 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3594 {
3595 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3597 if (state & GDK_CONTROL_MASK) { // "delete" handle
3598 if (n->p.knot == knot) {
3599 n->p.pos = n->pos;
3600 } else if (n->n.knot == knot) {
3601 n->n.pos = n->pos;
3602 }
3603 sp_node_update_handles(n);
3604 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3605 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3606 sp_nodepath_update_statusbar(nodepath);
3608 } else { // just select or add to selection, depending in Shift
3609 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3610 }
3611 }
3613 /**
3614 * Node handle grabbed callback.
3615 */
3616 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3617 {
3618 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3620 if (!n->selected) {
3621 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3622 }
3624 // remember the origin point of the handle
3625 if (n->p.knot == knot) {
3626 n->p.origin_radial = n->p.pos - n->pos;
3627 } else if (n->n.knot == knot) {
3628 n->n.origin_radial = n->n.pos - n->pos;
3629 } else {
3630 g_assert_not_reached();
3631 }
3633 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3634 }
3636 /**
3637 * Node handle ungrabbed callback.
3638 */
3639 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3640 {
3641 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3643 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3644 if (n->p.knot == knot) {
3645 n->p.origin_radial.a = 0;
3646 sp_knot_set_position(knot, &n->p.pos, state);
3647 } else if (n->n.knot == knot) {
3648 n->n.origin_radial.a = 0;
3649 sp_knot_set_position(knot, &n->n.pos, state);
3650 } else {
3651 g_assert_not_reached();
3652 }
3654 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3655 }
3657 /**
3658 * Node handle "request" signal callback.
3659 */
3660 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3661 {
3662 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3664 Inkscape::NodePath::NodeSide *me, *opposite;
3665 gint which;
3666 if (n->p.knot == knot) {
3667 me = &n->p;
3668 opposite = &n->n;
3669 which = -1;
3670 } else if (n->n.knot == knot) {
3671 me = &n->n;
3672 opposite = &n->p;
3673 which = 1;
3674 } else {
3675 me = opposite = NULL;
3676 which = 0;
3677 g_assert_not_reached();
3678 }
3680 SPDesktop *desktop = n->subpath->nodepath->desktop;
3681 SnapManager &m = desktop->namedview->snap_manager;
3682 m.setup(desktop, n->subpath->nodepath->item);
3683 Inkscape::SnappedPoint s ;
3685 Inkscape::NodePath::Node *othernode = opposite->other;
3686 if (othernode) {
3687 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3688 /* We are smooth node adjacent with line */
3689 NR::Point const delta = *p - n->pos;
3690 NR::Coord const len = NR::L2(delta);
3691 Inkscape::NodePath::Node *othernode = opposite->other;
3692 NR::Point const ndelta = n->pos - othernode->pos;
3693 NR::Coord const linelen = NR::L2(ndelta);
3694 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3695 NR::Coord const scal = dot(delta, ndelta) / linelen;
3696 (*p) = n->pos + (scal / linelen) * ndelta;
3697 }
3698 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3699 } else {
3700 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3701 }
3702 } else {
3703 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3704 }
3706 s.getPoint(*p);
3708 sp_node_adjust_handle(n, -which);
3710 return FALSE;
3711 }
3713 /**
3714 * Node handle moved callback.
3715 */
3716 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3717 {
3718 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3720 Inkscape::NodePath::NodeSide *me;
3721 Inkscape::NodePath::NodeSide *other;
3722 if (n->p.knot == knot) {
3723 me = &n->p;
3724 other = &n->n;
3725 } else if (n->n.knot == knot) {
3726 me = &n->n;
3727 other = &n->p;
3728 } else {
3729 me = NULL;
3730 other = NULL;
3731 g_assert_not_reached();
3732 }
3734 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3735 Radial rme(me->pos - n->pos);
3736 Radial rother(other->pos - n->pos);
3737 Radial rnew(*p - n->pos);
3739 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3740 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3741 /* 0 interpreted as "no snapping". */
3743 // 1. Snap to the closest PI/snaps angle, starting from zero.
3744 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3746 // 2. Snap to the original angle, its opposite and perpendiculars
3747 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3748 /* The closest PI/2 angle, starting from original angle */
3749 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3751 // Snap to the closest.
3752 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3753 ? a_snapped
3754 : a_ortho );
3755 }
3757 // 3. Snap to the angle of the opposite line, if any
3758 Inkscape::NodePath::Node *othernode = other->other;
3759 if (othernode) {
3760 NR::Point other_to_snap(0,0);
3761 if (sp_node_side_is_line(n, other)) {
3762 other_to_snap = othernode->pos - n->pos;
3763 } else {
3764 other_to_snap = other->pos - n->pos;
3765 }
3766 if (NR::L2(other_to_snap) > 1e-3) {
3767 Radial rother_to_snap(other_to_snap);
3768 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3769 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3771 // Snap to the closest.
3772 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3773 ? a_snapped
3774 : a_oppo );
3775 }
3776 }
3778 rnew.a = a_snapped;
3779 }
3781 if (state & GDK_MOD1_MASK) {
3782 // lock handle length
3783 rnew.r = me->origin_radial.r;
3784 }
3786 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3787 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3788 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3789 rother.a += rnew.a - rme.a;
3790 other->pos = NR::Point(rother) + n->pos;
3791 if (other->knot) {
3792 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3793 sp_knot_moveto(other->knot, &other->pos);
3794 }
3795 }
3797 me->pos = NR::Point(rnew) + n->pos;
3798 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3800 // move knot, but without emitting the signal:
3801 // we cannot emit a "moved" signal because we're now processing it
3802 sp_knot_moveto(me->knot, &(me->pos));
3804 update_object(n->subpath->nodepath);
3806 /* status text */
3807 SPDesktop *desktop = n->subpath->nodepath->desktop;
3808 if (!desktop) return;
3809 SPEventContext *ec = desktop->event_context;
3810 if (!ec) return;
3811 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3812 if (!mc) return;
3814 double degrees = 180 / M_PI * rnew.a;
3815 if (degrees > 180) degrees -= 360;
3816 if (degrees < -180) degrees += 360;
3817 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3818 degrees = angle_to_compass (degrees);
3820 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3822 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3823 _("<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);
3825 g_string_free(length, TRUE);
3826 }
3828 /**
3829 * Node handle event callback.
3830 */
3831 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3832 {
3833 gboolean ret = FALSE;
3834 switch (event->type) {
3835 case GDK_KEY_PRESS:
3836 switch (get_group0_keyval (&event->key)) {
3837 case GDK_space:
3838 if (event->key.state & GDK_BUTTON1_MASK) {
3839 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3840 stamp_repr(nodepath);
3841 ret = TRUE;
3842 }
3843 break;
3844 default:
3845 break;
3846 }
3847 break;
3848 case GDK_ENTER_NOTIFY:
3849 // we use an experimentally determined threshold that seems to work fine
3850 if (NR::L2(n->pos - knot->pos) < 0.75)
3851 Inkscape::NodePath::Path::active_node = n;
3852 break;
3853 case GDK_LEAVE_NOTIFY:
3854 // we use an experimentally determined threshold that seems to work fine
3855 if (NR::L2(n->pos - knot->pos) < 0.75)
3856 Inkscape::NodePath::Path::active_node = NULL;
3857 break;
3858 default:
3859 break;
3860 }
3862 return ret;
3863 }
3865 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3866 Radial &rme, Radial &rother, gboolean const both)
3867 {
3868 rme.a += angle;
3869 if ( both
3870 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3871 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3872 {
3873 rother.a += angle;
3874 }
3875 }
3877 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3878 Radial &rme, Radial &rother, gboolean const both)
3879 {
3880 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3882 gdouble r;
3883 if ( both
3884 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3885 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3886 {
3887 r = MAX(rme.r, rother.r);
3888 } else {
3889 r = rme.r;
3890 }
3892 gdouble const weird_angle = atan2(norm_angle, r);
3893 /* Bulia says norm_angle is just the visible distance that the
3894 * object's end must travel on the screen. Left as 'angle' for want of
3895 * a better name.*/
3897 rme.a += weird_angle;
3898 if ( both
3899 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3900 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3901 {
3902 rother.a += weird_angle;
3903 }
3904 }
3906 /**
3907 * Rotate one node.
3908 */
3909 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3910 {
3911 Inkscape::NodePath::NodeSide *me, *other;
3912 bool both = false;
3914 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3915 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3917 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3918 me = &(n->p);
3919 other = &(n->n);
3920 } else if (!n->p.other) {
3921 me = &(n->n);
3922 other = &(n->p);
3923 } else {
3924 if (which > 0) { // right handle
3925 if (xn > xp) {
3926 me = &(n->n);
3927 other = &(n->p);
3928 } else {
3929 me = &(n->p);
3930 other = &(n->n);
3931 }
3932 } else if (which < 0){ // left handle
3933 if (xn <= xp) {
3934 me = &(n->n);
3935 other = &(n->p);
3936 } else {
3937 me = &(n->p);
3938 other = &(n->n);
3939 }
3940 } else { // both handles
3941 me = &(n->n);
3942 other = &(n->p);
3943 both = true;
3944 }
3945 }
3947 Radial rme(me->pos - n->pos);
3948 Radial rother(other->pos - n->pos);
3950 if (screen) {
3951 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3952 } else {
3953 node_rotate_one_internal (*n, angle, rme, rother, both);
3954 }
3956 me->pos = n->pos + NR::Point(rme);
3958 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3959 other->pos = n->pos + NR::Point(rother);
3960 }
3962 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3963 // so here we just move all the knots without emitting move signals, for speed
3964 sp_node_update_handles(n, false);
3965 }
3967 /**
3968 * Rotate selected nodes.
3969 */
3970 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3971 {
3972 if (!nodepath || !nodepath->selected) return;
3974 if (g_list_length(nodepath->selected) == 1) {
3975 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3976 node_rotate_one (n, angle, which, screen);
3977 } else {
3978 // rotate as an object:
3980 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3981 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3982 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3983 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3984 box.expandTo (n->pos); // contain all selected nodes
3985 }
3987 gdouble rot;
3988 if (screen) {
3989 gdouble const zoom = nodepath->desktop->current_zoom();
3990 gdouble const zmove = angle / zoom;
3991 gdouble const r = NR::L2(box.max() - box.midpoint());
3992 rot = atan2(zmove, r);
3993 } else {
3994 rot = angle;
3995 }
3997 NR::Point rot_center;
3998 if (Inkscape::NodePath::Path::active_node == NULL)
3999 rot_center = box.midpoint();
4000 else
4001 rot_center = Inkscape::NodePath::Path::active_node->pos;
4003 NR::Matrix t =
4004 NR::Matrix (NR::translate(-rot_center)) *
4005 NR::Matrix (NR::rotate(rot)) *
4006 NR::Matrix (NR::translate(rot_center));
4008 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4009 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4010 n->pos *= t;
4011 n->n.pos *= t;
4012 n->p.pos *= t;
4013 sp_node_update_handles(n, false);
4014 }
4015 }
4017 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4018 }
4020 /**
4021 * Scale one node.
4022 */
4023 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4024 {
4025 bool both = false;
4026 Inkscape::NodePath::NodeSide *me, *other;
4028 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4029 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4031 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4032 me = &(n->p);
4033 other = &(n->n);
4034 n->code = NR_CURVETO;
4035 } else if (!n->p.other) {
4036 me = &(n->n);
4037 other = &(n->p);
4038 if (n->n.other)
4039 n->n.other->code = NR_CURVETO;
4040 } else {
4041 if (which > 0) { // right handle
4042 if (xn > xp) {
4043 me = &(n->n);
4044 other = &(n->p);
4045 if (n->n.other)
4046 n->n.other->code = NR_CURVETO;
4047 } else {
4048 me = &(n->p);
4049 other = &(n->n);
4050 n->code = NR_CURVETO;
4051 }
4052 } else if (which < 0){ // left handle
4053 if (xn <= xp) {
4054 me = &(n->n);
4055 other = &(n->p);
4056 if (n->n.other)
4057 n->n.other->code = NR_CURVETO;
4058 } else {
4059 me = &(n->p);
4060 other = &(n->n);
4061 n->code = NR_CURVETO;
4062 }
4063 } else { // both handles
4064 me = &(n->n);
4065 other = &(n->p);
4066 both = true;
4067 n->code = NR_CURVETO;
4068 if (n->n.other)
4069 n->n.other->code = NR_CURVETO;
4070 }
4071 }
4073 Radial rme(me->pos - n->pos);
4074 Radial rother(other->pos - n->pos);
4076 rme.r += grow;
4077 if (rme.r < 0) rme.r = 0;
4078 if (rme.a == HUGE_VAL) {
4079 if (me->other) { // if direction is unknown, initialize it towards the next node
4080 Radial rme_next(me->other->pos - n->pos);
4081 rme.a = rme_next.a;
4082 } else { // if there's no next, initialize to 0
4083 rme.a = 0;
4084 }
4085 }
4086 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4087 rother.r += grow;
4088 if (rother.r < 0) rother.r = 0;
4089 if (rother.a == HUGE_VAL) {
4090 rother.a = rme.a + M_PI;
4091 }
4092 }
4094 me->pos = n->pos + NR::Point(rme);
4096 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4097 other->pos = n->pos + NR::Point(rother);
4098 }
4100 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4101 // so here we just move all the knots without emitting move signals, for speed
4102 sp_node_update_handles(n, false);
4103 }
4105 /**
4106 * Scale selected nodes.
4107 */
4108 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4109 {
4110 if (!nodepath || !nodepath->selected) return;
4112 if (g_list_length(nodepath->selected) == 1) {
4113 // scale handles of the single selected node
4114 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4115 node_scale_one (n, grow, which);
4116 } else {
4117 // scale nodes as an "object":
4119 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4120 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4121 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4122 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4123 box.expandTo (n->pos); // contain all selected nodes
4124 }
4126 double scale = (box.maxExtent() + grow)/box.maxExtent();
4128 NR::Point scale_center;
4129 if (Inkscape::NodePath::Path::active_node == NULL)
4130 scale_center = box.midpoint();
4131 else
4132 scale_center = Inkscape::NodePath::Path::active_node->pos;
4134 NR::Matrix t =
4135 NR::Matrix (NR::translate(-scale_center)) *
4136 NR::Matrix (NR::scale(scale, scale)) *
4137 NR::Matrix (NR::translate(scale_center));
4139 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4140 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4141 n->pos *= t;
4142 n->n.pos *= t;
4143 n->p.pos *= t;
4144 sp_node_update_handles(n, false);
4145 }
4146 }
4148 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4149 }
4151 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4152 {
4153 if (!nodepath) return;
4154 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4155 }
4157 /**
4158 * Flip selected nodes horizontally/vertically.
4159 */
4160 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4161 {
4162 if (!nodepath || !nodepath->selected) return;
4164 if (g_list_length(nodepath->selected) == 1 && !center) {
4165 // flip handles of the single selected node
4166 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4167 double temp = n->p.pos[axis];
4168 n->p.pos[axis] = n->n.pos[axis];
4169 n->n.pos[axis] = temp;
4170 sp_node_update_handles(n, false);
4171 } else {
4172 // scale nodes as an "object":
4174 NR::Rect box = sp_node_selected_bbox (nodepath);
4175 if (!center) {
4176 center = box.midpoint();
4177 }
4178 NR::Matrix t =
4179 NR::Matrix (NR::translate(- *center)) *
4180 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4181 NR::Matrix (NR::translate(*center));
4183 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4184 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4185 n->pos *= t;
4186 n->n.pos *= t;
4187 n->p.pos *= t;
4188 sp_node_update_handles(n, false);
4189 }
4190 }
4192 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4193 }
4195 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4196 {
4197 g_assert (nodepath->selected);
4199 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4200 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4201 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4202 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4203 box.expandTo (n->pos); // contain all selected nodes
4204 }
4205 return box;
4206 }
4208 //-----------------------------------------------
4209 /**
4210 * Return new subpath under given nodepath.
4211 */
4212 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4213 {
4214 g_assert(nodepath);
4215 g_assert(nodepath->desktop);
4217 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4219 s->nodepath = nodepath;
4220 s->closed = FALSE;
4221 s->nodes = NULL;
4222 s->first = NULL;
4223 s->last = NULL;
4225 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4226 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4227 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4229 return s;
4230 }
4232 /**
4233 * Destroy nodes in subpath, then subpath itself.
4234 */
4235 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4236 {
4237 g_assert(subpath);
4238 g_assert(subpath->nodepath);
4239 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4241 while (subpath->nodes) {
4242 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4243 }
4245 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4247 g_free(subpath);
4248 }
4250 /**
4251 * Link head to tail in subpath.
4252 */
4253 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4254 {
4255 g_assert(!sp->closed);
4256 g_assert(sp->last != sp->first);
4257 g_assert(sp->first->code == NR_MOVETO);
4259 sp->closed = TRUE;
4261 //Link the head to the tail
4262 sp->first->p.other = sp->last;
4263 sp->last->n.other = sp->first;
4264 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4265 sp->first = sp->last;
4267 //Remove the extra end node
4268 sp_nodepath_node_destroy(sp->last->n.other);
4269 }
4271 /**
4272 * Open closed (loopy) subpath at node.
4273 */
4274 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4275 {
4276 g_assert(sp->closed);
4277 g_assert(n->subpath == sp);
4278 g_assert(sp->first == sp->last);
4280 /* We create new startpoint, current node will become last one */
4282 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4283 &n->pos, &n->pos, &n->n.pos);
4286 sp->closed = FALSE;
4288 //Unlink to make a head and tail
4289 sp->first = new_path;
4290 sp->last = n;
4291 n->n.other = NULL;
4292 new_path->p.other = NULL;
4293 }
4295 /**
4296 * Return new node in subpath with given properties.
4297 * \param pos Position of node.
4298 * \param ppos Handle position in previous direction
4299 * \param npos Handle position in previous direction
4300 */
4301 Inkscape::NodePath::Node *
4302 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)
4303 {
4304 g_assert(sp);
4305 g_assert(sp->nodepath);
4306 g_assert(sp->nodepath->desktop);
4308 if (nodechunk == NULL)
4309 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4311 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4313 n->subpath = sp;
4315 if (type != Inkscape::NodePath::NODE_NONE) {
4316 // use the type from sodipodi:nodetypes
4317 n->type = type;
4318 } else {
4319 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4320 // points are (almost) collinear
4321 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4322 // endnode, or a node with a retracted handle
4323 n->type = Inkscape::NodePath::NODE_CUSP;
4324 } else {
4325 n->type = Inkscape::NodePath::NODE_SMOOTH;
4326 }
4327 } else {
4328 n->type = Inkscape::NodePath::NODE_CUSP;
4329 }
4330 }
4332 n->code = code;
4333 n->selected = FALSE;
4334 n->pos = *pos;
4335 n->p.pos = *ppos;
4336 n->n.pos = *npos;
4338 n->dragging_out = NULL;
4340 Inkscape::NodePath::Node *prev;
4341 if (next) {
4342 //g_assert(g_list_find(sp->nodes, next));
4343 prev = next->p.other;
4344 } else {
4345 prev = sp->last;
4346 }
4348 if (prev)
4349 prev->n.other = n;
4350 else
4351 sp->first = n;
4353 if (next)
4354 next->p.other = n;
4355 else
4356 sp->last = n;
4358 n->p.other = prev;
4359 n->n.other = next;
4361 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"));
4362 sp_knot_set_position(n->knot, pos, 0);
4364 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4365 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4366 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4367 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4368 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4369 sp_knot_update_ctrl(n->knot);
4371 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4372 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4373 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4374 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4375 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4376 sp_knot_show(n->knot);
4378 // We only create handle knots and lines on demand
4379 n->p.knot = NULL;
4380 n->p.line = NULL;
4381 n->n.knot = NULL;
4382 n->n.line = NULL;
4384 sp->nodes = g_list_prepend(sp->nodes, n);
4386 return n;
4387 }
4389 /**
4390 * Destroy node and its knots, link neighbors in subpath.
4391 */
4392 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4393 {
4394 g_assert(node);
4395 g_assert(node->subpath);
4396 g_assert(SP_IS_KNOT(node->knot));
4398 Inkscape::NodePath::SubPath *sp = node->subpath;
4400 if (node->selected) { // first, deselect
4401 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4402 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4403 }
4405 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4407 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4408 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4409 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4410 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4411 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4412 g_object_unref(G_OBJECT(node->knot));
4414 if (node->p.knot) {
4415 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4416 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4417 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4418 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4419 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4420 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4421 g_object_unref(G_OBJECT(node->p.knot));
4422 node->p.knot = NULL;
4423 }
4425 if (node->n.knot) {
4426 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4427 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4428 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4429 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4430 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4431 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4432 g_object_unref(G_OBJECT(node->n.knot));
4433 node->n.knot = NULL;
4434 }
4436 if (node->p.line)
4437 gtk_object_destroy(GTK_OBJECT(node->p.line));
4438 if (node->n.line)
4439 gtk_object_destroy(GTK_OBJECT(node->n.line));
4441 if (sp->nodes) { // there are others nodes on the subpath
4442 if (sp->closed) {
4443 if (sp->first == node) {
4444 g_assert(sp->last == node);
4445 sp->first = node->n.other;
4446 sp->last = sp->first;
4447 }
4448 node->p.other->n.other = node->n.other;
4449 node->n.other->p.other = node->p.other;
4450 } else {
4451 if (sp->first == node) {
4452 sp->first = node->n.other;
4453 sp->first->code = NR_MOVETO;
4454 }
4455 if (sp->last == node) sp->last = node->p.other;
4456 if (node->p.other) node->p.other->n.other = node->n.other;
4457 if (node->n.other) node->n.other->p.other = node->p.other;
4458 }
4459 } else { // this was the last node on subpath
4460 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4461 }
4463 g_mem_chunk_free(nodechunk, node);
4464 }
4466 /**
4467 * Returns one of the node's two sides.
4468 * \param which Indicates which side.
4469 * \return Pointer to previous node side if which==-1, next if which==1.
4470 */
4471 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4472 {
4473 g_assert(node);
4475 switch (which) {
4476 case -1:
4477 return &node->p;
4478 case 1:
4479 return &node->n;
4480 default:
4481 break;
4482 }
4484 g_assert_not_reached();
4486 return NULL;
4487 }
4489 /**
4490 * Return the other side of the node, given one of its sides.
4491 */
4492 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4493 {
4494 g_assert(node);
4496 if (me == &node->p) return &node->n;
4497 if (me == &node->n) return &node->p;
4499 g_assert_not_reached();
4501 return NULL;
4502 }
4504 /**
4505 * Return NRPathcode on the given side of the node.
4506 */
4507 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4508 {
4509 g_assert(node);
4511 if (me == &node->p) {
4512 if (node->p.other) return (NRPathcode)node->code;
4513 return NR_MOVETO;
4514 }
4516 if (me == &node->n) {
4517 if (node->n.other) return (NRPathcode)node->n.other->code;
4518 return NR_MOVETO;
4519 }
4521 g_assert_not_reached();
4523 return NR_END;
4524 }
4526 /**
4527 * Return node with the given index
4528 */
4529 Inkscape::NodePath::Node *
4530 sp_nodepath_get_node_by_index(int index)
4531 {
4532 Inkscape::NodePath::Node *e = NULL;
4534 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4535 if (!nodepath) {
4536 return e;
4537 }
4539 //find segment
4540 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4542 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4543 int n = g_list_length(sp->nodes);
4544 if (sp->closed) {
4545 n++;
4546 }
4548 //if the piece belongs to this subpath grab it
4549 //otherwise move onto the next subpath
4550 if (index < n) {
4551 e = sp->first;
4552 for (int i = 0; i < index; ++i) {
4553 e = e->n.other;
4554 }
4555 break;
4556 } else {
4557 if (sp->closed) {
4558 index -= (n+1);
4559 } else {
4560 index -= n;
4561 }
4562 }
4563 }
4565 return e;
4566 }
4568 /**
4569 * Returns plain text meaning of node type.
4570 */
4571 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4572 {
4573 unsigned retracted = 0;
4574 bool endnode = false;
4576 for (int which = -1; which <= 1; which += 2) {
4577 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4578 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4579 retracted ++;
4580 if (!side->other)
4581 endnode = true;
4582 }
4584 if (retracted == 0) {
4585 if (endnode) {
4586 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4587 return _("end node");
4588 } else {
4589 switch (node->type) {
4590 case Inkscape::NodePath::NODE_CUSP:
4591 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4592 return _("cusp");
4593 case Inkscape::NodePath::NODE_SMOOTH:
4594 // TRANSLATORS: "smooth" is an adjective here
4595 return _("smooth");
4596 case Inkscape::NodePath::NODE_SYMM:
4597 return _("symmetric");
4598 }
4599 }
4600 } else if (retracted == 1) {
4601 if (endnode) {
4602 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4603 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4604 } else {
4605 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4606 }
4607 } else {
4608 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4609 }
4611 return NULL;
4612 }
4614 /**
4615 * Handles content of statusbar as long as node tool is active.
4616 */
4617 void
4618 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4619 {
4620 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");
4621 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4623 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4624 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4625 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4626 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4628 SPDesktop *desktop = NULL;
4629 if (nodepath) {
4630 desktop = nodepath->desktop;
4631 } else {
4632 desktop = SP_ACTIVE_DESKTOP;
4633 }
4635 SPEventContext *ec = desktop->event_context;
4636 if (!ec) return;
4637 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4638 if (!mc) return;
4640 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4642 if (selected_nodes == 0) {
4643 Inkscape::Selection *sel = desktop->selection;
4644 if (!sel || sel->isEmpty()) {
4645 mc->setF(Inkscape::NORMAL_MESSAGE,
4646 _("Select a single object to edit its nodes or handles."));
4647 } else {
4648 if (nodepath) {
4649 mc->setF(Inkscape::NORMAL_MESSAGE,
4650 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.",
4651 "<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.",
4652 total_nodes),
4653 total_nodes);
4654 } else {
4655 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4656 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4657 } else {
4658 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4659 }
4660 }
4661 }
4662 } else if (nodepath && selected_nodes == 1) {
4663 mc->setF(Inkscape::NORMAL_MESSAGE,
4664 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4665 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4666 total_nodes),
4667 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4668 } else {
4669 if (selected_subpaths > 1) {
4670 mc->setF(Inkscape::NORMAL_MESSAGE,
4671 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4672 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4673 total_nodes),
4674 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4675 } else {
4676 mc->setF(Inkscape::NORMAL_MESSAGE,
4677 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4678 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4679 total_nodes),
4680 selected_nodes, total_nodes, when_selected);
4681 }
4682 }
4683 }
4685 /*
4686 * returns a *copy* of the curve of that object.
4687 */
4688 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4689 if (!object)
4690 return NULL;
4692 SPCurve *curve = NULL;
4693 if (SP_IS_PATH(object)) {
4694 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4695 curve = curve_new->copy();
4696 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4697 const gchar *svgd = object->repr->attribute(key);
4698 if (svgd) {
4699 NArtBpath *bpath = sp_svg_read_path(svgd);
4700 SPCurve *curve_new = SPCurve::new_from_bpath(bpath);
4701 if (curve_new) {
4702 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4703 } else {
4704 g_free(bpath);
4705 }
4706 }
4707 }
4709 return curve;
4710 }
4712 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4713 if (!np || !np->object || !curve)
4714 return;
4716 if (SP_IS_PATH(np->object)) {
4717 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4718 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4719 } else {
4720 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4721 }
4722 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4723 // FIXME: this writing to string and then reading from string is bound to be slow.
4724 // create a method to convert from curve directly to 2geom...
4725 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
4726 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4727 g_free(svgpath);
4729 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4730 }
4731 }
4733 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4734 np->show_helperpath = show;
4736 if (show) {
4737 SPCurve *helper_curve = np->curve->copy();
4738 helper_curve->transform(np->i2d );
4739 if (!np->helper_path) {
4740 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4741 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);
4742 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4743 sp_canvas_item_move_to_z(np->helper_path, 0);
4744 sp_canvas_item_show(np->helper_path);
4745 } else {
4746 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4747 }
4748 helper_curve->unref();
4749 } else {
4750 if (np->helper_path) {
4751 GtkObject *temp = np->helper_path;
4752 np->helper_path = NULL;
4753 gtk_object_destroy(temp);
4754 }
4755 }
4756 }
4758 /* sp_nodepath_make_straight_path:
4759 * Prevents user from curving the path by dragging a segment or activating handles etc.
4760 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4761 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4762 */
4763 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4764 np->straight_path = true;
4765 np->show_handles = false;
4766 g_message("add code to make the path straight.");
4767 // do sp_nodepath_convert_node_type on all nodes?
4768 // coding tip: search for this text : "Make selected segments lines"
4769 }
4772 /*
4773 Local Variables:
4774 mode:c++
4775 c-file-style:"stroustrup"
4776 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4777 indent-tabs-mode:nil
4778 fill-column:99
4779 End:
4780 */
4781 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :