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/curve.h"
19 #include "display/sp-ctrlline.h"
20 #include "display/sodipodi-ctrl.h"
21 #include <glibmm/i18n.h>
22 #include "libnr/n-art-bpath.h"
23 #include "helper/units.h"
24 #include "knot.h"
25 #include "inkscape.h"
26 #include "document.h"
27 #include "sp-namedview.h"
28 #include "desktop.h"
29 #include "desktop-handles.h"
30 #include "snap.h"
31 #include "message-stack.h"
32 #include "message-context.h"
33 #include "node-context.h"
34 #include "selection-chemistry.h"
35 #include "selection.h"
36 #include "xml/repr.h"
37 #include "prefs-utils.h"
38 #include "sp-metrics.h"
39 #include "sp-path.h"
40 #include "libnr/nr-matrix-ops.h"
41 #include "splivarot.h"
42 #include "svg/svg.h"
43 #include "display/bezier-utils.h"
44 #include <vector>
45 #include <algorithm>
47 class NR::Matrix;
49 /// \todo
50 /// evil evil evil. FIXME: conflict of two different Path classes!
51 /// There is a conflict in the namespace between two classes named Path.
52 /// #include "sp-flowtext.h"
53 /// #include "sp-flowregion.h"
55 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
56 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
57 GType sp_flowregion_get_type (void);
58 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
59 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
60 GType sp_flowtext_get_type (void);
61 // end evil workaround
63 #include "helper/stlport.h"
66 /// \todo fixme: Implement these via preferences */
68 #define NODE_FILL 0xbfbfbf00
69 #define NODE_STROKE 0x000000ff
70 #define NODE_FILL_HI 0xff000000
71 #define NODE_STROKE_HI 0x000000ff
72 #define NODE_FILL_SEL 0x0000ffff
73 #define NODE_STROKE_SEL 0x000000ff
74 #define NODE_FILL_SEL_HI 0xff000000
75 #define NODE_STROKE_SEL_HI 0x000000ff
76 #define KNOT_FILL 0xffffffff
77 #define KNOT_STROKE 0x000000ff
78 #define KNOT_FILL_HI 0xff000000
79 #define KNOT_STROKE_HI 0x000000ff
81 static GMemChunk *nodechunk = NULL;
83 /* Creation from object */
85 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
86 static gchar *parse_nodetypes(gchar const *types, gint length);
88 /* Object updating */
90 static void stamp_repr(Inkscape::NodePath::Path *np);
91 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
92 static gchar *create_typestr(Inkscape::NodePath::Path *np);
94 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
96 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
98 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
100 /* Adjust handle placement, if the node or the other handle is moved */
101 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
102 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
104 /* Node event callbacks */
105 static void node_clicked(SPKnot *knot, guint state, gpointer data);
106 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
107 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
108 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
110 /* Handle event callbacks */
111 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
112 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
113 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
114 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
115 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
116 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
118 /* Constructors and destructors */
120 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
121 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
122 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
123 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
124 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
125 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
126 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
128 /* Helpers */
130 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
131 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
132 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
134 // active_node indicates mouseover node
135 static Inkscape::NodePath::Node *active_node = NULL;
137 /**
138 * \brief Creates new nodepath from item
139 */
140 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item)
141 {
142 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
144 /** \todo
145 * FIXME: remove this. We don't want to edit paths inside flowtext.
146 * Instead we will build our flowtext with cloned paths, so that the
147 * real paths are outside the flowtext and thus editable as usual.
148 */
149 if (SP_IS_FLOWTEXT(item)) {
150 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
151 if SP_IS_FLOWREGION(child) {
152 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
153 if (grandchild && SP_IS_PATH(grandchild)) {
154 item = SP_ITEM(grandchild);
155 break;
156 }
157 }
158 }
159 }
161 if (!SP_IS_PATH(item))
162 return NULL;
163 SPPath *path = SP_PATH(item);
164 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
165 if (curve == NULL)
166 return NULL;
168 NArtBpath *bpath = sp_curve_first_bpath(curve);
169 gint length = curve->end;
170 if (length == 0)
171 return NULL; // prevent crash for one-node paths
173 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
174 gchar *typestr = parse_nodetypes(nodetypes, length);
176 //Create new nodepath
177 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
178 if (!np)
179 return NULL;
181 // Set defaults
182 np->desktop = desktop;
183 np->path = path;
184 np->subpaths = NULL;
185 np->selected = NULL;
186 np->nodeContext = NULL; //Let the context that makes this set it
187 np->livarot_path = NULL;
188 np->local_change = 0;
190 // we need to update item's transform from the repr here,
191 // because they may be out of sync when we respond
192 // to a change in repr by regenerating nodepath --bb
193 sp_object_read_attr(SP_OBJECT(item), "transform");
195 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
196 np->d2i = np->i2d.inverse();
197 np->repr = repr;
199 // create the subpath(s) from the bpath
200 NArtBpath *b = bpath;
201 while (b->code != NR_END) {
202 b = subpath_from_bpath(np, b, typestr + (b - bpath));
203 }
205 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
206 np->subpaths = g_list_reverse(np->subpaths);
208 g_free(typestr);
209 sp_curve_unref(curve);
211 // create the livarot representation from the same item
212 np->livarot_path = Path_for_item(item, true, true);
213 if (np->livarot_path)
214 np->livarot_path->ConvertWithBackData(0.01);
216 return np;
217 }
219 /**
220 * Destroys nodepath's subpaths, then itself, also tell context about it.
221 */
222 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
224 if (!np) //soft fail, like delete
225 return;
227 while (np->subpaths) {
228 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
229 }
231 //Inform the context that made me, if any, that I am gone.
232 if (np->nodeContext)
233 np->nodeContext->nodepath = NULL;
235 g_assert(!np->selected);
237 if (np->livarot_path) {
238 delete np->livarot_path;
239 np->livarot_path = NULL;
240 }
242 np->desktop = NULL;
244 g_free(np);
245 }
248 /**
249 * Return the node count of a given NodeSubPath.
250 */
251 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
252 {
253 if (!subpath)
254 return 0;
255 gint nodeCount = g_list_length(subpath->nodes);
256 return nodeCount;
257 }
259 /**
260 * Return the node count of a given NodePath.
261 */
262 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
263 {
264 if (!np)
265 return 0;
266 gint nodeCount = 0;
267 for (GList *item = np->subpaths ; item ; item=item->next) {
268 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
269 nodeCount += g_list_length(subpath->nodes);
270 }
271 return nodeCount;
272 }
274 /**
275 * Return the subpath count of a given NodePath.
276 */
277 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
278 {
279 if (!np)
280 return 0;
281 return g_list_length (np->subpaths);
282 }
284 /**
285 * Return the selected node count of a given NodePath.
286 */
287 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
288 {
289 if (!np)
290 return 0;
291 return g_list_length (np->selected);
292 }
294 /**
295 * Return the number of subpaths where nodes are selected in a given NodePath.
296 */
297 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
298 {
299 if (!np)
300 return 0;
301 if (!np->selected)
302 return 0;
303 if (!np->selected->next)
304 return 1;
305 gint count = 0;
306 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
307 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
308 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
309 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
310 if (node->selected) {
311 count ++;
312 break;
313 }
314 }
315 }
316 return count;
317 }
319 /**
320 * Clean up a nodepath after editing.
321 *
322 * Currently we are deleting trivial subpaths.
323 */
324 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
325 {
326 GList *badSubPaths = NULL;
328 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
329 for (GList *l = nodepath->subpaths; l ; l=l->next) {
330 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
331 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
332 badSubPaths = g_list_append(badSubPaths, sp);
333 }
335 //Delete them. This second step is because sp_nodepath_subpath_destroy()
336 //also removes the subpath from nodepath->subpaths
337 for (GList *l = badSubPaths; l ; l=l->next) {
338 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
339 sp_nodepath_subpath_destroy(sp);
340 }
342 g_list_free(badSubPaths);
343 }
345 /**
346 * Create new nodepath from b, make it subpath of np.
347 * \param t The node type.
348 * \todo Fixme: t should be a proper type, rather than gchar
349 */
350 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
351 {
352 NR::Point ppos, pos, npos;
354 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
356 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
357 bool const closed = (b->code == NR_MOVETO);
359 pos = NR::Point(b->x3, b->y3) * np->i2d;
360 if (b[1].code == NR_CURVETO) {
361 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
362 } else {
363 npos = pos;
364 }
365 Inkscape::NodePath::Node *n;
366 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
367 g_assert(sp->first == n);
368 g_assert(sp->last == n);
370 b++;
371 t++;
372 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
373 pos = NR::Point(b->x3, b->y3) * np->i2d;
374 if (b->code == NR_CURVETO) {
375 ppos = NR::Point(b->x2, b->y2) * np->i2d;
376 } else {
377 ppos = pos;
378 }
379 if (b[1].code == NR_CURVETO) {
380 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
381 } else {
382 npos = pos;
383 }
384 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
385 b++;
386 t++;
387 }
389 if (closed) sp_nodepath_subpath_close(sp);
391 return b;
392 }
394 /**
395 * Convert from sodipodi:nodetypes to new style type string.
396 */
397 static gchar *parse_nodetypes(gchar const *types, gint length)
398 {
399 g_assert(length > 0);
401 gchar *typestr = g_new(gchar, length + 1);
403 gint pos = 0;
405 if (types) {
406 for (gint i = 0; types[i] && ( i < length ); i++) {
407 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
408 if (types[i] != '\0') {
409 switch (types[i]) {
410 case 's':
411 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
412 break;
413 case 'z':
414 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
415 break;
416 case 'c':
417 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
418 break;
419 default:
420 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
421 break;
422 }
423 }
424 }
425 }
427 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
429 return typestr;
430 }
432 /**
433 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
434 * updated but repr is not (for speed). Used during curve and node drag.
435 */
436 static void update_object(Inkscape::NodePath::Path *np)
437 {
438 g_assert(np);
440 SPCurve *curve = create_curve(np);
442 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
444 sp_curve_unref(curve);
445 }
447 /**
448 * Update XML path node with data from path object.
449 */
450 static void update_repr_internal(Inkscape::NodePath::Path *np)
451 {
452 g_assert(np);
454 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
456 SPCurve *curve = create_curve(np);
457 gchar *typestr = create_typestr(np);
458 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
460 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
461 np->local_change++;
462 repr->setAttribute("d", svgpath);
463 }
465 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
466 np->local_change++;
467 repr->setAttribute("sodipodi:nodetypes", typestr);
468 }
470 g_free(svgpath);
471 g_free(typestr);
472 sp_curve_unref(curve);
473 }
475 /**
476 * Update XML path node with data from path object, commit changes forever.
477 */
478 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np)
479 {
480 update_repr_internal(np);
481 sp_document_done(sp_desktop_document(np->desktop));
483 if (np->livarot_path) {
484 delete np->livarot_path;
485 np->livarot_path = NULL;
486 }
488 if (np->path && SP_IS_ITEM(np->path)) {
489 np->livarot_path = Path_for_item (np->path, true, true);
490 if (np->livarot_path)
491 np->livarot_path->ConvertWithBackData(0.01);
492 }
493 }
495 /**
496 * Update XML path node with data from path object, commit changes with undo.
497 */
498 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key)
499 {
500 update_repr_internal(np);
501 sp_document_maybe_done(sp_desktop_document(np->desktop), key);
503 if (np->livarot_path) {
504 delete np->livarot_path;
505 np->livarot_path = NULL;
506 }
508 if (np->path && SP_IS_ITEM(np->path)) {
509 np->livarot_path = Path_for_item (np->path, true, true);
510 if (np->livarot_path)
511 np->livarot_path->ConvertWithBackData(0.01);
512 }
513 }
515 /**
516 * Make duplicate of path, replace corresponding XML node in tree, commit.
517 */
518 static void stamp_repr(Inkscape::NodePath::Path *np)
519 {
520 g_assert(np);
522 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
523 Inkscape::XML::Node *new_repr = old_repr->duplicate();
525 // remember the position of the item
526 gint pos = old_repr->position();
527 // remember parent
528 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
530 SPCurve *curve = create_curve(np);
531 gchar *typestr = create_typestr(np);
533 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
535 new_repr->setAttribute("d", svgpath);
536 new_repr->setAttribute("sodipodi:nodetypes", typestr);
538 // add the new repr to the parent
539 parent->appendChild(new_repr);
540 // move to the saved position
541 new_repr->setPosition(pos > 0 ? pos : 0);
543 sp_document_done(sp_desktop_document(np->desktop));
545 Inkscape::GC::release(new_repr);
546 g_free(svgpath);
547 g_free(typestr);
548 sp_curve_unref(curve);
549 }
551 /**
552 * Create curve from path.
553 */
554 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
555 {
556 SPCurve *curve = sp_curve_new();
558 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
559 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
560 sp_curve_moveto(curve,
561 sp->first->pos * np->d2i);
562 Inkscape::NodePath::Node *n = sp->first->n.other;
563 while (n) {
564 NR::Point const end_pt = n->pos * np->d2i;
565 switch (n->code) {
566 case NR_LINETO:
567 sp_curve_lineto(curve, end_pt);
568 break;
569 case NR_CURVETO:
570 sp_curve_curveto(curve,
571 n->p.other->n.pos * np->d2i,
572 n->p.pos * np->d2i,
573 end_pt);
574 break;
575 default:
576 g_assert_not_reached();
577 break;
578 }
579 if (n != sp->last) {
580 n = n->n.other;
581 } else {
582 n = NULL;
583 }
584 }
585 if (sp->closed) {
586 sp_curve_closepath(curve);
587 }
588 }
590 return curve;
591 }
593 /**
594 * Convert path type string to sodipodi:nodetypes style.
595 */
596 static gchar *create_typestr(Inkscape::NodePath::Path *np)
597 {
598 gchar *typestr = g_new(gchar, 32);
599 gint len = 32;
600 gint pos = 0;
602 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
603 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
605 if (pos >= len) {
606 typestr = g_renew(gchar, typestr, len + 32);
607 len += 32;
608 }
610 typestr[pos++] = 'c';
612 Inkscape::NodePath::Node *n;
613 n = sp->first->n.other;
614 while (n) {
615 gchar code;
617 switch (n->type) {
618 case Inkscape::NodePath::NODE_CUSP:
619 code = 'c';
620 break;
621 case Inkscape::NodePath::NODE_SMOOTH:
622 code = 's';
623 break;
624 case Inkscape::NodePath::NODE_SYMM:
625 code = 'z';
626 break;
627 default:
628 g_assert_not_reached();
629 code = '\0';
630 break;
631 }
633 if (pos >= len) {
634 typestr = g_renew(gchar, typestr, len + 32);
635 len += 32;
636 }
638 typestr[pos++] = code;
640 if (n != sp->last) {
641 n = n->n.other;
642 } else {
643 n = NULL;
644 }
645 }
646 }
648 if (pos >= len) {
649 typestr = g_renew(gchar, typestr, len + 1);
650 len += 1;
651 }
653 typestr[pos++] = '\0';
655 return typestr;
656 }
658 /**
659 * Returns current path in context.
660 */
661 static Inkscape::NodePath::Path *sp_nodepath_current()
662 {
663 if (!SP_ACTIVE_DESKTOP) {
664 return NULL;
665 }
667 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
669 if (!SP_IS_NODE_CONTEXT(event_context)) {
670 return NULL;
671 }
673 return SP_NODE_CONTEXT(event_context)->nodepath;
674 }
678 /**
679 \brief Fills node and handle positions for three nodes, splitting line
680 marked by end at distance t.
681 */
682 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
683 {
684 g_assert(new_path != NULL);
685 g_assert(end != NULL);
687 g_assert(end->p.other == new_path);
688 Inkscape::NodePath::Node *start = new_path->p.other;
689 g_assert(start);
691 if (end->code == NR_LINETO) {
692 new_path->type =Inkscape::NodePath::NODE_CUSP;
693 new_path->code = NR_LINETO;
694 new_path->pos = (t * start->pos + (1 - t) * end->pos);
695 } else {
696 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
697 new_path->code = NR_CURVETO;
698 gdouble s = 1 - t;
699 for (int dim = 0; dim < 2; dim++) {
700 NR::Coord const f000 = start->pos[dim];
701 NR::Coord const f001 = start->n.pos[dim];
702 NR::Coord const f011 = end->p.pos[dim];
703 NR::Coord const f111 = end->pos[dim];
704 NR::Coord const f00t = s * f000 + t * f001;
705 NR::Coord const f01t = s * f001 + t * f011;
706 NR::Coord const f11t = s * f011 + t * f111;
707 NR::Coord const f0tt = s * f00t + t * f01t;
708 NR::Coord const f1tt = s * f01t + t * f11t;
709 NR::Coord const fttt = s * f0tt + t * f1tt;
710 start->n.pos[dim] = f00t;
711 new_path->p.pos[dim] = f0tt;
712 new_path->pos[dim] = fttt;
713 new_path->n.pos[dim] = f1tt;
714 end->p.pos[dim] = f11t;
715 }
716 }
717 }
719 /**
720 * Adds new node on direct line between two nodes, activates handles of all
721 * three nodes.
722 */
723 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
724 {
725 g_assert(end);
726 g_assert(end->subpath);
727 g_assert(g_list_find(end->subpath->nodes, end));
729 Inkscape::NodePath::Node *start = end->p.other;
730 g_assert( start->n.other == end );
731 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
732 end,
733 Inkscape::NodePath::NODE_SMOOTH,
734 (NRPathcode)end->code,
735 &start->pos, &start->pos, &start->n.pos);
736 sp_nodepath_line_midpoint(newnode, end, t);
738 sp_node_update_handles(start);
739 sp_node_update_handles(newnode);
740 sp_node_update_handles(end);
742 return newnode;
743 }
745 /**
746 \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
747 */
748 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
749 {
750 g_assert(node);
751 g_assert(node->subpath);
752 g_assert(g_list_find(node->subpath->nodes, node));
754 Inkscape::NodePath::SubPath *sp = node->subpath;
755 Inkscape::NodePath::Path *np = sp->nodepath;
757 if (sp->closed) {
758 sp_nodepath_subpath_open(sp, node);
759 return sp->first;
760 } else {
761 // no break for end nodes
762 if (node == sp->first) return NULL;
763 if (node == sp->last ) return NULL;
765 // create a new subpath
766 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
768 // duplicate the break node as start of the new subpath
769 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
771 while (node->n.other) { // copy the remaining nodes into the new subpath
772 Inkscape::NodePath::Node *n = node->n.other;
773 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);
774 if (n->selected) {
775 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
776 }
777 sp_nodepath_node_destroy(n); // remove the point on the original subpath
778 }
780 return newnode;
781 }
782 }
784 /**
785 * Duplicate node and connect to neighbours.
786 */
787 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
788 {
789 g_assert(node);
790 g_assert(node->subpath);
791 g_assert(g_list_find(node->subpath->nodes, node));
793 Inkscape::NodePath::SubPath *sp = node->subpath;
795 NRPathcode code = (NRPathcode) node->code;
796 if (code == NR_MOVETO) { // if node is the endnode,
797 node->code = NR_LINETO; // new one is inserted before it, so change that to line
798 }
800 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
802 if (!node->n.other || !node->p.other) // if node is an endnode, select it
803 return node;
804 else
805 return newnode; // otherwise select the newly created node
806 }
808 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
809 {
810 node->p.pos = (node->pos + (node->pos - node->n.pos));
811 }
813 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
814 {
815 node->n.pos = (node->pos + (node->pos - node->p.pos));
816 }
818 /**
819 * Change line type at node, with side effects on neighbours.
820 */
821 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
822 {
823 g_assert(end);
824 g_assert(end->subpath);
825 g_assert(end->p.other);
827 if (end->code == static_cast< guint > ( code ) )
828 return;
830 Inkscape::NodePath::Node *start = end->p.other;
832 end->code = code;
834 if (code == NR_LINETO) {
835 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
836 if (end->n.other) {
837 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
838 }
839 sp_node_adjust_handle(start, -1);
840 sp_node_adjust_handle(end, 1);
841 } else {
842 NR::Point delta = end->pos - start->pos;
843 start->n.pos = start->pos + delta / 3;
844 end->p.pos = end->pos - delta / 3;
845 sp_node_adjust_handle(start, 1);
846 sp_node_adjust_handle(end, -1);
847 }
849 sp_node_update_handles(start);
850 sp_node_update_handles(end);
851 }
853 /**
854 * Change node type, and its handles accordingly.
855 */
856 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
857 {
858 g_assert(node);
859 g_assert(node->subpath);
861 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
862 return node;
864 if ((node->p.other != NULL) && (node->n.other != NULL)) {
865 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
866 type =Inkscape::NodePath::NODE_CUSP;
867 }
868 }
870 node->type = type;
872 if (node->type == Inkscape::NodePath::NODE_CUSP) {
873 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
874 node->knot->setSize (node->selected? 11 : 9);
875 sp_knot_update_ctrl(node->knot);
876 } else {
877 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
878 node->knot->setSize (node->selected? 9 : 7);
879 sp_knot_update_ctrl(node->knot);
880 }
882 // if one of handles is mouseovered, preserve its position
883 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
884 sp_node_adjust_handle(node, 1);
885 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
886 sp_node_adjust_handle(node, -1);
887 } else {
888 sp_node_adjust_handles(node);
889 }
891 sp_node_update_handles(node);
893 sp_nodepath_update_statusbar(node->subpath->nodepath);
895 return node;
896 }
898 /**
899 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
900 * adjacent segments from lines to curves.
901 */
902 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
903 {
904 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
905 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
906 // convert adjacent segment BEFORE to curve
907 node->code = NR_CURVETO;
908 NR::Point delta;
909 if (node->n.other != NULL)
910 delta = node->n.other->pos - node->p.other->pos;
911 else
912 delta = node->pos - node->p.other->pos;
913 node->p.pos = node->pos - delta / 4;
914 sp_node_update_handles(node);
915 }
917 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
918 // convert adjacent segment AFTER to curve
919 node->n.other->code = NR_CURVETO;
920 NR::Point delta;
921 if (node->p.other != NULL)
922 delta = node->p.other->pos - node->n.other->pos;
923 else
924 delta = node->pos - node->n.other->pos;
925 node->n.pos = node->pos - delta / 4;
926 sp_node_update_handles(node);
927 }
928 }
930 sp_nodepath_set_node_type (node, type);
931 }
933 /**
934 * Move node to point, and adjust its and neighbouring handles.
935 */
936 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
937 {
938 NR::Point delta = p - node->pos;
939 node->pos = p;
941 node->p.pos += delta;
942 node->n.pos += delta;
944 if (node->p.other) {
945 if (node->code == NR_LINETO) {
946 sp_node_adjust_handle(node, 1);
947 sp_node_adjust_handle(node->p.other, -1);
948 }
949 }
950 if (node->n.other) {
951 if (node->n.other->code == NR_LINETO) {
952 sp_node_adjust_handle(node, -1);
953 sp_node_adjust_handle(node->n.other, 1);
954 }
955 }
957 // this function is only called from batch movers that will update display at the end
958 // themselves, so here we just move all the knots without emitting move signals, for speed
959 sp_node_update_handles(node, false);
960 }
962 /**
963 * Call sp_node_moveto() for node selection and handle possible snapping.
964 */
965 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
966 bool const snap = true)
967 {
968 NR::Coord best = NR_HUGE;
969 NR::Point delta(dx, dy);
970 NR::Point best_pt = delta;
972 if (snap) {
973 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
975 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
976 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
977 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
978 if (s.getDistance() < best) {
979 best = s.getDistance();
980 best_pt = s.getPoint() - n->pos;
981 }
982 }
983 }
985 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
986 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
987 sp_node_moveto(n, n->pos + best_pt);
988 }
990 // do not update repr here so that node dragging is acceptably fast
991 update_object(nodepath);
992 }
994 /**
995 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
996 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
997 near x = 0.
998 */
999 double
1000 sculpt_profile (double x, double alpha)
1001 {
1002 if (x >= 1)
1003 return 0;
1004 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1005 }
1007 double
1008 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1009 {
1010 // extremely primitive for now, don't have time to look for the real one
1011 double lower = NR::L2(b - a);
1012 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1013 return (lower + upper)/2;
1014 }
1016 void
1017 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1018 {
1019 n->pos = n->origin + delta;
1020 n->n.pos = n->n.origin + delta_n;
1021 n->p.pos = n->p.origin + delta_p;
1022 sp_node_adjust_handles(n);
1023 sp_node_update_handles(n, false);
1024 }
1026 /**
1027 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1028 * on how far they are from the dragged node n.
1029 */
1030 static void
1031 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1032 {
1033 g_assert (n);
1034 g_assert (nodepath);
1035 g_assert (n->subpath->nodepath == nodepath);
1037 double pressure = n->knot->pressure;
1038 if (pressure == 0)
1039 pressure = 0.5; // default
1040 pressure = CLAMP (pressure, 0.2, 0.8);
1042 // map pressure to alpha = 1/5 ... 5
1043 double alpha = 1 - 2 * fabs(pressure - 0.5);
1044 if (pressure > 0.5)
1045 alpha = 1/alpha;
1047 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1048 // Only one subpath has selected nodes:
1049 // use linear mode, where the distance from n to node being dragged is calculated along the path
1051 double n_sel_range = 0, p_sel_range = 0;
1052 guint n_nodes = 0, p_nodes = 0;
1053 guint n_sel_nodes = 0, p_sel_nodes = 0;
1055 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1056 {
1057 double n_range = 0, p_range = 0;
1058 bool n_going = true, p_going = true;
1059 Inkscape::NodePath::Node *n_node = n;
1060 Inkscape::NodePath::Node *p_node = n;
1061 do {
1062 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1063 if (n_node && n_going)
1064 n_node = n_node->n.other;
1065 if (n_node == NULL) {
1066 n_going = false;
1067 } else {
1068 n_nodes ++;
1069 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1070 if (n_node->selected) {
1071 n_sel_nodes ++;
1072 n_sel_range = n_range;
1073 }
1074 if (n_node == p_node) {
1075 n_going = false;
1076 p_going = false;
1077 }
1078 }
1079 if (p_node && p_going)
1080 p_node = p_node->p.other;
1081 if (p_node == NULL) {
1082 p_going = false;
1083 } else {
1084 p_nodes ++;
1085 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1086 if (p_node->selected) {
1087 p_sel_nodes ++;
1088 p_sel_range = p_range;
1089 }
1090 if (p_node == n_node) {
1091 n_going = false;
1092 p_going = false;
1093 }
1094 }
1095 } while (n_going || p_going);
1096 }
1098 // Second pass: actually move nodes in this subpath
1099 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1100 {
1101 double n_range = 0, p_range = 0;
1102 bool n_going = true, p_going = true;
1103 Inkscape::NodePath::Node *n_node = n;
1104 Inkscape::NodePath::Node *p_node = n;
1105 do {
1106 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1107 if (n_node && n_going)
1108 n_node = n_node->n.other;
1109 if (n_node == NULL) {
1110 n_going = false;
1111 } else {
1112 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1113 if (n_node->selected) {
1114 sp_nodepath_move_node_and_handles (n_node,
1115 sculpt_profile (n_range / n_sel_range, alpha) * delta,
1116 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha) * delta,
1117 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha) * delta);
1118 }
1119 if (n_node == p_node) {
1120 n_going = false;
1121 p_going = false;
1122 }
1123 }
1124 if (p_node && p_going)
1125 p_node = p_node->p.other;
1126 if (p_node == NULL) {
1127 p_going = false;
1128 } else {
1129 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1130 if (p_node->selected) {
1131 sp_nodepath_move_node_and_handles (p_node,
1132 sculpt_profile (p_range / p_sel_range, alpha) * delta,
1133 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha) * delta,
1134 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha) * delta);
1135 }
1136 if (p_node == n_node) {
1137 n_going = false;
1138 p_going = false;
1139 }
1140 }
1141 } while (n_going || p_going);
1142 }
1144 } else {
1145 // Multiple subpaths have selected nodes:
1146 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1147 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1148 // fix the pear-like shape when sculpting e.g. a ring
1150 // First pass: calculate range
1151 gdouble direct_range = 0;
1152 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1153 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1154 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1155 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1156 if (node->selected) {
1157 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1158 }
1159 }
1160 }
1162 // Second pass: actually move nodes
1163 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1164 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1165 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1166 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1167 if (node->selected) {
1168 sp_nodepath_move_node_and_handles (node,
1169 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha) * delta,
1170 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha) * delta,
1171 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha) * delta);
1172 }
1173 }
1174 }
1175 }
1177 // do not update repr here so that node dragging is acceptably fast
1178 update_object(nodepath);
1179 }
1182 /**
1183 * Move node selection to point, adjust its and neighbouring handles,
1184 * handle possible snapping, and commit the change with possible undo.
1185 */
1186 void
1187 sp_node_selected_move(gdouble dx, gdouble dy)
1188 {
1189 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1190 if (!nodepath) return;
1192 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1194 if (dx == 0) {
1195 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1196 } else if (dy == 0) {
1197 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1198 } else {
1199 sp_nodepath_update_repr(nodepath);
1200 }
1201 }
1203 /**
1204 * Move node selection off screen and commit the change.
1205 */
1206 void
1207 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1208 {
1209 // borrowed from sp_selection_move_screen in selection-chemistry.c
1210 // we find out the current zoom factor and divide deltas by it
1211 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1213 gdouble zoom = desktop->current_zoom();
1214 gdouble zdx = dx / zoom;
1215 gdouble zdy = dy / zoom;
1217 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1218 if (!nodepath) return;
1220 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1222 if (dx == 0) {
1223 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1224 } else if (dy == 0) {
1225 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1226 } else {
1227 sp_nodepath_update_repr(nodepath);
1228 }
1229 }
1231 /** If they don't yet exist, creates knot and line for the given side of the node */
1232 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1233 {
1234 if (!side->knot) {
1235 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"));
1237 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1238 side->knot->setSize (7);
1239 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1240 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1241 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1242 sp_knot_update_ctrl(side->knot);
1244 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1245 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1246 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1247 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1248 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1249 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1250 }
1252 if (!side->line) {
1253 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1254 SP_TYPE_CTRLLINE, NULL);
1255 }
1256 }
1258 /**
1259 * Ensure the given handle of the node is visible/invisible, update its screen position
1260 */
1261 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1262 {
1263 g_assert(node != NULL);
1265 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1266 NRPathcode code = sp_node_path_code_from_side(node, side);
1268 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1270 if (show_handle) {
1271 if (!side->knot) { // No handle knot at all
1272 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1273 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1274 side->knot->pos = side->pos;
1275 if (side->knot->item)
1276 SP_CTRL(side->knot->item)->moveto(side->pos);
1277 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1278 sp_knot_show(side->knot);
1279 } else {
1280 if (side->knot->pos != side->pos) { // only if it's really moved
1281 if (fire_move_signals) {
1282 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1283 } else {
1284 sp_knot_moveto(side->knot, &side->pos);
1285 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1286 }
1287 }
1288 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1289 sp_knot_show(side->knot);
1290 }
1291 }
1292 sp_canvas_item_show(side->line);
1293 } else {
1294 if (side->knot) {
1295 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1296 sp_knot_hide(side->knot);
1297 }
1298 }
1299 if (side->line) {
1300 sp_canvas_item_hide(side->line);
1301 }
1302 }
1303 }
1305 /**
1306 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1307 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1308 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1309 * updated; otherwise, just move the knots silently (used in batch moves).
1310 */
1311 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1312 {
1313 g_assert(node != NULL);
1315 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1316 sp_knot_show(node->knot);
1317 }
1319 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1320 if (fire_move_signals)
1321 sp_knot_set_position(node->knot, &node->pos, 0);
1322 else
1323 sp_knot_moveto(node->knot, &node->pos);
1324 }
1326 gboolean show_handles = node->selected;
1327 if (node->p.other != NULL) {
1328 if (node->p.other->selected) show_handles = TRUE;
1329 }
1330 if (node->n.other != NULL) {
1331 if (node->n.other->selected) show_handles = TRUE;
1332 }
1334 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1335 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1336 }
1338 /**
1339 * Call sp_node_update_handles() for all nodes on subpath.
1340 */
1341 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1342 {
1343 g_assert(subpath != NULL);
1345 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1346 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1347 }
1348 }
1350 /**
1351 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1352 */
1353 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1354 {
1355 g_assert(nodepath != NULL);
1357 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1358 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1359 }
1360 }
1362 /**
1363 * Adds all selected nodes in nodepath to list.
1364 */
1365 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1366 {
1367 StlConv<Node *>::list(l, selected);
1368 /// \todo this adds a copying, rework when the selection becomes a stl list
1369 }
1371 /**
1372 * Align selected nodes on the specified axis.
1373 */
1374 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1375 {
1376 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1377 return;
1378 }
1380 if ( !nodepath->selected->next ) { // only one node selected
1381 return;
1382 }
1383 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1384 NR::Point dest(pNode->pos);
1385 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1386 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1387 if (pNode) {
1388 dest[axis] = pNode->pos[axis];
1389 sp_node_moveto(pNode, dest);
1390 }
1391 }
1393 sp_nodepath_update_repr(nodepath);
1394 }
1396 /// Helper struct.
1397 struct NodeSort
1398 {
1399 Inkscape::NodePath::Node *_node;
1400 NR::Coord _coord;
1401 /// \todo use vectorof pointers instead of calling copy ctor
1402 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1403 _node(node), _coord(node->pos[axis])
1404 {}
1406 };
1408 static bool operator<(NodeSort const &a, NodeSort const &b)
1409 {
1410 return (a._coord < b._coord);
1411 }
1413 /**
1414 * Distribute selected nodes on the specified axis.
1415 */
1416 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1417 {
1418 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1419 return;
1420 }
1422 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1423 return;
1424 }
1426 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1427 std::vector<NodeSort> sorted;
1428 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1429 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1430 if (pNode) {
1431 NodeSort n(pNode, axis);
1432 sorted.push_back(n);
1433 //dest[axis] = pNode->pos[axis];
1434 //sp_node_moveto(pNode, dest);
1435 }
1436 }
1437 std::sort(sorted.begin(), sorted.end());
1438 unsigned int len = sorted.size();
1439 //overall bboxes span
1440 float dist = (sorted.back()._coord -
1441 sorted.front()._coord);
1442 //new distance between each bbox
1443 float step = (dist) / (len - 1);
1444 float pos = sorted.front()._coord;
1445 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1446 it < sorted.end();
1447 it ++ )
1448 {
1449 NR::Point dest((*it)._node->pos);
1450 dest[axis] = pos;
1451 sp_node_moveto((*it)._node, dest);
1452 pos += step;
1453 }
1455 sp_nodepath_update_repr(nodepath);
1456 }
1459 /**
1460 * Call sp_nodepath_line_add_node() for all selected segments.
1461 */
1462 void
1463 sp_node_selected_add_node(void)
1464 {
1465 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1466 if (!nodepath) {
1467 return;
1468 }
1470 GList *nl = NULL;
1472 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1473 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1474 g_assert(t->selected);
1475 if (t->p.other && t->p.other->selected) {
1476 nl = g_list_prepend(nl, t);
1477 }
1478 }
1480 while (nl) {
1481 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1482 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1483 sp_nodepath_node_select(n, TRUE, FALSE);
1484 nl = g_list_remove(nl, t);
1485 }
1487 /** \todo fixme: adjust ? */
1488 sp_nodepath_update_handles(nodepath);
1490 sp_nodepath_update_repr(nodepath);
1492 sp_nodepath_update_statusbar(nodepath);
1493 }
1495 /**
1496 * Select segment nearest to point
1497 */
1498 void
1499 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1500 {
1501 if (!nodepath) {
1502 return;
1503 }
1505 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1507 //find segment to segment
1508 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1510 gboolean force = FALSE;
1511 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1512 force = TRUE;
1513 }
1514 sp_nodepath_node_select(e, (gboolean) toggle, force);
1515 if (e->p.other)
1516 sp_nodepath_node_select(e->p.other, TRUE, force);
1518 sp_nodepath_update_handles(nodepath);
1520 sp_nodepath_update_statusbar(nodepath);
1521 }
1523 /**
1524 * Add a node nearest to point
1525 */
1526 void
1527 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1528 {
1529 if (!nodepath) {
1530 return;
1531 }
1533 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1535 //find segment to split
1536 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1538 //don't know why but t seems to flip for lines
1539 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1540 position.t = 1.0 - position.t;
1541 }
1542 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1543 sp_nodepath_node_select(n, FALSE, TRUE);
1545 /* fixme: adjust ? */
1546 sp_nodepath_update_handles(nodepath);
1548 sp_nodepath_update_repr(nodepath);
1550 sp_nodepath_update_statusbar(nodepath);
1551 }
1553 /*
1554 * Adjusts a segment so that t moves by a certain delta for dragging
1555 * converts lines to curves
1556 *
1557 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1558 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1559 */
1560 void
1561 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1562 {
1563 /* feel good is an arbitrary parameter that distributes the delta between handles
1564 * if t of the drag point is less than 1/6 distance form the endpoint only
1565 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1566 */
1567 double feel_good;
1568 if (t <= 1.0 / 6.0)
1569 feel_good = 0;
1570 else if (t <= 0.5)
1571 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1572 else if (t <= 5.0 / 6.0)
1573 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1574 else
1575 feel_good = 1;
1577 //if we're dragging a line convert it to a curve
1578 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1579 sp_nodepath_set_line_type(e, NR_CURVETO);
1580 }
1582 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1583 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1584 e->p.other->n.pos += offsetcoord0;
1585 e->p.pos += offsetcoord1;
1587 // adjust handles of adjacent nodes where necessary
1588 sp_node_adjust_handle(e,1);
1589 sp_node_adjust_handle(e->p.other,-1);
1591 sp_nodepath_update_handles(e->subpath->nodepath);
1593 update_object(e->subpath->nodepath);
1595 sp_nodepath_update_statusbar(e->subpath->nodepath);
1596 }
1599 /**
1600 * Call sp_nodepath_break() for all selected segments.
1601 */
1602 void sp_node_selected_break()
1603 {
1604 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1605 if (!nodepath) return;
1607 GList *temp = NULL;
1608 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1609 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1610 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1611 if (nn == NULL) continue; // no break, no new node
1612 temp = g_list_prepend(temp, nn);
1613 }
1615 if (temp) {
1616 sp_nodepath_deselect(nodepath);
1617 }
1618 for (GList *l = temp; l != NULL; l = l->next) {
1619 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1620 }
1622 sp_nodepath_update_handles(nodepath);
1624 sp_nodepath_update_repr(nodepath);
1625 }
1627 /**
1628 * Duplicate the selected node(s).
1629 */
1630 void sp_node_selected_duplicate()
1631 {
1632 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1633 if (!nodepath) {
1634 return;
1635 }
1637 GList *temp = NULL;
1638 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1639 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1640 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1641 if (nn == NULL) continue; // could not duplicate
1642 temp = g_list_prepend(temp, nn);
1643 }
1645 if (temp) {
1646 sp_nodepath_deselect(nodepath);
1647 }
1648 for (GList *l = temp; l != NULL; l = l->next) {
1649 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1650 }
1652 sp_nodepath_update_handles(nodepath);
1654 sp_nodepath_update_repr(nodepath);
1655 }
1657 /**
1658 * Join two nodes by merging them into one.
1659 */
1660 void sp_node_selected_join()
1661 {
1662 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1663 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1665 if (g_list_length(nodepath->selected) != 2) {
1666 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1667 return;
1668 }
1670 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1671 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1673 g_assert(a != b);
1674 g_assert(a->p.other || a->n.other);
1675 g_assert(b->p.other || b->n.other);
1677 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1678 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1679 return;
1680 }
1682 /* a and b are endpoints */
1684 NR::Point c;
1685 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1686 c = a->pos;
1687 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1688 c = b->pos;
1689 } else {
1690 c = (a->pos + b->pos) / 2;
1691 }
1693 if (a->subpath == b->subpath) {
1694 Inkscape::NodePath::SubPath *sp = a->subpath;
1695 sp_nodepath_subpath_close(sp);
1696 sp_node_moveto (sp->first, c);
1698 sp_nodepath_update_handles(sp->nodepath);
1699 sp_nodepath_update_repr(nodepath);
1700 return;
1701 }
1703 /* a and b are separate subpaths */
1704 Inkscape::NodePath::SubPath *sa = a->subpath;
1705 Inkscape::NodePath::SubPath *sb = b->subpath;
1706 NR::Point p;
1707 Inkscape::NodePath::Node *n;
1708 NRPathcode code;
1709 if (a == sa->first) {
1710 p = sa->first->n.pos;
1711 code = (NRPathcode)sa->first->n.other->code;
1712 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1713 n = sa->last;
1714 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1715 n = n->p.other;
1716 while (n) {
1717 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1718 n = n->p.other;
1719 if (n == sa->first) n = NULL;
1720 }
1721 sp_nodepath_subpath_destroy(sa);
1722 sa = t;
1723 } else if (a == sa->last) {
1724 p = sa->last->p.pos;
1725 code = (NRPathcode)sa->last->code;
1726 sp_nodepath_node_destroy(sa->last);
1727 } else {
1728 code = NR_END;
1729 g_assert_not_reached();
1730 }
1732 if (b == sb->first) {
1733 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1734 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1735 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1736 }
1737 } else if (b == sb->last) {
1738 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1739 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1740 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1741 }
1742 } else {
1743 g_assert_not_reached();
1744 }
1745 /* and now destroy sb */
1747 sp_nodepath_subpath_destroy(sb);
1749 sp_nodepath_update_handles(sa->nodepath);
1751 sp_nodepath_update_repr(nodepath);
1753 sp_nodepath_update_statusbar(nodepath);
1754 }
1756 /**
1757 * Join two nodes by adding a segment between them.
1758 */
1759 void sp_node_selected_join_segment()
1760 {
1761 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1762 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1764 if (g_list_length(nodepath->selected) != 2) {
1765 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1766 return;
1767 }
1769 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1770 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1772 g_assert(a != b);
1773 g_assert(a->p.other || a->n.other);
1774 g_assert(b->p.other || b->n.other);
1776 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1777 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1778 return;
1779 }
1781 if (a->subpath == b->subpath) {
1782 Inkscape::NodePath::SubPath *sp = a->subpath;
1784 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1785 sp->closed = TRUE;
1787 sp->first->p.other = sp->last;
1788 sp->last->n.other = sp->first;
1790 sp_node_handle_mirror_p_to_n(sp->last);
1791 sp_node_handle_mirror_n_to_p(sp->first);
1793 sp->first->code = sp->last->code;
1794 sp->first = sp->last;
1796 sp_nodepath_update_handles(sp->nodepath);
1798 sp_nodepath_update_repr(nodepath);
1800 return;
1801 }
1803 /* a and b are separate subpaths */
1804 Inkscape::NodePath::SubPath *sa = a->subpath;
1805 Inkscape::NodePath::SubPath *sb = b->subpath;
1807 Inkscape::NodePath::Node *n;
1808 NR::Point p;
1809 NRPathcode code;
1810 if (a == sa->first) {
1811 code = (NRPathcode) sa->first->n.other->code;
1812 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1813 n = sa->last;
1814 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1815 for (n = n->p.other; n != NULL; n = n->p.other) {
1816 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1817 }
1818 sp_nodepath_subpath_destroy(sa);
1819 sa = t;
1820 } else if (a == sa->last) {
1821 code = (NRPathcode)sa->last->code;
1822 } else {
1823 code = NR_END;
1824 g_assert_not_reached();
1825 }
1827 if (b == sb->first) {
1828 n = sb->first;
1829 sp_node_handle_mirror_p_to_n(sa->last);
1830 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1831 sp_node_handle_mirror_n_to_p(sa->last);
1832 for (n = n->n.other; n != NULL; n = n->n.other) {
1833 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1834 }
1835 } else if (b == sb->last) {
1836 n = sb->last;
1837 sp_node_handle_mirror_p_to_n(sa->last);
1838 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1839 sp_node_handle_mirror_n_to_p(sa->last);
1840 for (n = n->p.other; n != NULL; n = n->p.other) {
1841 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1842 }
1843 } else {
1844 g_assert_not_reached();
1845 }
1846 /* and now destroy sb */
1848 sp_nodepath_subpath_destroy(sb);
1850 sp_nodepath_update_handles(sa->nodepath);
1852 sp_nodepath_update_repr(nodepath);
1853 }
1855 /**
1856 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1857 */
1858 void sp_node_delete_preserve(GList *nodes_to_delete)
1859 {
1860 GSList *nodepaths = NULL;
1862 while (nodes_to_delete) {
1863 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1864 Inkscape::NodePath::SubPath *sp = node->subpath;
1865 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1866 Inkscape::NodePath::Node *sample_cursor = NULL;
1867 Inkscape::NodePath::Node *sample_end = NULL;
1868 Inkscape::NodePath::Node *delete_cursor = node;
1869 bool just_delete = false;
1871 //find the start of this contiguous selection
1872 //move left to the first node that is not selected
1873 //or the start of the non-closed path
1874 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1875 delete_cursor = curr;
1876 }
1878 //just delete at the beginning of an open path
1879 if (!delete_cursor->p.other) {
1880 sample_cursor = delete_cursor;
1881 just_delete = true;
1882 } else {
1883 sample_cursor = delete_cursor->p.other;
1884 }
1886 //calculate points for each segment
1887 int rate = 5;
1888 float period = 1.0 / rate;
1889 std::vector<NR::Point> data;
1890 if (!just_delete) {
1891 data.push_back(sample_cursor->pos);
1892 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1893 //just delete at the end of an open path
1894 if (!sp->closed && curr->n.other == sp->last) {
1895 just_delete = true;
1896 break;
1897 }
1899 //sample points on the contiguous selected segment
1900 NR::Point *bez;
1901 bez = new NR::Point [4];
1902 bez[0] = curr->pos;
1903 bez[1] = curr->n.pos;
1904 bez[2] = curr->n.other->p.pos;
1905 bez[3] = curr->n.other->pos;
1906 for (int i=1; i<rate; i++) {
1907 gdouble t = i * period;
1908 NR::Point p = bezier_pt(3, bez, t);
1909 data.push_back(p);
1910 }
1911 data.push_back(curr->n.other->pos);
1913 sample_end = curr->n.other;
1914 //break if we've come full circle or hit the end of the selection
1915 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1916 break;
1917 }
1918 }
1919 }
1921 if (!just_delete) {
1922 //calculate the best fitting single segment and adjust the endpoints
1923 NR::Point *adata;
1924 adata = new NR::Point [data.size()];
1925 copy(data.begin(), data.end(), adata);
1927 NR::Point *bez;
1928 bez = new NR::Point [4];
1929 //would decreasing error create a better fitting approximation?
1930 gdouble error = 1.0;
1931 gint ret;
1932 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1934 //adjust endpoints
1935 sample_cursor->n.pos = bez[1];
1936 sample_end->p.pos = bez[2];
1937 }
1939 //destroy this contiguous selection
1940 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
1941 Inkscape::NodePath::Node *temp = delete_cursor;
1942 if (delete_cursor->n.other == delete_cursor) {
1943 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
1944 delete_cursor = NULL;
1945 } else {
1946 delete_cursor = delete_cursor->n.other;
1947 }
1948 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
1949 sp_nodepath_node_destroy(temp);
1950 }
1952 sp_nodepath_update_handles(nodepath);
1954 if (!g_slist_find(nodepaths, nodepath))
1955 nodepaths = g_slist_prepend (nodepaths, nodepath);
1956 }
1958 for (GSList *i = nodepaths; i; i = i->next) {
1959 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
1960 // different nodepaths will give us one undo event per nodepath
1961 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
1963 // if the entire nodepath is removed, delete the selected object.
1964 if (nodepath->subpaths == NULL ||
1965 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
1966 //at least 2
1967 sp_nodepath_get_node_count(nodepath) < 2) {
1968 SPDocument *document = sp_desktop_document (nodepath->desktop);
1969 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
1970 //delete this nodepath's object, not the entire selection! (though at this time, this
1971 //does not matter)
1972 sp_selection_delete();
1973 sp_document_done (document);
1974 } else {
1975 sp_nodepath_update_repr(nodepath);
1976 sp_nodepath_update_statusbar(nodepath);
1977 }
1978 }
1980 g_slist_free (nodepaths);
1981 }
1983 /**
1984 * Delete one or more selected nodes.
1985 */
1986 void sp_node_selected_delete()
1987 {
1988 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1989 if (!nodepath) return;
1990 if (!nodepath->selected) return;
1992 /** \todo fixme: do it the right way */
1993 while (nodepath->selected) {
1994 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1995 sp_nodepath_node_destroy(node);
1996 }
1999 //clean up the nodepath (such as for trivial subpaths)
2000 sp_nodepath_cleanup(nodepath);
2002 sp_nodepath_update_handles(nodepath);
2004 // if the entire nodepath is removed, delete the selected object.
2005 if (nodepath->subpaths == NULL ||
2006 sp_nodepath_get_node_count(nodepath) < 2) {
2007 SPDocument *document = sp_desktop_document (nodepath->desktop);
2008 sp_selection_delete();
2009 sp_document_done (document);
2010 return;
2011 }
2013 sp_nodepath_update_repr(nodepath);
2015 sp_nodepath_update_statusbar(nodepath);
2016 }
2018 /**
2019 * Delete one or more segments between two selected nodes.
2020 * This is the code for 'split'.
2021 */
2022 void
2023 sp_node_selected_delete_segment(void)
2024 {
2025 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2026 Inkscape::NodePath::Node *curr, *next; //Iterators
2028 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2029 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2031 if (g_list_length(nodepath->selected) != 2) {
2032 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2033 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2034 return;
2035 }
2037 //Selected nodes, not inclusive
2038 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2039 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2041 if ( ( a==b) || //same node
2042 (a->subpath != b->subpath ) || //not the same path
2043 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2044 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2045 {
2046 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2047 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2048 return;
2049 }
2051 //###########################################
2052 //# BEGIN EDITS
2053 //###########################################
2054 //##################################
2055 //# CLOSED PATH
2056 //##################################
2057 if (a->subpath->closed) {
2060 gboolean reversed = FALSE;
2062 //Since we can go in a circle, we need to find the shorter distance.
2063 // a->b or b->a
2064 start = end = NULL;
2065 int distance = 0;
2066 int minDistance = 0;
2067 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2068 if (curr==b) {
2069 //printf("a to b:%d\n", distance);
2070 start = a;//go from a to b
2071 end = b;
2072 minDistance = distance;
2073 //printf("A to B :\n");
2074 break;
2075 }
2076 distance++;
2077 }
2079 //try again, the other direction
2080 distance = 0;
2081 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2082 if (curr==a) {
2083 //printf("b to a:%d\n", distance);
2084 if (distance < minDistance) {
2085 start = b; //we go from b to a
2086 end = a;
2087 reversed = TRUE;
2088 //printf("B to A\n");
2089 }
2090 break;
2091 }
2092 distance++;
2093 }
2096 //Copy everything from 'end' to 'start' to a new subpath
2097 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2098 for (curr=end ; curr ; curr=curr->n.other) {
2099 NRPathcode code = (NRPathcode) curr->code;
2100 if (curr == end)
2101 code = NR_MOVETO;
2102 sp_nodepath_node_new(t, NULL,
2103 (Inkscape::NodePath::NodeType)curr->type, code,
2104 &curr->p.pos, &curr->pos, &curr->n.pos);
2105 if (curr == start)
2106 break;
2107 }
2108 sp_nodepath_subpath_destroy(a->subpath);
2111 }
2115 //##################################
2116 //# OPEN PATH
2117 //##################################
2118 else {
2120 //We need to get the direction of the list between A and B
2121 //Can we walk from a to b?
2122 start = end = NULL;
2123 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2124 if (curr==b) {
2125 start = a; //did it! we go from a to b
2126 end = b;
2127 //printf("A to B\n");
2128 break;
2129 }
2130 }
2131 if (!start) {//didn't work? let's try the other direction
2132 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2133 if (curr==a) {
2134 start = b; //did it! we go from b to a
2135 end = a;
2136 //printf("B to A\n");
2137 break;
2138 }
2139 }
2140 }
2141 if (!start) {
2142 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2143 _("Cannot find path between nodes."));
2144 return;
2145 }
2149 //Copy everything after 'end' to a new subpath
2150 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2151 for (curr=end ; curr ; curr=curr->n.other) {
2152 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2153 &curr->p.pos, &curr->pos, &curr->n.pos);
2154 }
2156 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2157 for (curr = start->n.other ; curr ; curr=next) {
2158 next = curr->n.other;
2159 sp_nodepath_node_destroy(curr);
2160 }
2162 }
2163 //###########################################
2164 //# END EDITS
2165 //###########################################
2167 //clean up the nodepath (such as for trivial subpaths)
2168 sp_nodepath_cleanup(nodepath);
2170 sp_nodepath_update_handles(nodepath);
2172 sp_nodepath_update_repr(nodepath);
2174 sp_nodepath_update_statusbar(nodepath);
2175 }
2177 /**
2178 * Call sp_nodepath_set_line() for all selected segments.
2179 */
2180 void
2181 sp_node_selected_set_line_type(NRPathcode code)
2182 {
2183 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2184 if (nodepath == NULL) return;
2186 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2187 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2188 g_assert(n->selected);
2189 if (n->p.other && n->p.other->selected) {
2190 sp_nodepath_set_line_type(n, code);
2191 }
2192 }
2194 sp_nodepath_update_repr(nodepath);
2195 }
2197 /**
2198 * Call sp_nodepath_convert_node_type() for all selected nodes.
2199 */
2200 void
2201 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2202 {
2203 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2204 if (nodepath == NULL) return;
2206 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2207 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2208 }
2210 sp_nodepath_update_repr(nodepath);
2211 }
2213 /**
2214 * Change select status of node, update its own and neighbour handles.
2215 */
2216 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2217 {
2218 node->selected = selected;
2220 if (selected) {
2221 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2222 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2223 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2224 sp_knot_update_ctrl(node->knot);
2225 } else {
2226 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2227 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2228 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2229 sp_knot_update_ctrl(node->knot);
2230 }
2232 sp_node_update_handles(node);
2233 if (node->n.other) sp_node_update_handles(node->n.other);
2234 if (node->p.other) sp_node_update_handles(node->p.other);
2235 }
2237 /**
2238 \brief Select a node
2239 \param node The node to select
2240 \param incremental If true, add to selection, otherwise deselect others
2241 \param override If true, always select this node, otherwise toggle selected status
2242 */
2243 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2244 {
2245 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2247 if (incremental) {
2248 if (override) {
2249 if (!g_list_find(nodepath->selected, node)) {
2250 nodepath->selected = g_list_prepend(nodepath->selected, node);
2251 }
2252 sp_node_set_selected(node, TRUE);
2253 } else { // toggle
2254 if (node->selected) {
2255 g_assert(g_list_find(nodepath->selected, node));
2256 nodepath->selected = g_list_remove(nodepath->selected, node);
2257 } else {
2258 g_assert(!g_list_find(nodepath->selected, node));
2259 nodepath->selected = g_list_prepend(nodepath->selected, node);
2260 }
2261 sp_node_set_selected(node, !node->selected);
2262 }
2263 } else {
2264 sp_nodepath_deselect(nodepath);
2265 nodepath->selected = g_list_prepend(nodepath->selected, node);
2266 sp_node_set_selected(node, TRUE);
2267 }
2269 sp_nodepath_update_statusbar(nodepath);
2270 }
2273 /**
2274 \brief Deselect all nodes in the nodepath
2275 */
2276 void
2277 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2278 {
2279 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2281 while (nodepath->selected) {
2282 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2283 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2284 }
2285 sp_nodepath_update_statusbar(nodepath);
2286 }
2288 /**
2289 \brief Select or invert selection of all nodes in the nodepath
2290 */
2291 void
2292 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2293 {
2294 if (!nodepath) return;
2296 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2297 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2298 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2299 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2300 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2301 }
2302 }
2303 }
2305 /**
2306 * If nothing selected, does the same as sp_nodepath_select_all();
2307 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2308 * (i.e., similar to "select all in layer", with the "selected" subpaths
2309 * being treated as "layers" in the path).
2310 */
2311 void
2312 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2313 {
2314 if (!nodepath) return;
2316 if (g_list_length (nodepath->selected) == 0) {
2317 sp_nodepath_select_all (nodepath, invert);
2318 return;
2319 }
2321 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2322 GSList *subpaths = NULL;
2324 for (GList *l = copy; l != NULL; l = l->next) {
2325 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2326 Inkscape::NodePath::SubPath *subpath = n->subpath;
2327 if (!g_slist_find (subpaths, subpath))
2328 subpaths = g_slist_prepend (subpaths, subpath);
2329 }
2331 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2332 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2333 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2334 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2335 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2336 }
2337 }
2339 g_slist_free (subpaths);
2340 g_list_free (copy);
2341 }
2343 /**
2344 * \brief Select the node after the last selected; if none is selected,
2345 * select the first within path.
2346 */
2347 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2348 {
2349 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2351 Inkscape::NodePath::Node *last = NULL;
2352 if (nodepath->selected) {
2353 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2354 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2355 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2356 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2357 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2358 if (node->selected) {
2359 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2360 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2361 if (spl->next) { // there's a next subpath
2362 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2363 last = subpath_next->first;
2364 } else if (spl->prev) { // there's a previous subpath
2365 last = NULL; // to be set later to the first node of first subpath
2366 } else {
2367 last = node->n.other;
2368 }
2369 } else {
2370 last = node->n.other;
2371 }
2372 } else {
2373 if (node->n.other) {
2374 last = node->n.other;
2375 } else {
2376 if (spl->next) { // there's a next subpath
2377 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2378 last = subpath_next->first;
2379 } else if (spl->prev) { // there's a previous subpath
2380 last = NULL; // to be set later to the first node of first subpath
2381 } else {
2382 last = (Inkscape::NodePath::Node *) subpath->first;
2383 }
2384 }
2385 }
2386 }
2387 }
2388 }
2389 sp_nodepath_deselect(nodepath);
2390 }
2392 if (last) { // there's at least one more node after selected
2393 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2394 } else { // no more nodes, select the first one in first subpath
2395 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2396 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2397 }
2398 }
2400 /**
2401 * \brief Select the node before the first selected; if none is selected,
2402 * select the last within path
2403 */
2404 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2405 {
2406 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2408 Inkscape::NodePath::Node *last = NULL;
2409 if (nodepath->selected) {
2410 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2411 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2412 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2413 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2414 if (node->selected) {
2415 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2416 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2417 if (spl->prev) { // there's a prev subpath
2418 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2419 last = subpath_prev->last;
2420 } else if (spl->next) { // there's a next subpath
2421 last = NULL; // to be set later to the last node of last subpath
2422 } else {
2423 last = node->p.other;
2424 }
2425 } else {
2426 last = node->p.other;
2427 }
2428 } else {
2429 if (node->p.other) {
2430 last = node->p.other;
2431 } else {
2432 if (spl->prev) { // there's a prev subpath
2433 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2434 last = subpath_prev->last;
2435 } else if (spl->next) { // there's a next subpath
2436 last = NULL; // to be set later to the last node of last subpath
2437 } else {
2438 last = (Inkscape::NodePath::Node *) subpath->last;
2439 }
2440 }
2441 }
2442 }
2443 }
2444 }
2445 sp_nodepath_deselect(nodepath);
2446 }
2448 if (last) { // there's at least one more node before selected
2449 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2450 } else { // no more nodes, select the last one in last subpath
2451 GList *spl = g_list_last(nodepath->subpaths);
2452 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2453 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2454 }
2455 }
2457 /**
2458 * \brief Select all nodes that are within the rectangle.
2459 */
2460 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2461 {
2462 if (!incremental) {
2463 sp_nodepath_deselect(nodepath);
2464 }
2466 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2467 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2468 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2469 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2471 if (b.contains(node->pos)) {
2472 sp_nodepath_node_select(node, TRUE, TRUE);
2473 }
2474 }
2475 }
2476 }
2479 /**
2480 \brief Saves all nodes' and handles' current positions in their origin members
2481 */
2482 void
2483 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2484 {
2485 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2486 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2487 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2488 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2489 n->origin = n->pos;
2490 n->p.origin = n->p.pos;
2491 n->n.origin = n->n.pos;
2492 }
2493 }
2494 }
2496 /**
2497 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2498 */
2499 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2500 {
2501 if (!nodepath->selected) {
2502 return NULL;
2503 }
2505 GList *r = NULL;
2506 guint i = 0;
2507 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2508 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2509 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2510 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2511 i++;
2512 if (node->selected) {
2513 r = g_list_append(r, GINT_TO_POINTER(i));
2514 }
2515 }
2516 }
2517 return r;
2518 }
2520 /**
2521 \brief Restores selection by selecting nodes whose positions are in the list
2522 */
2523 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2524 {
2525 sp_nodepath_deselect(nodepath);
2527 guint i = 0;
2528 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2529 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2530 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2531 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2532 i++;
2533 if (g_list_find(r, GINT_TO_POINTER(i))) {
2534 sp_nodepath_node_select(node, TRUE, TRUE);
2535 }
2536 }
2537 }
2539 }
2541 /**
2542 \brief Adjusts handle according to node type and line code.
2543 */
2544 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2545 {
2546 double len, otherlen, linelen;
2548 g_assert(node);
2550 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2551 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2553 /** \todo fixme: */
2554 if (me->other == NULL) return;
2555 if (other->other == NULL) return;
2557 /* I have line */
2559 NRPathcode mecode, ocode;
2560 if (which_adjust == 1) {
2561 mecode = (NRPathcode)me->other->code;
2562 ocode = (NRPathcode)node->code;
2563 } else {
2564 mecode = (NRPathcode)node->code;
2565 ocode = (NRPathcode)other->other->code;
2566 }
2568 if (mecode == NR_LINETO) return;
2570 /* I am curve */
2572 if (other->other == NULL) return;
2574 /* Other has line */
2576 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2578 NR::Point delta;
2579 if (ocode == NR_LINETO) {
2580 /* other is lineto, we are either smooth or symm */
2581 Inkscape::NodePath::Node *othernode = other->other;
2582 len = NR::L2(me->pos - node->pos);
2583 delta = node->pos - othernode->pos;
2584 linelen = NR::L2(delta);
2585 if (linelen < 1e-18)
2586 return;
2587 me->pos = node->pos + (len / linelen)*delta;
2588 return;
2589 }
2591 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2593 me->pos = 2 * node->pos - other->pos;
2594 return;
2595 }
2597 /* We are smooth */
2599 len = NR::L2(me->pos - node->pos);
2600 delta = other->pos - node->pos;
2601 otherlen = NR::L2(delta);
2602 if (otherlen < 1e-18) return;
2604 me->pos = node->pos - (len / otherlen) * delta;
2605 }
2607 /**
2608 \brief Adjusts both handles according to node type and line code
2609 */
2610 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2611 {
2612 g_assert(node);
2614 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2616 /* we are either smooth or symm */
2618 if (node->p.other == NULL) return;
2620 if (node->n.other == NULL) return;
2622 if (node->code == NR_LINETO) {
2623 if (node->n.other->code == NR_LINETO) return;
2624 sp_node_adjust_handle(node, 1);
2625 return;
2626 }
2628 if (node->n.other->code == NR_LINETO) {
2629 if (node->code == NR_LINETO) return;
2630 sp_node_adjust_handle(node, -1);
2631 return;
2632 }
2634 /* both are curves */
2635 NR::Point const delta( node->n.pos - node->p.pos );
2637 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2638 node->p.pos = node->pos - delta / 2;
2639 node->n.pos = node->pos + delta / 2;
2640 return;
2641 }
2643 /* We are smooth */
2644 double plen = NR::L2(node->p.pos - node->pos);
2645 if (plen < 1e-18) return;
2646 double nlen = NR::L2(node->n.pos - node->pos);
2647 if (nlen < 1e-18) return;
2648 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2649 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2650 }
2652 /**
2653 * Node event callback.
2654 */
2655 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2656 {
2657 gboolean ret = FALSE;
2658 switch (event->type) {
2659 case GDK_ENTER_NOTIFY:
2660 active_node = n;
2661 break;
2662 case GDK_LEAVE_NOTIFY:
2663 active_node = NULL;
2664 break;
2665 case GDK_KEY_PRESS:
2666 switch (get_group0_keyval (&event->key)) {
2667 case GDK_space:
2668 if (event->key.state & GDK_BUTTON1_MASK) {
2669 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2670 stamp_repr(nodepath);
2671 ret = TRUE;
2672 }
2673 break;
2674 default:
2675 break;
2676 }
2677 break;
2678 default:
2679 break;
2680 }
2682 return ret;
2683 }
2685 /**
2686 * Handle keypress on node; directly called.
2687 */
2688 gboolean node_key(GdkEvent *event)
2689 {
2690 Inkscape::NodePath::Path *np;
2692 // there is no way to verify nodes so set active_node to nil when deleting!!
2693 if (active_node == NULL) return FALSE;
2695 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2696 gint ret = FALSE;
2697 switch (get_group0_keyval (&event->key)) {
2698 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2699 case GDK_BackSpace:
2700 np = active_node->subpath->nodepath;
2701 sp_nodepath_node_destroy(active_node);
2702 sp_nodepath_update_repr(np);
2703 active_node = NULL;
2704 ret = TRUE;
2705 break;
2706 case GDK_c:
2707 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2708 ret = TRUE;
2709 break;
2710 case GDK_s:
2711 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2712 ret = TRUE;
2713 break;
2714 case GDK_y:
2715 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2716 ret = TRUE;
2717 break;
2718 case GDK_b:
2719 sp_nodepath_node_break(active_node);
2720 ret = TRUE;
2721 break;
2722 }
2723 return ret;
2724 }
2725 return FALSE;
2726 }
2728 /**
2729 * Mouseclick on node callback.
2730 */
2731 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2732 {
2733 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2735 if (state & GDK_CONTROL_MASK) {
2736 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2738 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2739 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2740 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2741 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2742 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2743 } else {
2744 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2745 }
2746 sp_nodepath_update_repr(nodepath);
2747 sp_nodepath_update_statusbar(nodepath);
2749 } else { //ctrl+alt+click: delete node
2750 GList *node_to_delete = NULL;
2751 node_to_delete = g_list_append(node_to_delete, n);
2752 sp_node_delete_preserve(node_to_delete);
2753 }
2755 } else {
2756 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2757 }
2758 }
2760 /**
2761 * Mouse grabbed node callback.
2762 */
2763 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2764 {
2765 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2767 if (!n->selected) {
2768 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2769 }
2771 sp_nodepath_remember_origins (n->subpath->nodepath);
2772 }
2774 /**
2775 * Mouse ungrabbed node callback.
2776 */
2777 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2778 {
2779 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2781 n->dragging_out = NULL;
2783 sp_nodepath_update_repr(n->subpath->nodepath);
2784 }
2786 /**
2787 * The point on a line, given by its angle, closest to the given point.
2788 * \param p A point.
2789 * \param a Angle of the line; it is assumed to go through coordinate origin.
2790 * \param closest Pointer to the point struct where the result is stored.
2791 * \todo FIXME: use dot product perhaps?
2792 */
2793 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2794 {
2795 if (a == HUGE_VAL) { // vertical
2796 *closest = NR::Point(0, (*p)[NR::Y]);
2797 } else {
2798 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2799 (*closest)[NR::Y] = a * (*closest)[NR::X];
2800 }
2801 }
2803 /**
2804 * Distance from the point to a line given by its angle.
2805 * \param p A point.
2806 * \param a Angle of the line; it is assumed to go through coordinate origin.
2807 */
2808 static double point_line_distance(NR::Point *p, double a)
2809 {
2810 NR::Point c;
2811 point_line_closest(p, a, &c);
2812 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]));
2813 }
2815 /**
2816 * Callback for node "request" signal.
2817 * \todo fixme: This goes to "moved" event? (lauris)
2818 */
2819 static gboolean
2820 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2821 {
2822 double yn, xn, yp, xp;
2823 double an, ap, na, pa;
2824 double d_an, d_ap, d_na, d_pa;
2825 gboolean collinear = FALSE;
2826 NR::Point c;
2827 NR::Point pr;
2829 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2831 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2832 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2834 NR::Point mouse = (*p);
2836 if (!n->dragging_out) {
2837 // This is the first drag-out event; find out which handle to drag out
2838 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2839 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2841 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2842 return FALSE;
2844 Inkscape::NodePath::NodeSide *opposite;
2845 if (appr_p > appr_n) { // closer to p
2846 n->dragging_out = &n->p;
2847 opposite = &n->n;
2848 n->code = NR_CURVETO;
2849 } else if (appr_p < appr_n) { // closer to n
2850 n->dragging_out = &n->n;
2851 opposite = &n->p;
2852 n->n.other->code = NR_CURVETO;
2853 } else { // p and n nodes are the same
2854 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2855 n->dragging_out = &n->p;
2856 opposite = &n->n;
2857 n->code = NR_CURVETO;
2858 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2859 n->dragging_out = &n->n;
2860 opposite = &n->p;
2861 n->n.other->code = NR_CURVETO;
2862 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2863 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);
2864 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);
2865 if (appr_other_p > appr_other_n) { // closer to other's p handle
2866 n->dragging_out = &n->n;
2867 opposite = &n->p;
2868 n->n.other->code = NR_CURVETO;
2869 } else { // closer to other's n handle
2870 n->dragging_out = &n->p;
2871 opposite = &n->n;
2872 n->code = NR_CURVETO;
2873 }
2874 }
2875 }
2877 // if there's another handle, make sure the one we drag out starts parallel to it
2878 if (opposite->pos != n->pos) {
2879 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2880 }
2882 // knots might not be created yet!
2883 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2884 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2885 }
2887 // pass this on to the handle-moved callback
2888 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2889 sp_node_update_handles(n);
2890 return TRUE;
2891 }
2893 if (state & GDK_CONTROL_MASK) { // constrained motion
2895 // calculate relative distances of handles
2896 // n handle:
2897 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2898 xn = n->n.pos[NR::X] - n->pos[NR::X];
2899 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2900 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2901 if (n->n.other) { // if there is the next point
2902 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2903 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2904 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2905 }
2906 }
2907 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2908 if (yn < 0) { xn = -xn; yn = -yn; }
2910 // p handle:
2911 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2912 xp = n->p.pos[NR::X] - n->pos[NR::X];
2913 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2914 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2915 if (n->p.other) {
2916 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2917 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2918 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2919 }
2920 }
2921 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2922 if (yp < 0) { xp = -xp; yp = -yp; }
2924 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2925 // sliding on handles, only if at least one of the handles is non-vertical
2926 // (otherwise it's the same as ctrl+drag anyway)
2928 // calculate angles of the handles
2929 if (xn == 0) {
2930 if (yn == 0) { // no handle, consider it the continuation of the other one
2931 an = 0;
2932 collinear = TRUE;
2933 }
2934 else an = 0; // vertical; set the angle to horizontal
2935 } else an = yn/xn;
2937 if (xp == 0) {
2938 if (yp == 0) { // no handle, consider it the continuation of the other one
2939 ap = an;
2940 }
2941 else ap = 0; // vertical; set the angle to horizontal
2942 } else ap = yp/xp;
2944 if (collinear) an = ap;
2946 // angles of the perpendiculars; HUGE_VAL means vertical
2947 if (an == 0) na = HUGE_VAL; else na = -1/an;
2948 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2950 // mouse point relative to the node's original pos
2951 pr = (*p) - n->origin;
2953 // distances to the four lines (two handles and two perpendiculars)
2954 d_an = point_line_distance(&pr, an);
2955 d_na = point_line_distance(&pr, na);
2956 d_ap = point_line_distance(&pr, ap);
2957 d_pa = point_line_distance(&pr, pa);
2959 // find out which line is the closest, save its closest point in c
2960 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2961 point_line_closest(&pr, an, &c);
2962 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2963 point_line_closest(&pr, ap, &c);
2964 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2965 point_line_closest(&pr, na, &c);
2966 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2967 point_line_closest(&pr, pa, &c);
2968 }
2970 // move the node to the closest point
2971 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2972 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2973 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2975 } else { // constraining to hor/vert
2977 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2978 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2979 } else { // snap to vert
2980 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2981 }
2982 }
2983 } else { // move freely
2984 if (state & GDK_MOD1_MASK) { // sculpt
2985 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
2986 } else {
2987 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2988 (*p)[NR::X] - n->pos[NR::X],
2989 (*p)[NR::Y] - n->pos[NR::Y],
2990 (state & GDK_SHIFT_MASK) == 0);
2991 }
2992 }
2994 n->subpath->nodepath->desktop->scroll_to_point(p);
2996 return TRUE;
2997 }
2999 /**
3000 * Node handle clicked callback.
3001 */
3002 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3003 {
3004 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3006 if (state & GDK_CONTROL_MASK) { // "delete" handle
3007 if (n->p.knot == knot) {
3008 n->p.pos = n->pos;
3009 } else if (n->n.knot == knot) {
3010 n->n.pos = n->pos;
3011 }
3012 sp_node_update_handles(n);
3013 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3014 sp_nodepath_update_repr(nodepath);
3015 sp_nodepath_update_statusbar(nodepath);
3017 } else { // just select or add to selection, depending in Shift
3018 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3019 }
3020 }
3022 /**
3023 * Node handle grabbed callback.
3024 */
3025 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3026 {
3027 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3029 if (!n->selected) {
3030 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3031 }
3033 // remember the origin point of the handle
3034 if (n->p.knot == knot) {
3035 n->p.origin_radial = n->p.pos - n->pos;
3036 } else if (n->n.knot == knot) {
3037 n->n.origin_radial = n->n.pos - n->pos;
3038 } else {
3039 g_assert_not_reached();
3040 }
3042 }
3044 /**
3045 * Node handle ungrabbed callback.
3046 */
3047 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3048 {
3049 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3051 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3052 if (n->p.knot == knot) {
3053 n->p.origin_radial.a = 0;
3054 sp_knot_set_position(knot, &n->p.pos, state);
3055 } else if (n->n.knot == knot) {
3056 n->n.origin_radial.a = 0;
3057 sp_knot_set_position(knot, &n->n.pos, state);
3058 } else {
3059 g_assert_not_reached();
3060 }
3062 sp_nodepath_update_repr(n->subpath->nodepath);
3063 }
3065 /**
3066 * Node handle "request" signal callback.
3067 */
3068 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3069 {
3070 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3072 Inkscape::NodePath::NodeSide *me, *opposite;
3073 gint which;
3074 if (n->p.knot == knot) {
3075 me = &n->p;
3076 opposite = &n->n;
3077 which = -1;
3078 } else if (n->n.knot == knot) {
3079 me = &n->n;
3080 opposite = &n->p;
3081 which = 1;
3082 } else {
3083 me = opposite = NULL;
3084 which = 0;
3085 g_assert_not_reached();
3086 }
3088 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3090 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3092 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3093 /* We are smooth node adjacent with line */
3094 NR::Point const delta = *p - n->pos;
3095 NR::Coord const len = NR::L2(delta);
3096 Inkscape::NodePath::Node *othernode = opposite->other;
3097 NR::Point const ndelta = n->pos - othernode->pos;
3098 NR::Coord const linelen = NR::L2(ndelta);
3099 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3100 NR::Coord const scal = dot(delta, ndelta) / linelen;
3101 (*p) = n->pos + (scal / linelen) * ndelta;
3102 }
3103 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3104 } else {
3105 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3106 }
3108 sp_node_adjust_handle(n, -which);
3110 return FALSE;
3111 }
3113 /**
3114 * Node handle moved callback.
3115 */
3116 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3117 {
3118 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3120 Inkscape::NodePath::NodeSide *me;
3121 Inkscape::NodePath::NodeSide *other;
3122 if (n->p.knot == knot) {
3123 me = &n->p;
3124 other = &n->n;
3125 } else if (n->n.knot == knot) {
3126 me = &n->n;
3127 other = &n->p;
3128 } else {
3129 me = NULL;
3130 other = NULL;
3131 g_assert_not_reached();
3132 }
3134 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3135 Radial rme(me->pos - n->pos);
3136 Radial rother(other->pos - n->pos);
3137 Radial rnew(*p - n->pos);
3139 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3140 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3141 /* 0 interpreted as "no snapping". */
3143 // The closest PI/snaps angle, starting from zero.
3144 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3145 if (me->origin_radial.a == HUGE_VAL) {
3146 // ortho doesn't exist: original handle was zero length.
3147 rnew.a = a_snapped;
3148 } else {
3149 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3150 * its opposite and perpendiculars). */
3151 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3153 // Snap to the closest.
3154 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3155 ? a_snapped
3156 : a_ortho );
3157 }
3158 }
3160 if (state & GDK_MOD1_MASK) {
3161 // lock handle length
3162 rnew.r = me->origin_radial.r;
3163 }
3165 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3166 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3167 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3168 rother.a += rnew.a - rme.a;
3169 other->pos = NR::Point(rother) + n->pos;
3170 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3171 sp_knot_set_position(other->knot, &other->pos, 0);
3172 }
3174 me->pos = NR::Point(rnew) + n->pos;
3175 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3177 // this is what sp_knot_set_position does, but without emitting the signal:
3178 // we cannot emit a "moved" signal because we're now processing it
3179 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3181 knot->desktop->set_coordinate_status(me->pos);
3183 update_object(n->subpath->nodepath);
3185 /* status text */
3186 SPDesktop *desktop = n->subpath->nodepath->desktop;
3187 if (!desktop) return;
3188 SPEventContext *ec = desktop->event_context;
3189 if (!ec) return;
3190 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3191 if (!mc) return;
3193 double degrees = 180 / M_PI * rnew.a;
3194 if (degrees > 180) degrees -= 360;
3195 if (degrees < -180) degrees += 360;
3196 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3197 degrees = angle_to_compass (degrees);
3199 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3201 mc->setF(Inkscape::NORMAL_MESSAGE,
3202 _("<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);
3204 g_string_free(length, TRUE);
3205 }
3207 /**
3208 * Node handle event callback.
3209 */
3210 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3211 {
3212 gboolean ret = FALSE;
3213 switch (event->type) {
3214 case GDK_KEY_PRESS:
3215 switch (get_group0_keyval (&event->key)) {
3216 case GDK_space:
3217 if (event->key.state & GDK_BUTTON1_MASK) {
3218 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3219 stamp_repr(nodepath);
3220 ret = TRUE;
3221 }
3222 break;
3223 default:
3224 break;
3225 }
3226 break;
3227 default:
3228 break;
3229 }
3231 return ret;
3232 }
3234 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3235 Radial &rme, Radial &rother, gboolean const both)
3236 {
3237 rme.a += angle;
3238 if ( both
3239 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3240 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3241 {
3242 rother.a += angle;
3243 }
3244 }
3246 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3247 Radial &rme, Radial &rother, gboolean const both)
3248 {
3249 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3251 gdouble r;
3252 if ( both
3253 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3254 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3255 {
3256 r = MAX(rme.r, rother.r);
3257 } else {
3258 r = rme.r;
3259 }
3261 gdouble const weird_angle = atan2(norm_angle, r);
3262 /* Bulia says norm_angle is just the visible distance that the
3263 * object's end must travel on the screen. Left as 'angle' for want of
3264 * a better name.*/
3266 rme.a += weird_angle;
3267 if ( both
3268 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3269 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3270 {
3271 rother.a += weird_angle;
3272 }
3273 }
3275 /**
3276 * Rotate one node.
3277 */
3278 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3279 {
3280 Inkscape::NodePath::NodeSide *me, *other;
3281 bool both = false;
3283 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3284 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3286 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3287 me = &(n->p);
3288 other = &(n->n);
3289 } else if (!n->p.other) {
3290 me = &(n->n);
3291 other = &(n->p);
3292 } else {
3293 if (which > 0) { // right handle
3294 if (xn > xp) {
3295 me = &(n->n);
3296 other = &(n->p);
3297 } else {
3298 me = &(n->p);
3299 other = &(n->n);
3300 }
3301 } else if (which < 0){ // left handle
3302 if (xn <= xp) {
3303 me = &(n->n);
3304 other = &(n->p);
3305 } else {
3306 me = &(n->p);
3307 other = &(n->n);
3308 }
3309 } else { // both handles
3310 me = &(n->n);
3311 other = &(n->p);
3312 both = true;
3313 }
3314 }
3316 Radial rme(me->pos - n->pos);
3317 Radial rother(other->pos - n->pos);
3319 if (screen) {
3320 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3321 } else {
3322 node_rotate_one_internal (*n, angle, rme, rother, both);
3323 }
3325 me->pos = n->pos + NR::Point(rme);
3327 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3328 other->pos = n->pos + NR::Point(rother);
3329 }
3331 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3332 // so here we just move all the knots without emitting move signals, for speed
3333 sp_node_update_handles(n, false);
3334 }
3336 /**
3337 * Rotate selected nodes.
3338 */
3339 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3340 {
3341 if (!nodepath || !nodepath->selected) return;
3343 if (g_list_length(nodepath->selected) == 1) {
3344 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3345 node_rotate_one (n, angle, which, screen);
3346 } else {
3347 // rotate as an object:
3349 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3350 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3351 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3352 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3353 box.expandTo (n->pos); // contain all selected nodes
3354 }
3356 gdouble rot;
3357 if (screen) {
3358 gdouble const zoom = nodepath->desktop->current_zoom();
3359 gdouble const zmove = angle / zoom;
3360 gdouble const r = NR::L2(box.max() - box.midpoint());
3361 rot = atan2(zmove, r);
3362 } else {
3363 rot = angle;
3364 }
3366 NR::Matrix t =
3367 NR::Matrix (NR::translate(-box.midpoint())) *
3368 NR::Matrix (NR::rotate(rot)) *
3369 NR::Matrix (NR::translate(box.midpoint()));
3371 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3372 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3373 n->pos *= t;
3374 n->n.pos *= t;
3375 n->p.pos *= t;
3376 sp_node_update_handles(n, false);
3377 }
3378 }
3380 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3381 }
3383 /**
3384 * Scale one node.
3385 */
3386 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3387 {
3388 bool both = false;
3389 Inkscape::NodePath::NodeSide *me, *other;
3391 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3392 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3394 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3395 me = &(n->p);
3396 other = &(n->n);
3397 n->code = NR_CURVETO;
3398 } else if (!n->p.other) {
3399 me = &(n->n);
3400 other = &(n->p);
3401 if (n->n.other)
3402 n->n.other->code = NR_CURVETO;
3403 } else {
3404 if (which > 0) { // right handle
3405 if (xn > xp) {
3406 me = &(n->n);
3407 other = &(n->p);
3408 if (n->n.other)
3409 n->n.other->code = NR_CURVETO;
3410 } else {
3411 me = &(n->p);
3412 other = &(n->n);
3413 n->code = NR_CURVETO;
3414 }
3415 } else if (which < 0){ // left handle
3416 if (xn <= xp) {
3417 me = &(n->n);
3418 other = &(n->p);
3419 if (n->n.other)
3420 n->n.other->code = NR_CURVETO;
3421 } else {
3422 me = &(n->p);
3423 other = &(n->n);
3424 n->code = NR_CURVETO;
3425 }
3426 } else { // both handles
3427 me = &(n->n);
3428 other = &(n->p);
3429 both = true;
3430 n->code = NR_CURVETO;
3431 if (n->n.other)
3432 n->n.other->code = NR_CURVETO;
3433 }
3434 }
3436 Radial rme(me->pos - n->pos);
3437 Radial rother(other->pos - n->pos);
3439 rme.r += grow;
3440 if (rme.r < 0) rme.r = 0;
3441 if (rme.a == HUGE_VAL) {
3442 if (me->other) { // if direction is unknown, initialize it towards the next node
3443 Radial rme_next(me->other->pos - n->pos);
3444 rme.a = rme_next.a;
3445 } else { // if there's no next, initialize to 0
3446 rme.a = 0;
3447 }
3448 }
3449 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3450 rother.r += grow;
3451 if (rother.r < 0) rother.r = 0;
3452 if (rother.a == HUGE_VAL) {
3453 rother.a = rme.a + M_PI;
3454 }
3455 }
3457 me->pos = n->pos + NR::Point(rme);
3459 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3460 other->pos = n->pos + NR::Point(rother);
3461 }
3463 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3464 // so here we just move all the knots without emitting move signals, for speed
3465 sp_node_update_handles(n, false);
3466 }
3468 /**
3469 * Scale selected nodes.
3470 */
3471 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3472 {
3473 if (!nodepath || !nodepath->selected) return;
3475 if (g_list_length(nodepath->selected) == 1) {
3476 // scale handles of the single selected node
3477 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3478 node_scale_one (n, grow, which);
3479 } else {
3480 // scale nodes as an "object":
3482 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3483 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3484 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3485 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3486 box.expandTo (n->pos); // contain all selected nodes
3487 }
3489 double scale = (box.maxExtent() + grow)/box.maxExtent();
3491 NR::Matrix t =
3492 NR::Matrix (NR::translate(-box.midpoint())) *
3493 NR::Matrix (NR::scale(scale, scale)) *
3494 NR::Matrix (NR::translate(box.midpoint()));
3496 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3497 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3498 n->pos *= t;
3499 n->n.pos *= t;
3500 n->p.pos *= t;
3501 sp_node_update_handles(n, false);
3502 }
3503 }
3505 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3506 }
3508 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3509 {
3510 if (!nodepath) return;
3511 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3512 }
3514 /**
3515 * Flip selected nodes horizontally/vertically.
3516 */
3517 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3518 {
3519 if (!nodepath || !nodepath->selected) return;
3521 if (g_list_length(nodepath->selected) == 1) {
3522 // flip handles of the single selected node
3523 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3524 double temp = n->p.pos[axis];
3525 n->p.pos[axis] = n->n.pos[axis];
3526 n->n.pos[axis] = temp;
3527 sp_node_update_handles(n, false);
3528 } else {
3529 // scale nodes as an "object":
3531 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3532 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3533 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3534 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3535 box.expandTo (n->pos); // contain all selected nodes
3536 }
3538 NR::Matrix t =
3539 NR::Matrix (NR::translate(-box.midpoint())) *
3540 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3541 NR::Matrix (NR::translate(box.midpoint()));
3543 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3544 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3545 n->pos *= t;
3546 n->n.pos *= t;
3547 n->p.pos *= t;
3548 sp_node_update_handles(n, false);
3549 }
3550 }
3552 sp_nodepath_update_repr(nodepath);
3553 }
3555 //-----------------------------------------------
3556 /**
3557 * Return new subpath under given nodepath.
3558 */
3559 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3560 {
3561 g_assert(nodepath);
3562 g_assert(nodepath->desktop);
3564 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3566 s->nodepath = nodepath;
3567 s->closed = FALSE;
3568 s->nodes = NULL;
3569 s->first = NULL;
3570 s->last = NULL;
3572 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3573 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3574 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3576 return s;
3577 }
3579 /**
3580 * Destroy nodes in subpath, then subpath itself.
3581 */
3582 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3583 {
3584 g_assert(subpath);
3585 g_assert(subpath->nodepath);
3586 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3588 while (subpath->nodes) {
3589 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3590 }
3592 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3594 g_free(subpath);
3595 }
3597 /**
3598 * Link head to tail in subpath.
3599 */
3600 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3601 {
3602 g_assert(!sp->closed);
3603 g_assert(sp->last != sp->first);
3604 g_assert(sp->first->code == NR_MOVETO);
3606 sp->closed = TRUE;
3608 //Link the head to the tail
3609 sp->first->p.other = sp->last;
3610 sp->last->n.other = sp->first;
3611 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3612 sp->first = sp->last;
3614 //Remove the extra end node
3615 sp_nodepath_node_destroy(sp->last->n.other);
3616 }
3618 /**
3619 * Open closed (loopy) subpath at node.
3620 */
3621 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3622 {
3623 g_assert(sp->closed);
3624 g_assert(n->subpath == sp);
3625 g_assert(sp->first == sp->last);
3627 /* We create new startpoint, current node will become last one */
3629 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3630 &n->pos, &n->pos, &n->n.pos);
3633 sp->closed = FALSE;
3635 //Unlink to make a head and tail
3636 sp->first = new_path;
3637 sp->last = n;
3638 n->n.other = NULL;
3639 new_path->p.other = NULL;
3640 }
3642 /**
3643 * Returns area in triangle given by points; may be negative.
3644 */
3645 inline double
3646 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3647 {
3648 return (p1[NR::X]*p2[NR::Y] + p1[NR::Y]*p3[NR::X] + p2[NR::X]*p3[NR::Y] - p2[NR::Y]*p3[NR::X] - p1[NR::Y]*p2[NR::X] - p1[NR::X]*p3[NR::Y]);
3649 }
3651 /**
3652 * Return new node in subpath with given properties.
3653 * \param pos Position of node.
3654 * \param ppos Handle position in previous direction
3655 * \param npos Handle position in previous direction
3656 */
3657 Inkscape::NodePath::Node *
3658 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)
3659 {
3660 g_assert(sp);
3661 g_assert(sp->nodepath);
3662 g_assert(sp->nodepath->desktop);
3664 if (nodechunk == NULL)
3665 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3667 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3669 n->subpath = sp;
3671 if (type != Inkscape::NodePath::NODE_NONE) {
3672 // use the type from sodipodi:nodetypes
3673 n->type = type;
3674 } else {
3675 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3676 // points are (almost) collinear
3677 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3678 // endnode, or a node with a retracted handle
3679 n->type = Inkscape::NodePath::NODE_CUSP;
3680 } else {
3681 n->type = Inkscape::NodePath::NODE_SMOOTH;
3682 }
3683 } else {
3684 n->type = Inkscape::NodePath::NODE_CUSP;
3685 }
3686 }
3688 n->code = code;
3689 n->selected = FALSE;
3690 n->pos = *pos;
3691 n->p.pos = *ppos;
3692 n->n.pos = *npos;
3694 n->dragging_out = NULL;
3696 Inkscape::NodePath::Node *prev;
3697 if (next) {
3698 //g_assert(g_list_find(sp->nodes, next));
3699 prev = next->p.other;
3700 } else {
3701 prev = sp->last;
3702 }
3704 if (prev)
3705 prev->n.other = n;
3706 else
3707 sp->first = n;
3709 if (next)
3710 next->p.other = n;
3711 else
3712 sp->last = n;
3714 n->p.other = prev;
3715 n->n.other = next;
3717 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"));
3718 sp_knot_set_position(n->knot, pos, 0);
3720 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3721 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3722 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3723 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3724 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3725 sp_knot_update_ctrl(n->knot);
3727 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3728 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3729 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3730 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3731 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3732 sp_knot_show(n->knot);
3734 // We only create handle knots and lines on demand
3735 n->p.knot = NULL;
3736 n->p.line = NULL;
3737 n->n.knot = NULL;
3738 n->n.line = NULL;
3740 sp->nodes = g_list_prepend(sp->nodes, n);
3742 return n;
3743 }
3745 /**
3746 * Destroy node and its knots, link neighbors in subpath.
3747 */
3748 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3749 {
3750 g_assert(node);
3751 g_assert(node->subpath);
3752 g_assert(SP_IS_KNOT(node->knot));
3754 Inkscape::NodePath::SubPath *sp = node->subpath;
3756 if (node->selected) { // first, deselect
3757 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3758 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3759 }
3761 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3763 g_object_unref(G_OBJECT(node->knot));
3764 if (node->p.knot)
3765 g_object_unref(G_OBJECT(node->p.knot));
3766 if (node->n.knot)
3767 g_object_unref(G_OBJECT(node->n.knot));
3769 if (node->p.line)
3770 gtk_object_destroy(GTK_OBJECT(node->p.line));
3771 if (node->n.line)
3772 gtk_object_destroy(GTK_OBJECT(node->n.line));
3774 if (sp->nodes) { // there are others nodes on the subpath
3775 if (sp->closed) {
3776 if (sp->first == node) {
3777 g_assert(sp->last == node);
3778 sp->first = node->n.other;
3779 sp->last = sp->first;
3780 }
3781 node->p.other->n.other = node->n.other;
3782 node->n.other->p.other = node->p.other;
3783 } else {
3784 if (sp->first == node) {
3785 sp->first = node->n.other;
3786 sp->first->code = NR_MOVETO;
3787 }
3788 if (sp->last == node) sp->last = node->p.other;
3789 if (node->p.other) node->p.other->n.other = node->n.other;
3790 if (node->n.other) node->n.other->p.other = node->p.other;
3791 }
3792 } else { // this was the last node on subpath
3793 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3794 }
3796 g_mem_chunk_free(nodechunk, node);
3797 }
3799 /**
3800 * Returns one of the node's two sides.
3801 * \param which Indicates which side.
3802 * \return Pointer to previous node side if which==-1, next if which==1.
3803 */
3804 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3805 {
3806 g_assert(node);
3808 switch (which) {
3809 case -1:
3810 return &node->p;
3811 case 1:
3812 return &node->n;
3813 default:
3814 break;
3815 }
3817 g_assert_not_reached();
3819 return NULL;
3820 }
3822 /**
3823 * Return the other side of the node, given one of its sides.
3824 */
3825 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3826 {
3827 g_assert(node);
3829 if (me == &node->p) return &node->n;
3830 if (me == &node->n) return &node->p;
3832 g_assert_not_reached();
3834 return NULL;
3835 }
3837 /**
3838 * Return NRPathcode on the given side of the node.
3839 */
3840 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3841 {
3842 g_assert(node);
3844 if (me == &node->p) {
3845 if (node->p.other) return (NRPathcode)node->code;
3846 return NR_MOVETO;
3847 }
3849 if (me == &node->n) {
3850 if (node->n.other) return (NRPathcode)node->n.other->code;
3851 return NR_MOVETO;
3852 }
3854 g_assert_not_reached();
3856 return NR_END;
3857 }
3859 /**
3860 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3861 */
3862 Inkscape::NodePath::Node *
3863 sp_nodepath_get_node_by_index(int index)
3864 {
3865 Inkscape::NodePath::Node *e = NULL;
3867 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3868 if (!nodepath) {
3869 return e;
3870 }
3872 //find segment
3873 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3875 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3876 int n = g_list_length(sp->nodes);
3877 if (sp->closed) {
3878 n++;
3879 }
3881 //if the piece belongs to this subpath grab it
3882 //otherwise move onto the next subpath
3883 if (index < n) {
3884 e = sp->first;
3885 for (int i = 0; i < index; ++i) {
3886 e = e->n.other;
3887 }
3888 break;
3889 } else {
3890 if (sp->closed) {
3891 index -= (n+1);
3892 } else {
3893 index -= n;
3894 }
3895 }
3896 }
3898 return e;
3899 }
3901 /**
3902 * Returns plain text meaning of node type.
3903 */
3904 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3905 {
3906 unsigned retracted = 0;
3907 bool endnode = false;
3909 for (int which = -1; which <= 1; which += 2) {
3910 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3911 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3912 retracted ++;
3913 if (!side->other)
3914 endnode = true;
3915 }
3917 if (retracted == 0) {
3918 if (endnode) {
3919 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3920 return _("end node");
3921 } else {
3922 switch (node->type) {
3923 case Inkscape::NodePath::NODE_CUSP:
3924 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3925 return _("cusp");
3926 case Inkscape::NodePath::NODE_SMOOTH:
3927 // TRANSLATORS: "smooth" is an adjective here
3928 return _("smooth");
3929 case Inkscape::NodePath::NODE_SYMM:
3930 return _("symmetric");
3931 }
3932 }
3933 } else if (retracted == 1) {
3934 if (endnode) {
3935 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3936 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3937 } else {
3938 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3939 }
3940 } else {
3941 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3942 }
3944 return NULL;
3945 }
3947 /**
3948 * Handles content of statusbar as long as node tool is active.
3949 */
3950 void
3951 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3952 {
3953 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");
3954 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3956 gint total_nodes = sp_nodepath_get_node_count(nodepath);
3957 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
3958 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
3959 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
3961 SPDesktop *desktop = NULL;
3962 if (nodepath) {
3963 desktop = nodepath->desktop;
3964 } else {
3965 desktop = SP_ACTIVE_DESKTOP;
3966 }
3968 SPEventContext *ec = desktop->event_context;
3969 if (!ec) return;
3970 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3971 if (!mc) return;
3973 if (selected_nodes == 0) {
3974 Inkscape::Selection *sel = desktop->selection;
3975 if (!sel || sel->isEmpty()) {
3976 mc->setF(Inkscape::NORMAL_MESSAGE,
3977 _("Select a single object to edit its nodes or handles."));
3978 } else {
3979 if (nodepath) {
3980 mc->setF(Inkscape::NORMAL_MESSAGE,
3981 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.",
3982 "<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.",
3983 total_nodes),
3984 total_nodes);
3985 } else {
3986 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3987 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3988 } else {
3989 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3990 }
3991 }
3992 }
3993 } else if (nodepath && selected_nodes == 1) {
3994 mc->setF(Inkscape::NORMAL_MESSAGE,
3995 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3996 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3997 total_nodes),
3998 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3999 } else {
4000 if (selected_subpaths > 1) {
4001 mc->setF(Inkscape::NORMAL_MESSAGE,
4002 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4003 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4004 total_nodes),
4005 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4006 } else {
4007 mc->setF(Inkscape::NORMAL_MESSAGE,
4008 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4009 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4010 total_nodes),
4011 selected_nodes, total_nodes, when_selected);
4012 }
4013 }
4014 }
4017 /*
4018 Local Variables:
4019 mode:c++
4020 c-file-style:"stroustrup"
4021 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4022 indent-tabs-mode:nil
4023 fill-column:99
4024 End:
4025 */
4026 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :