455a0e7d8c67c2468148f847381592c9c6131993
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 "verbs.h"
44 #include "display/bezier-utils.h"
45 #include <vector>
46 #include <algorithm>
48 class NR::Matrix;
50 /// \todo
51 /// evil evil evil. FIXME: conflict of two different Path classes!
52 /// There is a conflict in the namespace between two classes named Path.
53 /// #include "sp-flowtext.h"
54 /// #include "sp-flowregion.h"
56 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
57 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
58 GType sp_flowregion_get_type (void);
59 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
60 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
61 GType sp_flowtext_get_type (void);
62 // end evil workaround
64 #include "helper/stlport.h"
67 /// \todo fixme: Implement these via preferences */
69 #define NODE_FILL 0xbfbfbf00
70 #define NODE_STROKE 0x000000ff
71 #define NODE_FILL_HI 0xff000000
72 #define NODE_STROKE_HI 0x000000ff
73 #define NODE_FILL_SEL 0x0000ffff
74 #define NODE_STROKE_SEL 0x000000ff
75 #define NODE_FILL_SEL_HI 0xff000000
76 #define NODE_STROKE_SEL_HI 0x000000ff
77 #define KNOT_FILL 0xffffffff
78 #define KNOT_STROKE 0x000000ff
79 #define KNOT_FILL_HI 0xff000000
80 #define KNOT_STROKE_HI 0x000000ff
82 static GMemChunk *nodechunk = NULL;
84 /* Creation from object */
86 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
87 static gchar *parse_nodetypes(gchar const *types, gint length);
89 /* Object updating */
91 static void stamp_repr(Inkscape::NodePath::Path *np);
92 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
93 static gchar *create_typestr(Inkscape::NodePath::Path *np);
95 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
97 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
99 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
101 /* Adjust handle placement, if the node or the other handle is moved */
102 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
103 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
105 /* Node event callbacks */
106 static void node_clicked(SPKnot *knot, guint state, gpointer data);
107 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
108 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
109 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
111 /* Handle event callbacks */
112 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
113 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
114 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
115 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
116 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
117 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
119 /* Constructors and destructors */
121 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
122 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
123 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
124 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
125 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
126 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
127 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
129 /* Helpers */
131 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
132 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
133 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
135 // active_node indicates mouseover node
136 static Inkscape::NodePath::Node *active_node = NULL;
138 /**
139 * \brief Creates new nodepath from item
140 */
141 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item, bool show_handles)
142 {
143 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
145 /** \todo
146 * FIXME: remove this. We don't want to edit paths inside flowtext.
147 * Instead we will build our flowtext with cloned paths, so that the
148 * real paths are outside the flowtext and thus editable as usual.
149 */
150 if (SP_IS_FLOWTEXT(item)) {
151 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
152 if SP_IS_FLOWREGION(child) {
153 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
154 if (grandchild && SP_IS_PATH(grandchild)) {
155 item = SP_ITEM(grandchild);
156 break;
157 }
158 }
159 }
160 }
162 if (!SP_IS_PATH(item))
163 return NULL;
164 SPPath *path = SP_PATH(item);
165 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
166 if (curve == NULL)
167 return NULL;
169 NArtBpath *bpath = sp_curve_first_bpath(curve);
170 gint length = curve->end;
171 if (length == 0)
172 return NULL; // prevent crash for one-node paths
174 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
175 gchar *typestr = parse_nodetypes(nodetypes, length);
177 //Create new nodepath
178 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
179 if (!np)
180 return NULL;
182 // Set defaults
183 np->desktop = desktop;
184 np->path = path;
185 np->subpaths = NULL;
186 np->selected = NULL;
187 np->nodeContext = NULL; //Let the context that makes this set it
188 np->livarot_path = NULL;
189 np->local_change = 0;
190 np->show_handles = show_handles;
192 // we need to update item's transform from the repr here,
193 // because they may be out of sync when we respond
194 // to a change in repr by regenerating nodepath --bb
195 sp_object_read_attr(SP_OBJECT(item), "transform");
197 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
198 np->d2i = np->i2d.inverse();
199 np->repr = repr;
201 // create the subpath(s) from the bpath
202 NArtBpath *b = bpath;
203 while (b->code != NR_END) {
204 b = subpath_from_bpath(np, b, typestr + (b - bpath));
205 }
207 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
208 np->subpaths = g_list_reverse(np->subpaths);
210 g_free(typestr);
211 sp_curve_unref(curve);
213 // create the livarot representation from the same item
214 np->livarot_path = Path_for_item(item, true, true);
215 if (np->livarot_path)
216 np->livarot_path->ConvertWithBackData(0.01);
218 return np;
219 }
221 /**
222 * Destroys nodepath's subpaths, then itself, also tell context about it.
223 */
224 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
226 if (!np) //soft fail, like delete
227 return;
229 while (np->subpaths) {
230 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
231 }
233 //Inform the context that made me, if any, that I am gone.
234 if (np->nodeContext)
235 np->nodeContext->nodepath = NULL;
237 g_assert(!np->selected);
239 if (np->livarot_path) {
240 delete np->livarot_path;
241 np->livarot_path = NULL;
242 }
244 np->desktop = NULL;
246 g_free(np);
247 }
250 /**
251 * Return the node count of a given NodeSubPath.
252 */
253 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
254 {
255 if (!subpath)
256 return 0;
257 gint nodeCount = g_list_length(subpath->nodes);
258 return nodeCount;
259 }
261 /**
262 * Return the node count of a given NodePath.
263 */
264 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
265 {
266 if (!np)
267 return 0;
268 gint nodeCount = 0;
269 for (GList *item = np->subpaths ; item ; item=item->next) {
270 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
271 nodeCount += g_list_length(subpath->nodes);
272 }
273 return nodeCount;
274 }
276 /**
277 * Return the subpath count of a given NodePath.
278 */
279 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
280 {
281 if (!np)
282 return 0;
283 return g_list_length (np->subpaths);
284 }
286 /**
287 * Return the selected node count of a given NodePath.
288 */
289 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
290 {
291 if (!np)
292 return 0;
293 return g_list_length (np->selected);
294 }
296 /**
297 * Return the number of subpaths where nodes are selected in a given NodePath.
298 */
299 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
300 {
301 if (!np)
302 return 0;
303 if (!np->selected)
304 return 0;
305 if (!np->selected->next)
306 return 1;
307 gint count = 0;
308 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
309 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
310 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
311 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
312 if (node->selected) {
313 count ++;
314 break;
315 }
316 }
317 }
318 return count;
319 }
321 /**
322 * Clean up a nodepath after editing.
323 *
324 * Currently we are deleting trivial subpaths.
325 */
326 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
327 {
328 GList *badSubPaths = NULL;
330 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
331 for (GList *l = nodepath->subpaths; l ; l=l->next) {
332 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
333 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
334 badSubPaths = g_list_append(badSubPaths, sp);
335 }
337 //Delete them. This second step is because sp_nodepath_subpath_destroy()
338 //also removes the subpath from nodepath->subpaths
339 for (GList *l = badSubPaths; l ; l=l->next) {
340 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
341 sp_nodepath_subpath_destroy(sp);
342 }
344 g_list_free(badSubPaths);
345 }
347 /**
348 * Create new nodepath from b, make it subpath of np.
349 * \param t The node type.
350 * \todo Fixme: t should be a proper type, rather than gchar
351 */
352 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
353 {
354 NR::Point ppos, pos, npos;
356 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
358 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
359 bool const closed = (b->code == NR_MOVETO);
361 pos = NR::Point(b->x3, b->y3) * np->i2d;
362 if (b[1].code == NR_CURVETO) {
363 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
364 } else {
365 npos = pos;
366 }
367 Inkscape::NodePath::Node *n;
368 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
369 g_assert(sp->first == n);
370 g_assert(sp->last == n);
372 b++;
373 t++;
374 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
375 pos = NR::Point(b->x3, b->y3) * np->i2d;
376 if (b->code == NR_CURVETO) {
377 ppos = NR::Point(b->x2, b->y2) * np->i2d;
378 } else {
379 ppos = pos;
380 }
381 if (b[1].code == NR_CURVETO) {
382 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
383 } else {
384 npos = pos;
385 }
386 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
387 b++;
388 t++;
389 }
391 if (closed) sp_nodepath_subpath_close(sp);
393 return b;
394 }
396 /**
397 * Convert from sodipodi:nodetypes to new style type string.
398 */
399 static gchar *parse_nodetypes(gchar const *types, gint length)
400 {
401 g_assert(length > 0);
403 gchar *typestr = g_new(gchar, length + 1);
405 gint pos = 0;
407 if (types) {
408 for (gint i = 0; types[i] && ( i < length ); i++) {
409 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
410 if (types[i] != '\0') {
411 switch (types[i]) {
412 case 's':
413 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
414 break;
415 case 'z':
416 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
417 break;
418 case 'c':
419 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
420 break;
421 default:
422 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
423 break;
424 }
425 }
426 }
427 }
429 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
431 return typestr;
432 }
434 /**
435 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
436 * updated but repr is not (for speed). Used during curve and node drag.
437 */
438 static void update_object(Inkscape::NodePath::Path *np)
439 {
440 g_assert(np);
442 SPCurve *curve = create_curve(np);
444 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
446 sp_curve_unref(curve);
447 }
449 /**
450 * Update XML path node with data from path object.
451 */
452 static void update_repr_internal(Inkscape::NodePath::Path *np)
453 {
454 g_assert(np);
456 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
458 SPCurve *curve = create_curve(np);
459 gchar *typestr = create_typestr(np);
460 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
462 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
463 np->local_change++;
464 repr->setAttribute("d", svgpath);
465 }
467 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
468 np->local_change++;
469 repr->setAttribute("sodipodi:nodetypes", typestr);
470 }
472 g_free(svgpath);
473 g_free(typestr);
474 sp_curve_unref(curve);
475 }
477 /**
478 * Update XML path node with data from path object, commit changes forever.
479 */
480 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np)
481 {
482 update_repr_internal(np);
483 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
484 /* TODO: annotate */ "nodepath.cpp:484");
486 if (np->livarot_path) {
487 delete np->livarot_path;
488 np->livarot_path = NULL;
489 }
491 if (np->path && SP_IS_ITEM(np->path)) {
492 np->livarot_path = Path_for_item (np->path, true, true);
493 if (np->livarot_path)
494 np->livarot_path->ConvertWithBackData(0.01);
495 }
496 }
498 /**
499 * Update XML path node with data from path object, commit changes with undo.
500 */
501 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key)
502 {
503 update_repr_internal(np);
504 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
505 /* TODO: annotate */ "nodepath.cpp:505");
507 if (np->livarot_path) {
508 delete np->livarot_path;
509 np->livarot_path = NULL;
510 }
512 if (np->path && SP_IS_ITEM(np->path)) {
513 np->livarot_path = Path_for_item (np->path, true, true);
514 if (np->livarot_path)
515 np->livarot_path->ConvertWithBackData(0.01);
516 }
517 }
519 /**
520 * Make duplicate of path, replace corresponding XML node in tree, commit.
521 */
522 static void stamp_repr(Inkscape::NodePath::Path *np)
523 {
524 g_assert(np);
526 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
527 Inkscape::XML::Node *new_repr = old_repr->duplicate();
529 // remember the position of the item
530 gint pos = old_repr->position();
531 // remember parent
532 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
534 SPCurve *curve = create_curve(np);
535 gchar *typestr = create_typestr(np);
537 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
539 new_repr->setAttribute("d", svgpath);
540 new_repr->setAttribute("sodipodi:nodetypes", typestr);
542 // add the new repr to the parent
543 parent->appendChild(new_repr);
544 // move to the saved position
545 new_repr->setPosition(pos > 0 ? pos : 0);
547 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
548 /* TODO: annotate */ "nodepath.cpp:548");
550 Inkscape::GC::release(new_repr);
551 g_free(svgpath);
552 g_free(typestr);
553 sp_curve_unref(curve);
554 }
556 /**
557 * Create curve from path.
558 */
559 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
560 {
561 SPCurve *curve = sp_curve_new();
563 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
564 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
565 sp_curve_moveto(curve,
566 sp->first->pos * np->d2i);
567 Inkscape::NodePath::Node *n = sp->first->n.other;
568 while (n) {
569 NR::Point const end_pt = n->pos * np->d2i;
570 switch (n->code) {
571 case NR_LINETO:
572 sp_curve_lineto(curve, end_pt);
573 break;
574 case NR_CURVETO:
575 sp_curve_curveto(curve,
576 n->p.other->n.pos * np->d2i,
577 n->p.pos * np->d2i,
578 end_pt);
579 break;
580 default:
581 g_assert_not_reached();
582 break;
583 }
584 if (n != sp->last) {
585 n = n->n.other;
586 } else {
587 n = NULL;
588 }
589 }
590 if (sp->closed) {
591 sp_curve_closepath(curve);
592 }
593 }
595 return curve;
596 }
598 /**
599 * Convert path type string to sodipodi:nodetypes style.
600 */
601 static gchar *create_typestr(Inkscape::NodePath::Path *np)
602 {
603 gchar *typestr = g_new(gchar, 32);
604 gint len = 32;
605 gint pos = 0;
607 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
608 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
610 if (pos >= len) {
611 typestr = g_renew(gchar, typestr, len + 32);
612 len += 32;
613 }
615 typestr[pos++] = 'c';
617 Inkscape::NodePath::Node *n;
618 n = sp->first->n.other;
619 while (n) {
620 gchar code;
622 switch (n->type) {
623 case Inkscape::NodePath::NODE_CUSP:
624 code = 'c';
625 break;
626 case Inkscape::NodePath::NODE_SMOOTH:
627 code = 's';
628 break;
629 case Inkscape::NodePath::NODE_SYMM:
630 code = 'z';
631 break;
632 default:
633 g_assert_not_reached();
634 code = '\0';
635 break;
636 }
638 if (pos >= len) {
639 typestr = g_renew(gchar, typestr, len + 32);
640 len += 32;
641 }
643 typestr[pos++] = code;
645 if (n != sp->last) {
646 n = n->n.other;
647 } else {
648 n = NULL;
649 }
650 }
651 }
653 if (pos >= len) {
654 typestr = g_renew(gchar, typestr, len + 1);
655 len += 1;
656 }
658 typestr[pos++] = '\0';
660 return typestr;
661 }
663 /**
664 * Returns current path in context.
665 */
666 static Inkscape::NodePath::Path *sp_nodepath_current()
667 {
668 if (!SP_ACTIVE_DESKTOP) {
669 return NULL;
670 }
672 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
674 if (!SP_IS_NODE_CONTEXT(event_context)) {
675 return NULL;
676 }
678 return SP_NODE_CONTEXT(event_context)->nodepath;
679 }
683 /**
684 \brief Fills node and handle positions for three nodes, splitting line
685 marked by end at distance t.
686 */
687 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
688 {
689 g_assert(new_path != NULL);
690 g_assert(end != NULL);
692 g_assert(end->p.other == new_path);
693 Inkscape::NodePath::Node *start = new_path->p.other;
694 g_assert(start);
696 if (end->code == NR_LINETO) {
697 new_path->type =Inkscape::NodePath::NODE_CUSP;
698 new_path->code = NR_LINETO;
699 new_path->pos = (t * start->pos + (1 - t) * end->pos);
700 } else {
701 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
702 new_path->code = NR_CURVETO;
703 gdouble s = 1 - t;
704 for (int dim = 0; dim < 2; dim++) {
705 NR::Coord const f000 = start->pos[dim];
706 NR::Coord const f001 = start->n.pos[dim];
707 NR::Coord const f011 = end->p.pos[dim];
708 NR::Coord const f111 = end->pos[dim];
709 NR::Coord const f00t = s * f000 + t * f001;
710 NR::Coord const f01t = s * f001 + t * f011;
711 NR::Coord const f11t = s * f011 + t * f111;
712 NR::Coord const f0tt = s * f00t + t * f01t;
713 NR::Coord const f1tt = s * f01t + t * f11t;
714 NR::Coord const fttt = s * f0tt + t * f1tt;
715 start->n.pos[dim] = f00t;
716 new_path->p.pos[dim] = f0tt;
717 new_path->pos[dim] = fttt;
718 new_path->n.pos[dim] = f1tt;
719 end->p.pos[dim] = f11t;
720 }
721 }
722 }
724 /**
725 * Adds new node on direct line between two nodes, activates handles of all
726 * three nodes.
727 */
728 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
729 {
730 g_assert(end);
731 g_assert(end->subpath);
732 g_assert(g_list_find(end->subpath->nodes, end));
734 Inkscape::NodePath::Node *start = end->p.other;
735 g_assert( start->n.other == end );
736 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
737 end,
738 Inkscape::NodePath::NODE_SMOOTH,
739 (NRPathcode)end->code,
740 &start->pos, &start->pos, &start->n.pos);
741 sp_nodepath_line_midpoint(newnode, end, t);
743 sp_node_update_handles(start);
744 sp_node_update_handles(newnode);
745 sp_node_update_handles(end);
747 return newnode;
748 }
750 /**
751 \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
752 */
753 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
754 {
755 g_assert(node);
756 g_assert(node->subpath);
757 g_assert(g_list_find(node->subpath->nodes, node));
759 Inkscape::NodePath::SubPath *sp = node->subpath;
760 Inkscape::NodePath::Path *np = sp->nodepath;
762 if (sp->closed) {
763 sp_nodepath_subpath_open(sp, node);
764 return sp->first;
765 } else {
766 // no break for end nodes
767 if (node == sp->first) return NULL;
768 if (node == sp->last ) return NULL;
770 // create a new subpath
771 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
773 // duplicate the break node as start of the new subpath
774 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
776 while (node->n.other) { // copy the remaining nodes into the new subpath
777 Inkscape::NodePath::Node *n = node->n.other;
778 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);
779 if (n->selected) {
780 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
781 }
782 sp_nodepath_node_destroy(n); // remove the point on the original subpath
783 }
785 return newnode;
786 }
787 }
789 /**
790 * Duplicate node and connect to neighbours.
791 */
792 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
793 {
794 g_assert(node);
795 g_assert(node->subpath);
796 g_assert(g_list_find(node->subpath->nodes, node));
798 Inkscape::NodePath::SubPath *sp = node->subpath;
800 NRPathcode code = (NRPathcode) node->code;
801 if (code == NR_MOVETO) { // if node is the endnode,
802 node->code = NR_LINETO; // new one is inserted before it, so change that to line
803 }
805 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
807 if (!node->n.other || !node->p.other) // if node is an endnode, select it
808 return node;
809 else
810 return newnode; // otherwise select the newly created node
811 }
813 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
814 {
815 node->p.pos = (node->pos + (node->pos - node->n.pos));
816 }
818 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
819 {
820 node->n.pos = (node->pos + (node->pos - node->p.pos));
821 }
823 /**
824 * Change line type at node, with side effects on neighbours.
825 */
826 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
827 {
828 g_assert(end);
829 g_assert(end->subpath);
830 g_assert(end->p.other);
832 if (end->code == static_cast< guint > ( code ) )
833 return;
835 Inkscape::NodePath::Node *start = end->p.other;
837 end->code = code;
839 if (code == NR_LINETO) {
840 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
841 if (end->n.other) {
842 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
843 }
844 sp_node_adjust_handle(start, -1);
845 sp_node_adjust_handle(end, 1);
846 } else {
847 NR::Point delta = end->pos - start->pos;
848 start->n.pos = start->pos + delta / 3;
849 end->p.pos = end->pos - delta / 3;
850 sp_node_adjust_handle(start, 1);
851 sp_node_adjust_handle(end, -1);
852 }
854 sp_node_update_handles(start);
855 sp_node_update_handles(end);
856 }
858 /**
859 * Change node type, and its handles accordingly.
860 */
861 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
862 {
863 g_assert(node);
864 g_assert(node->subpath);
866 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
867 return node;
869 if ((node->p.other != NULL) && (node->n.other != NULL)) {
870 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
871 type =Inkscape::NodePath::NODE_CUSP;
872 }
873 }
875 node->type = type;
877 if (node->type == Inkscape::NodePath::NODE_CUSP) {
878 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
879 node->knot->setSize (node->selected? 11 : 9);
880 sp_knot_update_ctrl(node->knot);
881 } else {
882 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
883 node->knot->setSize (node->selected? 9 : 7);
884 sp_knot_update_ctrl(node->knot);
885 }
887 // if one of handles is mouseovered, preserve its position
888 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
889 sp_node_adjust_handle(node, 1);
890 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
891 sp_node_adjust_handle(node, -1);
892 } else {
893 sp_node_adjust_handles(node);
894 }
896 sp_node_update_handles(node);
898 sp_nodepath_update_statusbar(node->subpath->nodepath);
900 return node;
901 }
903 /**
904 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
905 * adjacent segments from lines to curves.
906 */
907 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
908 {
909 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
910 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
911 // convert adjacent segment BEFORE to curve
912 node->code = NR_CURVETO;
913 NR::Point delta;
914 if (node->n.other != NULL)
915 delta = node->n.other->pos - node->p.other->pos;
916 else
917 delta = node->pos - node->p.other->pos;
918 node->p.pos = node->pos - delta / 4;
919 sp_node_update_handles(node);
920 }
922 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
923 // convert adjacent segment AFTER to curve
924 node->n.other->code = NR_CURVETO;
925 NR::Point delta;
926 if (node->p.other != NULL)
927 delta = node->p.other->pos - node->n.other->pos;
928 else
929 delta = node->pos - node->n.other->pos;
930 node->n.pos = node->pos - delta / 4;
931 sp_node_update_handles(node);
932 }
933 }
935 sp_nodepath_set_node_type (node, type);
936 }
938 /**
939 * Move node to point, and adjust its and neighbouring handles.
940 */
941 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
942 {
943 NR::Point delta = p - node->pos;
944 node->pos = p;
946 node->p.pos += delta;
947 node->n.pos += delta;
949 if (node->p.other) {
950 if (node->code == NR_LINETO) {
951 sp_node_adjust_handle(node, 1);
952 sp_node_adjust_handle(node->p.other, -1);
953 }
954 }
955 if (node->n.other) {
956 if (node->n.other->code == NR_LINETO) {
957 sp_node_adjust_handle(node, -1);
958 sp_node_adjust_handle(node->n.other, 1);
959 }
960 }
962 // this function is only called from batch movers that will update display at the end
963 // themselves, so here we just move all the knots without emitting move signals, for speed
964 sp_node_update_handles(node, false);
965 }
967 /**
968 * Call sp_node_moveto() for node selection and handle possible snapping.
969 */
970 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
971 bool const snap = true)
972 {
973 NR::Coord best = NR_HUGE;
974 NR::Point delta(dx, dy);
975 NR::Point best_pt = delta;
977 if (snap) {
978 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
980 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
981 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
982 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
983 if (s.getDistance() < best) {
984 best = s.getDistance();
985 best_pt = s.getPoint() - n->pos;
986 }
987 }
988 }
990 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
991 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
992 sp_node_moveto(n, n->pos + best_pt);
993 }
995 // do not update repr here so that node dragging is acceptably fast
996 update_object(nodepath);
997 }
999 /**
1000 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1001 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1002 near x = 0.
1003 */
1004 double
1005 sculpt_profile (double x, double alpha, guint profile)
1006 {
1007 if (x >= 1)
1008 return 0;
1009 if (x <= 0)
1010 return 1;
1012 switch (profile) {
1013 case SCULPT_PROFILE_LINEAR:
1014 return 1 - x;
1015 case SCULPT_PROFILE_BELL:
1016 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1017 case SCULPT_PROFILE_ELLIPTIC:
1018 return sqrt(1 - x*x);
1019 }
1021 return 1;
1022 }
1024 double
1025 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1026 {
1027 // extremely primitive for now, don't have time to look for the real one
1028 double lower = NR::L2(b - a);
1029 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1030 return (lower + upper)/2;
1031 }
1033 void
1034 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1035 {
1036 n->pos = n->origin + delta;
1037 n->n.pos = n->n.origin + delta_n;
1038 n->p.pos = n->p.origin + delta_p;
1039 sp_node_adjust_handles(n);
1040 sp_node_update_handles(n, false);
1041 }
1043 /**
1044 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1045 * on how far they are from the dragged node n.
1046 */
1047 static void
1048 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1049 {
1050 g_assert (n);
1051 g_assert (nodepath);
1052 g_assert (n->subpath->nodepath == nodepath);
1054 double pressure = n->knot->pressure;
1055 if (pressure == 0)
1056 pressure = 0.5; // default
1057 pressure = CLAMP (pressure, 0.2, 0.8);
1059 // map pressure to alpha = 1/5 ... 5
1060 double alpha = 1 - 2 * fabs(pressure - 0.5);
1061 if (pressure > 0.5)
1062 alpha = 1/alpha;
1064 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1066 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1067 // Only one subpath has selected nodes:
1068 // use linear mode, where the distance from n to node being dragged is calculated along the path
1070 double n_sel_range = 0, p_sel_range = 0;
1071 guint n_nodes = 0, p_nodes = 0;
1072 guint n_sel_nodes = 0, p_sel_nodes = 0;
1074 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1075 {
1076 double n_range = 0, p_range = 0;
1077 bool n_going = true, p_going = true;
1078 Inkscape::NodePath::Node *n_node = n;
1079 Inkscape::NodePath::Node *p_node = n;
1080 do {
1081 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1082 if (n_node && n_going)
1083 n_node = n_node->n.other;
1084 if (n_node == NULL) {
1085 n_going = false;
1086 } else {
1087 n_nodes ++;
1088 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1089 if (n_node->selected) {
1090 n_sel_nodes ++;
1091 n_sel_range = n_range;
1092 }
1093 if (n_node == p_node) {
1094 n_going = false;
1095 p_going = false;
1096 }
1097 }
1098 if (p_node && p_going)
1099 p_node = p_node->p.other;
1100 if (p_node == NULL) {
1101 p_going = false;
1102 } else {
1103 p_nodes ++;
1104 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1105 if (p_node->selected) {
1106 p_sel_nodes ++;
1107 p_sel_range = p_range;
1108 }
1109 if (p_node == n_node) {
1110 n_going = false;
1111 p_going = false;
1112 }
1113 }
1114 } while (n_going || p_going);
1115 }
1117 // Second pass: actually move nodes in this subpath
1118 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1119 {
1120 double n_range = 0, p_range = 0;
1121 bool n_going = true, p_going = true;
1122 Inkscape::NodePath::Node *n_node = n;
1123 Inkscape::NodePath::Node *p_node = n;
1124 do {
1125 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1126 if (n_node && n_going)
1127 n_node = n_node->n.other;
1128 if (n_node == NULL) {
1129 n_going = false;
1130 } else {
1131 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1132 if (n_node->selected) {
1133 sp_nodepath_move_node_and_handles (n_node,
1134 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1135 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1136 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1137 }
1138 if (n_node == p_node) {
1139 n_going = false;
1140 p_going = false;
1141 }
1142 }
1143 if (p_node && p_going)
1144 p_node = p_node->p.other;
1145 if (p_node == NULL) {
1146 p_going = false;
1147 } else {
1148 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1149 if (p_node->selected) {
1150 sp_nodepath_move_node_and_handles (p_node,
1151 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1152 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1153 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1154 }
1155 if (p_node == n_node) {
1156 n_going = false;
1157 p_going = false;
1158 }
1159 }
1160 } while (n_going || p_going);
1161 }
1163 } else {
1164 // Multiple subpaths have selected nodes:
1165 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1166 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1167 // fix the pear-like shape when sculpting e.g. a ring
1169 // First pass: calculate range
1170 gdouble direct_range = 0;
1171 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1172 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1173 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1174 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1175 if (node->selected) {
1176 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1177 }
1178 }
1179 }
1181 // Second pass: actually move nodes
1182 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1183 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1184 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1185 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1186 if (node->selected) {
1187 if (direct_range > 1e-6) {
1188 sp_nodepath_move_node_and_handles (node,
1189 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1190 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1191 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1192 } else {
1193 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1194 }
1196 }
1197 }
1198 }
1199 }
1201 // do not update repr here so that node dragging is acceptably fast
1202 update_object(nodepath);
1203 }
1206 /**
1207 * Move node selection to point, adjust its and neighbouring handles,
1208 * handle possible snapping, and commit the change with possible undo.
1209 */
1210 void
1211 sp_node_selected_move(gdouble dx, gdouble dy)
1212 {
1213 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1214 if (!nodepath) return;
1216 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1218 if (dx == 0) {
1219 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1220 } else if (dy == 0) {
1221 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1222 } else {
1223 sp_nodepath_update_repr(nodepath);
1224 }
1225 }
1227 /**
1228 * Move node selection off screen and commit the change.
1229 */
1230 void
1231 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1232 {
1233 // borrowed from sp_selection_move_screen in selection-chemistry.c
1234 // we find out the current zoom factor and divide deltas by it
1235 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1237 gdouble zoom = desktop->current_zoom();
1238 gdouble zdx = dx / zoom;
1239 gdouble zdy = dy / zoom;
1241 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1242 if (!nodepath) return;
1244 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1246 if (dx == 0) {
1247 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1248 } else if (dy == 0) {
1249 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1250 } else {
1251 sp_nodepath_update_repr(nodepath);
1252 }
1253 }
1255 /** If they don't yet exist, creates knot and line for the given side of the node */
1256 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1257 {
1258 if (!side->knot) {
1259 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"));
1261 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1262 side->knot->setSize (7);
1263 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1264 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1265 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1266 sp_knot_update_ctrl(side->knot);
1268 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1269 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1270 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1271 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1272 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1273 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1274 }
1276 if (!side->line) {
1277 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1278 SP_TYPE_CTRLLINE, NULL);
1279 }
1280 }
1282 /**
1283 * Ensure the given handle of the node is visible/invisible, update its screen position
1284 */
1285 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1286 {
1287 g_assert(node != NULL);
1289 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1290 NRPathcode code = sp_node_path_code_from_side(node, side);
1292 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1294 if (show_handle) {
1295 if (!side->knot) { // No handle knot at all
1296 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1297 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1298 side->knot->pos = side->pos;
1299 if (side->knot->item)
1300 SP_CTRL(side->knot->item)->moveto(side->pos);
1301 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1302 sp_knot_show(side->knot);
1303 } else {
1304 if (side->knot->pos != side->pos) { // only if it's really moved
1305 if (fire_move_signals) {
1306 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1307 } else {
1308 sp_knot_moveto(side->knot, &side->pos);
1309 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1310 }
1311 }
1312 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1313 sp_knot_show(side->knot);
1314 }
1315 }
1316 sp_canvas_item_show(side->line);
1317 } else {
1318 if (side->knot) {
1319 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1320 sp_knot_hide(side->knot);
1321 }
1322 }
1323 if (side->line) {
1324 sp_canvas_item_hide(side->line);
1325 }
1326 }
1327 }
1329 /**
1330 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1331 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1332 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1333 * updated; otherwise, just move the knots silently (used in batch moves).
1334 */
1335 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1336 {
1337 g_assert(node != NULL);
1339 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1340 sp_knot_show(node->knot);
1341 }
1343 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1344 if (fire_move_signals)
1345 sp_knot_set_position(node->knot, &node->pos, 0);
1346 else
1347 sp_knot_moveto(node->knot, &node->pos);
1348 }
1350 gboolean show_handles = node->selected;
1351 if (node->p.other != NULL) {
1352 if (node->p.other->selected) show_handles = TRUE;
1353 }
1354 if (node->n.other != NULL) {
1355 if (node->n.other->selected) show_handles = TRUE;
1356 }
1358 if (node->subpath->nodepath->show_handles == false)
1359 show_handles = FALSE;
1361 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1362 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1363 }
1365 /**
1366 * Call sp_node_update_handles() for all nodes on subpath.
1367 */
1368 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1369 {
1370 g_assert(subpath != NULL);
1372 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1373 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1374 }
1375 }
1377 /**
1378 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1379 */
1380 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1381 {
1382 g_assert(nodepath != NULL);
1384 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1385 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1386 }
1387 }
1389 void
1390 sp_nodepath_show_handles(bool show)
1391 {
1392 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1393 if (nodepath == NULL) return;
1395 nodepath->show_handles = show;
1396 sp_nodepath_update_handles(nodepath);
1397 }
1399 /**
1400 * Adds all selected nodes in nodepath to list.
1401 */
1402 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1403 {
1404 StlConv<Node *>::list(l, selected);
1405 /// \todo this adds a copying, rework when the selection becomes a stl list
1406 }
1408 /**
1409 * Align selected nodes on the specified axis.
1410 */
1411 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1412 {
1413 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1414 return;
1415 }
1417 if ( !nodepath->selected->next ) { // only one node selected
1418 return;
1419 }
1420 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1421 NR::Point dest(pNode->pos);
1422 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1423 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1424 if (pNode) {
1425 dest[axis] = pNode->pos[axis];
1426 sp_node_moveto(pNode, dest);
1427 }
1428 }
1430 sp_nodepath_update_repr(nodepath);
1431 }
1433 /// Helper struct.
1434 struct NodeSort
1435 {
1436 Inkscape::NodePath::Node *_node;
1437 NR::Coord _coord;
1438 /// \todo use vectorof pointers instead of calling copy ctor
1439 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1440 _node(node), _coord(node->pos[axis])
1441 {}
1443 };
1445 static bool operator<(NodeSort const &a, NodeSort const &b)
1446 {
1447 return (a._coord < b._coord);
1448 }
1450 /**
1451 * Distribute selected nodes on the specified axis.
1452 */
1453 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1454 {
1455 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1456 return;
1457 }
1459 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1460 return;
1461 }
1463 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1464 std::vector<NodeSort> sorted;
1465 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1466 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1467 if (pNode) {
1468 NodeSort n(pNode, axis);
1469 sorted.push_back(n);
1470 //dest[axis] = pNode->pos[axis];
1471 //sp_node_moveto(pNode, dest);
1472 }
1473 }
1474 std::sort(sorted.begin(), sorted.end());
1475 unsigned int len = sorted.size();
1476 //overall bboxes span
1477 float dist = (sorted.back()._coord -
1478 sorted.front()._coord);
1479 //new distance between each bbox
1480 float step = (dist) / (len - 1);
1481 float pos = sorted.front()._coord;
1482 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1483 it < sorted.end();
1484 it ++ )
1485 {
1486 NR::Point dest((*it)._node->pos);
1487 dest[axis] = pos;
1488 sp_node_moveto((*it)._node, dest);
1489 pos += step;
1490 }
1492 sp_nodepath_update_repr(nodepath);
1493 }
1496 /**
1497 * Call sp_nodepath_line_add_node() for all selected segments.
1498 */
1499 void
1500 sp_node_selected_add_node(void)
1501 {
1502 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1503 if (!nodepath) {
1504 return;
1505 }
1507 GList *nl = NULL;
1509 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1510 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1511 g_assert(t->selected);
1512 if (t->p.other && t->p.other->selected) {
1513 nl = g_list_prepend(nl, t);
1514 }
1515 }
1517 while (nl) {
1518 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1519 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1520 sp_nodepath_node_select(n, TRUE, FALSE);
1521 nl = g_list_remove(nl, t);
1522 }
1524 /** \todo fixme: adjust ? */
1525 sp_nodepath_update_handles(nodepath);
1527 sp_nodepath_update_repr(nodepath);
1529 sp_nodepath_update_statusbar(nodepath);
1530 }
1532 /**
1533 * Select segment nearest to point
1534 */
1535 void
1536 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1537 {
1538 if (!nodepath) {
1539 return;
1540 }
1542 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1544 //find segment to segment
1545 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1547 gboolean force = FALSE;
1548 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1549 force = TRUE;
1550 }
1551 sp_nodepath_node_select(e, (gboolean) toggle, force);
1552 if (e->p.other)
1553 sp_nodepath_node_select(e->p.other, TRUE, force);
1555 sp_nodepath_update_handles(nodepath);
1557 sp_nodepath_update_statusbar(nodepath);
1558 }
1560 /**
1561 * Add a node nearest to point
1562 */
1563 void
1564 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1565 {
1566 if (!nodepath) {
1567 return;
1568 }
1570 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1572 //find segment to split
1573 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1575 //don't know why but t seems to flip for lines
1576 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1577 position.t = 1.0 - position.t;
1578 }
1579 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1580 sp_nodepath_node_select(n, FALSE, TRUE);
1582 /* fixme: adjust ? */
1583 sp_nodepath_update_handles(nodepath);
1585 sp_nodepath_update_repr(nodepath);
1587 sp_nodepath_update_statusbar(nodepath);
1588 }
1590 /*
1591 * Adjusts a segment so that t moves by a certain delta for dragging
1592 * converts lines to curves
1593 *
1594 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1595 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1596 */
1597 void
1598 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1599 {
1600 /* feel good is an arbitrary parameter that distributes the delta between handles
1601 * if t of the drag point is less than 1/6 distance form the endpoint only
1602 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1603 */
1604 double feel_good;
1605 if (t <= 1.0 / 6.0)
1606 feel_good = 0;
1607 else if (t <= 0.5)
1608 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1609 else if (t <= 5.0 / 6.0)
1610 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1611 else
1612 feel_good = 1;
1614 //if we're dragging a line convert it to a curve
1615 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1616 sp_nodepath_set_line_type(e, NR_CURVETO);
1617 }
1619 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1620 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1621 e->p.other->n.pos += offsetcoord0;
1622 e->p.pos += offsetcoord1;
1624 // adjust handles of adjacent nodes where necessary
1625 sp_node_adjust_handle(e,1);
1626 sp_node_adjust_handle(e->p.other,-1);
1628 sp_nodepath_update_handles(e->subpath->nodepath);
1630 update_object(e->subpath->nodepath);
1632 sp_nodepath_update_statusbar(e->subpath->nodepath);
1633 }
1636 /**
1637 * Call sp_nodepath_break() for all selected segments.
1638 */
1639 void sp_node_selected_break()
1640 {
1641 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1642 if (!nodepath) return;
1644 GList *temp = NULL;
1645 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1646 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1647 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1648 if (nn == NULL) continue; // no break, no new node
1649 temp = g_list_prepend(temp, nn);
1650 }
1652 if (temp) {
1653 sp_nodepath_deselect(nodepath);
1654 }
1655 for (GList *l = temp; l != NULL; l = l->next) {
1656 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1657 }
1659 sp_nodepath_update_handles(nodepath);
1661 sp_nodepath_update_repr(nodepath);
1662 }
1664 /**
1665 * Duplicate the selected node(s).
1666 */
1667 void sp_node_selected_duplicate()
1668 {
1669 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1670 if (!nodepath) {
1671 return;
1672 }
1674 GList *temp = NULL;
1675 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1676 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1677 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1678 if (nn == NULL) continue; // could not duplicate
1679 temp = g_list_prepend(temp, nn);
1680 }
1682 if (temp) {
1683 sp_nodepath_deselect(nodepath);
1684 }
1685 for (GList *l = temp; l != NULL; l = l->next) {
1686 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1687 }
1689 sp_nodepath_update_handles(nodepath);
1691 sp_nodepath_update_repr(nodepath);
1692 }
1694 /**
1695 * Join two nodes by merging them into one.
1696 */
1697 void sp_node_selected_join()
1698 {
1699 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1700 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1702 if (g_list_length(nodepath->selected) != 2) {
1703 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1704 return;
1705 }
1707 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1708 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1710 g_assert(a != b);
1711 g_assert(a->p.other || a->n.other);
1712 g_assert(b->p.other || b->n.other);
1714 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1715 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1716 return;
1717 }
1719 /* a and b are endpoints */
1721 NR::Point c;
1722 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1723 c = a->pos;
1724 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1725 c = b->pos;
1726 } else {
1727 c = (a->pos + b->pos) / 2;
1728 }
1730 if (a->subpath == b->subpath) {
1731 Inkscape::NodePath::SubPath *sp = a->subpath;
1732 sp_nodepath_subpath_close(sp);
1733 sp_node_moveto (sp->first, c);
1735 sp_nodepath_update_handles(sp->nodepath);
1736 sp_nodepath_update_repr(nodepath);
1737 return;
1738 }
1740 /* a and b are separate subpaths */
1741 Inkscape::NodePath::SubPath *sa = a->subpath;
1742 Inkscape::NodePath::SubPath *sb = b->subpath;
1743 NR::Point p;
1744 Inkscape::NodePath::Node *n;
1745 NRPathcode code;
1746 if (a == sa->first) {
1747 p = sa->first->n.pos;
1748 code = (NRPathcode)sa->first->n.other->code;
1749 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1750 n = sa->last;
1751 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1752 n = n->p.other;
1753 while (n) {
1754 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1755 n = n->p.other;
1756 if (n == sa->first) n = NULL;
1757 }
1758 sp_nodepath_subpath_destroy(sa);
1759 sa = t;
1760 } else if (a == sa->last) {
1761 p = sa->last->p.pos;
1762 code = (NRPathcode)sa->last->code;
1763 sp_nodepath_node_destroy(sa->last);
1764 } else {
1765 code = NR_END;
1766 g_assert_not_reached();
1767 }
1769 if (b == sb->first) {
1770 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1771 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1772 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1773 }
1774 } else if (b == sb->last) {
1775 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1776 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1777 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1778 }
1779 } else {
1780 g_assert_not_reached();
1781 }
1782 /* and now destroy sb */
1784 sp_nodepath_subpath_destroy(sb);
1786 sp_nodepath_update_handles(sa->nodepath);
1788 sp_nodepath_update_repr(nodepath);
1790 sp_nodepath_update_statusbar(nodepath);
1791 }
1793 /**
1794 * Join two nodes by adding a segment between them.
1795 */
1796 void sp_node_selected_join_segment()
1797 {
1798 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1799 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1801 if (g_list_length(nodepath->selected) != 2) {
1802 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1803 return;
1804 }
1806 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1807 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1809 g_assert(a != b);
1810 g_assert(a->p.other || a->n.other);
1811 g_assert(b->p.other || b->n.other);
1813 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1814 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1815 return;
1816 }
1818 if (a->subpath == b->subpath) {
1819 Inkscape::NodePath::SubPath *sp = a->subpath;
1821 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1822 sp->closed = TRUE;
1824 sp->first->p.other = sp->last;
1825 sp->last->n.other = sp->first;
1827 sp_node_handle_mirror_p_to_n(sp->last);
1828 sp_node_handle_mirror_n_to_p(sp->first);
1830 sp->first->code = sp->last->code;
1831 sp->first = sp->last;
1833 sp_nodepath_update_handles(sp->nodepath);
1835 sp_nodepath_update_repr(nodepath);
1837 return;
1838 }
1840 /* a and b are separate subpaths */
1841 Inkscape::NodePath::SubPath *sa = a->subpath;
1842 Inkscape::NodePath::SubPath *sb = b->subpath;
1844 Inkscape::NodePath::Node *n;
1845 NR::Point p;
1846 NRPathcode code;
1847 if (a == sa->first) {
1848 code = (NRPathcode) sa->first->n.other->code;
1849 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1850 n = sa->last;
1851 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1852 for (n = n->p.other; n != NULL; n = n->p.other) {
1853 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1854 }
1855 sp_nodepath_subpath_destroy(sa);
1856 sa = t;
1857 } else if (a == sa->last) {
1858 code = (NRPathcode)sa->last->code;
1859 } else {
1860 code = NR_END;
1861 g_assert_not_reached();
1862 }
1864 if (b == sb->first) {
1865 n = sb->first;
1866 sp_node_handle_mirror_p_to_n(sa->last);
1867 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1868 sp_node_handle_mirror_n_to_p(sa->last);
1869 for (n = n->n.other; n != NULL; n = n->n.other) {
1870 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1871 }
1872 } else if (b == sb->last) {
1873 n = sb->last;
1874 sp_node_handle_mirror_p_to_n(sa->last);
1875 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1876 sp_node_handle_mirror_n_to_p(sa->last);
1877 for (n = n->p.other; n != NULL; n = n->p.other) {
1878 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1879 }
1880 } else {
1881 g_assert_not_reached();
1882 }
1883 /* and now destroy sb */
1885 sp_nodepath_subpath_destroy(sb);
1887 sp_nodepath_update_handles(sa->nodepath);
1889 sp_nodepath_update_repr(nodepath);
1890 }
1892 /**
1893 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1894 */
1895 void sp_node_delete_preserve(GList *nodes_to_delete)
1896 {
1897 GSList *nodepaths = NULL;
1899 while (nodes_to_delete) {
1900 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1901 Inkscape::NodePath::SubPath *sp = node->subpath;
1902 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1903 Inkscape::NodePath::Node *sample_cursor = NULL;
1904 Inkscape::NodePath::Node *sample_end = NULL;
1905 Inkscape::NodePath::Node *delete_cursor = node;
1906 bool just_delete = false;
1908 //find the start of this contiguous selection
1909 //move left to the first node that is not selected
1910 //or the start of the non-closed path
1911 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1912 delete_cursor = curr;
1913 }
1915 //just delete at the beginning of an open path
1916 if (!delete_cursor->p.other) {
1917 sample_cursor = delete_cursor;
1918 just_delete = true;
1919 } else {
1920 sample_cursor = delete_cursor->p.other;
1921 }
1923 //calculate points for each segment
1924 int rate = 5;
1925 float period = 1.0 / rate;
1926 std::vector<NR::Point> data;
1927 if (!just_delete) {
1928 data.push_back(sample_cursor->pos);
1929 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1930 //just delete at the end of an open path
1931 if (!sp->closed && curr->n.other == sp->last) {
1932 just_delete = true;
1933 break;
1934 }
1936 //sample points on the contiguous selected segment
1937 NR::Point *bez;
1938 bez = new NR::Point [4];
1939 bez[0] = curr->pos;
1940 bez[1] = curr->n.pos;
1941 bez[2] = curr->n.other->p.pos;
1942 bez[3] = curr->n.other->pos;
1943 for (int i=1; i<rate; i++) {
1944 gdouble t = i * period;
1945 NR::Point p = bezier_pt(3, bez, t);
1946 data.push_back(p);
1947 }
1948 data.push_back(curr->n.other->pos);
1950 sample_end = curr->n.other;
1951 //break if we've come full circle or hit the end of the selection
1952 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1953 break;
1954 }
1955 }
1956 }
1958 if (!just_delete) {
1959 //calculate the best fitting single segment and adjust the endpoints
1960 NR::Point *adata;
1961 adata = new NR::Point [data.size()];
1962 copy(data.begin(), data.end(), adata);
1964 NR::Point *bez;
1965 bez = new NR::Point [4];
1966 //would decreasing error create a better fitting approximation?
1967 gdouble error = 1.0;
1968 gint ret;
1969 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1971 //adjust endpoints
1972 sample_cursor->n.pos = bez[1];
1973 sample_end->p.pos = bez[2];
1974 }
1976 //destroy this contiguous selection
1977 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
1978 Inkscape::NodePath::Node *temp = delete_cursor;
1979 if (delete_cursor->n.other == delete_cursor) {
1980 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
1981 delete_cursor = NULL;
1982 } else {
1983 delete_cursor = delete_cursor->n.other;
1984 }
1985 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
1986 sp_nodepath_node_destroy(temp);
1987 }
1989 sp_nodepath_update_handles(nodepath);
1991 if (!g_slist_find(nodepaths, nodepath))
1992 nodepaths = g_slist_prepend (nodepaths, nodepath);
1993 }
1995 for (GSList *i = nodepaths; i; i = i->next) {
1996 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
1997 // different nodepaths will give us one undo event per nodepath
1998 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2000 // if the entire nodepath is removed, delete the selected object.
2001 if (nodepath->subpaths == NULL ||
2002 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2003 //at least 2
2004 sp_nodepath_get_node_count(nodepath) < 2) {
2005 SPDocument *document = sp_desktop_document (nodepath->desktop);
2006 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2007 //delete this nodepath's object, not the entire selection! (though at this time, this
2008 //does not matter)
2009 sp_selection_delete();
2010 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2011 /* TODO: annotate */ "nodepath.cpp:2011");
2012 } else {
2013 sp_nodepath_update_repr(nodepath);
2014 sp_nodepath_update_statusbar(nodepath);
2015 }
2016 }
2018 g_slist_free (nodepaths);
2019 }
2021 /**
2022 * Delete one or more selected nodes.
2023 */
2024 void sp_node_selected_delete()
2025 {
2026 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2027 if (!nodepath) return;
2028 if (!nodepath->selected) return;
2030 /** \todo fixme: do it the right way */
2031 while (nodepath->selected) {
2032 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2033 sp_nodepath_node_destroy(node);
2034 }
2037 //clean up the nodepath (such as for trivial subpaths)
2038 sp_nodepath_cleanup(nodepath);
2040 sp_nodepath_update_handles(nodepath);
2042 // if the entire nodepath is removed, delete the selected object.
2043 if (nodepath->subpaths == NULL ||
2044 sp_nodepath_get_node_count(nodepath) < 2) {
2045 SPDocument *document = sp_desktop_document (nodepath->desktop);
2046 sp_selection_delete();
2047 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2048 /* TODO: annotate */ "nodepath.cpp:2048");
2049 return;
2050 }
2052 sp_nodepath_update_repr(nodepath);
2054 sp_nodepath_update_statusbar(nodepath);
2055 }
2057 /**
2058 * Delete one or more segments between two selected nodes.
2059 * This is the code for 'split'.
2060 */
2061 void
2062 sp_node_selected_delete_segment(void)
2063 {
2064 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2065 Inkscape::NodePath::Node *curr, *next; //Iterators
2067 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2068 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2070 if (g_list_length(nodepath->selected) != 2) {
2071 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2072 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2073 return;
2074 }
2076 //Selected nodes, not inclusive
2077 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2078 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2080 if ( ( a==b) || //same node
2081 (a->subpath != b->subpath ) || //not the same path
2082 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2083 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2084 {
2085 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2086 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2087 return;
2088 }
2090 //###########################################
2091 //# BEGIN EDITS
2092 //###########################################
2093 //##################################
2094 //# CLOSED PATH
2095 //##################################
2096 if (a->subpath->closed) {
2099 gboolean reversed = FALSE;
2101 //Since we can go in a circle, we need to find the shorter distance.
2102 // a->b or b->a
2103 start = end = NULL;
2104 int distance = 0;
2105 int minDistance = 0;
2106 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2107 if (curr==b) {
2108 //printf("a to b:%d\n", distance);
2109 start = a;//go from a to b
2110 end = b;
2111 minDistance = distance;
2112 //printf("A to B :\n");
2113 break;
2114 }
2115 distance++;
2116 }
2118 //try again, the other direction
2119 distance = 0;
2120 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2121 if (curr==a) {
2122 //printf("b to a:%d\n", distance);
2123 if (distance < minDistance) {
2124 start = b; //we go from b to a
2125 end = a;
2126 reversed = TRUE;
2127 //printf("B to A\n");
2128 }
2129 break;
2130 }
2131 distance++;
2132 }
2135 //Copy everything from 'end' to 'start' to a new subpath
2136 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2137 for (curr=end ; curr ; curr=curr->n.other) {
2138 NRPathcode code = (NRPathcode) curr->code;
2139 if (curr == end)
2140 code = NR_MOVETO;
2141 sp_nodepath_node_new(t, NULL,
2142 (Inkscape::NodePath::NodeType)curr->type, code,
2143 &curr->p.pos, &curr->pos, &curr->n.pos);
2144 if (curr == start)
2145 break;
2146 }
2147 sp_nodepath_subpath_destroy(a->subpath);
2150 }
2154 //##################################
2155 //# OPEN PATH
2156 //##################################
2157 else {
2159 //We need to get the direction of the list between A and B
2160 //Can we walk from a to b?
2161 start = end = NULL;
2162 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2163 if (curr==b) {
2164 start = a; //did it! we go from a to b
2165 end = b;
2166 //printf("A to B\n");
2167 break;
2168 }
2169 }
2170 if (!start) {//didn't work? let's try the other direction
2171 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2172 if (curr==a) {
2173 start = b; //did it! we go from b to a
2174 end = a;
2175 //printf("B to A\n");
2176 break;
2177 }
2178 }
2179 }
2180 if (!start) {
2181 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2182 _("Cannot find path between nodes."));
2183 return;
2184 }
2188 //Copy everything after 'end' to a new subpath
2189 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2190 for (curr=end ; curr ; curr=curr->n.other) {
2191 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2192 &curr->p.pos, &curr->pos, &curr->n.pos);
2193 }
2195 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2196 for (curr = start->n.other ; curr ; curr=next) {
2197 next = curr->n.other;
2198 sp_nodepath_node_destroy(curr);
2199 }
2201 }
2202 //###########################################
2203 //# END EDITS
2204 //###########################################
2206 //clean up the nodepath (such as for trivial subpaths)
2207 sp_nodepath_cleanup(nodepath);
2209 sp_nodepath_update_handles(nodepath);
2211 sp_nodepath_update_repr(nodepath);
2213 sp_nodepath_update_statusbar(nodepath);
2214 }
2216 /**
2217 * Call sp_nodepath_set_line() for all selected segments.
2218 */
2219 void
2220 sp_node_selected_set_line_type(NRPathcode code)
2221 {
2222 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2223 if (nodepath == NULL) return;
2225 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2226 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2227 g_assert(n->selected);
2228 if (n->p.other && n->p.other->selected) {
2229 sp_nodepath_set_line_type(n, code);
2230 }
2231 }
2233 sp_nodepath_update_repr(nodepath);
2234 }
2236 /**
2237 * Call sp_nodepath_convert_node_type() for all selected nodes.
2238 */
2239 void
2240 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2241 {
2242 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2243 if (nodepath == NULL) return;
2245 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2246 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2247 }
2249 sp_nodepath_update_repr(nodepath);
2250 }
2252 /**
2253 * Change select status of node, update its own and neighbour handles.
2254 */
2255 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2256 {
2257 node->selected = selected;
2259 if (selected) {
2260 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2261 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2262 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2263 sp_knot_update_ctrl(node->knot);
2264 } else {
2265 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2266 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2267 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2268 sp_knot_update_ctrl(node->knot);
2269 }
2271 sp_node_update_handles(node);
2272 if (node->n.other) sp_node_update_handles(node->n.other);
2273 if (node->p.other) sp_node_update_handles(node->p.other);
2274 }
2276 /**
2277 \brief Select a node
2278 \param node The node to select
2279 \param incremental If true, add to selection, otherwise deselect others
2280 \param override If true, always select this node, otherwise toggle selected status
2281 */
2282 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2283 {
2284 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2286 if (incremental) {
2287 if (override) {
2288 if (!g_list_find(nodepath->selected, node)) {
2289 nodepath->selected = g_list_prepend(nodepath->selected, node);
2290 }
2291 sp_node_set_selected(node, TRUE);
2292 } else { // toggle
2293 if (node->selected) {
2294 g_assert(g_list_find(nodepath->selected, node));
2295 nodepath->selected = g_list_remove(nodepath->selected, node);
2296 } else {
2297 g_assert(!g_list_find(nodepath->selected, node));
2298 nodepath->selected = g_list_prepend(nodepath->selected, node);
2299 }
2300 sp_node_set_selected(node, !node->selected);
2301 }
2302 } else {
2303 sp_nodepath_deselect(nodepath);
2304 nodepath->selected = g_list_prepend(nodepath->selected, node);
2305 sp_node_set_selected(node, TRUE);
2306 }
2308 sp_nodepath_update_statusbar(nodepath);
2309 }
2312 /**
2313 \brief Deselect all nodes in the nodepath
2314 */
2315 void
2316 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2317 {
2318 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2320 while (nodepath->selected) {
2321 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2322 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2323 }
2324 sp_nodepath_update_statusbar(nodepath);
2325 }
2327 /**
2328 \brief Select or invert selection of all nodes in the nodepath
2329 */
2330 void
2331 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2332 {
2333 if (!nodepath) return;
2335 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2336 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2337 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2338 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2339 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2340 }
2341 }
2342 }
2344 /**
2345 * If nothing selected, does the same as sp_nodepath_select_all();
2346 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2347 * (i.e., similar to "select all in layer", with the "selected" subpaths
2348 * being treated as "layers" in the path).
2349 */
2350 void
2351 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2352 {
2353 if (!nodepath) return;
2355 if (g_list_length (nodepath->selected) == 0) {
2356 sp_nodepath_select_all (nodepath, invert);
2357 return;
2358 }
2360 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2361 GSList *subpaths = NULL;
2363 for (GList *l = copy; l != NULL; l = l->next) {
2364 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2365 Inkscape::NodePath::SubPath *subpath = n->subpath;
2366 if (!g_slist_find (subpaths, subpath))
2367 subpaths = g_slist_prepend (subpaths, subpath);
2368 }
2370 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2371 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2372 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2373 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2374 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2375 }
2376 }
2378 g_slist_free (subpaths);
2379 g_list_free (copy);
2380 }
2382 /**
2383 * \brief Select the node after the last selected; if none is selected,
2384 * select the first within path.
2385 */
2386 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2387 {
2388 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2390 Inkscape::NodePath::Node *last = NULL;
2391 if (nodepath->selected) {
2392 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2393 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2394 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2395 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2396 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2397 if (node->selected) {
2398 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2399 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2400 if (spl->next) { // there's a next subpath
2401 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2402 last = subpath_next->first;
2403 } else if (spl->prev) { // there's a previous subpath
2404 last = NULL; // to be set later to the first node of first subpath
2405 } else {
2406 last = node->n.other;
2407 }
2408 } else {
2409 last = node->n.other;
2410 }
2411 } else {
2412 if (node->n.other) {
2413 last = node->n.other;
2414 } else {
2415 if (spl->next) { // there's a next subpath
2416 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2417 last = subpath_next->first;
2418 } else if (spl->prev) { // there's a previous subpath
2419 last = NULL; // to be set later to the first node of first subpath
2420 } else {
2421 last = (Inkscape::NodePath::Node *) subpath->first;
2422 }
2423 }
2424 }
2425 }
2426 }
2427 }
2428 sp_nodepath_deselect(nodepath);
2429 }
2431 if (last) { // there's at least one more node after selected
2432 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2433 } else { // no more nodes, select the first one in first subpath
2434 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2435 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2436 }
2437 }
2439 /**
2440 * \brief Select the node before the first selected; if none is selected,
2441 * select the last within path
2442 */
2443 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2444 {
2445 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2447 Inkscape::NodePath::Node *last = NULL;
2448 if (nodepath->selected) {
2449 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2450 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2451 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2452 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2453 if (node->selected) {
2454 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2455 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2456 if (spl->prev) { // there's a prev subpath
2457 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2458 last = subpath_prev->last;
2459 } else if (spl->next) { // there's a next subpath
2460 last = NULL; // to be set later to the last node of last subpath
2461 } else {
2462 last = node->p.other;
2463 }
2464 } else {
2465 last = node->p.other;
2466 }
2467 } else {
2468 if (node->p.other) {
2469 last = node->p.other;
2470 } else {
2471 if (spl->prev) { // there's a prev subpath
2472 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2473 last = subpath_prev->last;
2474 } else if (spl->next) { // there's a next subpath
2475 last = NULL; // to be set later to the last node of last subpath
2476 } else {
2477 last = (Inkscape::NodePath::Node *) subpath->last;
2478 }
2479 }
2480 }
2481 }
2482 }
2483 }
2484 sp_nodepath_deselect(nodepath);
2485 }
2487 if (last) { // there's at least one more node before selected
2488 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2489 } else { // no more nodes, select the last one in last subpath
2490 GList *spl = g_list_last(nodepath->subpaths);
2491 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2492 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2493 }
2494 }
2496 /**
2497 * \brief Select all nodes that are within the rectangle.
2498 */
2499 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2500 {
2501 if (!incremental) {
2502 sp_nodepath_deselect(nodepath);
2503 }
2505 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2506 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2507 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2508 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2510 if (b.contains(node->pos)) {
2511 sp_nodepath_node_select(node, TRUE, TRUE);
2512 }
2513 }
2514 }
2515 }
2518 void
2519 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2520 {
2521 g_assert (n);
2522 g_assert (nodepath);
2523 g_assert (n->subpath->nodepath == nodepath);
2525 if (g_list_length (nodepath->selected) == 0) {
2526 if (grow > 0) {
2527 sp_nodepath_node_select(n, TRUE, TRUE);
2528 }
2529 return;
2530 }
2532 if (g_list_length (nodepath->selected) == 1) {
2533 if (grow < 0) {
2534 sp_nodepath_deselect (nodepath);
2535 return;
2536 }
2537 }
2539 double n_sel_range = 0, p_sel_range = 0;
2540 Inkscape::NodePath::Node *farthest_n_node = n;
2541 Inkscape::NodePath::Node *farthest_p_node = n;
2543 // Calculate ranges
2544 {
2545 double n_range = 0, p_range = 0;
2546 bool n_going = true, p_going = true;
2547 Inkscape::NodePath::Node *n_node = n;
2548 Inkscape::NodePath::Node *p_node = n;
2549 do {
2550 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2551 if (n_node && n_going)
2552 n_node = n_node->n.other;
2553 if (n_node == NULL) {
2554 n_going = false;
2555 } else {
2556 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2557 if (n_node->selected) {
2558 n_sel_range = n_range;
2559 farthest_n_node = n_node;
2560 }
2561 if (n_node == p_node) {
2562 n_going = false;
2563 p_going = false;
2564 }
2565 }
2566 if (p_node && p_going)
2567 p_node = p_node->p.other;
2568 if (p_node == NULL) {
2569 p_going = false;
2570 } else {
2571 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2572 if (p_node->selected) {
2573 p_sel_range = p_range;
2574 farthest_p_node = p_node;
2575 }
2576 if (p_node == n_node) {
2577 n_going = false;
2578 p_going = false;
2579 }
2580 }
2581 } while (n_going || p_going);
2582 }
2584 if (grow > 0) {
2585 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2586 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2587 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2588 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2589 }
2590 } else {
2591 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2592 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2593 } else if (farthest_p_node && farthest_p_node->selected) {
2594 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2595 }
2596 }
2597 }
2599 void
2600 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2601 {
2602 g_assert (n);
2603 g_assert (nodepath);
2604 g_assert (n->subpath->nodepath == nodepath);
2606 if (g_list_length (nodepath->selected) == 0) {
2607 if (grow > 0) {
2608 sp_nodepath_node_select(n, TRUE, TRUE);
2609 }
2610 return;
2611 }
2613 if (g_list_length (nodepath->selected) == 1) {
2614 if (grow < 0) {
2615 sp_nodepath_deselect (nodepath);
2616 return;
2617 }
2618 }
2620 Inkscape::NodePath::Node *farthest_selected = NULL;
2621 double farthest_dist = 0;
2623 Inkscape::NodePath::Node *closest_unselected = NULL;
2624 double closest_dist = NR_HUGE;
2626 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2627 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2628 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2629 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2630 if (node == n)
2631 continue;
2632 if (node->selected) {
2633 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2634 farthest_dist = NR::L2(node->pos - n->pos);
2635 farthest_selected = node;
2636 }
2637 } else {
2638 if (NR::L2(node->pos - n->pos) < closest_dist) {
2639 closest_dist = NR::L2(node->pos - n->pos);
2640 closest_unselected = node;
2641 }
2642 }
2643 }
2644 }
2646 if (grow > 0) {
2647 if (closest_unselected) {
2648 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2649 }
2650 } else {
2651 if (farthest_selected) {
2652 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2653 }
2654 }
2655 }
2658 /**
2659 \brief Saves all nodes' and handles' current positions in their origin members
2660 */
2661 void
2662 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2663 {
2664 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2665 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2666 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2667 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2668 n->origin = n->pos;
2669 n->p.origin = n->p.pos;
2670 n->n.origin = n->n.pos;
2671 }
2672 }
2673 }
2675 /**
2676 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2677 */
2678 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2679 {
2680 if (!nodepath->selected) {
2681 return NULL;
2682 }
2684 GList *r = NULL;
2685 guint i = 0;
2686 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2687 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2688 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2689 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2690 i++;
2691 if (node->selected) {
2692 r = g_list_append(r, GINT_TO_POINTER(i));
2693 }
2694 }
2695 }
2696 return r;
2697 }
2699 /**
2700 \brief Restores selection by selecting nodes whose positions are in the list
2701 */
2702 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2703 {
2704 sp_nodepath_deselect(nodepath);
2706 guint i = 0;
2707 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2708 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2709 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2710 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2711 i++;
2712 if (g_list_find(r, GINT_TO_POINTER(i))) {
2713 sp_nodepath_node_select(node, TRUE, TRUE);
2714 }
2715 }
2716 }
2718 }
2720 /**
2721 \brief Adjusts handle according to node type and line code.
2722 */
2723 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2724 {
2725 double len, otherlen, linelen;
2727 g_assert(node);
2729 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2730 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2732 /** \todo fixme: */
2733 if (me->other == NULL) return;
2734 if (other->other == NULL) return;
2736 /* I have line */
2738 NRPathcode mecode, ocode;
2739 if (which_adjust == 1) {
2740 mecode = (NRPathcode)me->other->code;
2741 ocode = (NRPathcode)node->code;
2742 } else {
2743 mecode = (NRPathcode)node->code;
2744 ocode = (NRPathcode)other->other->code;
2745 }
2747 if (mecode == NR_LINETO) return;
2749 /* I am curve */
2751 if (other->other == NULL) return;
2753 /* Other has line */
2755 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2757 NR::Point delta;
2758 if (ocode == NR_LINETO) {
2759 /* other is lineto, we are either smooth or symm */
2760 Inkscape::NodePath::Node *othernode = other->other;
2761 len = NR::L2(me->pos - node->pos);
2762 delta = node->pos - othernode->pos;
2763 linelen = NR::L2(delta);
2764 if (linelen < 1e-18)
2765 return;
2766 me->pos = node->pos + (len / linelen)*delta;
2767 return;
2768 }
2770 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2772 me->pos = 2 * node->pos - other->pos;
2773 return;
2774 }
2776 /* We are smooth */
2778 len = NR::L2(me->pos - node->pos);
2779 delta = other->pos - node->pos;
2780 otherlen = NR::L2(delta);
2781 if (otherlen < 1e-18) return;
2783 me->pos = node->pos - (len / otherlen) * delta;
2784 }
2786 /**
2787 \brief Adjusts both handles according to node type and line code
2788 */
2789 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2790 {
2791 g_assert(node);
2793 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2795 /* we are either smooth or symm */
2797 if (node->p.other == NULL) return;
2799 if (node->n.other == NULL) return;
2801 if (node->code == NR_LINETO) {
2802 if (node->n.other->code == NR_LINETO) return;
2803 sp_node_adjust_handle(node, 1);
2804 return;
2805 }
2807 if (node->n.other->code == NR_LINETO) {
2808 if (node->code == NR_LINETO) return;
2809 sp_node_adjust_handle(node, -1);
2810 return;
2811 }
2813 /* both are curves */
2814 NR::Point const delta( node->n.pos - node->p.pos );
2816 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2817 node->p.pos = node->pos - delta / 2;
2818 node->n.pos = node->pos + delta / 2;
2819 return;
2820 }
2822 /* We are smooth */
2823 double plen = NR::L2(node->p.pos - node->pos);
2824 if (plen < 1e-18) return;
2825 double nlen = NR::L2(node->n.pos - node->pos);
2826 if (nlen < 1e-18) return;
2827 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2828 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2829 }
2831 /**
2832 * Node event callback.
2833 */
2834 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2835 {
2836 gboolean ret = FALSE;
2837 switch (event->type) {
2838 case GDK_ENTER_NOTIFY:
2839 active_node = n;
2840 break;
2841 case GDK_LEAVE_NOTIFY:
2842 active_node = NULL;
2843 break;
2844 case GDK_KEY_PRESS:
2845 switch (get_group0_keyval (&event->key)) {
2846 case GDK_space:
2847 if (event->key.state & GDK_BUTTON1_MASK) {
2848 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2849 stamp_repr(nodepath);
2850 ret = TRUE;
2851 }
2852 break;
2853 case GDK_Page_Up:
2854 if (event->key.state & GDK_CONTROL_MASK) {
2855 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2856 } else {
2857 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2858 }
2859 break;
2860 case GDK_Page_Down:
2861 if (event->key.state & GDK_CONTROL_MASK) {
2862 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2863 } else {
2864 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2865 }
2866 break;
2867 default:
2868 break;
2869 }
2870 break;
2871 default:
2872 break;
2873 }
2875 return ret;
2876 }
2878 /**
2879 * Handle keypress on node; directly called.
2880 */
2881 gboolean node_key(GdkEvent *event)
2882 {
2883 Inkscape::NodePath::Path *np;
2885 // there is no way to verify nodes so set active_node to nil when deleting!!
2886 if (active_node == NULL) return FALSE;
2888 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2889 gint ret = FALSE;
2890 switch (get_group0_keyval (&event->key)) {
2891 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2892 case GDK_BackSpace:
2893 np = active_node->subpath->nodepath;
2894 sp_nodepath_node_destroy(active_node);
2895 sp_nodepath_update_repr(np);
2896 active_node = NULL;
2897 ret = TRUE;
2898 break;
2899 case GDK_c:
2900 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2901 ret = TRUE;
2902 break;
2903 case GDK_s:
2904 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2905 ret = TRUE;
2906 break;
2907 case GDK_y:
2908 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2909 ret = TRUE;
2910 break;
2911 case GDK_b:
2912 sp_nodepath_node_break(active_node);
2913 ret = TRUE;
2914 break;
2915 }
2916 return ret;
2917 }
2918 return FALSE;
2919 }
2921 /**
2922 * Mouseclick on node callback.
2923 */
2924 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2925 {
2926 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2928 if (state & GDK_CONTROL_MASK) {
2929 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2931 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2932 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2933 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2934 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2935 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2936 } else {
2937 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2938 }
2939 sp_nodepath_update_repr(nodepath);
2940 sp_nodepath_update_statusbar(nodepath);
2942 } else { //ctrl+alt+click: delete node
2943 GList *node_to_delete = NULL;
2944 node_to_delete = g_list_append(node_to_delete, n);
2945 sp_node_delete_preserve(node_to_delete);
2946 }
2948 } else {
2949 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2950 }
2951 }
2953 /**
2954 * Mouse grabbed node callback.
2955 */
2956 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2957 {
2958 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2960 if (!n->selected) {
2961 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2962 }
2964 sp_nodepath_remember_origins (n->subpath->nodepath);
2965 }
2967 /**
2968 * Mouse ungrabbed node callback.
2969 */
2970 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2971 {
2972 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2974 n->dragging_out = NULL;
2976 sp_nodepath_update_repr(n->subpath->nodepath);
2977 }
2979 /**
2980 * The point on a line, given by its angle, closest to the given point.
2981 * \param p A point.
2982 * \param a Angle of the line; it is assumed to go through coordinate origin.
2983 * \param closest Pointer to the point struct where the result is stored.
2984 * \todo FIXME: use dot product perhaps?
2985 */
2986 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2987 {
2988 if (a == HUGE_VAL) { // vertical
2989 *closest = NR::Point(0, (*p)[NR::Y]);
2990 } else {
2991 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2992 (*closest)[NR::Y] = a * (*closest)[NR::X];
2993 }
2994 }
2996 /**
2997 * Distance from the point to a line given by its angle.
2998 * \param p A point.
2999 * \param a Angle of the line; it is assumed to go through coordinate origin.
3000 */
3001 static double point_line_distance(NR::Point *p, double a)
3002 {
3003 NR::Point c;
3004 point_line_closest(p, a, &c);
3005 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]));
3006 }
3008 /**
3009 * Callback for node "request" signal.
3010 * \todo fixme: This goes to "moved" event? (lauris)
3011 */
3012 static gboolean
3013 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3014 {
3015 double yn, xn, yp, xp;
3016 double an, ap, na, pa;
3017 double d_an, d_ap, d_na, d_pa;
3018 gboolean collinear = FALSE;
3019 NR::Point c;
3020 NR::Point pr;
3022 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3024 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3025 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3027 NR::Point mouse = (*p);
3029 if (!n->dragging_out) {
3030 // This is the first drag-out event; find out which handle to drag out
3031 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3032 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3034 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3035 return FALSE;
3037 Inkscape::NodePath::NodeSide *opposite;
3038 if (appr_p > appr_n) { // closer to p
3039 n->dragging_out = &n->p;
3040 opposite = &n->n;
3041 n->code = NR_CURVETO;
3042 } else if (appr_p < appr_n) { // closer to n
3043 n->dragging_out = &n->n;
3044 opposite = &n->p;
3045 n->n.other->code = NR_CURVETO;
3046 } else { // p and n nodes are the same
3047 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3048 n->dragging_out = &n->p;
3049 opposite = &n->n;
3050 n->code = NR_CURVETO;
3051 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3052 n->dragging_out = &n->n;
3053 opposite = &n->p;
3054 n->n.other->code = NR_CURVETO;
3055 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3056 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);
3057 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);
3058 if (appr_other_p > appr_other_n) { // closer to other's p handle
3059 n->dragging_out = &n->n;
3060 opposite = &n->p;
3061 n->n.other->code = NR_CURVETO;
3062 } else { // closer to other's n handle
3063 n->dragging_out = &n->p;
3064 opposite = &n->n;
3065 n->code = NR_CURVETO;
3066 }
3067 }
3068 }
3070 // if there's another handle, make sure the one we drag out starts parallel to it
3071 if (opposite->pos != n->pos) {
3072 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3073 }
3075 // knots might not be created yet!
3076 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3077 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3078 }
3080 // pass this on to the handle-moved callback
3081 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3082 sp_node_update_handles(n);
3083 return TRUE;
3084 }
3086 if (state & GDK_CONTROL_MASK) { // constrained motion
3088 // calculate relative distances of handles
3089 // n handle:
3090 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3091 xn = n->n.pos[NR::X] - n->pos[NR::X];
3092 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3093 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3094 if (n->n.other) { // if there is the next point
3095 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3096 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3097 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3098 }
3099 }
3100 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3101 if (yn < 0) { xn = -xn; yn = -yn; }
3103 // p handle:
3104 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3105 xp = n->p.pos[NR::X] - n->pos[NR::X];
3106 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3107 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3108 if (n->p.other) {
3109 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3110 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3111 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3112 }
3113 }
3114 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3115 if (yp < 0) { xp = -xp; yp = -yp; }
3117 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3118 // sliding on handles, only if at least one of the handles is non-vertical
3119 // (otherwise it's the same as ctrl+drag anyway)
3121 // calculate angles of the handles
3122 if (xn == 0) {
3123 if (yn == 0) { // no handle, consider it the continuation of the other one
3124 an = 0;
3125 collinear = TRUE;
3126 }
3127 else an = 0; // vertical; set the angle to horizontal
3128 } else an = yn/xn;
3130 if (xp == 0) {
3131 if (yp == 0) { // no handle, consider it the continuation of the other one
3132 ap = an;
3133 }
3134 else ap = 0; // vertical; set the angle to horizontal
3135 } else ap = yp/xp;
3137 if (collinear) an = ap;
3139 // angles of the perpendiculars; HUGE_VAL means vertical
3140 if (an == 0) na = HUGE_VAL; else na = -1/an;
3141 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3143 // mouse point relative to the node's original pos
3144 pr = (*p) - n->origin;
3146 // distances to the four lines (two handles and two perpendiculars)
3147 d_an = point_line_distance(&pr, an);
3148 d_na = point_line_distance(&pr, na);
3149 d_ap = point_line_distance(&pr, ap);
3150 d_pa = point_line_distance(&pr, pa);
3152 // find out which line is the closest, save its closest point in c
3153 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3154 point_line_closest(&pr, an, &c);
3155 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3156 point_line_closest(&pr, ap, &c);
3157 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3158 point_line_closest(&pr, na, &c);
3159 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3160 point_line_closest(&pr, pa, &c);
3161 }
3163 // move the node to the closest point
3164 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3165 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3166 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3168 } else { // constraining to hor/vert
3170 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3171 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3172 } else { // snap to vert
3173 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3174 }
3175 }
3176 } else { // move freely
3177 if (state & GDK_MOD1_MASK) { // sculpt
3178 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3179 } else {
3180 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3181 (*p)[NR::X] - n->pos[NR::X],
3182 (*p)[NR::Y] - n->pos[NR::Y],
3183 (state & GDK_SHIFT_MASK) == 0);
3184 }
3185 }
3187 n->subpath->nodepath->desktop->scroll_to_point(p);
3189 return TRUE;
3190 }
3192 /**
3193 * Node handle clicked callback.
3194 */
3195 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3196 {
3197 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3199 if (state & GDK_CONTROL_MASK) { // "delete" handle
3200 if (n->p.knot == knot) {
3201 n->p.pos = n->pos;
3202 } else if (n->n.knot == knot) {
3203 n->n.pos = n->pos;
3204 }
3205 sp_node_update_handles(n);
3206 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3207 sp_nodepath_update_repr(nodepath);
3208 sp_nodepath_update_statusbar(nodepath);
3210 } else { // just select or add to selection, depending in Shift
3211 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3212 }
3213 }
3215 /**
3216 * Node handle grabbed callback.
3217 */
3218 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3219 {
3220 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3222 if (!n->selected) {
3223 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3224 }
3226 // remember the origin point of the handle
3227 if (n->p.knot == knot) {
3228 n->p.origin_radial = n->p.pos - n->pos;
3229 } else if (n->n.knot == knot) {
3230 n->n.origin_radial = n->n.pos - n->pos;
3231 } else {
3232 g_assert_not_reached();
3233 }
3235 }
3237 /**
3238 * Node handle ungrabbed callback.
3239 */
3240 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3241 {
3242 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3244 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3245 if (n->p.knot == knot) {
3246 n->p.origin_radial.a = 0;
3247 sp_knot_set_position(knot, &n->p.pos, state);
3248 } else if (n->n.knot == knot) {
3249 n->n.origin_radial.a = 0;
3250 sp_knot_set_position(knot, &n->n.pos, state);
3251 } else {
3252 g_assert_not_reached();
3253 }
3255 sp_nodepath_update_repr(n->subpath->nodepath);
3256 }
3258 /**
3259 * Node handle "request" signal callback.
3260 */
3261 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3262 {
3263 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3265 Inkscape::NodePath::NodeSide *me, *opposite;
3266 gint which;
3267 if (n->p.knot == knot) {
3268 me = &n->p;
3269 opposite = &n->n;
3270 which = -1;
3271 } else if (n->n.knot == knot) {
3272 me = &n->n;
3273 opposite = &n->p;
3274 which = 1;
3275 } else {
3276 me = opposite = NULL;
3277 which = 0;
3278 g_assert_not_reached();
3279 }
3281 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3283 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3285 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3286 /* We are smooth node adjacent with line */
3287 NR::Point const delta = *p - n->pos;
3288 NR::Coord const len = NR::L2(delta);
3289 Inkscape::NodePath::Node *othernode = opposite->other;
3290 NR::Point const ndelta = n->pos - othernode->pos;
3291 NR::Coord const linelen = NR::L2(ndelta);
3292 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3293 NR::Coord const scal = dot(delta, ndelta) / linelen;
3294 (*p) = n->pos + (scal / linelen) * ndelta;
3295 }
3296 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3297 } else {
3298 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3299 }
3301 sp_node_adjust_handle(n, -which);
3303 return FALSE;
3304 }
3306 /**
3307 * Node handle moved callback.
3308 */
3309 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3310 {
3311 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3313 Inkscape::NodePath::NodeSide *me;
3314 Inkscape::NodePath::NodeSide *other;
3315 if (n->p.knot == knot) {
3316 me = &n->p;
3317 other = &n->n;
3318 } else if (n->n.knot == knot) {
3319 me = &n->n;
3320 other = &n->p;
3321 } else {
3322 me = NULL;
3323 other = NULL;
3324 g_assert_not_reached();
3325 }
3327 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3328 Radial rme(me->pos - n->pos);
3329 Radial rother(other->pos - n->pos);
3330 Radial rnew(*p - n->pos);
3332 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3333 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3334 /* 0 interpreted as "no snapping". */
3336 // The closest PI/snaps angle, starting from zero.
3337 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3338 if (me->origin_radial.a == HUGE_VAL) {
3339 // ortho doesn't exist: original handle was zero length.
3340 rnew.a = a_snapped;
3341 } else {
3342 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3343 * its opposite and perpendiculars). */
3344 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3346 // Snap to the closest.
3347 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3348 ? a_snapped
3349 : a_ortho );
3350 }
3351 }
3353 if (state & GDK_MOD1_MASK) {
3354 // lock handle length
3355 rnew.r = me->origin_radial.r;
3356 }
3358 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3359 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3360 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3361 rother.a += rnew.a - rme.a;
3362 other->pos = NR::Point(rother) + n->pos;
3363 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3364 sp_knot_set_position(other->knot, &other->pos, 0);
3365 }
3367 me->pos = NR::Point(rnew) + n->pos;
3368 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3370 // this is what sp_knot_set_position does, but without emitting the signal:
3371 // we cannot emit a "moved" signal because we're now processing it
3372 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3374 knot->desktop->set_coordinate_status(me->pos);
3376 update_object(n->subpath->nodepath);
3378 /* status text */
3379 SPDesktop *desktop = n->subpath->nodepath->desktop;
3380 if (!desktop) return;
3381 SPEventContext *ec = desktop->event_context;
3382 if (!ec) return;
3383 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3384 if (!mc) return;
3386 double degrees = 180 / M_PI * rnew.a;
3387 if (degrees > 180) degrees -= 360;
3388 if (degrees < -180) degrees += 360;
3389 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3390 degrees = angle_to_compass (degrees);
3392 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3394 mc->setF(Inkscape::NORMAL_MESSAGE,
3395 _("<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);
3397 g_string_free(length, TRUE);
3398 }
3400 /**
3401 * Node handle event callback.
3402 */
3403 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3404 {
3405 gboolean ret = FALSE;
3406 switch (event->type) {
3407 case GDK_KEY_PRESS:
3408 switch (get_group0_keyval (&event->key)) {
3409 case GDK_space:
3410 if (event->key.state & GDK_BUTTON1_MASK) {
3411 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3412 stamp_repr(nodepath);
3413 ret = TRUE;
3414 }
3415 break;
3416 default:
3417 break;
3418 }
3419 break;
3420 default:
3421 break;
3422 }
3424 return ret;
3425 }
3427 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3428 Radial &rme, Radial &rother, gboolean const both)
3429 {
3430 rme.a += angle;
3431 if ( both
3432 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3433 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3434 {
3435 rother.a += angle;
3436 }
3437 }
3439 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3440 Radial &rme, Radial &rother, gboolean const both)
3441 {
3442 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3444 gdouble r;
3445 if ( both
3446 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3447 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3448 {
3449 r = MAX(rme.r, rother.r);
3450 } else {
3451 r = rme.r;
3452 }
3454 gdouble const weird_angle = atan2(norm_angle, r);
3455 /* Bulia says norm_angle is just the visible distance that the
3456 * object's end must travel on the screen. Left as 'angle' for want of
3457 * a better name.*/
3459 rme.a += weird_angle;
3460 if ( both
3461 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3462 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3463 {
3464 rother.a += weird_angle;
3465 }
3466 }
3468 /**
3469 * Rotate one node.
3470 */
3471 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3472 {
3473 Inkscape::NodePath::NodeSide *me, *other;
3474 bool both = false;
3476 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3477 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3479 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3480 me = &(n->p);
3481 other = &(n->n);
3482 } else if (!n->p.other) {
3483 me = &(n->n);
3484 other = &(n->p);
3485 } else {
3486 if (which > 0) { // right handle
3487 if (xn > xp) {
3488 me = &(n->n);
3489 other = &(n->p);
3490 } else {
3491 me = &(n->p);
3492 other = &(n->n);
3493 }
3494 } else if (which < 0){ // left handle
3495 if (xn <= xp) {
3496 me = &(n->n);
3497 other = &(n->p);
3498 } else {
3499 me = &(n->p);
3500 other = &(n->n);
3501 }
3502 } else { // both handles
3503 me = &(n->n);
3504 other = &(n->p);
3505 both = true;
3506 }
3507 }
3509 Radial rme(me->pos - n->pos);
3510 Radial rother(other->pos - n->pos);
3512 if (screen) {
3513 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3514 } else {
3515 node_rotate_one_internal (*n, angle, rme, rother, both);
3516 }
3518 me->pos = n->pos + NR::Point(rme);
3520 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3521 other->pos = n->pos + NR::Point(rother);
3522 }
3524 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3525 // so here we just move all the knots without emitting move signals, for speed
3526 sp_node_update_handles(n, false);
3527 }
3529 /**
3530 * Rotate selected nodes.
3531 */
3532 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3533 {
3534 if (!nodepath || !nodepath->selected) return;
3536 if (g_list_length(nodepath->selected) == 1) {
3537 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3538 node_rotate_one (n, angle, which, screen);
3539 } else {
3540 // rotate as an object:
3542 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3543 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3544 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3545 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3546 box.expandTo (n->pos); // contain all selected nodes
3547 }
3549 gdouble rot;
3550 if (screen) {
3551 gdouble const zoom = nodepath->desktop->current_zoom();
3552 gdouble const zmove = angle / zoom;
3553 gdouble const r = NR::L2(box.max() - box.midpoint());
3554 rot = atan2(zmove, r);
3555 } else {
3556 rot = angle;
3557 }
3559 NR::Matrix t =
3560 NR::Matrix (NR::translate(-box.midpoint())) *
3561 NR::Matrix (NR::rotate(rot)) *
3562 NR::Matrix (NR::translate(box.midpoint()));
3564 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3565 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3566 n->pos *= t;
3567 n->n.pos *= t;
3568 n->p.pos *= t;
3569 sp_node_update_handles(n, false);
3570 }
3571 }
3573 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3574 }
3576 /**
3577 * Scale one node.
3578 */
3579 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3580 {
3581 bool both = false;
3582 Inkscape::NodePath::NodeSide *me, *other;
3584 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3585 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3587 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3588 me = &(n->p);
3589 other = &(n->n);
3590 n->code = NR_CURVETO;
3591 } else if (!n->p.other) {
3592 me = &(n->n);
3593 other = &(n->p);
3594 if (n->n.other)
3595 n->n.other->code = NR_CURVETO;
3596 } else {
3597 if (which > 0) { // right handle
3598 if (xn > xp) {
3599 me = &(n->n);
3600 other = &(n->p);
3601 if (n->n.other)
3602 n->n.other->code = NR_CURVETO;
3603 } else {
3604 me = &(n->p);
3605 other = &(n->n);
3606 n->code = NR_CURVETO;
3607 }
3608 } else if (which < 0){ // left handle
3609 if (xn <= xp) {
3610 me = &(n->n);
3611 other = &(n->p);
3612 if (n->n.other)
3613 n->n.other->code = NR_CURVETO;
3614 } else {
3615 me = &(n->p);
3616 other = &(n->n);
3617 n->code = NR_CURVETO;
3618 }
3619 } else { // both handles
3620 me = &(n->n);
3621 other = &(n->p);
3622 both = true;
3623 n->code = NR_CURVETO;
3624 if (n->n.other)
3625 n->n.other->code = NR_CURVETO;
3626 }
3627 }
3629 Radial rme(me->pos - n->pos);
3630 Radial rother(other->pos - n->pos);
3632 rme.r += grow;
3633 if (rme.r < 0) rme.r = 0;
3634 if (rme.a == HUGE_VAL) {
3635 if (me->other) { // if direction is unknown, initialize it towards the next node
3636 Radial rme_next(me->other->pos - n->pos);
3637 rme.a = rme_next.a;
3638 } else { // if there's no next, initialize to 0
3639 rme.a = 0;
3640 }
3641 }
3642 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3643 rother.r += grow;
3644 if (rother.r < 0) rother.r = 0;
3645 if (rother.a == HUGE_VAL) {
3646 rother.a = rme.a + M_PI;
3647 }
3648 }
3650 me->pos = n->pos + NR::Point(rme);
3652 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3653 other->pos = n->pos + NR::Point(rother);
3654 }
3656 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3657 // so here we just move all the knots without emitting move signals, for speed
3658 sp_node_update_handles(n, false);
3659 }
3661 /**
3662 * Scale selected nodes.
3663 */
3664 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3665 {
3666 if (!nodepath || !nodepath->selected) return;
3668 if (g_list_length(nodepath->selected) == 1) {
3669 // scale handles of the single selected node
3670 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3671 node_scale_one (n, grow, which);
3672 } else {
3673 // scale nodes as an "object":
3675 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3676 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3677 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3678 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3679 box.expandTo (n->pos); // contain all selected nodes
3680 }
3682 double scale = (box.maxExtent() + grow)/box.maxExtent();
3684 NR::Matrix t =
3685 NR::Matrix (NR::translate(-box.midpoint())) *
3686 NR::Matrix (NR::scale(scale, scale)) *
3687 NR::Matrix (NR::translate(box.midpoint()));
3689 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3690 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3691 n->pos *= t;
3692 n->n.pos *= t;
3693 n->p.pos *= t;
3694 sp_node_update_handles(n, false);
3695 }
3696 }
3698 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3699 }
3701 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3702 {
3703 if (!nodepath) return;
3704 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3705 }
3707 /**
3708 * Flip selected nodes horizontally/vertically.
3709 */
3710 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3711 {
3712 if (!nodepath || !nodepath->selected) return;
3714 if (g_list_length(nodepath->selected) == 1) {
3715 // flip handles of the single selected node
3716 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3717 double temp = n->p.pos[axis];
3718 n->p.pos[axis] = n->n.pos[axis];
3719 n->n.pos[axis] = temp;
3720 sp_node_update_handles(n, false);
3721 } else {
3722 // scale nodes as an "object":
3724 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3725 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3726 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3727 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3728 box.expandTo (n->pos); // contain all selected nodes
3729 }
3731 NR::Matrix t =
3732 NR::Matrix (NR::translate(-box.midpoint())) *
3733 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3734 NR::Matrix (NR::translate(box.midpoint()));
3736 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3737 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3738 n->pos *= t;
3739 n->n.pos *= t;
3740 n->p.pos *= t;
3741 sp_node_update_handles(n, false);
3742 }
3743 }
3745 sp_nodepath_update_repr(nodepath);
3746 }
3748 //-----------------------------------------------
3749 /**
3750 * Return new subpath under given nodepath.
3751 */
3752 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3753 {
3754 g_assert(nodepath);
3755 g_assert(nodepath->desktop);
3757 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3759 s->nodepath = nodepath;
3760 s->closed = FALSE;
3761 s->nodes = NULL;
3762 s->first = NULL;
3763 s->last = NULL;
3765 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3766 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3767 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3769 return s;
3770 }
3772 /**
3773 * Destroy nodes in subpath, then subpath itself.
3774 */
3775 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3776 {
3777 g_assert(subpath);
3778 g_assert(subpath->nodepath);
3779 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3781 while (subpath->nodes) {
3782 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3783 }
3785 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3787 g_free(subpath);
3788 }
3790 /**
3791 * Link head to tail in subpath.
3792 */
3793 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3794 {
3795 g_assert(!sp->closed);
3796 g_assert(sp->last != sp->first);
3797 g_assert(sp->first->code == NR_MOVETO);
3799 sp->closed = TRUE;
3801 //Link the head to the tail
3802 sp->first->p.other = sp->last;
3803 sp->last->n.other = sp->first;
3804 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3805 sp->first = sp->last;
3807 //Remove the extra end node
3808 sp_nodepath_node_destroy(sp->last->n.other);
3809 }
3811 /**
3812 * Open closed (loopy) subpath at node.
3813 */
3814 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3815 {
3816 g_assert(sp->closed);
3817 g_assert(n->subpath == sp);
3818 g_assert(sp->first == sp->last);
3820 /* We create new startpoint, current node will become last one */
3822 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3823 &n->pos, &n->pos, &n->n.pos);
3826 sp->closed = FALSE;
3828 //Unlink to make a head and tail
3829 sp->first = new_path;
3830 sp->last = n;
3831 n->n.other = NULL;
3832 new_path->p.other = NULL;
3833 }
3835 /**
3836 * Returns area in triangle given by points; may be negative.
3837 */
3838 inline double
3839 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3840 {
3841 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]);
3842 }
3844 /**
3845 * Return new node in subpath with given properties.
3846 * \param pos Position of node.
3847 * \param ppos Handle position in previous direction
3848 * \param npos Handle position in previous direction
3849 */
3850 Inkscape::NodePath::Node *
3851 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)
3852 {
3853 g_assert(sp);
3854 g_assert(sp->nodepath);
3855 g_assert(sp->nodepath->desktop);
3857 if (nodechunk == NULL)
3858 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3860 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3862 n->subpath = sp;
3864 if (type != Inkscape::NodePath::NODE_NONE) {
3865 // use the type from sodipodi:nodetypes
3866 n->type = type;
3867 } else {
3868 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3869 // points are (almost) collinear
3870 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3871 // endnode, or a node with a retracted handle
3872 n->type = Inkscape::NodePath::NODE_CUSP;
3873 } else {
3874 n->type = Inkscape::NodePath::NODE_SMOOTH;
3875 }
3876 } else {
3877 n->type = Inkscape::NodePath::NODE_CUSP;
3878 }
3879 }
3881 n->code = code;
3882 n->selected = FALSE;
3883 n->pos = *pos;
3884 n->p.pos = *ppos;
3885 n->n.pos = *npos;
3887 n->dragging_out = NULL;
3889 Inkscape::NodePath::Node *prev;
3890 if (next) {
3891 //g_assert(g_list_find(sp->nodes, next));
3892 prev = next->p.other;
3893 } else {
3894 prev = sp->last;
3895 }
3897 if (prev)
3898 prev->n.other = n;
3899 else
3900 sp->first = n;
3902 if (next)
3903 next->p.other = n;
3904 else
3905 sp->last = n;
3907 n->p.other = prev;
3908 n->n.other = next;
3910 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"));
3911 sp_knot_set_position(n->knot, pos, 0);
3913 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3914 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3915 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3916 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3917 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3918 sp_knot_update_ctrl(n->knot);
3920 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3921 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3922 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3923 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3924 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3925 sp_knot_show(n->knot);
3927 // We only create handle knots and lines on demand
3928 n->p.knot = NULL;
3929 n->p.line = NULL;
3930 n->n.knot = NULL;
3931 n->n.line = NULL;
3933 sp->nodes = g_list_prepend(sp->nodes, n);
3935 return n;
3936 }
3938 /**
3939 * Destroy node and its knots, link neighbors in subpath.
3940 */
3941 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3942 {
3943 g_assert(node);
3944 g_assert(node->subpath);
3945 g_assert(SP_IS_KNOT(node->knot));
3947 Inkscape::NodePath::SubPath *sp = node->subpath;
3949 if (node->selected) { // first, deselect
3950 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3951 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3952 }
3954 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3956 g_object_unref(G_OBJECT(node->knot));
3957 if (node->p.knot)
3958 g_object_unref(G_OBJECT(node->p.knot));
3959 if (node->n.knot)
3960 g_object_unref(G_OBJECT(node->n.knot));
3962 if (node->p.line)
3963 gtk_object_destroy(GTK_OBJECT(node->p.line));
3964 if (node->n.line)
3965 gtk_object_destroy(GTK_OBJECT(node->n.line));
3967 if (sp->nodes) { // there are others nodes on the subpath
3968 if (sp->closed) {
3969 if (sp->first == node) {
3970 g_assert(sp->last == node);
3971 sp->first = node->n.other;
3972 sp->last = sp->first;
3973 }
3974 node->p.other->n.other = node->n.other;
3975 node->n.other->p.other = node->p.other;
3976 } else {
3977 if (sp->first == node) {
3978 sp->first = node->n.other;
3979 sp->first->code = NR_MOVETO;
3980 }
3981 if (sp->last == node) sp->last = node->p.other;
3982 if (node->p.other) node->p.other->n.other = node->n.other;
3983 if (node->n.other) node->n.other->p.other = node->p.other;
3984 }
3985 } else { // this was the last node on subpath
3986 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3987 }
3989 g_mem_chunk_free(nodechunk, node);
3990 }
3992 /**
3993 * Returns one of the node's two sides.
3994 * \param which Indicates which side.
3995 * \return Pointer to previous node side if which==-1, next if which==1.
3996 */
3997 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3998 {
3999 g_assert(node);
4001 switch (which) {
4002 case -1:
4003 return &node->p;
4004 case 1:
4005 return &node->n;
4006 default:
4007 break;
4008 }
4010 g_assert_not_reached();
4012 return NULL;
4013 }
4015 /**
4016 * Return the other side of the node, given one of its sides.
4017 */
4018 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4019 {
4020 g_assert(node);
4022 if (me == &node->p) return &node->n;
4023 if (me == &node->n) return &node->p;
4025 g_assert_not_reached();
4027 return NULL;
4028 }
4030 /**
4031 * Return NRPathcode on the given side of the node.
4032 */
4033 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4034 {
4035 g_assert(node);
4037 if (me == &node->p) {
4038 if (node->p.other) return (NRPathcode)node->code;
4039 return NR_MOVETO;
4040 }
4042 if (me == &node->n) {
4043 if (node->n.other) return (NRPathcode)node->n.other->code;
4044 return NR_MOVETO;
4045 }
4047 g_assert_not_reached();
4049 return NR_END;
4050 }
4052 /**
4053 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4054 */
4055 Inkscape::NodePath::Node *
4056 sp_nodepath_get_node_by_index(int index)
4057 {
4058 Inkscape::NodePath::Node *e = NULL;
4060 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4061 if (!nodepath) {
4062 return e;
4063 }
4065 //find segment
4066 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4068 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4069 int n = g_list_length(sp->nodes);
4070 if (sp->closed) {
4071 n++;
4072 }
4074 //if the piece belongs to this subpath grab it
4075 //otherwise move onto the next subpath
4076 if (index < n) {
4077 e = sp->first;
4078 for (int i = 0; i < index; ++i) {
4079 e = e->n.other;
4080 }
4081 break;
4082 } else {
4083 if (sp->closed) {
4084 index -= (n+1);
4085 } else {
4086 index -= n;
4087 }
4088 }
4089 }
4091 return e;
4092 }
4094 /**
4095 * Returns plain text meaning of node type.
4096 */
4097 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4098 {
4099 unsigned retracted = 0;
4100 bool endnode = false;
4102 for (int which = -1; which <= 1; which += 2) {
4103 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4104 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4105 retracted ++;
4106 if (!side->other)
4107 endnode = true;
4108 }
4110 if (retracted == 0) {
4111 if (endnode) {
4112 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4113 return _("end node");
4114 } else {
4115 switch (node->type) {
4116 case Inkscape::NodePath::NODE_CUSP:
4117 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4118 return _("cusp");
4119 case Inkscape::NodePath::NODE_SMOOTH:
4120 // TRANSLATORS: "smooth" is an adjective here
4121 return _("smooth");
4122 case Inkscape::NodePath::NODE_SYMM:
4123 return _("symmetric");
4124 }
4125 }
4126 } else if (retracted == 1) {
4127 if (endnode) {
4128 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4129 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4130 } else {
4131 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4132 }
4133 } else {
4134 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4135 }
4137 return NULL;
4138 }
4140 /**
4141 * Handles content of statusbar as long as node tool is active.
4142 */
4143 void
4144 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
4145 {
4146 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");
4147 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4149 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4150 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4151 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4152 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4154 SPDesktop *desktop = NULL;
4155 if (nodepath) {
4156 desktop = nodepath->desktop;
4157 } else {
4158 desktop = SP_ACTIVE_DESKTOP;
4159 }
4161 SPEventContext *ec = desktop->event_context;
4162 if (!ec) return;
4163 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4164 if (!mc) return;
4166 if (selected_nodes == 0) {
4167 Inkscape::Selection *sel = desktop->selection;
4168 if (!sel || sel->isEmpty()) {
4169 mc->setF(Inkscape::NORMAL_MESSAGE,
4170 _("Select a single object to edit its nodes or handles."));
4171 } else {
4172 if (nodepath) {
4173 mc->setF(Inkscape::NORMAL_MESSAGE,
4174 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.",
4175 "<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.",
4176 total_nodes),
4177 total_nodes);
4178 } else {
4179 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4180 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4181 } else {
4182 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4183 }
4184 }
4185 }
4186 } else if (nodepath && selected_nodes == 1) {
4187 mc->setF(Inkscape::NORMAL_MESSAGE,
4188 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4189 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4190 total_nodes),
4191 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4192 } else {
4193 if (selected_subpaths > 1) {
4194 mc->setF(Inkscape::NORMAL_MESSAGE,
4195 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4196 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4197 total_nodes),
4198 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4199 } else {
4200 mc->setF(Inkscape::NORMAL_MESSAGE,
4201 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4202 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4203 total_nodes),
4204 selected_nodes, total_nodes, when_selected);
4205 }
4206 }
4207 }
4210 /*
4211 Local Variables:
4212 mode:c++
4213 c-file-style:"stroustrup"
4214 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4215 indent-tabs-mode:nil
4216 fill-column:99
4217 End:
4218 */
4219 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :