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