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