b8de67ef0895e86cfe06dd2c6ff8b0e00b62da37
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 double n_sel_range = 0, p_sel_range = 0;
1048 guint n_nodes = 0, p_nodes = 0;
1049 guint n_sel_nodes = 0, p_sel_nodes = 0;
1051 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1052 {
1053 double n_range = 0, p_range = 0;
1054 bool n_going = true, p_going = true;
1055 Inkscape::NodePath::Node *n_node = n;
1056 Inkscape::NodePath::Node *p_node = n;
1057 do {
1058 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1059 if (n_node && n_going)
1060 n_node = n_node->n.other;
1061 if (n_node == NULL) {
1062 n_going = false;
1063 } else {
1064 n_nodes ++;
1065 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1066 if (n_node->selected) {
1067 n_sel_nodes ++;
1068 n_sel_range = n_range;
1069 }
1070 if (n_node == p_node) {
1071 n_going = false;
1072 p_going = false;
1073 }
1074 }
1075 if (p_node && p_going)
1076 p_node = p_node->p.other;
1077 if (p_node == NULL) {
1078 p_going = false;
1079 } else {
1080 p_nodes ++;
1081 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1082 if (p_node->selected) {
1083 p_sel_nodes ++;
1084 p_sel_range = p_range;
1085 }
1086 if (p_node == n_node) {
1087 n_going = false;
1088 p_going = false;
1089 }
1090 }
1091 } while (n_going || p_going);
1092 }
1094 // Second pass: actually move nodes
1095 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1096 {
1097 double n_range = 0, p_range = 0;
1098 bool n_going = true, p_going = true;
1099 Inkscape::NodePath::Node *n_node = n;
1100 Inkscape::NodePath::Node *p_node = n;
1101 do {
1102 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1103 if (n_node && n_going)
1104 n_node = n_node->n.other;
1105 if (n_node == NULL) {
1106 n_going = false;
1107 } else {
1108 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1109 if (n_node->selected) {
1110 sp_nodepath_move_node_and_handles (n_node,
1111 sculpt_profile (n_range / n_sel_range, alpha) * delta,
1112 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha) * delta,
1113 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha) * delta);
1114 }
1115 if (n_node == p_node) {
1116 n_going = false;
1117 p_going = false;
1118 }
1119 }
1120 if (p_node && p_going)
1121 p_node = p_node->p.other;
1122 if (p_node == NULL) {
1123 p_going = false;
1124 } else {
1125 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1126 if (p_node->selected) {
1127 sp_nodepath_move_node_and_handles (p_node,
1128 sculpt_profile (p_range / p_sel_range, alpha) * delta,
1129 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha) * delta,
1130 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha) * delta);
1131 }
1132 if (p_node == n_node) {
1133 n_going = false;
1134 p_going = false;
1135 }
1136 }
1137 } while (n_going || p_going);
1138 }
1140 // do not update repr here so that node dragging is acceptably fast
1141 update_object(nodepath);
1142 }
1145 /**
1146 * Move node selection to point, adjust its and neighbouring handles,
1147 * handle possible snapping, and commit the change with possible undo.
1148 */
1149 void
1150 sp_node_selected_move(gdouble dx, gdouble dy)
1151 {
1152 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1153 if (!nodepath) return;
1155 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1157 if (dx == 0) {
1158 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1159 } else if (dy == 0) {
1160 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1161 } else {
1162 sp_nodepath_update_repr(nodepath);
1163 }
1164 }
1166 /**
1167 * Move node selection off screen and commit the change.
1168 */
1169 void
1170 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1171 {
1172 // borrowed from sp_selection_move_screen in selection-chemistry.c
1173 // we find out the current zoom factor and divide deltas by it
1174 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1176 gdouble zoom = desktop->current_zoom();
1177 gdouble zdx = dx / zoom;
1178 gdouble zdy = dy / zoom;
1180 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1181 if (!nodepath) return;
1183 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1185 if (dx == 0) {
1186 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1187 } else if (dy == 0) {
1188 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1189 } else {
1190 sp_nodepath_update_repr(nodepath);
1191 }
1192 }
1194 /** If they don't yet exist, creates knot and line for the given side of the node */
1195 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1196 {
1197 if (!side->knot) {
1198 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"));
1200 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1201 side->knot->setSize (7);
1202 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1203 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1204 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1205 sp_knot_update_ctrl(side->knot);
1207 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1208 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1209 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1210 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1211 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1212 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1213 }
1215 if (!side->line) {
1216 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1217 SP_TYPE_CTRLLINE, NULL);
1218 }
1219 }
1221 /**
1222 * Ensure the given handle of the node is visible/invisible, update its screen position
1223 */
1224 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1225 {
1226 g_assert(node != NULL);
1228 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1229 NRPathcode code = sp_node_path_code_from_side(node, side);
1231 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1233 if (show_handle) {
1234 if (!side->knot) { // No handle knot at all
1235 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1236 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1237 side->knot->pos = side->pos;
1238 if (side->knot->item)
1239 SP_CTRL(side->knot->item)->moveto(side->pos);
1240 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1241 sp_knot_show(side->knot);
1242 } else {
1243 if (side->knot->pos != side->pos) { // only if it's really moved
1244 if (fire_move_signals) {
1245 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1246 } else {
1247 sp_knot_moveto(side->knot, &side->pos);
1248 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1249 }
1250 }
1251 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1252 sp_knot_show(side->knot);
1253 }
1254 }
1255 sp_canvas_item_show(side->line);
1256 } else {
1257 if (side->knot) {
1258 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1259 sp_knot_hide(side->knot);
1260 }
1261 }
1262 if (side->line) {
1263 sp_canvas_item_hide(side->line);
1264 }
1265 }
1266 }
1268 /**
1269 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1270 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1271 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1272 * updated; otherwise, just move the knots silently (used in batch moves).
1273 */
1274 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1275 {
1276 g_assert(node != NULL);
1278 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1279 sp_knot_show(node->knot);
1280 }
1282 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1283 if (fire_move_signals)
1284 sp_knot_set_position(node->knot, &node->pos, 0);
1285 else
1286 sp_knot_moveto(node->knot, &node->pos);
1287 }
1289 gboolean show_handles = node->selected;
1290 if (node->p.other != NULL) {
1291 if (node->p.other->selected) show_handles = TRUE;
1292 }
1293 if (node->n.other != NULL) {
1294 if (node->n.other->selected) show_handles = TRUE;
1295 }
1297 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1298 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1299 }
1301 /**
1302 * Call sp_node_update_handles() for all nodes on subpath.
1303 */
1304 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1305 {
1306 g_assert(subpath != NULL);
1308 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1309 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1310 }
1311 }
1313 /**
1314 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1315 */
1316 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1317 {
1318 g_assert(nodepath != NULL);
1320 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1321 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1322 }
1323 }
1325 /**
1326 * Adds all selected nodes in nodepath to list.
1327 */
1328 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1329 {
1330 StlConv<Node *>::list(l, selected);
1331 /// \todo this adds a copying, rework when the selection becomes a stl list
1332 }
1334 /**
1335 * Align selected nodes on the specified axis.
1336 */
1337 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1338 {
1339 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1340 return;
1341 }
1343 if ( !nodepath->selected->next ) { // only one node selected
1344 return;
1345 }
1346 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1347 NR::Point dest(pNode->pos);
1348 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1349 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1350 if (pNode) {
1351 dest[axis] = pNode->pos[axis];
1352 sp_node_moveto(pNode, dest);
1353 }
1354 }
1356 sp_nodepath_update_repr(nodepath);
1357 }
1359 /// Helper struct.
1360 struct NodeSort
1361 {
1362 Inkscape::NodePath::Node *_node;
1363 NR::Coord _coord;
1364 /// \todo use vectorof pointers instead of calling copy ctor
1365 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1366 _node(node), _coord(node->pos[axis])
1367 {}
1369 };
1371 static bool operator<(NodeSort const &a, NodeSort const &b)
1372 {
1373 return (a._coord < b._coord);
1374 }
1376 /**
1377 * Distribute selected nodes on the specified axis.
1378 */
1379 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1380 {
1381 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1382 return;
1383 }
1385 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1386 return;
1387 }
1389 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1390 std::vector<NodeSort> sorted;
1391 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1392 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1393 if (pNode) {
1394 NodeSort n(pNode, axis);
1395 sorted.push_back(n);
1396 //dest[axis] = pNode->pos[axis];
1397 //sp_node_moveto(pNode, dest);
1398 }
1399 }
1400 std::sort(sorted.begin(), sorted.end());
1401 unsigned int len = sorted.size();
1402 //overall bboxes span
1403 float dist = (sorted.back()._coord -
1404 sorted.front()._coord);
1405 //new distance between each bbox
1406 float step = (dist) / (len - 1);
1407 float pos = sorted.front()._coord;
1408 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1409 it < sorted.end();
1410 it ++ )
1411 {
1412 NR::Point dest((*it)._node->pos);
1413 dest[axis] = pos;
1414 sp_node_moveto((*it)._node, dest);
1415 pos += step;
1416 }
1418 sp_nodepath_update_repr(nodepath);
1419 }
1422 /**
1423 * Call sp_nodepath_line_add_node() for all selected segments.
1424 */
1425 void
1426 sp_node_selected_add_node(void)
1427 {
1428 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1429 if (!nodepath) {
1430 return;
1431 }
1433 GList *nl = NULL;
1435 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1436 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1437 g_assert(t->selected);
1438 if (t->p.other && t->p.other->selected) {
1439 nl = g_list_prepend(nl, t);
1440 }
1441 }
1443 while (nl) {
1444 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1445 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1446 sp_nodepath_node_select(n, TRUE, FALSE);
1447 nl = g_list_remove(nl, t);
1448 }
1450 /** \todo fixme: adjust ? */
1451 sp_nodepath_update_handles(nodepath);
1453 sp_nodepath_update_repr(nodepath);
1455 sp_nodepath_update_statusbar(nodepath);
1456 }
1458 /**
1459 * Select segment nearest to point
1460 */
1461 void
1462 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1463 {
1464 if (!nodepath) {
1465 return;
1466 }
1468 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1470 //find segment to segment
1471 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1473 gboolean force = FALSE;
1474 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1475 force = TRUE;
1476 }
1477 sp_nodepath_node_select(e, (gboolean) toggle, force);
1478 if (e->p.other)
1479 sp_nodepath_node_select(e->p.other, TRUE, force);
1481 sp_nodepath_update_handles(nodepath);
1483 sp_nodepath_update_statusbar(nodepath);
1484 }
1486 /**
1487 * Add a node nearest to point
1488 */
1489 void
1490 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1491 {
1492 if (!nodepath) {
1493 return;
1494 }
1496 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1498 //find segment to split
1499 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1501 //don't know why but t seems to flip for lines
1502 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1503 position.t = 1.0 - position.t;
1504 }
1505 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1506 sp_nodepath_node_select(n, FALSE, TRUE);
1508 /* fixme: adjust ? */
1509 sp_nodepath_update_handles(nodepath);
1511 sp_nodepath_update_repr(nodepath);
1513 sp_nodepath_update_statusbar(nodepath);
1514 }
1516 /*
1517 * Adjusts a segment so that t moves by a certain delta for dragging
1518 * converts lines to curves
1519 *
1520 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1521 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1522 */
1523 void
1524 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1525 {
1526 /* feel good is an arbitrary parameter that distributes the delta between handles
1527 * if t of the drag point is less than 1/6 distance form the endpoint only
1528 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1529 */
1530 double feel_good;
1531 if (t <= 1.0 / 6.0)
1532 feel_good = 0;
1533 else if (t <= 0.5)
1534 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1535 else if (t <= 5.0 / 6.0)
1536 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1537 else
1538 feel_good = 1;
1540 //if we're dragging a line convert it to a curve
1541 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1542 sp_nodepath_set_line_type(e, NR_CURVETO);
1543 }
1545 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1546 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1547 e->p.other->n.pos += offsetcoord0;
1548 e->p.pos += offsetcoord1;
1550 // adjust handles of adjacent nodes where necessary
1551 sp_node_adjust_handle(e,1);
1552 sp_node_adjust_handle(e->p.other,-1);
1554 sp_nodepath_update_handles(e->subpath->nodepath);
1556 update_object(e->subpath->nodepath);
1558 sp_nodepath_update_statusbar(e->subpath->nodepath);
1559 }
1562 /**
1563 * Call sp_nodepath_break() for all selected segments.
1564 */
1565 void sp_node_selected_break()
1566 {
1567 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1568 if (!nodepath) return;
1570 GList *temp = NULL;
1571 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1572 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1573 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1574 if (nn == NULL) continue; // no break, no new node
1575 temp = g_list_prepend(temp, nn);
1576 }
1578 if (temp) {
1579 sp_nodepath_deselect(nodepath);
1580 }
1581 for (GList *l = temp; l != NULL; l = l->next) {
1582 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1583 }
1585 sp_nodepath_update_handles(nodepath);
1587 sp_nodepath_update_repr(nodepath);
1588 }
1590 /**
1591 * Duplicate the selected node(s).
1592 */
1593 void sp_node_selected_duplicate()
1594 {
1595 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1596 if (!nodepath) {
1597 return;
1598 }
1600 GList *temp = NULL;
1601 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1602 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1603 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1604 if (nn == NULL) continue; // could not duplicate
1605 temp = g_list_prepend(temp, nn);
1606 }
1608 if (temp) {
1609 sp_nodepath_deselect(nodepath);
1610 }
1611 for (GList *l = temp; l != NULL; l = l->next) {
1612 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1613 }
1615 sp_nodepath_update_handles(nodepath);
1617 sp_nodepath_update_repr(nodepath);
1618 }
1620 /**
1621 * Join two nodes by merging them into one.
1622 */
1623 void sp_node_selected_join()
1624 {
1625 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1626 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1628 if (g_list_length(nodepath->selected) != 2) {
1629 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1630 return;
1631 }
1633 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1634 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1636 g_assert(a != b);
1637 g_assert(a->p.other || a->n.other);
1638 g_assert(b->p.other || b->n.other);
1640 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1641 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1642 return;
1643 }
1645 /* a and b are endpoints */
1647 NR::Point c;
1648 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1649 c = a->pos;
1650 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1651 c = b->pos;
1652 } else {
1653 c = (a->pos + b->pos) / 2;
1654 }
1656 if (a->subpath == b->subpath) {
1657 Inkscape::NodePath::SubPath *sp = a->subpath;
1658 sp_nodepath_subpath_close(sp);
1659 sp_node_moveto (sp->first, c);
1661 sp_nodepath_update_handles(sp->nodepath);
1662 sp_nodepath_update_repr(nodepath);
1663 return;
1664 }
1666 /* a and b are separate subpaths */
1667 Inkscape::NodePath::SubPath *sa = a->subpath;
1668 Inkscape::NodePath::SubPath *sb = b->subpath;
1669 NR::Point p;
1670 Inkscape::NodePath::Node *n;
1671 NRPathcode code;
1672 if (a == sa->first) {
1673 p = sa->first->n.pos;
1674 code = (NRPathcode)sa->first->n.other->code;
1675 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1676 n = sa->last;
1677 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1678 n = n->p.other;
1679 while (n) {
1680 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1681 n = n->p.other;
1682 if (n == sa->first) n = NULL;
1683 }
1684 sp_nodepath_subpath_destroy(sa);
1685 sa = t;
1686 } else if (a == sa->last) {
1687 p = sa->last->p.pos;
1688 code = (NRPathcode)sa->last->code;
1689 sp_nodepath_node_destroy(sa->last);
1690 } else {
1691 code = NR_END;
1692 g_assert_not_reached();
1693 }
1695 if (b == sb->first) {
1696 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1697 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1698 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1699 }
1700 } else if (b == sb->last) {
1701 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1702 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1703 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1704 }
1705 } else {
1706 g_assert_not_reached();
1707 }
1708 /* and now destroy sb */
1710 sp_nodepath_subpath_destroy(sb);
1712 sp_nodepath_update_handles(sa->nodepath);
1714 sp_nodepath_update_repr(nodepath);
1716 sp_nodepath_update_statusbar(nodepath);
1717 }
1719 /**
1720 * Join two nodes by adding a segment between them.
1721 */
1722 void sp_node_selected_join_segment()
1723 {
1724 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1725 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1727 if (g_list_length(nodepath->selected) != 2) {
1728 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1729 return;
1730 }
1732 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1733 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1735 g_assert(a != b);
1736 g_assert(a->p.other || a->n.other);
1737 g_assert(b->p.other || b->n.other);
1739 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1740 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1741 return;
1742 }
1744 if (a->subpath == b->subpath) {
1745 Inkscape::NodePath::SubPath *sp = a->subpath;
1747 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1748 sp->closed = TRUE;
1750 sp->first->p.other = sp->last;
1751 sp->last->n.other = sp->first;
1753 sp_node_handle_mirror_p_to_n(sp->last);
1754 sp_node_handle_mirror_n_to_p(sp->first);
1756 sp->first->code = sp->last->code;
1757 sp->first = sp->last;
1759 sp_nodepath_update_handles(sp->nodepath);
1761 sp_nodepath_update_repr(nodepath);
1763 return;
1764 }
1766 /* a and b are separate subpaths */
1767 Inkscape::NodePath::SubPath *sa = a->subpath;
1768 Inkscape::NodePath::SubPath *sb = b->subpath;
1770 Inkscape::NodePath::Node *n;
1771 NR::Point p;
1772 NRPathcode code;
1773 if (a == sa->first) {
1774 code = (NRPathcode) sa->first->n.other->code;
1775 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1776 n = sa->last;
1777 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1778 for (n = n->p.other; n != NULL; n = n->p.other) {
1779 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1780 }
1781 sp_nodepath_subpath_destroy(sa);
1782 sa = t;
1783 } else if (a == sa->last) {
1784 code = (NRPathcode)sa->last->code;
1785 } else {
1786 code = NR_END;
1787 g_assert_not_reached();
1788 }
1790 if (b == sb->first) {
1791 n = sb->first;
1792 sp_node_handle_mirror_p_to_n(sa->last);
1793 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1794 sp_node_handle_mirror_n_to_p(sa->last);
1795 for (n = n->n.other; n != NULL; n = n->n.other) {
1796 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1797 }
1798 } else if (b == sb->last) {
1799 n = sb->last;
1800 sp_node_handle_mirror_p_to_n(sa->last);
1801 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1802 sp_node_handle_mirror_n_to_p(sa->last);
1803 for (n = n->p.other; n != NULL; n = n->p.other) {
1804 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1805 }
1806 } else {
1807 g_assert_not_reached();
1808 }
1809 /* and now destroy sb */
1811 sp_nodepath_subpath_destroy(sb);
1813 sp_nodepath_update_handles(sa->nodepath);
1815 sp_nodepath_update_repr(nodepath);
1816 }
1818 /**
1819 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1820 */
1821 void sp_node_delete_preserve(GList *nodes_to_delete)
1822 {
1823 GSList *nodepaths = NULL;
1825 while (nodes_to_delete) {
1826 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1827 Inkscape::NodePath::SubPath *sp = node->subpath;
1828 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1829 Inkscape::NodePath::Node *sample_cursor = NULL;
1830 Inkscape::NodePath::Node *sample_end = NULL;
1831 Inkscape::NodePath::Node *delete_cursor = node;
1832 bool just_delete = false;
1834 //find the start of this contiguous selection
1835 //move left to the first node that is not selected
1836 //or the start of the non-closed path
1837 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1838 delete_cursor = curr;
1839 }
1841 //just delete at the beginning of an open path
1842 if (!delete_cursor->p.other) {
1843 sample_cursor = delete_cursor;
1844 just_delete = true;
1845 } else {
1846 sample_cursor = delete_cursor->p.other;
1847 }
1849 //calculate points for each segment
1850 int rate = 5;
1851 float period = 1.0 / rate;
1852 std::vector<NR::Point> data;
1853 if (!just_delete) {
1854 data.push_back(sample_cursor->pos);
1855 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1856 //just delete at the end of an open path
1857 if (!sp->closed && curr->n.other == sp->last) {
1858 just_delete = true;
1859 break;
1860 }
1862 //sample points on the contiguous selected segment
1863 NR::Point *bez;
1864 bez = new NR::Point [4];
1865 bez[0] = curr->pos;
1866 bez[1] = curr->n.pos;
1867 bez[2] = curr->n.other->p.pos;
1868 bez[3] = curr->n.other->pos;
1869 for (int i=1; i<rate; i++) {
1870 gdouble t = i * period;
1871 NR::Point p = bezier_pt(3, bez, t);
1872 data.push_back(p);
1873 }
1874 data.push_back(curr->n.other->pos);
1876 sample_end = curr->n.other;
1877 //break if we've come full circle or hit the end of the selection
1878 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1879 break;
1880 }
1881 }
1882 }
1884 if (!just_delete) {
1885 //calculate the best fitting single segment and adjust the endpoints
1886 NR::Point *adata;
1887 adata = new NR::Point [data.size()];
1888 copy(data.begin(), data.end(), adata);
1890 NR::Point *bez;
1891 bez = new NR::Point [4];
1892 //would decreasing error create a better fitting approximation?
1893 gdouble error = 1.0;
1894 gint ret;
1895 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1897 //adjust endpoints
1898 sample_cursor->n.pos = bez[1];
1899 sample_end->p.pos = bez[2];
1900 }
1902 //destroy this contiguous selection
1903 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
1904 Inkscape::NodePath::Node *temp = delete_cursor;
1905 if (delete_cursor->n.other == delete_cursor) {
1906 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
1907 delete_cursor = NULL;
1908 } else {
1909 delete_cursor = delete_cursor->n.other;
1910 }
1911 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
1912 sp_nodepath_node_destroy(temp);
1913 }
1915 sp_nodepath_update_handles(nodepath);
1917 if (!g_slist_find(nodepaths, nodepath))
1918 nodepaths = g_slist_prepend (nodepaths, nodepath);
1919 }
1921 for (GSList *i = nodepaths; i; i = i->next) {
1922 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
1923 // different nodepaths will give us one undo event per nodepath
1924 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
1926 // if the entire nodepath is removed, delete the selected object.
1927 if (nodepath->subpaths == NULL ||
1928 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
1929 //at least 2
1930 sp_nodepath_get_node_count(nodepath) < 2) {
1931 SPDocument *document = sp_desktop_document (nodepath->desktop);
1932 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
1933 //delete this nodepath's object, not the entire selection! (though at this time, this
1934 //does not matter)
1935 sp_selection_delete();
1936 sp_document_done (document);
1937 } else {
1938 sp_nodepath_update_repr(nodepath);
1939 sp_nodepath_update_statusbar(nodepath);
1940 }
1941 }
1943 g_slist_free (nodepaths);
1944 }
1946 /**
1947 * Delete one or more selected nodes.
1948 */
1949 void sp_node_selected_delete()
1950 {
1951 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1952 if (!nodepath) return;
1953 if (!nodepath->selected) return;
1955 /** \todo fixme: do it the right way */
1956 while (nodepath->selected) {
1957 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1958 sp_nodepath_node_destroy(node);
1959 }
1962 //clean up the nodepath (such as for trivial subpaths)
1963 sp_nodepath_cleanup(nodepath);
1965 sp_nodepath_update_handles(nodepath);
1967 // if the entire nodepath is removed, delete the selected object.
1968 if (nodepath->subpaths == NULL ||
1969 sp_nodepath_get_node_count(nodepath) < 2) {
1970 SPDocument *document = sp_desktop_document (nodepath->desktop);
1971 sp_selection_delete();
1972 sp_document_done (document);
1973 return;
1974 }
1976 sp_nodepath_update_repr(nodepath);
1978 sp_nodepath_update_statusbar(nodepath);
1979 }
1981 /**
1982 * Delete one or more segments between two selected nodes.
1983 * This is the code for 'split'.
1984 */
1985 void
1986 sp_node_selected_delete_segment(void)
1987 {
1988 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1989 Inkscape::NodePath::Node *curr, *next; //Iterators
1991 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1992 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1994 if (g_list_length(nodepath->selected) != 2) {
1995 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1996 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1997 return;
1998 }
2000 //Selected nodes, not inclusive
2001 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2002 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2004 if ( ( a==b) || //same node
2005 (a->subpath != b->subpath ) || //not the same path
2006 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2007 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2008 {
2009 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2010 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2011 return;
2012 }
2014 //###########################################
2015 //# BEGIN EDITS
2016 //###########################################
2017 //##################################
2018 //# CLOSED PATH
2019 //##################################
2020 if (a->subpath->closed) {
2023 gboolean reversed = FALSE;
2025 //Since we can go in a circle, we need to find the shorter distance.
2026 // a->b or b->a
2027 start = end = NULL;
2028 int distance = 0;
2029 int minDistance = 0;
2030 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2031 if (curr==b) {
2032 //printf("a to b:%d\n", distance);
2033 start = a;//go from a to b
2034 end = b;
2035 minDistance = distance;
2036 //printf("A to B :\n");
2037 break;
2038 }
2039 distance++;
2040 }
2042 //try again, the other direction
2043 distance = 0;
2044 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2045 if (curr==a) {
2046 //printf("b to a:%d\n", distance);
2047 if (distance < minDistance) {
2048 start = b; //we go from b to a
2049 end = a;
2050 reversed = TRUE;
2051 //printf("B to A\n");
2052 }
2053 break;
2054 }
2055 distance++;
2056 }
2059 //Copy everything from 'end' to 'start' to a new subpath
2060 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2061 for (curr=end ; curr ; curr=curr->n.other) {
2062 NRPathcode code = (NRPathcode) curr->code;
2063 if (curr == end)
2064 code = NR_MOVETO;
2065 sp_nodepath_node_new(t, NULL,
2066 (Inkscape::NodePath::NodeType)curr->type, code,
2067 &curr->p.pos, &curr->pos, &curr->n.pos);
2068 if (curr == start)
2069 break;
2070 }
2071 sp_nodepath_subpath_destroy(a->subpath);
2074 }
2078 //##################################
2079 //# OPEN PATH
2080 //##################################
2081 else {
2083 //We need to get the direction of the list between A and B
2084 //Can we walk from a to b?
2085 start = end = NULL;
2086 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2087 if (curr==b) {
2088 start = a; //did it! we go from a to b
2089 end = b;
2090 //printf("A to B\n");
2091 break;
2092 }
2093 }
2094 if (!start) {//didn't work? let's try the other direction
2095 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2096 if (curr==a) {
2097 start = b; //did it! we go from b to a
2098 end = a;
2099 //printf("B to A\n");
2100 break;
2101 }
2102 }
2103 }
2104 if (!start) {
2105 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2106 _("Cannot find path between nodes."));
2107 return;
2108 }
2112 //Copy everything after 'end' to a new subpath
2113 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2114 for (curr=end ; curr ; curr=curr->n.other) {
2115 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2116 &curr->p.pos, &curr->pos, &curr->n.pos);
2117 }
2119 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2120 for (curr = start->n.other ; curr ; curr=next) {
2121 next = curr->n.other;
2122 sp_nodepath_node_destroy(curr);
2123 }
2125 }
2126 //###########################################
2127 //# END EDITS
2128 //###########################################
2130 //clean up the nodepath (such as for trivial subpaths)
2131 sp_nodepath_cleanup(nodepath);
2133 sp_nodepath_update_handles(nodepath);
2135 sp_nodepath_update_repr(nodepath);
2137 sp_nodepath_update_statusbar(nodepath);
2138 }
2140 /**
2141 * Call sp_nodepath_set_line() for all selected segments.
2142 */
2143 void
2144 sp_node_selected_set_line_type(NRPathcode code)
2145 {
2146 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2147 if (nodepath == NULL) return;
2149 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2150 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2151 g_assert(n->selected);
2152 if (n->p.other && n->p.other->selected) {
2153 sp_nodepath_set_line_type(n, code);
2154 }
2155 }
2157 sp_nodepath_update_repr(nodepath);
2158 }
2160 /**
2161 * Call sp_nodepath_convert_node_type() for all selected nodes.
2162 */
2163 void
2164 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2165 {
2166 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2167 if (nodepath == NULL) return;
2169 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2170 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2171 }
2173 sp_nodepath_update_repr(nodepath);
2174 }
2176 /**
2177 * Change select status of node, update its own and neighbour handles.
2178 */
2179 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2180 {
2181 node->selected = selected;
2183 if (selected) {
2184 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2185 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2186 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2187 sp_knot_update_ctrl(node->knot);
2188 } else {
2189 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2190 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2191 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2192 sp_knot_update_ctrl(node->knot);
2193 }
2195 sp_node_update_handles(node);
2196 if (node->n.other) sp_node_update_handles(node->n.other);
2197 if (node->p.other) sp_node_update_handles(node->p.other);
2198 }
2200 /**
2201 \brief Select a node
2202 \param node The node to select
2203 \param incremental If true, add to selection, otherwise deselect others
2204 \param override If true, always select this node, otherwise toggle selected status
2205 */
2206 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2207 {
2208 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2210 if (incremental) {
2211 if (override) {
2212 if (!g_list_find(nodepath->selected, node)) {
2213 nodepath->selected = g_list_prepend(nodepath->selected, node);
2214 }
2215 sp_node_set_selected(node, TRUE);
2216 } else { // toggle
2217 if (node->selected) {
2218 g_assert(g_list_find(nodepath->selected, node));
2219 nodepath->selected = g_list_remove(nodepath->selected, node);
2220 } else {
2221 g_assert(!g_list_find(nodepath->selected, node));
2222 nodepath->selected = g_list_prepend(nodepath->selected, node);
2223 }
2224 sp_node_set_selected(node, !node->selected);
2225 }
2226 } else {
2227 sp_nodepath_deselect(nodepath);
2228 nodepath->selected = g_list_prepend(nodepath->selected, node);
2229 sp_node_set_selected(node, TRUE);
2230 }
2232 sp_nodepath_update_statusbar(nodepath);
2233 }
2236 /**
2237 \brief Deselect all nodes in the nodepath
2238 */
2239 void
2240 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2241 {
2242 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2244 while (nodepath->selected) {
2245 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2246 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2247 }
2248 sp_nodepath_update_statusbar(nodepath);
2249 }
2251 /**
2252 \brief Select or invert selection of all nodes in the nodepath
2253 */
2254 void
2255 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2256 {
2257 if (!nodepath) return;
2259 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2260 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2261 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2262 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2263 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2264 }
2265 }
2266 }
2268 /**
2269 * If nothing selected, does the same as sp_nodepath_select_all();
2270 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2271 * (i.e., similar to "select all in layer", with the "selected" subpaths
2272 * being treated as "layers" in the path).
2273 */
2274 void
2275 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2276 {
2277 if (!nodepath) return;
2279 if (g_list_length (nodepath->selected) == 0) {
2280 sp_nodepath_select_all (nodepath, invert);
2281 return;
2282 }
2284 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2285 GSList *subpaths = NULL;
2287 for (GList *l = copy; l != NULL; l = l->next) {
2288 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2289 Inkscape::NodePath::SubPath *subpath = n->subpath;
2290 if (!g_slist_find (subpaths, subpath))
2291 subpaths = g_slist_prepend (subpaths, subpath);
2292 }
2294 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2295 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2296 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2297 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2298 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2299 }
2300 }
2302 g_slist_free (subpaths);
2303 g_list_free (copy);
2304 }
2306 /**
2307 * \brief Select the node after the last selected; if none is selected,
2308 * select the first within path.
2309 */
2310 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2311 {
2312 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2314 Inkscape::NodePath::Node *last = NULL;
2315 if (nodepath->selected) {
2316 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2317 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2318 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2319 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2320 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2321 if (node->selected) {
2322 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2323 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2324 if (spl->next) { // there's a next subpath
2325 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2326 last = subpath_next->first;
2327 } else if (spl->prev) { // there's a previous subpath
2328 last = NULL; // to be set later to the first node of first subpath
2329 } else {
2330 last = node->n.other;
2331 }
2332 } else {
2333 last = node->n.other;
2334 }
2335 } else {
2336 if (node->n.other) {
2337 last = node->n.other;
2338 } else {
2339 if (spl->next) { // there's a next subpath
2340 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2341 last = subpath_next->first;
2342 } else if (spl->prev) { // there's a previous subpath
2343 last = NULL; // to be set later to the first node of first subpath
2344 } else {
2345 last = (Inkscape::NodePath::Node *) subpath->first;
2346 }
2347 }
2348 }
2349 }
2350 }
2351 }
2352 sp_nodepath_deselect(nodepath);
2353 }
2355 if (last) { // there's at least one more node after selected
2356 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2357 } else { // no more nodes, select the first one in first subpath
2358 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2359 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2360 }
2361 }
2363 /**
2364 * \brief Select the node before the first selected; if none is selected,
2365 * select the last within path
2366 */
2367 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2368 {
2369 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2371 Inkscape::NodePath::Node *last = NULL;
2372 if (nodepath->selected) {
2373 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2374 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2375 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2376 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2377 if (node->selected) {
2378 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2379 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2380 if (spl->prev) { // there's a prev subpath
2381 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2382 last = subpath_prev->last;
2383 } else if (spl->next) { // there's a next subpath
2384 last = NULL; // to be set later to the last node of last subpath
2385 } else {
2386 last = node->p.other;
2387 }
2388 } else {
2389 last = node->p.other;
2390 }
2391 } else {
2392 if (node->p.other) {
2393 last = node->p.other;
2394 } else {
2395 if (spl->prev) { // there's a prev subpath
2396 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2397 last = subpath_prev->last;
2398 } else if (spl->next) { // there's a next subpath
2399 last = NULL; // to be set later to the last node of last subpath
2400 } else {
2401 last = (Inkscape::NodePath::Node *) subpath->last;
2402 }
2403 }
2404 }
2405 }
2406 }
2407 }
2408 sp_nodepath_deselect(nodepath);
2409 }
2411 if (last) { // there's at least one more node before selected
2412 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2413 } else { // no more nodes, select the last one in last subpath
2414 GList *spl = g_list_last(nodepath->subpaths);
2415 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2416 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2417 }
2418 }
2420 /**
2421 * \brief Select all nodes that are within the rectangle.
2422 */
2423 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2424 {
2425 if (!incremental) {
2426 sp_nodepath_deselect(nodepath);
2427 }
2429 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2430 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2431 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2432 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2434 if (b.contains(node->pos)) {
2435 sp_nodepath_node_select(node, TRUE, TRUE);
2436 }
2437 }
2438 }
2439 }
2442 /**
2443 \brief Saves all nodes' and handles' current positions in their origin members
2444 */
2445 void
2446 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2447 {
2448 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2449 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2450 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2451 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2452 n->origin = n->pos;
2453 n->p.origin = n->p.pos;
2454 n->n.origin = n->n.pos;
2455 }
2456 }
2457 }
2459 /**
2460 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2461 */
2462 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2463 {
2464 if (!nodepath->selected) {
2465 return NULL;
2466 }
2468 GList *r = NULL;
2469 guint i = 0;
2470 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2471 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2472 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2473 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2474 i++;
2475 if (node->selected) {
2476 r = g_list_append(r, GINT_TO_POINTER(i));
2477 }
2478 }
2479 }
2480 return r;
2481 }
2483 /**
2484 \brief Restores selection by selecting nodes whose positions are in the list
2485 */
2486 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2487 {
2488 sp_nodepath_deselect(nodepath);
2490 guint i = 0;
2491 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2492 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2493 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2494 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2495 i++;
2496 if (g_list_find(r, GINT_TO_POINTER(i))) {
2497 sp_nodepath_node_select(node, TRUE, TRUE);
2498 }
2499 }
2500 }
2502 }
2504 /**
2505 \brief Adjusts handle according to node type and line code.
2506 */
2507 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2508 {
2509 double len, otherlen, linelen;
2511 g_assert(node);
2513 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2514 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2516 /** \todo fixme: */
2517 if (me->other == NULL) return;
2518 if (other->other == NULL) return;
2520 /* I have line */
2522 NRPathcode mecode, ocode;
2523 if (which_adjust == 1) {
2524 mecode = (NRPathcode)me->other->code;
2525 ocode = (NRPathcode)node->code;
2526 } else {
2527 mecode = (NRPathcode)node->code;
2528 ocode = (NRPathcode)other->other->code;
2529 }
2531 if (mecode == NR_LINETO) return;
2533 /* I am curve */
2535 if (other->other == NULL) return;
2537 /* Other has line */
2539 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2541 NR::Point delta;
2542 if (ocode == NR_LINETO) {
2543 /* other is lineto, we are either smooth or symm */
2544 Inkscape::NodePath::Node *othernode = other->other;
2545 len = NR::L2(me->pos - node->pos);
2546 delta = node->pos - othernode->pos;
2547 linelen = NR::L2(delta);
2548 if (linelen < 1e-18)
2549 return;
2550 me->pos = node->pos + (len / linelen)*delta;
2551 return;
2552 }
2554 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2556 me->pos = 2 * node->pos - other->pos;
2557 return;
2558 }
2560 /* We are smooth */
2562 len = NR::L2(me->pos - node->pos);
2563 delta = other->pos - node->pos;
2564 otherlen = NR::L2(delta);
2565 if (otherlen < 1e-18) return;
2567 me->pos = node->pos - (len / otherlen) * delta;
2568 }
2570 /**
2571 \brief Adjusts both handles according to node type and line code
2572 */
2573 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2574 {
2575 g_assert(node);
2577 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2579 /* we are either smooth or symm */
2581 if (node->p.other == NULL) return;
2583 if (node->n.other == NULL) return;
2585 if (node->code == NR_LINETO) {
2586 if (node->n.other->code == NR_LINETO) return;
2587 sp_node_adjust_handle(node, 1);
2588 return;
2589 }
2591 if (node->n.other->code == NR_LINETO) {
2592 if (node->code == NR_LINETO) return;
2593 sp_node_adjust_handle(node, -1);
2594 return;
2595 }
2597 /* both are curves */
2598 NR::Point const delta( node->n.pos - node->p.pos );
2600 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2601 node->p.pos = node->pos - delta / 2;
2602 node->n.pos = node->pos + delta / 2;
2603 return;
2604 }
2606 /* We are smooth */
2607 double plen = NR::L2(node->p.pos - node->pos);
2608 if (plen < 1e-18) return;
2609 double nlen = NR::L2(node->n.pos - node->pos);
2610 if (nlen < 1e-18) return;
2611 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2612 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2613 }
2615 /**
2616 * Node event callback.
2617 */
2618 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2619 {
2620 gboolean ret = FALSE;
2621 switch (event->type) {
2622 case GDK_ENTER_NOTIFY:
2623 active_node = n;
2624 break;
2625 case GDK_LEAVE_NOTIFY:
2626 active_node = NULL;
2627 break;
2628 case GDK_KEY_PRESS:
2629 switch (get_group0_keyval (&event->key)) {
2630 case GDK_space:
2631 if (event->key.state & GDK_BUTTON1_MASK) {
2632 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2633 stamp_repr(nodepath);
2634 ret = TRUE;
2635 }
2636 break;
2637 default:
2638 break;
2639 }
2640 break;
2641 default:
2642 break;
2643 }
2645 return ret;
2646 }
2648 /**
2649 * Handle keypress on node; directly called.
2650 */
2651 gboolean node_key(GdkEvent *event)
2652 {
2653 Inkscape::NodePath::Path *np;
2655 // there is no way to verify nodes so set active_node to nil when deleting!!
2656 if (active_node == NULL) return FALSE;
2658 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2659 gint ret = FALSE;
2660 switch (get_group0_keyval (&event->key)) {
2661 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2662 case GDK_BackSpace:
2663 np = active_node->subpath->nodepath;
2664 sp_nodepath_node_destroy(active_node);
2665 sp_nodepath_update_repr(np);
2666 active_node = NULL;
2667 ret = TRUE;
2668 break;
2669 case GDK_c:
2670 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2671 ret = TRUE;
2672 break;
2673 case GDK_s:
2674 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2675 ret = TRUE;
2676 break;
2677 case GDK_y:
2678 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2679 ret = TRUE;
2680 break;
2681 case GDK_b:
2682 sp_nodepath_node_break(active_node);
2683 ret = TRUE;
2684 break;
2685 }
2686 return ret;
2687 }
2688 return FALSE;
2689 }
2691 /**
2692 * Mouseclick on node callback.
2693 */
2694 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2695 {
2696 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2698 if (state & GDK_CONTROL_MASK) {
2699 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2701 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2702 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2703 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2704 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2705 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2706 } else {
2707 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2708 }
2709 sp_nodepath_update_repr(nodepath);
2710 sp_nodepath_update_statusbar(nodepath);
2712 } else { //ctrl+alt+click: delete node
2713 GList *node_to_delete = NULL;
2714 node_to_delete = g_list_append(node_to_delete, n);
2715 sp_node_delete_preserve(node_to_delete);
2716 }
2718 } else {
2719 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2720 }
2721 }
2723 /**
2724 * Mouse grabbed node callback.
2725 */
2726 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2727 {
2728 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2730 if (!n->selected) {
2731 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2732 }
2734 sp_nodepath_remember_origins (n->subpath->nodepath);
2735 }
2737 /**
2738 * Mouse ungrabbed node callback.
2739 */
2740 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2741 {
2742 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2744 n->dragging_out = NULL;
2746 sp_nodepath_update_repr(n->subpath->nodepath);
2747 }
2749 /**
2750 * The point on a line, given by its angle, closest to the given point.
2751 * \param p A point.
2752 * \param a Angle of the line; it is assumed to go through coordinate origin.
2753 * \param closest Pointer to the point struct where the result is stored.
2754 * \todo FIXME: use dot product perhaps?
2755 */
2756 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2757 {
2758 if (a == HUGE_VAL) { // vertical
2759 *closest = NR::Point(0, (*p)[NR::Y]);
2760 } else {
2761 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2762 (*closest)[NR::Y] = a * (*closest)[NR::X];
2763 }
2764 }
2766 /**
2767 * Distance from the point to a line given by its angle.
2768 * \param p A point.
2769 * \param a Angle of the line; it is assumed to go through coordinate origin.
2770 */
2771 static double point_line_distance(NR::Point *p, double a)
2772 {
2773 NR::Point c;
2774 point_line_closest(p, a, &c);
2775 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]));
2776 }
2778 /**
2779 * Callback for node "request" signal.
2780 * \todo fixme: This goes to "moved" event? (lauris)
2781 */
2782 static gboolean
2783 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2784 {
2785 double yn, xn, yp, xp;
2786 double an, ap, na, pa;
2787 double d_an, d_ap, d_na, d_pa;
2788 gboolean collinear = FALSE;
2789 NR::Point c;
2790 NR::Point pr;
2792 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2794 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2795 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2797 NR::Point mouse = (*p);
2799 if (!n->dragging_out) {
2800 // This is the first drag-out event; find out which handle to drag out
2801 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2802 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2804 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2805 return FALSE;
2807 Inkscape::NodePath::NodeSide *opposite;
2808 if (appr_p > appr_n) { // closer to p
2809 n->dragging_out = &n->p;
2810 opposite = &n->n;
2811 n->code = NR_CURVETO;
2812 } else if (appr_p < appr_n) { // closer to n
2813 n->dragging_out = &n->n;
2814 opposite = &n->p;
2815 n->n.other->code = NR_CURVETO;
2816 } else { // p and n nodes are the same
2817 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2818 n->dragging_out = &n->p;
2819 opposite = &n->n;
2820 n->code = NR_CURVETO;
2821 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2822 n->dragging_out = &n->n;
2823 opposite = &n->p;
2824 n->n.other->code = NR_CURVETO;
2825 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2826 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);
2827 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);
2828 if (appr_other_p > appr_other_n) { // closer to other's p handle
2829 n->dragging_out = &n->n;
2830 opposite = &n->p;
2831 n->n.other->code = NR_CURVETO;
2832 } else { // closer to other's n handle
2833 n->dragging_out = &n->p;
2834 opposite = &n->n;
2835 n->code = NR_CURVETO;
2836 }
2837 }
2838 }
2840 // if there's another handle, make sure the one we drag out starts parallel to it
2841 if (opposite->pos != n->pos) {
2842 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2843 }
2845 // knots might not be created yet!
2846 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2847 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2848 }
2850 // pass this on to the handle-moved callback
2851 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2852 sp_node_update_handles(n);
2853 return TRUE;
2854 }
2856 if (state & GDK_CONTROL_MASK) { // constrained motion
2858 // calculate relative distances of handles
2859 // n handle:
2860 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2861 xn = n->n.pos[NR::X] - n->pos[NR::X];
2862 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2863 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2864 if (n->n.other) { // if there is the next point
2865 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2866 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2867 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2868 }
2869 }
2870 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2871 if (yn < 0) { xn = -xn; yn = -yn; }
2873 // p handle:
2874 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2875 xp = n->p.pos[NR::X] - n->pos[NR::X];
2876 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2877 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2878 if (n->p.other) {
2879 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2880 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2881 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2882 }
2883 }
2884 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2885 if (yp < 0) { xp = -xp; yp = -yp; }
2887 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2888 // sliding on handles, only if at least one of the handles is non-vertical
2889 // (otherwise it's the same as ctrl+drag anyway)
2891 // calculate angles of the handles
2892 if (xn == 0) {
2893 if (yn == 0) { // no handle, consider it the continuation of the other one
2894 an = 0;
2895 collinear = TRUE;
2896 }
2897 else an = 0; // vertical; set the angle to horizontal
2898 } else an = yn/xn;
2900 if (xp == 0) {
2901 if (yp == 0) { // no handle, consider it the continuation of the other one
2902 ap = an;
2903 }
2904 else ap = 0; // vertical; set the angle to horizontal
2905 } else ap = yp/xp;
2907 if (collinear) an = ap;
2909 // angles of the perpendiculars; HUGE_VAL means vertical
2910 if (an == 0) na = HUGE_VAL; else na = -1/an;
2911 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2913 // mouse point relative to the node's original pos
2914 pr = (*p) - n->origin;
2916 // distances to the four lines (two handles and two perpendiculars)
2917 d_an = point_line_distance(&pr, an);
2918 d_na = point_line_distance(&pr, na);
2919 d_ap = point_line_distance(&pr, ap);
2920 d_pa = point_line_distance(&pr, pa);
2922 // find out which line is the closest, save its closest point in c
2923 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2924 point_line_closest(&pr, an, &c);
2925 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2926 point_line_closest(&pr, ap, &c);
2927 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2928 point_line_closest(&pr, na, &c);
2929 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2930 point_line_closest(&pr, pa, &c);
2931 }
2933 // move the node to the closest point
2934 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2935 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2936 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2938 } else { // constraining to hor/vert
2940 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2941 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2942 } else { // snap to vert
2943 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2944 }
2945 }
2946 } else { // move freely
2947 if (state & GDK_MOD1_MASK) { // sculpt
2948 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
2949 } else {
2950 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2951 (*p)[NR::X] - n->pos[NR::X],
2952 (*p)[NR::Y] - n->pos[NR::Y],
2953 (state & GDK_SHIFT_MASK) == 0);
2954 }
2955 }
2957 n->subpath->nodepath->desktop->scroll_to_point(p);
2959 return TRUE;
2960 }
2962 /**
2963 * Node handle clicked callback.
2964 */
2965 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
2966 {
2967 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2969 if (state & GDK_CONTROL_MASK) { // "delete" handle
2970 if (n->p.knot == knot) {
2971 n->p.pos = n->pos;
2972 } else if (n->n.knot == knot) {
2973 n->n.pos = n->pos;
2974 }
2975 sp_node_update_handles(n);
2976 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2977 sp_nodepath_update_repr(nodepath);
2978 sp_nodepath_update_statusbar(nodepath);
2980 } else { // just select or add to selection, depending in Shift
2981 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2982 }
2983 }
2985 /**
2986 * Node handle grabbed callback.
2987 */
2988 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
2989 {
2990 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2992 if (!n->selected) {
2993 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2994 }
2996 // remember the origin point of the handle
2997 if (n->p.knot == knot) {
2998 n->p.origin_radial = n->p.pos - n->pos;
2999 } else if (n->n.knot == knot) {
3000 n->n.origin_radial = n->n.pos - n->pos;
3001 } else {
3002 g_assert_not_reached();
3003 }
3005 }
3007 /**
3008 * Node handle ungrabbed callback.
3009 */
3010 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3011 {
3012 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3014 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3015 if (n->p.knot == knot) {
3016 n->p.origin_radial.a = 0;
3017 sp_knot_set_position(knot, &n->p.pos, state);
3018 } else if (n->n.knot == knot) {
3019 n->n.origin_radial.a = 0;
3020 sp_knot_set_position(knot, &n->n.pos, state);
3021 } else {
3022 g_assert_not_reached();
3023 }
3025 sp_nodepath_update_repr(n->subpath->nodepath);
3026 }
3028 /**
3029 * Node handle "request" signal callback.
3030 */
3031 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3032 {
3033 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3035 Inkscape::NodePath::NodeSide *me, *opposite;
3036 gint which;
3037 if (n->p.knot == knot) {
3038 me = &n->p;
3039 opposite = &n->n;
3040 which = -1;
3041 } else if (n->n.knot == knot) {
3042 me = &n->n;
3043 opposite = &n->p;
3044 which = 1;
3045 } else {
3046 me = opposite = NULL;
3047 which = 0;
3048 g_assert_not_reached();
3049 }
3051 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3053 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3055 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3056 /* We are smooth node adjacent with line */
3057 NR::Point const delta = *p - n->pos;
3058 NR::Coord const len = NR::L2(delta);
3059 Inkscape::NodePath::Node *othernode = opposite->other;
3060 NR::Point const ndelta = n->pos - othernode->pos;
3061 NR::Coord const linelen = NR::L2(ndelta);
3062 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3063 NR::Coord const scal = dot(delta, ndelta) / linelen;
3064 (*p) = n->pos + (scal / linelen) * ndelta;
3065 }
3066 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3067 } else {
3068 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3069 }
3071 sp_node_adjust_handle(n, -which);
3073 return FALSE;
3074 }
3076 /**
3077 * Node handle moved callback.
3078 */
3079 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3080 {
3081 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3083 Inkscape::NodePath::NodeSide *me;
3084 Inkscape::NodePath::NodeSide *other;
3085 if (n->p.knot == knot) {
3086 me = &n->p;
3087 other = &n->n;
3088 } else if (n->n.knot == knot) {
3089 me = &n->n;
3090 other = &n->p;
3091 } else {
3092 me = NULL;
3093 other = NULL;
3094 g_assert_not_reached();
3095 }
3097 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3098 Radial rme(me->pos - n->pos);
3099 Radial rother(other->pos - n->pos);
3100 Radial rnew(*p - n->pos);
3102 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3103 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3104 /* 0 interpreted as "no snapping". */
3106 // The closest PI/snaps angle, starting from zero.
3107 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3108 if (me->origin_radial.a == HUGE_VAL) {
3109 // ortho doesn't exist: original handle was zero length.
3110 rnew.a = a_snapped;
3111 } else {
3112 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3113 * its opposite and perpendiculars). */
3114 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3116 // Snap to the closest.
3117 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3118 ? a_snapped
3119 : a_ortho );
3120 }
3121 }
3123 if (state & GDK_MOD1_MASK) {
3124 // lock handle length
3125 rnew.r = me->origin_radial.r;
3126 }
3128 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3129 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3130 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3131 rother.a += rnew.a - rme.a;
3132 other->pos = NR::Point(rother) + n->pos;
3133 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3134 sp_knot_set_position(other->knot, &other->pos, 0);
3135 }
3137 me->pos = NR::Point(rnew) + n->pos;
3138 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3140 // this is what sp_knot_set_position does, but without emitting the signal:
3141 // we cannot emit a "moved" signal because we're now processing it
3142 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3144 knot->desktop->set_coordinate_status(me->pos);
3146 update_object(n->subpath->nodepath);
3148 /* status text */
3149 SPDesktop *desktop = n->subpath->nodepath->desktop;
3150 if (!desktop) return;
3151 SPEventContext *ec = desktop->event_context;
3152 if (!ec) return;
3153 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3154 if (!mc) return;
3156 double degrees = 180 / M_PI * rnew.a;
3157 if (degrees > 180) degrees -= 360;
3158 if (degrees < -180) degrees += 360;
3159 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3160 degrees = angle_to_compass (degrees);
3162 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3164 mc->setF(Inkscape::NORMAL_MESSAGE,
3165 _("<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);
3167 g_string_free(length, TRUE);
3168 }
3170 /**
3171 * Node handle event callback.
3172 */
3173 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3174 {
3175 gboolean ret = FALSE;
3176 switch (event->type) {
3177 case GDK_KEY_PRESS:
3178 switch (get_group0_keyval (&event->key)) {
3179 case GDK_space:
3180 if (event->key.state & GDK_BUTTON1_MASK) {
3181 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3182 stamp_repr(nodepath);
3183 ret = TRUE;
3184 }
3185 break;
3186 default:
3187 break;
3188 }
3189 break;
3190 default:
3191 break;
3192 }
3194 return ret;
3195 }
3197 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3198 Radial &rme, Radial &rother, gboolean const both)
3199 {
3200 rme.a += angle;
3201 if ( both
3202 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3203 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3204 {
3205 rother.a += angle;
3206 }
3207 }
3209 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3210 Radial &rme, Radial &rother, gboolean const both)
3211 {
3212 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3214 gdouble r;
3215 if ( both
3216 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3217 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3218 {
3219 r = MAX(rme.r, rother.r);
3220 } else {
3221 r = rme.r;
3222 }
3224 gdouble const weird_angle = atan2(norm_angle, r);
3225 /* Bulia says norm_angle is just the visible distance that the
3226 * object's end must travel on the screen. Left as 'angle' for want of
3227 * a better name.*/
3229 rme.a += weird_angle;
3230 if ( both
3231 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3232 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3233 {
3234 rother.a += weird_angle;
3235 }
3236 }
3238 /**
3239 * Rotate one node.
3240 */
3241 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3242 {
3243 Inkscape::NodePath::NodeSide *me, *other;
3244 bool both = false;
3246 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3247 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3249 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3250 me = &(n->p);
3251 other = &(n->n);
3252 } else if (!n->p.other) {
3253 me = &(n->n);
3254 other = &(n->p);
3255 } else {
3256 if (which > 0) { // right handle
3257 if (xn > xp) {
3258 me = &(n->n);
3259 other = &(n->p);
3260 } else {
3261 me = &(n->p);
3262 other = &(n->n);
3263 }
3264 } else if (which < 0){ // left handle
3265 if (xn <= xp) {
3266 me = &(n->n);
3267 other = &(n->p);
3268 } else {
3269 me = &(n->p);
3270 other = &(n->n);
3271 }
3272 } else { // both handles
3273 me = &(n->n);
3274 other = &(n->p);
3275 both = true;
3276 }
3277 }
3279 Radial rme(me->pos - n->pos);
3280 Radial rother(other->pos - n->pos);
3282 if (screen) {
3283 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3284 } else {
3285 node_rotate_one_internal (*n, angle, rme, rother, both);
3286 }
3288 me->pos = n->pos + NR::Point(rme);
3290 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3291 other->pos = n->pos + NR::Point(rother);
3292 }
3294 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3295 // so here we just move all the knots without emitting move signals, for speed
3296 sp_node_update_handles(n, false);
3297 }
3299 /**
3300 * Rotate selected nodes.
3301 */
3302 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3303 {
3304 if (!nodepath || !nodepath->selected) return;
3306 if (g_list_length(nodepath->selected) == 1) {
3307 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3308 node_rotate_one (n, angle, which, screen);
3309 } else {
3310 // rotate as an object:
3312 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3313 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3314 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3315 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3316 box.expandTo (n->pos); // contain all selected nodes
3317 }
3319 gdouble rot;
3320 if (screen) {
3321 gdouble const zoom = nodepath->desktop->current_zoom();
3322 gdouble const zmove = angle / zoom;
3323 gdouble const r = NR::L2(box.max() - box.midpoint());
3324 rot = atan2(zmove, r);
3325 } else {
3326 rot = angle;
3327 }
3329 NR::Matrix t =
3330 NR::Matrix (NR::translate(-box.midpoint())) *
3331 NR::Matrix (NR::rotate(rot)) *
3332 NR::Matrix (NR::translate(box.midpoint()));
3334 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3335 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3336 n->pos *= t;
3337 n->n.pos *= t;
3338 n->p.pos *= t;
3339 sp_node_update_handles(n, false);
3340 }
3341 }
3343 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3344 }
3346 /**
3347 * Scale one node.
3348 */
3349 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3350 {
3351 bool both = false;
3352 Inkscape::NodePath::NodeSide *me, *other;
3354 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3355 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3357 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3358 me = &(n->p);
3359 other = &(n->n);
3360 n->code = NR_CURVETO;
3361 } else if (!n->p.other) {
3362 me = &(n->n);
3363 other = &(n->p);
3364 if (n->n.other)
3365 n->n.other->code = NR_CURVETO;
3366 } else {
3367 if (which > 0) { // right handle
3368 if (xn > xp) {
3369 me = &(n->n);
3370 other = &(n->p);
3371 if (n->n.other)
3372 n->n.other->code = NR_CURVETO;
3373 } else {
3374 me = &(n->p);
3375 other = &(n->n);
3376 n->code = NR_CURVETO;
3377 }
3378 } else if (which < 0){ // left handle
3379 if (xn <= xp) {
3380 me = &(n->n);
3381 other = &(n->p);
3382 if (n->n.other)
3383 n->n.other->code = NR_CURVETO;
3384 } else {
3385 me = &(n->p);
3386 other = &(n->n);
3387 n->code = NR_CURVETO;
3388 }
3389 } else { // both handles
3390 me = &(n->n);
3391 other = &(n->p);
3392 both = true;
3393 n->code = NR_CURVETO;
3394 if (n->n.other)
3395 n->n.other->code = NR_CURVETO;
3396 }
3397 }
3399 Radial rme(me->pos - n->pos);
3400 Radial rother(other->pos - n->pos);
3402 rme.r += grow;
3403 if (rme.r < 0) rme.r = 0;
3404 if (rme.a == HUGE_VAL) {
3405 if (me->other) { // if direction is unknown, initialize it towards the next node
3406 Radial rme_next(me->other->pos - n->pos);
3407 rme.a = rme_next.a;
3408 } else { // if there's no next, initialize to 0
3409 rme.a = 0;
3410 }
3411 }
3412 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3413 rother.r += grow;
3414 if (rother.r < 0) rother.r = 0;
3415 if (rother.a == HUGE_VAL) {
3416 rother.a = rme.a + M_PI;
3417 }
3418 }
3420 me->pos = n->pos + NR::Point(rme);
3422 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3423 other->pos = n->pos + NR::Point(rother);
3424 }
3426 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3427 // so here we just move all the knots without emitting move signals, for speed
3428 sp_node_update_handles(n, false);
3429 }
3431 /**
3432 * Scale selected nodes.
3433 */
3434 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3435 {
3436 if (!nodepath || !nodepath->selected) return;
3438 if (g_list_length(nodepath->selected) == 1) {
3439 // scale handles of the single selected node
3440 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3441 node_scale_one (n, grow, which);
3442 } else {
3443 // scale nodes as an "object":
3445 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3446 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3447 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3448 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3449 box.expandTo (n->pos); // contain all selected nodes
3450 }
3452 double scale = (box.maxExtent() + grow)/box.maxExtent();
3454 NR::Matrix t =
3455 NR::Matrix (NR::translate(-box.midpoint())) *
3456 NR::Matrix (NR::scale(scale, scale)) *
3457 NR::Matrix (NR::translate(box.midpoint()));
3459 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3460 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3461 n->pos *= t;
3462 n->n.pos *= t;
3463 n->p.pos *= t;
3464 sp_node_update_handles(n, false);
3465 }
3466 }
3468 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3469 }
3471 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3472 {
3473 if (!nodepath) return;
3474 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3475 }
3477 /**
3478 * Flip selected nodes horizontally/vertically.
3479 */
3480 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3481 {
3482 if (!nodepath || !nodepath->selected) return;
3484 if (g_list_length(nodepath->selected) == 1) {
3485 // flip handles of the single selected node
3486 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3487 double temp = n->p.pos[axis];
3488 n->p.pos[axis] = n->n.pos[axis];
3489 n->n.pos[axis] = temp;
3490 sp_node_update_handles(n, false);
3491 } else {
3492 // scale nodes as an "object":
3494 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3495 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3496 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3497 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3498 box.expandTo (n->pos); // contain all selected nodes
3499 }
3501 NR::Matrix t =
3502 NR::Matrix (NR::translate(-box.midpoint())) *
3503 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3504 NR::Matrix (NR::translate(box.midpoint()));
3506 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3507 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3508 n->pos *= t;
3509 n->n.pos *= t;
3510 n->p.pos *= t;
3511 sp_node_update_handles(n, false);
3512 }
3513 }
3515 sp_nodepath_update_repr(nodepath);
3516 }
3518 //-----------------------------------------------
3519 /**
3520 * Return new subpath under given nodepath.
3521 */
3522 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3523 {
3524 g_assert(nodepath);
3525 g_assert(nodepath->desktop);
3527 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3529 s->nodepath = nodepath;
3530 s->closed = FALSE;
3531 s->nodes = NULL;
3532 s->first = NULL;
3533 s->last = NULL;
3535 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3536 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3537 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3539 return s;
3540 }
3542 /**
3543 * Destroy nodes in subpath, then subpath itself.
3544 */
3545 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3546 {
3547 g_assert(subpath);
3548 g_assert(subpath->nodepath);
3549 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3551 while (subpath->nodes) {
3552 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3553 }
3555 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3557 g_free(subpath);
3558 }
3560 /**
3561 * Link head to tail in subpath.
3562 */
3563 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3564 {
3565 g_assert(!sp->closed);
3566 g_assert(sp->last != sp->first);
3567 g_assert(sp->first->code == NR_MOVETO);
3569 sp->closed = TRUE;
3571 //Link the head to the tail
3572 sp->first->p.other = sp->last;
3573 sp->last->n.other = sp->first;
3574 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3575 sp->first = sp->last;
3577 //Remove the extra end node
3578 sp_nodepath_node_destroy(sp->last->n.other);
3579 }
3581 /**
3582 * Open closed (loopy) subpath at node.
3583 */
3584 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3585 {
3586 g_assert(sp->closed);
3587 g_assert(n->subpath == sp);
3588 g_assert(sp->first == sp->last);
3590 /* We create new startpoint, current node will become last one */
3592 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3593 &n->pos, &n->pos, &n->n.pos);
3596 sp->closed = FALSE;
3598 //Unlink to make a head and tail
3599 sp->first = new_path;
3600 sp->last = n;
3601 n->n.other = NULL;
3602 new_path->p.other = NULL;
3603 }
3605 /**
3606 * Returns area in triangle given by points; may be negative.
3607 */
3608 inline double
3609 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3610 {
3611 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]);
3612 }
3614 /**
3615 * Return new node in subpath with given properties.
3616 * \param pos Position of node.
3617 * \param ppos Handle position in previous direction
3618 * \param npos Handle position in previous direction
3619 */
3620 Inkscape::NodePath::Node *
3621 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)
3622 {
3623 g_assert(sp);
3624 g_assert(sp->nodepath);
3625 g_assert(sp->nodepath->desktop);
3627 if (nodechunk == NULL)
3628 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3630 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3632 n->subpath = sp;
3634 if (type != Inkscape::NodePath::NODE_NONE) {
3635 // use the type from sodipodi:nodetypes
3636 n->type = type;
3637 } else {
3638 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3639 // points are (almost) collinear
3640 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3641 // endnode, or a node with a retracted handle
3642 n->type = Inkscape::NodePath::NODE_CUSP;
3643 } else {
3644 n->type = Inkscape::NodePath::NODE_SMOOTH;
3645 }
3646 } else {
3647 n->type = Inkscape::NodePath::NODE_CUSP;
3648 }
3649 }
3651 n->code = code;
3652 n->selected = FALSE;
3653 n->pos = *pos;
3654 n->p.pos = *ppos;
3655 n->n.pos = *npos;
3657 n->dragging_out = NULL;
3659 Inkscape::NodePath::Node *prev;
3660 if (next) {
3661 //g_assert(g_list_find(sp->nodes, next));
3662 prev = next->p.other;
3663 } else {
3664 prev = sp->last;
3665 }
3667 if (prev)
3668 prev->n.other = n;
3669 else
3670 sp->first = n;
3672 if (next)
3673 next->p.other = n;
3674 else
3675 sp->last = n;
3677 n->p.other = prev;
3678 n->n.other = next;
3680 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"));
3681 sp_knot_set_position(n->knot, pos, 0);
3683 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3684 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3685 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3686 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3687 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3688 sp_knot_update_ctrl(n->knot);
3690 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3691 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3692 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3693 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3694 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3695 sp_knot_show(n->knot);
3697 // We only create handle knots and lines on demand
3698 n->p.knot = NULL;
3699 n->p.line = NULL;
3700 n->n.knot = NULL;
3701 n->n.line = NULL;
3703 sp->nodes = g_list_prepend(sp->nodes, n);
3705 return n;
3706 }
3708 /**
3709 * Destroy node and its knots, link neighbors in subpath.
3710 */
3711 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3712 {
3713 g_assert(node);
3714 g_assert(node->subpath);
3715 g_assert(SP_IS_KNOT(node->knot));
3717 Inkscape::NodePath::SubPath *sp = node->subpath;
3719 if (node->selected) { // first, deselect
3720 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3721 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3722 }
3724 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3726 g_object_unref(G_OBJECT(node->knot));
3727 if (node->p.knot)
3728 g_object_unref(G_OBJECT(node->p.knot));
3729 if (node->n.knot)
3730 g_object_unref(G_OBJECT(node->n.knot));
3732 if (node->p.line)
3733 gtk_object_destroy(GTK_OBJECT(node->p.line));
3734 if (node->n.line)
3735 gtk_object_destroy(GTK_OBJECT(node->n.line));
3737 if (sp->nodes) { // there are others nodes on the subpath
3738 if (sp->closed) {
3739 if (sp->first == node) {
3740 g_assert(sp->last == node);
3741 sp->first = node->n.other;
3742 sp->last = sp->first;
3743 }
3744 node->p.other->n.other = node->n.other;
3745 node->n.other->p.other = node->p.other;
3746 } else {
3747 if (sp->first == node) {
3748 sp->first = node->n.other;
3749 sp->first->code = NR_MOVETO;
3750 }
3751 if (sp->last == node) sp->last = node->p.other;
3752 if (node->p.other) node->p.other->n.other = node->n.other;
3753 if (node->n.other) node->n.other->p.other = node->p.other;
3754 }
3755 } else { // this was the last node on subpath
3756 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3757 }
3759 g_mem_chunk_free(nodechunk, node);
3760 }
3762 /**
3763 * Returns one of the node's two sides.
3764 * \param which Indicates which side.
3765 * \return Pointer to previous node side if which==-1, next if which==1.
3766 */
3767 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3768 {
3769 g_assert(node);
3771 switch (which) {
3772 case -1:
3773 return &node->p;
3774 case 1:
3775 return &node->n;
3776 default:
3777 break;
3778 }
3780 g_assert_not_reached();
3782 return NULL;
3783 }
3785 /**
3786 * Return the other side of the node, given one of its sides.
3787 */
3788 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3789 {
3790 g_assert(node);
3792 if (me == &node->p) return &node->n;
3793 if (me == &node->n) return &node->p;
3795 g_assert_not_reached();
3797 return NULL;
3798 }
3800 /**
3801 * Return NRPathcode on the given side of the node.
3802 */
3803 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3804 {
3805 g_assert(node);
3807 if (me == &node->p) {
3808 if (node->p.other) return (NRPathcode)node->code;
3809 return NR_MOVETO;
3810 }
3812 if (me == &node->n) {
3813 if (node->n.other) return (NRPathcode)node->n.other->code;
3814 return NR_MOVETO;
3815 }
3817 g_assert_not_reached();
3819 return NR_END;
3820 }
3822 /**
3823 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3824 */
3825 Inkscape::NodePath::Node *
3826 sp_nodepath_get_node_by_index(int index)
3827 {
3828 Inkscape::NodePath::Node *e = NULL;
3830 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3831 if (!nodepath) {
3832 return e;
3833 }
3835 //find segment
3836 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3838 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3839 int n = g_list_length(sp->nodes);
3840 if (sp->closed) {
3841 n++;
3842 }
3844 //if the piece belongs to this subpath grab it
3845 //otherwise move onto the next subpath
3846 if (index < n) {
3847 e = sp->first;
3848 for (int i = 0; i < index; ++i) {
3849 e = e->n.other;
3850 }
3851 break;
3852 } else {
3853 if (sp->closed) {
3854 index -= (n+1);
3855 } else {
3856 index -= n;
3857 }
3858 }
3859 }
3861 return e;
3862 }
3864 /**
3865 * Returns plain text meaning of node type.
3866 */
3867 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3868 {
3869 unsigned retracted = 0;
3870 bool endnode = false;
3872 for (int which = -1; which <= 1; which += 2) {
3873 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3874 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3875 retracted ++;
3876 if (!side->other)
3877 endnode = true;
3878 }
3880 if (retracted == 0) {
3881 if (endnode) {
3882 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3883 return _("end node");
3884 } else {
3885 switch (node->type) {
3886 case Inkscape::NodePath::NODE_CUSP:
3887 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3888 return _("cusp");
3889 case Inkscape::NodePath::NODE_SMOOTH:
3890 // TRANSLATORS: "smooth" is an adjective here
3891 return _("smooth");
3892 case Inkscape::NodePath::NODE_SYMM:
3893 return _("symmetric");
3894 }
3895 }
3896 } else if (retracted == 1) {
3897 if (endnode) {
3898 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3899 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3900 } else {
3901 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3902 }
3903 } else {
3904 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3905 }
3907 return NULL;
3908 }
3910 /**
3911 * Handles content of statusbar as long as node tool is active.
3912 */
3913 void
3914 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3915 {
3916 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");
3917 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3919 gint total_nodes = sp_nodepath_get_node_count(nodepath);
3920 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
3921 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
3922 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
3924 SPDesktop *desktop = NULL;
3925 if (nodepath) {
3926 desktop = nodepath->desktop;
3927 } else {
3928 desktop = SP_ACTIVE_DESKTOP;
3929 }
3931 SPEventContext *ec = desktop->event_context;
3932 if (!ec) return;
3933 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3934 if (!mc) return;
3936 if (selected_nodes == 0) {
3937 Inkscape::Selection *sel = desktop->selection;
3938 if (!sel || sel->isEmpty()) {
3939 mc->setF(Inkscape::NORMAL_MESSAGE,
3940 _("Select a single object to edit its nodes or handles."));
3941 } else {
3942 if (nodepath) {
3943 mc->setF(Inkscape::NORMAL_MESSAGE,
3944 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.",
3945 "<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.",
3946 total_nodes),
3947 total_nodes);
3948 } else {
3949 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3950 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3951 } else {
3952 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3953 }
3954 }
3955 }
3956 } else if (nodepath && selected_nodes == 1) {
3957 mc->setF(Inkscape::NORMAL_MESSAGE,
3958 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3959 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3960 total_nodes),
3961 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3962 } else {
3963 if (selected_subpaths > 1) {
3964 mc->setF(Inkscape::NORMAL_MESSAGE,
3965 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
3966 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
3967 total_nodes),
3968 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
3969 } else {
3970 mc->setF(Inkscape::NORMAL_MESSAGE,
3971 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3972 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3973 total_nodes),
3974 selected_nodes, total_nodes, when_selected);
3975 }
3976 }
3977 }
3980 /*
3981 Local Variables:
3982 mode:c++
3983 c-file-style:"stroustrup"
3984 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3985 indent-tabs-mode:nil
3986 fill-column:99
3987 End:
3988 */
3989 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :