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, const gchar *annotation)
481 {
482 update_repr_internal(np);
483 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
484 annotation);
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, const gchar *annotation)
502 {
503 update_repr_internal(np);
504 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
505 annotation);
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 _("Stamp"));
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", _("Move nodes vertically"));
1220 } else if (dy == 0) {
1221 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1222 } else {
1223 sp_nodepath_update_repr(nodepath, _("Move nodes"));
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", _("Move nodes vertically"));
1248 } else if (dy == 0) {
1249 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1250 } else {
1251 sp_nodepath_update_repr(nodepath, _("Move nodes"));
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, _("Align nodes"));
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, _("Distribute nodes"));
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 int n_added = 0;
1511 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1512 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1513 g_assert(t->selected);
1514 if (t->p.other && t->p.other->selected) {
1515 nl = g_list_prepend(nl, t);
1516 }
1517 }
1519 while (nl) {
1520 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1521 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1522 sp_nodepath_node_select(n, TRUE, FALSE);
1523 n_added ++;
1524 nl = g_list_remove(nl, t);
1525 }
1527 /** \todo fixme: adjust ? */
1528 sp_nodepath_update_handles(nodepath);
1530 if (n_added > 1) {
1531 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1532 } else if (n_added > 0) {
1533 sp_nodepath_update_repr(nodepath, _("Add node"));
1534 }
1536 sp_nodepath_update_statusbar(nodepath);
1537 }
1539 /**
1540 * Select segment nearest to point
1541 */
1542 void
1543 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1544 {
1545 if (!nodepath) {
1546 return;
1547 }
1549 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1551 //find segment to segment
1552 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1554 gboolean force = FALSE;
1555 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1556 force = TRUE;
1557 }
1558 sp_nodepath_node_select(e, (gboolean) toggle, force);
1559 if (e->p.other)
1560 sp_nodepath_node_select(e->p.other, TRUE, force);
1562 sp_nodepath_update_handles(nodepath);
1564 sp_nodepath_update_statusbar(nodepath);
1565 }
1567 /**
1568 * Add a node nearest to point
1569 */
1570 void
1571 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1572 {
1573 if (!nodepath) {
1574 return;
1575 }
1577 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1579 //find segment to split
1580 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1582 //don't know why but t seems to flip for lines
1583 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1584 position.t = 1.0 - position.t;
1585 }
1586 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1587 sp_nodepath_node_select(n, FALSE, TRUE);
1589 /* fixme: adjust ? */
1590 sp_nodepath_update_handles(nodepath);
1592 sp_nodepath_update_repr(nodepath, _("Add node"));
1594 sp_nodepath_update_statusbar(nodepath);
1595 }
1597 /*
1598 * Adjusts a segment so that t moves by a certain delta for dragging
1599 * converts lines to curves
1600 *
1601 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1602 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1603 */
1604 void
1605 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1606 {
1607 /* feel good is an arbitrary parameter that distributes the delta between handles
1608 * if t of the drag point is less than 1/6 distance form the endpoint only
1609 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1610 */
1611 double feel_good;
1612 if (t <= 1.0 / 6.0)
1613 feel_good = 0;
1614 else if (t <= 0.5)
1615 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1616 else if (t <= 5.0 / 6.0)
1617 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1618 else
1619 feel_good = 1;
1621 //if we're dragging a line convert it to a curve
1622 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1623 sp_nodepath_set_line_type(e, NR_CURVETO);
1624 }
1626 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1627 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1628 e->p.other->n.pos += offsetcoord0;
1629 e->p.pos += offsetcoord1;
1631 // adjust handles of adjacent nodes where necessary
1632 sp_node_adjust_handle(e,1);
1633 sp_node_adjust_handle(e->p.other,-1);
1635 sp_nodepath_update_handles(e->subpath->nodepath);
1637 update_object(e->subpath->nodepath);
1639 sp_nodepath_update_statusbar(e->subpath->nodepath);
1640 }
1643 /**
1644 * Call sp_nodepath_break() for all selected segments.
1645 */
1646 void sp_node_selected_break()
1647 {
1648 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1649 if (!nodepath) return;
1651 GList *temp = NULL;
1652 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1653 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1654 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1655 if (nn == NULL) continue; // no break, no new node
1656 temp = g_list_prepend(temp, nn);
1657 }
1659 if (temp) {
1660 sp_nodepath_deselect(nodepath);
1661 }
1662 for (GList *l = temp; l != NULL; l = l->next) {
1663 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1664 }
1666 sp_nodepath_update_handles(nodepath);
1668 sp_nodepath_update_repr(nodepath, _("Break path"));
1669 }
1671 /**
1672 * Duplicate the selected node(s).
1673 */
1674 void sp_node_selected_duplicate()
1675 {
1676 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1677 if (!nodepath) {
1678 return;
1679 }
1681 GList *temp = NULL;
1682 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1683 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1684 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1685 if (nn == NULL) continue; // could not duplicate
1686 temp = g_list_prepend(temp, nn);
1687 }
1689 if (temp) {
1690 sp_nodepath_deselect(nodepath);
1691 }
1692 for (GList *l = temp; l != NULL; l = l->next) {
1693 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1694 }
1696 sp_nodepath_update_handles(nodepath);
1698 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1699 }
1701 /**
1702 * Join two nodes by merging them into one.
1703 */
1704 void sp_node_selected_join()
1705 {
1706 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1707 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1709 if (g_list_length(nodepath->selected) != 2) {
1710 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1711 return;
1712 }
1714 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1715 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1717 g_assert(a != b);
1718 g_assert(a->p.other || a->n.other);
1719 g_assert(b->p.other || b->n.other);
1721 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1722 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1723 return;
1724 }
1726 /* a and b are endpoints */
1728 NR::Point c;
1729 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1730 c = a->pos;
1731 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1732 c = b->pos;
1733 } else {
1734 c = (a->pos + b->pos) / 2;
1735 }
1737 if (a->subpath == b->subpath) {
1738 Inkscape::NodePath::SubPath *sp = a->subpath;
1739 sp_nodepath_subpath_close(sp);
1740 sp_node_moveto (sp->first, c);
1742 sp_nodepath_update_handles(sp->nodepath);
1743 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1744 return;
1745 }
1747 /* a and b are separate subpaths */
1748 Inkscape::NodePath::SubPath *sa = a->subpath;
1749 Inkscape::NodePath::SubPath *sb = b->subpath;
1750 NR::Point p;
1751 Inkscape::NodePath::Node *n;
1752 NRPathcode code;
1753 if (a == sa->first) {
1754 p = sa->first->n.pos;
1755 code = (NRPathcode)sa->first->n.other->code;
1756 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1757 n = sa->last;
1758 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1759 n = n->p.other;
1760 while (n) {
1761 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1762 n = n->p.other;
1763 if (n == sa->first) n = NULL;
1764 }
1765 sp_nodepath_subpath_destroy(sa);
1766 sa = t;
1767 } else if (a == sa->last) {
1768 p = sa->last->p.pos;
1769 code = (NRPathcode)sa->last->code;
1770 sp_nodepath_node_destroy(sa->last);
1771 } else {
1772 code = NR_END;
1773 g_assert_not_reached();
1774 }
1776 if (b == sb->first) {
1777 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1778 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1779 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1780 }
1781 } else if (b == sb->last) {
1782 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1783 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1784 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1785 }
1786 } else {
1787 g_assert_not_reached();
1788 }
1789 /* and now destroy sb */
1791 sp_nodepath_subpath_destroy(sb);
1793 sp_nodepath_update_handles(sa->nodepath);
1795 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1797 sp_nodepath_update_statusbar(nodepath);
1798 }
1800 /**
1801 * Join two nodes by adding a segment between them.
1802 */
1803 void sp_node_selected_join_segment()
1804 {
1805 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1806 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1808 if (g_list_length(nodepath->selected) != 2) {
1809 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1810 return;
1811 }
1813 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1814 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1816 g_assert(a != b);
1817 g_assert(a->p.other || a->n.other);
1818 g_assert(b->p.other || b->n.other);
1820 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1821 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1822 return;
1823 }
1825 if (a->subpath == b->subpath) {
1826 Inkscape::NodePath::SubPath *sp = a->subpath;
1828 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1829 sp->closed = TRUE;
1831 sp->first->p.other = sp->last;
1832 sp->last->n.other = sp->first;
1834 sp_node_handle_mirror_p_to_n(sp->last);
1835 sp_node_handle_mirror_n_to_p(sp->first);
1837 sp->first->code = sp->last->code;
1838 sp->first = sp->last;
1840 sp_nodepath_update_handles(sp->nodepath);
1842 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1844 return;
1845 }
1847 /* a and b are separate subpaths */
1848 Inkscape::NodePath::SubPath *sa = a->subpath;
1849 Inkscape::NodePath::SubPath *sb = b->subpath;
1851 Inkscape::NodePath::Node *n;
1852 NR::Point p;
1853 NRPathcode code;
1854 if (a == sa->first) {
1855 code = (NRPathcode) sa->first->n.other->code;
1856 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1857 n = sa->last;
1858 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1859 for (n = n->p.other; n != NULL; n = n->p.other) {
1860 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1861 }
1862 sp_nodepath_subpath_destroy(sa);
1863 sa = t;
1864 } else if (a == sa->last) {
1865 code = (NRPathcode)sa->last->code;
1866 } else {
1867 code = NR_END;
1868 g_assert_not_reached();
1869 }
1871 if (b == sb->first) {
1872 n = sb->first;
1873 sp_node_handle_mirror_p_to_n(sa->last);
1874 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1875 sp_node_handle_mirror_n_to_p(sa->last);
1876 for (n = n->n.other; n != NULL; n = n->n.other) {
1877 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1878 }
1879 } else if (b == sb->last) {
1880 n = sb->last;
1881 sp_node_handle_mirror_p_to_n(sa->last);
1882 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1883 sp_node_handle_mirror_n_to_p(sa->last);
1884 for (n = n->p.other; n != NULL; n = n->p.other) {
1885 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1886 }
1887 } else {
1888 g_assert_not_reached();
1889 }
1890 /* and now destroy sb */
1892 sp_nodepath_subpath_destroy(sb);
1894 sp_nodepath_update_handles(sa->nodepath);
1896 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1897 }
1899 /**
1900 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1901 */
1902 void sp_node_delete_preserve(GList *nodes_to_delete)
1903 {
1904 GSList *nodepaths = NULL;
1906 while (nodes_to_delete) {
1907 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1908 Inkscape::NodePath::SubPath *sp = node->subpath;
1909 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1910 Inkscape::NodePath::Node *sample_cursor = NULL;
1911 Inkscape::NodePath::Node *sample_end = NULL;
1912 Inkscape::NodePath::Node *delete_cursor = node;
1913 bool just_delete = false;
1915 //find the start of this contiguous selection
1916 //move left to the first node that is not selected
1917 //or the start of the non-closed path
1918 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1919 delete_cursor = curr;
1920 }
1922 //just delete at the beginning of an open path
1923 if (!delete_cursor->p.other) {
1924 sample_cursor = delete_cursor;
1925 just_delete = true;
1926 } else {
1927 sample_cursor = delete_cursor->p.other;
1928 }
1930 //calculate points for each segment
1931 int rate = 5;
1932 float period = 1.0 / rate;
1933 std::vector<NR::Point> data;
1934 if (!just_delete) {
1935 data.push_back(sample_cursor->pos);
1936 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1937 //just delete at the end of an open path
1938 if (!sp->closed && curr->n.other == sp->last) {
1939 just_delete = true;
1940 break;
1941 }
1943 //sample points on the contiguous selected segment
1944 NR::Point *bez;
1945 bez = new NR::Point [4];
1946 bez[0] = curr->pos;
1947 bez[1] = curr->n.pos;
1948 bez[2] = curr->n.other->p.pos;
1949 bez[3] = curr->n.other->pos;
1950 for (int i=1; i<rate; i++) {
1951 gdouble t = i * period;
1952 NR::Point p = bezier_pt(3, bez, t);
1953 data.push_back(p);
1954 }
1955 data.push_back(curr->n.other->pos);
1957 sample_end = curr->n.other;
1958 //break if we've come full circle or hit the end of the selection
1959 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1960 break;
1961 }
1962 }
1963 }
1965 if (!just_delete) {
1966 //calculate the best fitting single segment and adjust the endpoints
1967 NR::Point *adata;
1968 adata = new NR::Point [data.size()];
1969 copy(data.begin(), data.end(), adata);
1971 NR::Point *bez;
1972 bez = new NR::Point [4];
1973 //would decreasing error create a better fitting approximation?
1974 gdouble error = 1.0;
1975 gint ret;
1976 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1978 //adjust endpoints
1979 sample_cursor->n.pos = bez[1];
1980 sample_end->p.pos = bez[2];
1981 }
1983 //destroy this contiguous selection
1984 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
1985 Inkscape::NodePath::Node *temp = delete_cursor;
1986 if (delete_cursor->n.other == delete_cursor) {
1987 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
1988 delete_cursor = NULL;
1989 } else {
1990 delete_cursor = delete_cursor->n.other;
1991 }
1992 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
1993 sp_nodepath_node_destroy(temp);
1994 }
1996 sp_nodepath_update_handles(nodepath);
1998 if (!g_slist_find(nodepaths, nodepath))
1999 nodepaths = g_slist_prepend (nodepaths, nodepath);
2000 }
2002 for (GSList *i = nodepaths; i; i = i->next) {
2003 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2004 // different nodepaths will give us one undo event per nodepath
2005 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2007 // if the entire nodepath is removed, delete the selected object.
2008 if (nodepath->subpaths == NULL ||
2009 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2010 //at least 2
2011 sp_nodepath_get_node_count(nodepath) < 2) {
2012 SPDocument *document = sp_desktop_document (nodepath->desktop);
2013 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2014 //delete this nodepath's object, not the entire selection! (though at this time, this
2015 //does not matter)
2016 sp_selection_delete();
2017 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2018 _("Delete nodes"));
2019 } else {
2020 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2021 sp_nodepath_update_statusbar(nodepath);
2022 }
2023 }
2025 g_slist_free (nodepaths);
2026 }
2028 /**
2029 * Delete one or more selected nodes.
2030 */
2031 void sp_node_selected_delete()
2032 {
2033 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2034 if (!nodepath) return;
2035 if (!nodepath->selected) return;
2037 /** \todo fixme: do it the right way */
2038 while (nodepath->selected) {
2039 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2040 sp_nodepath_node_destroy(node);
2041 }
2044 //clean up the nodepath (such as for trivial subpaths)
2045 sp_nodepath_cleanup(nodepath);
2047 sp_nodepath_update_handles(nodepath);
2049 // if the entire nodepath is removed, delete the selected object.
2050 if (nodepath->subpaths == NULL ||
2051 sp_nodepath_get_node_count(nodepath) < 2) {
2052 SPDocument *document = sp_desktop_document (nodepath->desktop);
2053 sp_selection_delete();
2054 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2055 _("Delete nodes"));
2056 return;
2057 }
2059 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2061 sp_nodepath_update_statusbar(nodepath);
2062 }
2064 /**
2065 * Delete one or more segments between two selected nodes.
2066 * This is the code for 'split'.
2067 */
2068 void
2069 sp_node_selected_delete_segment(void)
2070 {
2071 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2072 Inkscape::NodePath::Node *curr, *next; //Iterators
2074 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2075 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2077 if (g_list_length(nodepath->selected) != 2) {
2078 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2079 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2080 return;
2081 }
2083 //Selected nodes, not inclusive
2084 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2085 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2087 if ( ( a==b) || //same node
2088 (a->subpath != b->subpath ) || //not the same path
2089 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2090 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2091 {
2092 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2093 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2094 return;
2095 }
2097 //###########################################
2098 //# BEGIN EDITS
2099 //###########################################
2100 //##################################
2101 //# CLOSED PATH
2102 //##################################
2103 if (a->subpath->closed) {
2106 gboolean reversed = FALSE;
2108 //Since we can go in a circle, we need to find the shorter distance.
2109 // a->b or b->a
2110 start = end = NULL;
2111 int distance = 0;
2112 int minDistance = 0;
2113 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2114 if (curr==b) {
2115 //printf("a to b:%d\n", distance);
2116 start = a;//go from a to b
2117 end = b;
2118 minDistance = distance;
2119 //printf("A to B :\n");
2120 break;
2121 }
2122 distance++;
2123 }
2125 //try again, the other direction
2126 distance = 0;
2127 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2128 if (curr==a) {
2129 //printf("b to a:%d\n", distance);
2130 if (distance < minDistance) {
2131 start = b; //we go from b to a
2132 end = a;
2133 reversed = TRUE;
2134 //printf("B to A\n");
2135 }
2136 break;
2137 }
2138 distance++;
2139 }
2142 //Copy everything from 'end' to 'start' to a new subpath
2143 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2144 for (curr=end ; curr ; curr=curr->n.other) {
2145 NRPathcode code = (NRPathcode) curr->code;
2146 if (curr == end)
2147 code = NR_MOVETO;
2148 sp_nodepath_node_new(t, NULL,
2149 (Inkscape::NodePath::NodeType)curr->type, code,
2150 &curr->p.pos, &curr->pos, &curr->n.pos);
2151 if (curr == start)
2152 break;
2153 }
2154 sp_nodepath_subpath_destroy(a->subpath);
2157 }
2161 //##################################
2162 //# OPEN PATH
2163 //##################################
2164 else {
2166 //We need to get the direction of the list between A and B
2167 //Can we walk from a to b?
2168 start = end = NULL;
2169 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2170 if (curr==b) {
2171 start = a; //did it! we go from a to b
2172 end = b;
2173 //printf("A to B\n");
2174 break;
2175 }
2176 }
2177 if (!start) {//didn't work? let's try the other direction
2178 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2179 if (curr==a) {
2180 start = b; //did it! we go from b to a
2181 end = a;
2182 //printf("B to A\n");
2183 break;
2184 }
2185 }
2186 }
2187 if (!start) {
2188 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2189 _("Cannot find path between nodes."));
2190 return;
2191 }
2195 //Copy everything after 'end' to a new subpath
2196 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2197 for (curr=end ; curr ; curr=curr->n.other) {
2198 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2199 &curr->p.pos, &curr->pos, &curr->n.pos);
2200 }
2202 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2203 for (curr = start->n.other ; curr ; curr=next) {
2204 next = curr->n.other;
2205 sp_nodepath_node_destroy(curr);
2206 }
2208 }
2209 //###########################################
2210 //# END EDITS
2211 //###########################################
2213 //clean up the nodepath (such as for trivial subpaths)
2214 sp_nodepath_cleanup(nodepath);
2216 sp_nodepath_update_handles(nodepath);
2218 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2220 sp_nodepath_update_statusbar(nodepath);
2221 }
2223 /**
2224 * Call sp_nodepath_set_line() for all selected segments.
2225 */
2226 void
2227 sp_node_selected_set_line_type(NRPathcode code)
2228 {
2229 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2230 if (nodepath == NULL) return;
2232 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2233 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2234 g_assert(n->selected);
2235 if (n->p.other && n->p.other->selected) {
2236 sp_nodepath_set_line_type(n, code);
2237 }
2238 }
2240 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2241 }
2243 /**
2244 * Call sp_nodepath_convert_node_type() for all selected nodes.
2245 */
2246 void
2247 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2248 {
2249 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2250 if (nodepath == NULL) return;
2252 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2253 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2254 }
2256 sp_nodepath_update_repr(nodepath, _("Change node type"));
2257 }
2259 /**
2260 * Change select status of node, update its own and neighbour handles.
2261 */
2262 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2263 {
2264 node->selected = selected;
2266 if (selected) {
2267 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2268 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2269 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2270 sp_knot_update_ctrl(node->knot);
2271 } else {
2272 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2273 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2274 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2275 sp_knot_update_ctrl(node->knot);
2276 }
2278 sp_node_update_handles(node);
2279 if (node->n.other) sp_node_update_handles(node->n.other);
2280 if (node->p.other) sp_node_update_handles(node->p.other);
2281 }
2283 /**
2284 \brief Select a node
2285 \param node The node to select
2286 \param incremental If true, add to selection, otherwise deselect others
2287 \param override If true, always select this node, otherwise toggle selected status
2288 */
2289 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2290 {
2291 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2293 if (incremental) {
2294 if (override) {
2295 if (!g_list_find(nodepath->selected, node)) {
2296 nodepath->selected = g_list_prepend(nodepath->selected, node);
2297 }
2298 sp_node_set_selected(node, TRUE);
2299 } else { // toggle
2300 if (node->selected) {
2301 g_assert(g_list_find(nodepath->selected, node));
2302 nodepath->selected = g_list_remove(nodepath->selected, node);
2303 } else {
2304 g_assert(!g_list_find(nodepath->selected, node));
2305 nodepath->selected = g_list_prepend(nodepath->selected, node);
2306 }
2307 sp_node_set_selected(node, !node->selected);
2308 }
2309 } else {
2310 sp_nodepath_deselect(nodepath);
2311 nodepath->selected = g_list_prepend(nodepath->selected, node);
2312 sp_node_set_selected(node, TRUE);
2313 }
2315 sp_nodepath_update_statusbar(nodepath);
2316 }
2319 /**
2320 \brief Deselect all nodes in the nodepath
2321 */
2322 void
2323 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2324 {
2325 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2327 while (nodepath->selected) {
2328 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2329 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2330 }
2331 sp_nodepath_update_statusbar(nodepath);
2332 }
2334 /**
2335 \brief Select or invert selection of all nodes in the nodepath
2336 */
2337 void
2338 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2339 {
2340 if (!nodepath) return;
2342 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2343 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2344 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2345 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2346 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2347 }
2348 }
2349 }
2351 /**
2352 * If nothing selected, does the same as sp_nodepath_select_all();
2353 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2354 * (i.e., similar to "select all in layer", with the "selected" subpaths
2355 * being treated as "layers" in the path).
2356 */
2357 void
2358 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2359 {
2360 if (!nodepath) return;
2362 if (g_list_length (nodepath->selected) == 0) {
2363 sp_nodepath_select_all (nodepath, invert);
2364 return;
2365 }
2367 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2368 GSList *subpaths = NULL;
2370 for (GList *l = copy; l != NULL; l = l->next) {
2371 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2372 Inkscape::NodePath::SubPath *subpath = n->subpath;
2373 if (!g_slist_find (subpaths, subpath))
2374 subpaths = g_slist_prepend (subpaths, subpath);
2375 }
2377 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2378 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2379 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2380 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2381 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2382 }
2383 }
2385 g_slist_free (subpaths);
2386 g_list_free (copy);
2387 }
2389 /**
2390 * \brief Select the node after the last selected; if none is selected,
2391 * select the first within path.
2392 */
2393 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2394 {
2395 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2397 Inkscape::NodePath::Node *last = NULL;
2398 if (nodepath->selected) {
2399 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2400 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2401 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2402 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2403 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2404 if (node->selected) {
2405 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2406 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2407 if (spl->next) { // there's a next subpath
2408 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2409 last = subpath_next->first;
2410 } else if (spl->prev) { // there's a previous subpath
2411 last = NULL; // to be set later to the first node of first subpath
2412 } else {
2413 last = node->n.other;
2414 }
2415 } else {
2416 last = node->n.other;
2417 }
2418 } else {
2419 if (node->n.other) {
2420 last = node->n.other;
2421 } else {
2422 if (spl->next) { // there's a next subpath
2423 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2424 last = subpath_next->first;
2425 } else if (spl->prev) { // there's a previous subpath
2426 last = NULL; // to be set later to the first node of first subpath
2427 } else {
2428 last = (Inkscape::NodePath::Node *) subpath->first;
2429 }
2430 }
2431 }
2432 }
2433 }
2434 }
2435 sp_nodepath_deselect(nodepath);
2436 }
2438 if (last) { // there's at least one more node after selected
2439 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2440 } else { // no more nodes, select the first one in first subpath
2441 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2442 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2443 }
2444 }
2446 /**
2447 * \brief Select the node before the first selected; if none is selected,
2448 * select the last within path
2449 */
2450 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2451 {
2452 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2454 Inkscape::NodePath::Node *last = NULL;
2455 if (nodepath->selected) {
2456 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2457 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2458 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2459 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2460 if (node->selected) {
2461 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2462 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2463 if (spl->prev) { // there's a prev subpath
2464 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2465 last = subpath_prev->last;
2466 } else if (spl->next) { // there's a next subpath
2467 last = NULL; // to be set later to the last node of last subpath
2468 } else {
2469 last = node->p.other;
2470 }
2471 } else {
2472 last = node->p.other;
2473 }
2474 } else {
2475 if (node->p.other) {
2476 last = node->p.other;
2477 } else {
2478 if (spl->prev) { // there's a prev subpath
2479 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2480 last = subpath_prev->last;
2481 } else if (spl->next) { // there's a next subpath
2482 last = NULL; // to be set later to the last node of last subpath
2483 } else {
2484 last = (Inkscape::NodePath::Node *) subpath->last;
2485 }
2486 }
2487 }
2488 }
2489 }
2490 }
2491 sp_nodepath_deselect(nodepath);
2492 }
2494 if (last) { // there's at least one more node before selected
2495 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2496 } else { // no more nodes, select the last one in last subpath
2497 GList *spl = g_list_last(nodepath->subpaths);
2498 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2499 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2500 }
2501 }
2503 /**
2504 * \brief Select all nodes that are within the rectangle.
2505 */
2506 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2507 {
2508 if (!incremental) {
2509 sp_nodepath_deselect(nodepath);
2510 }
2512 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2513 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2514 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2515 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2517 if (b.contains(node->pos)) {
2518 sp_nodepath_node_select(node, TRUE, TRUE);
2519 }
2520 }
2521 }
2522 }
2525 void
2526 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2527 {
2528 g_assert (n);
2529 g_assert (nodepath);
2530 g_assert (n->subpath->nodepath == nodepath);
2532 if (g_list_length (nodepath->selected) == 0) {
2533 if (grow > 0) {
2534 sp_nodepath_node_select(n, TRUE, TRUE);
2535 }
2536 return;
2537 }
2539 if (g_list_length (nodepath->selected) == 1) {
2540 if (grow < 0) {
2541 sp_nodepath_deselect (nodepath);
2542 return;
2543 }
2544 }
2546 double n_sel_range = 0, p_sel_range = 0;
2547 Inkscape::NodePath::Node *farthest_n_node = n;
2548 Inkscape::NodePath::Node *farthest_p_node = n;
2550 // Calculate ranges
2551 {
2552 double n_range = 0, p_range = 0;
2553 bool n_going = true, p_going = true;
2554 Inkscape::NodePath::Node *n_node = n;
2555 Inkscape::NodePath::Node *p_node = n;
2556 do {
2557 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2558 if (n_node && n_going)
2559 n_node = n_node->n.other;
2560 if (n_node == NULL) {
2561 n_going = false;
2562 } else {
2563 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2564 if (n_node->selected) {
2565 n_sel_range = n_range;
2566 farthest_n_node = n_node;
2567 }
2568 if (n_node == p_node) {
2569 n_going = false;
2570 p_going = false;
2571 }
2572 }
2573 if (p_node && p_going)
2574 p_node = p_node->p.other;
2575 if (p_node == NULL) {
2576 p_going = false;
2577 } else {
2578 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2579 if (p_node->selected) {
2580 p_sel_range = p_range;
2581 farthest_p_node = p_node;
2582 }
2583 if (p_node == n_node) {
2584 n_going = false;
2585 p_going = false;
2586 }
2587 }
2588 } while (n_going || p_going);
2589 }
2591 if (grow > 0) {
2592 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2593 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2594 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2595 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2596 }
2597 } else {
2598 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2599 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2600 } else if (farthest_p_node && farthest_p_node->selected) {
2601 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2602 }
2603 }
2604 }
2606 void
2607 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2608 {
2609 g_assert (n);
2610 g_assert (nodepath);
2611 g_assert (n->subpath->nodepath == nodepath);
2613 if (g_list_length (nodepath->selected) == 0) {
2614 if (grow > 0) {
2615 sp_nodepath_node_select(n, TRUE, TRUE);
2616 }
2617 return;
2618 }
2620 if (g_list_length (nodepath->selected) == 1) {
2621 if (grow < 0) {
2622 sp_nodepath_deselect (nodepath);
2623 return;
2624 }
2625 }
2627 Inkscape::NodePath::Node *farthest_selected = NULL;
2628 double farthest_dist = 0;
2630 Inkscape::NodePath::Node *closest_unselected = NULL;
2631 double closest_dist = NR_HUGE;
2633 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2634 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2635 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2636 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2637 if (node == n)
2638 continue;
2639 if (node->selected) {
2640 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2641 farthest_dist = NR::L2(node->pos - n->pos);
2642 farthest_selected = node;
2643 }
2644 } else {
2645 if (NR::L2(node->pos - n->pos) < closest_dist) {
2646 closest_dist = NR::L2(node->pos - n->pos);
2647 closest_unselected = node;
2648 }
2649 }
2650 }
2651 }
2653 if (grow > 0) {
2654 if (closest_unselected) {
2655 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2656 }
2657 } else {
2658 if (farthest_selected) {
2659 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2660 }
2661 }
2662 }
2665 /**
2666 \brief Saves all nodes' and handles' current positions in their origin members
2667 */
2668 void
2669 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2670 {
2671 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2672 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2673 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2674 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2675 n->origin = n->pos;
2676 n->p.origin = n->p.pos;
2677 n->n.origin = n->n.pos;
2678 }
2679 }
2680 }
2682 /**
2683 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2684 */
2685 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2686 {
2687 if (!nodepath->selected) {
2688 return NULL;
2689 }
2691 GList *r = NULL;
2692 guint i = 0;
2693 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2694 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2695 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2696 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2697 i++;
2698 if (node->selected) {
2699 r = g_list_append(r, GINT_TO_POINTER(i));
2700 }
2701 }
2702 }
2703 return r;
2704 }
2706 /**
2707 \brief Restores selection by selecting nodes whose positions are in the list
2708 */
2709 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2710 {
2711 sp_nodepath_deselect(nodepath);
2713 guint i = 0;
2714 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2715 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2716 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2717 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2718 i++;
2719 if (g_list_find(r, GINT_TO_POINTER(i))) {
2720 sp_nodepath_node_select(node, TRUE, TRUE);
2721 }
2722 }
2723 }
2725 }
2727 /**
2728 \brief Adjusts handle according to node type and line code.
2729 */
2730 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2731 {
2732 double len, otherlen, linelen;
2734 g_assert(node);
2736 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2737 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2739 /** \todo fixme: */
2740 if (me->other == NULL) return;
2741 if (other->other == NULL) return;
2743 /* I have line */
2745 NRPathcode mecode, ocode;
2746 if (which_adjust == 1) {
2747 mecode = (NRPathcode)me->other->code;
2748 ocode = (NRPathcode)node->code;
2749 } else {
2750 mecode = (NRPathcode)node->code;
2751 ocode = (NRPathcode)other->other->code;
2752 }
2754 if (mecode == NR_LINETO) return;
2756 /* I am curve */
2758 if (other->other == NULL) return;
2760 /* Other has line */
2762 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2764 NR::Point delta;
2765 if (ocode == NR_LINETO) {
2766 /* other is lineto, we are either smooth or symm */
2767 Inkscape::NodePath::Node *othernode = other->other;
2768 len = NR::L2(me->pos - node->pos);
2769 delta = node->pos - othernode->pos;
2770 linelen = NR::L2(delta);
2771 if (linelen < 1e-18)
2772 return;
2773 me->pos = node->pos + (len / linelen)*delta;
2774 return;
2775 }
2777 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2779 me->pos = 2 * node->pos - other->pos;
2780 return;
2781 }
2783 /* We are smooth */
2785 len = NR::L2(me->pos - node->pos);
2786 delta = other->pos - node->pos;
2787 otherlen = NR::L2(delta);
2788 if (otherlen < 1e-18) return;
2790 me->pos = node->pos - (len / otherlen) * delta;
2791 }
2793 /**
2794 \brief Adjusts both handles according to node type and line code
2795 */
2796 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2797 {
2798 g_assert(node);
2800 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2802 /* we are either smooth or symm */
2804 if (node->p.other == NULL) return;
2806 if (node->n.other == NULL) return;
2808 if (node->code == NR_LINETO) {
2809 if (node->n.other->code == NR_LINETO) return;
2810 sp_node_adjust_handle(node, 1);
2811 return;
2812 }
2814 if (node->n.other->code == NR_LINETO) {
2815 if (node->code == NR_LINETO) return;
2816 sp_node_adjust_handle(node, -1);
2817 return;
2818 }
2820 /* both are curves */
2821 NR::Point const delta( node->n.pos - node->p.pos );
2823 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2824 node->p.pos = node->pos - delta / 2;
2825 node->n.pos = node->pos + delta / 2;
2826 return;
2827 }
2829 /* We are smooth */
2830 double plen = NR::L2(node->p.pos - node->pos);
2831 if (plen < 1e-18) return;
2832 double nlen = NR::L2(node->n.pos - node->pos);
2833 if (nlen < 1e-18) return;
2834 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2835 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2836 }
2838 /**
2839 * Node event callback.
2840 */
2841 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2842 {
2843 gboolean ret = FALSE;
2844 switch (event->type) {
2845 case GDK_ENTER_NOTIFY:
2846 active_node = n;
2847 break;
2848 case GDK_LEAVE_NOTIFY:
2849 active_node = NULL;
2850 break;
2851 case GDK_KEY_PRESS:
2852 switch (get_group0_keyval (&event->key)) {
2853 case GDK_space:
2854 if (event->key.state & GDK_BUTTON1_MASK) {
2855 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2856 stamp_repr(nodepath);
2857 ret = TRUE;
2858 }
2859 break;
2860 case GDK_Page_Up:
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 case GDK_Page_Down:
2868 if (event->key.state & GDK_CONTROL_MASK) {
2869 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2870 } else {
2871 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2872 }
2873 break;
2874 default:
2875 break;
2876 }
2877 break;
2878 default:
2879 break;
2880 }
2882 return ret;
2883 }
2885 /**
2886 * Handle keypress on node; directly called.
2887 */
2888 gboolean node_key(GdkEvent *event)
2889 {
2890 Inkscape::NodePath::Path *np;
2892 // there is no way to verify nodes so set active_node to nil when deleting!!
2893 if (active_node == NULL) return FALSE;
2895 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2896 gint ret = FALSE;
2897 switch (get_group0_keyval (&event->key)) {
2898 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2899 case GDK_BackSpace:
2900 np = active_node->subpath->nodepath;
2901 sp_nodepath_node_destroy(active_node);
2902 sp_nodepath_update_repr(np, _("Delete node"));
2903 active_node = NULL;
2904 ret = TRUE;
2905 break;
2906 case GDK_c:
2907 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2908 ret = TRUE;
2909 break;
2910 case GDK_s:
2911 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2912 ret = TRUE;
2913 break;
2914 case GDK_y:
2915 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2916 ret = TRUE;
2917 break;
2918 case GDK_b:
2919 sp_nodepath_node_break(active_node);
2920 ret = TRUE;
2921 break;
2922 }
2923 return ret;
2924 }
2925 return FALSE;
2926 }
2928 /**
2929 * Mouseclick on node callback.
2930 */
2931 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2932 {
2933 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2935 if (state & GDK_CONTROL_MASK) {
2936 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2938 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2939 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2940 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2941 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2942 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2943 } else {
2944 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2945 }
2946 sp_nodepath_update_repr(nodepath, _("Change node type"));
2947 sp_nodepath_update_statusbar(nodepath);
2949 } else { //ctrl+alt+click: delete node
2950 GList *node_to_delete = NULL;
2951 node_to_delete = g_list_append(node_to_delete, n);
2952 sp_node_delete_preserve(node_to_delete);
2953 }
2955 } else {
2956 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2957 }
2958 }
2960 /**
2961 * Mouse grabbed node callback.
2962 */
2963 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2964 {
2965 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2967 if (!n->selected) {
2968 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2969 }
2971 sp_nodepath_remember_origins (n->subpath->nodepath);
2972 }
2974 /**
2975 * Mouse ungrabbed node callback.
2976 */
2977 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2978 {
2979 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2981 n->dragging_out = NULL;
2983 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
2984 }
2986 /**
2987 * The point on a line, given by its angle, closest to the given point.
2988 * \param p A point.
2989 * \param a Angle of the line; it is assumed to go through coordinate origin.
2990 * \param closest Pointer to the point struct where the result is stored.
2991 * \todo FIXME: use dot product perhaps?
2992 */
2993 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2994 {
2995 if (a == HUGE_VAL) { // vertical
2996 *closest = NR::Point(0, (*p)[NR::Y]);
2997 } else {
2998 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2999 (*closest)[NR::Y] = a * (*closest)[NR::X];
3000 }
3001 }
3003 /**
3004 * Distance from the point to a line given by its angle.
3005 * \param p A point.
3006 * \param a Angle of the line; it is assumed to go through coordinate origin.
3007 */
3008 static double point_line_distance(NR::Point *p, double a)
3009 {
3010 NR::Point c;
3011 point_line_closest(p, a, &c);
3012 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]));
3013 }
3015 /**
3016 * Callback for node "request" signal.
3017 * \todo fixme: This goes to "moved" event? (lauris)
3018 */
3019 static gboolean
3020 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3021 {
3022 double yn, xn, yp, xp;
3023 double an, ap, na, pa;
3024 double d_an, d_ap, d_na, d_pa;
3025 gboolean collinear = FALSE;
3026 NR::Point c;
3027 NR::Point pr;
3029 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3031 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3032 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3034 NR::Point mouse = (*p);
3036 if (!n->dragging_out) {
3037 // This is the first drag-out event; find out which handle to drag out
3038 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3039 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3041 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3042 return FALSE;
3044 Inkscape::NodePath::NodeSide *opposite;
3045 if (appr_p > appr_n) { // closer to p
3046 n->dragging_out = &n->p;
3047 opposite = &n->n;
3048 n->code = NR_CURVETO;
3049 } else if (appr_p < appr_n) { // closer to n
3050 n->dragging_out = &n->n;
3051 opposite = &n->p;
3052 n->n.other->code = NR_CURVETO;
3053 } else { // p and n nodes are the same
3054 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3055 n->dragging_out = &n->p;
3056 opposite = &n->n;
3057 n->code = NR_CURVETO;
3058 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3059 n->dragging_out = &n->n;
3060 opposite = &n->p;
3061 n->n.other->code = NR_CURVETO;
3062 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3063 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);
3064 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);
3065 if (appr_other_p > appr_other_n) { // closer to other's p handle
3066 n->dragging_out = &n->n;
3067 opposite = &n->p;
3068 n->n.other->code = NR_CURVETO;
3069 } else { // closer to other's n handle
3070 n->dragging_out = &n->p;
3071 opposite = &n->n;
3072 n->code = NR_CURVETO;
3073 }
3074 }
3075 }
3077 // if there's another handle, make sure the one we drag out starts parallel to it
3078 if (opposite->pos != n->pos) {
3079 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3080 }
3082 // knots might not be created yet!
3083 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3084 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3085 }
3087 // pass this on to the handle-moved callback
3088 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3089 sp_node_update_handles(n);
3090 return TRUE;
3091 }
3093 if (state & GDK_CONTROL_MASK) { // constrained motion
3095 // calculate relative distances of handles
3096 // n handle:
3097 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3098 xn = n->n.pos[NR::X] - n->pos[NR::X];
3099 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3100 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3101 if (n->n.other) { // if there is the next point
3102 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3103 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3104 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3105 }
3106 }
3107 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3108 if (yn < 0) { xn = -xn; yn = -yn; }
3110 // p handle:
3111 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3112 xp = n->p.pos[NR::X] - n->pos[NR::X];
3113 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3114 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3115 if (n->p.other) {
3116 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3117 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3118 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3119 }
3120 }
3121 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3122 if (yp < 0) { xp = -xp; yp = -yp; }
3124 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3125 // sliding on handles, only if at least one of the handles is non-vertical
3126 // (otherwise it's the same as ctrl+drag anyway)
3128 // calculate angles of the handles
3129 if (xn == 0) {
3130 if (yn == 0) { // no handle, consider it the continuation of the other one
3131 an = 0;
3132 collinear = TRUE;
3133 }
3134 else an = 0; // vertical; set the angle to horizontal
3135 } else an = yn/xn;
3137 if (xp == 0) {
3138 if (yp == 0) { // no handle, consider it the continuation of the other one
3139 ap = an;
3140 }
3141 else ap = 0; // vertical; set the angle to horizontal
3142 } else ap = yp/xp;
3144 if (collinear) an = ap;
3146 // angles of the perpendiculars; HUGE_VAL means vertical
3147 if (an == 0) na = HUGE_VAL; else na = -1/an;
3148 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3150 // mouse point relative to the node's original pos
3151 pr = (*p) - n->origin;
3153 // distances to the four lines (two handles and two perpendiculars)
3154 d_an = point_line_distance(&pr, an);
3155 d_na = point_line_distance(&pr, na);
3156 d_ap = point_line_distance(&pr, ap);
3157 d_pa = point_line_distance(&pr, pa);
3159 // find out which line is the closest, save its closest point in c
3160 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3161 point_line_closest(&pr, an, &c);
3162 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3163 point_line_closest(&pr, ap, &c);
3164 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3165 point_line_closest(&pr, na, &c);
3166 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3167 point_line_closest(&pr, pa, &c);
3168 }
3170 // move the node to the closest point
3171 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3172 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3173 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3175 } else { // constraining to hor/vert
3177 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3178 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3179 } else { // snap to vert
3180 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3181 }
3182 }
3183 } else { // move freely
3184 if (state & GDK_MOD1_MASK) { // sculpt
3185 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3186 } else {
3187 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3188 (*p)[NR::X] - n->pos[NR::X],
3189 (*p)[NR::Y] - n->pos[NR::Y],
3190 (state & GDK_SHIFT_MASK) == 0);
3191 }
3192 }
3194 n->subpath->nodepath->desktop->scroll_to_point(p);
3196 return TRUE;
3197 }
3199 /**
3200 * Node handle clicked callback.
3201 */
3202 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3203 {
3204 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3206 if (state & GDK_CONTROL_MASK) { // "delete" handle
3207 if (n->p.knot == knot) {
3208 n->p.pos = n->pos;
3209 } else if (n->n.knot == knot) {
3210 n->n.pos = n->pos;
3211 }
3212 sp_node_update_handles(n);
3213 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3214 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3215 sp_nodepath_update_statusbar(nodepath);
3217 } else { // just select or add to selection, depending in Shift
3218 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3219 }
3220 }
3222 /**
3223 * Node handle grabbed callback.
3224 */
3225 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3226 {
3227 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3229 if (!n->selected) {
3230 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3231 }
3233 // remember the origin point of the handle
3234 if (n->p.knot == knot) {
3235 n->p.origin_radial = n->p.pos - n->pos;
3236 } else if (n->n.knot == knot) {
3237 n->n.origin_radial = n->n.pos - n->pos;
3238 } else {
3239 g_assert_not_reached();
3240 }
3242 }
3244 /**
3245 * Node handle ungrabbed callback.
3246 */
3247 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3248 {
3249 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3251 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3252 if (n->p.knot == knot) {
3253 n->p.origin_radial.a = 0;
3254 sp_knot_set_position(knot, &n->p.pos, state);
3255 } else if (n->n.knot == knot) {
3256 n->n.origin_radial.a = 0;
3257 sp_knot_set_position(knot, &n->n.pos, state);
3258 } else {
3259 g_assert_not_reached();
3260 }
3262 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3263 }
3265 /**
3266 * Node handle "request" signal callback.
3267 */
3268 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3269 {
3270 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3272 Inkscape::NodePath::NodeSide *me, *opposite;
3273 gint which;
3274 if (n->p.knot == knot) {
3275 me = &n->p;
3276 opposite = &n->n;
3277 which = -1;
3278 } else if (n->n.knot == knot) {
3279 me = &n->n;
3280 opposite = &n->p;
3281 which = 1;
3282 } else {
3283 me = opposite = NULL;
3284 which = 0;
3285 g_assert_not_reached();
3286 }
3288 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3290 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3292 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3293 /* We are smooth node adjacent with line */
3294 NR::Point const delta = *p - n->pos;
3295 NR::Coord const len = NR::L2(delta);
3296 Inkscape::NodePath::Node *othernode = opposite->other;
3297 NR::Point const ndelta = n->pos - othernode->pos;
3298 NR::Coord const linelen = NR::L2(ndelta);
3299 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3300 NR::Coord const scal = dot(delta, ndelta) / linelen;
3301 (*p) = n->pos + (scal / linelen) * ndelta;
3302 }
3303 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3304 } else {
3305 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3306 }
3308 sp_node_adjust_handle(n, -which);
3310 return FALSE;
3311 }
3313 /**
3314 * Node handle moved callback.
3315 */
3316 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3317 {
3318 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3320 Inkscape::NodePath::NodeSide *me;
3321 Inkscape::NodePath::NodeSide *other;
3322 if (n->p.knot == knot) {
3323 me = &n->p;
3324 other = &n->n;
3325 } else if (n->n.knot == knot) {
3326 me = &n->n;
3327 other = &n->p;
3328 } else {
3329 me = NULL;
3330 other = NULL;
3331 g_assert_not_reached();
3332 }
3334 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3335 Radial rme(me->pos - n->pos);
3336 Radial rother(other->pos - n->pos);
3337 Radial rnew(*p - n->pos);
3339 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3340 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3341 /* 0 interpreted as "no snapping". */
3343 // The closest PI/snaps angle, starting from zero.
3344 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3345 if (me->origin_radial.a == HUGE_VAL) {
3346 // ortho doesn't exist: original handle was zero length.
3347 rnew.a = a_snapped;
3348 } else {
3349 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3350 * its opposite and perpendiculars). */
3351 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3353 // Snap to the closest.
3354 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3355 ? a_snapped
3356 : a_ortho );
3357 }
3358 }
3360 if (state & GDK_MOD1_MASK) {
3361 // lock handle length
3362 rnew.r = me->origin_radial.r;
3363 }
3365 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3366 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3367 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3368 rother.a += rnew.a - rme.a;
3369 other->pos = NR::Point(rother) + n->pos;
3370 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3371 sp_knot_set_position(other->knot, &other->pos, 0);
3372 }
3374 me->pos = NR::Point(rnew) + n->pos;
3375 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3377 // this is what sp_knot_set_position does, but without emitting the signal:
3378 // we cannot emit a "moved" signal because we're now processing it
3379 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3381 knot->desktop->set_coordinate_status(me->pos);
3383 update_object(n->subpath->nodepath);
3385 /* status text */
3386 SPDesktop *desktop = n->subpath->nodepath->desktop;
3387 if (!desktop) return;
3388 SPEventContext *ec = desktop->event_context;
3389 if (!ec) return;
3390 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3391 if (!mc) return;
3393 double degrees = 180 / M_PI * rnew.a;
3394 if (degrees > 180) degrees -= 360;
3395 if (degrees < -180) degrees += 360;
3396 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3397 degrees = angle_to_compass (degrees);
3399 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3401 mc->setF(Inkscape::NORMAL_MESSAGE,
3402 _("<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);
3404 g_string_free(length, TRUE);
3405 }
3407 /**
3408 * Node handle event callback.
3409 */
3410 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3411 {
3412 gboolean ret = FALSE;
3413 switch (event->type) {
3414 case GDK_KEY_PRESS:
3415 switch (get_group0_keyval (&event->key)) {
3416 case GDK_space:
3417 if (event->key.state & GDK_BUTTON1_MASK) {
3418 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3419 stamp_repr(nodepath);
3420 ret = TRUE;
3421 }
3422 break;
3423 default:
3424 break;
3425 }
3426 break;
3427 default:
3428 break;
3429 }
3431 return ret;
3432 }
3434 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3435 Radial &rme, Radial &rother, gboolean const both)
3436 {
3437 rme.a += angle;
3438 if ( both
3439 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3440 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3441 {
3442 rother.a += angle;
3443 }
3444 }
3446 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3447 Radial &rme, Radial &rother, gboolean const both)
3448 {
3449 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3451 gdouble r;
3452 if ( both
3453 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3454 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3455 {
3456 r = MAX(rme.r, rother.r);
3457 } else {
3458 r = rme.r;
3459 }
3461 gdouble const weird_angle = atan2(norm_angle, r);
3462 /* Bulia says norm_angle is just the visible distance that the
3463 * object's end must travel on the screen. Left as 'angle' for want of
3464 * a better name.*/
3466 rme.a += weird_angle;
3467 if ( both
3468 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3469 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3470 {
3471 rother.a += weird_angle;
3472 }
3473 }
3475 /**
3476 * Rotate one node.
3477 */
3478 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3479 {
3480 Inkscape::NodePath::NodeSide *me, *other;
3481 bool both = false;
3483 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3484 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3486 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3487 me = &(n->p);
3488 other = &(n->n);
3489 } else if (!n->p.other) {
3490 me = &(n->n);
3491 other = &(n->p);
3492 } else {
3493 if (which > 0) { // right handle
3494 if (xn > xp) {
3495 me = &(n->n);
3496 other = &(n->p);
3497 } else {
3498 me = &(n->p);
3499 other = &(n->n);
3500 }
3501 } else if (which < 0){ // left handle
3502 if (xn <= xp) {
3503 me = &(n->n);
3504 other = &(n->p);
3505 } else {
3506 me = &(n->p);
3507 other = &(n->n);
3508 }
3509 } else { // both handles
3510 me = &(n->n);
3511 other = &(n->p);
3512 both = true;
3513 }
3514 }
3516 Radial rme(me->pos - n->pos);
3517 Radial rother(other->pos - n->pos);
3519 if (screen) {
3520 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3521 } else {
3522 node_rotate_one_internal (*n, angle, rme, rother, both);
3523 }
3525 me->pos = n->pos + NR::Point(rme);
3527 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3528 other->pos = n->pos + NR::Point(rother);
3529 }
3531 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3532 // so here we just move all the knots without emitting move signals, for speed
3533 sp_node_update_handles(n, false);
3534 }
3536 /**
3537 * Rotate selected nodes.
3538 */
3539 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3540 {
3541 if (!nodepath || !nodepath->selected) return;
3543 if (g_list_length(nodepath->selected) == 1) {
3544 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3545 node_rotate_one (n, angle, which, screen);
3546 } else {
3547 // rotate as an object:
3549 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3550 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3551 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3552 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3553 box.expandTo (n->pos); // contain all selected nodes
3554 }
3556 gdouble rot;
3557 if (screen) {
3558 gdouble const zoom = nodepath->desktop->current_zoom();
3559 gdouble const zmove = angle / zoom;
3560 gdouble const r = NR::L2(box.max() - box.midpoint());
3561 rot = atan2(zmove, r);
3562 } else {
3563 rot = angle;
3564 }
3566 NR::Matrix t =
3567 NR::Matrix (NR::translate(-box.midpoint())) *
3568 NR::Matrix (NR::rotate(rot)) *
3569 NR::Matrix (NR::translate(box.midpoint()));
3571 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3572 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3573 n->pos *= t;
3574 n->n.pos *= t;
3575 n->p.pos *= t;
3576 sp_node_update_handles(n, false);
3577 }
3578 }
3580 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3581 }
3583 /**
3584 * Scale one node.
3585 */
3586 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3587 {
3588 bool both = false;
3589 Inkscape::NodePath::NodeSide *me, *other;
3591 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3592 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3594 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3595 me = &(n->p);
3596 other = &(n->n);
3597 n->code = NR_CURVETO;
3598 } else if (!n->p.other) {
3599 me = &(n->n);
3600 other = &(n->p);
3601 if (n->n.other)
3602 n->n.other->code = NR_CURVETO;
3603 } else {
3604 if (which > 0) { // right handle
3605 if (xn > xp) {
3606 me = &(n->n);
3607 other = &(n->p);
3608 if (n->n.other)
3609 n->n.other->code = NR_CURVETO;
3610 } else {
3611 me = &(n->p);
3612 other = &(n->n);
3613 n->code = NR_CURVETO;
3614 }
3615 } else if (which < 0){ // left handle
3616 if (xn <= xp) {
3617 me = &(n->n);
3618 other = &(n->p);
3619 if (n->n.other)
3620 n->n.other->code = NR_CURVETO;
3621 } else {
3622 me = &(n->p);
3623 other = &(n->n);
3624 n->code = NR_CURVETO;
3625 }
3626 } else { // both handles
3627 me = &(n->n);
3628 other = &(n->p);
3629 both = true;
3630 n->code = NR_CURVETO;
3631 if (n->n.other)
3632 n->n.other->code = NR_CURVETO;
3633 }
3634 }
3636 Radial rme(me->pos - n->pos);
3637 Radial rother(other->pos - n->pos);
3639 rme.r += grow;
3640 if (rme.r < 0) rme.r = 0;
3641 if (rme.a == HUGE_VAL) {
3642 if (me->other) { // if direction is unknown, initialize it towards the next node
3643 Radial rme_next(me->other->pos - n->pos);
3644 rme.a = rme_next.a;
3645 } else { // if there's no next, initialize to 0
3646 rme.a = 0;
3647 }
3648 }
3649 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3650 rother.r += grow;
3651 if (rother.r < 0) rother.r = 0;
3652 if (rother.a == HUGE_VAL) {
3653 rother.a = rme.a + M_PI;
3654 }
3655 }
3657 me->pos = n->pos + NR::Point(rme);
3659 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3660 other->pos = n->pos + NR::Point(rother);
3661 }
3663 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3664 // so here we just move all the knots without emitting move signals, for speed
3665 sp_node_update_handles(n, false);
3666 }
3668 /**
3669 * Scale selected nodes.
3670 */
3671 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3672 {
3673 if (!nodepath || !nodepath->selected) return;
3675 if (g_list_length(nodepath->selected) == 1) {
3676 // scale handles of the single selected node
3677 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3678 node_scale_one (n, grow, which);
3679 } else {
3680 // scale nodes as an "object":
3682 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3683 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3684 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3685 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3686 box.expandTo (n->pos); // contain all selected nodes
3687 }
3689 double scale = (box.maxExtent() + grow)/box.maxExtent();
3691 NR::Matrix t =
3692 NR::Matrix (NR::translate(-box.midpoint())) *
3693 NR::Matrix (NR::scale(scale, scale)) *
3694 NR::Matrix (NR::translate(box.midpoint()));
3696 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3697 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3698 n->pos *= t;
3699 n->n.pos *= t;
3700 n->p.pos *= t;
3701 sp_node_update_handles(n, false);
3702 }
3703 }
3705 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3706 }
3708 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3709 {
3710 if (!nodepath) return;
3711 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3712 }
3714 /**
3715 * Flip selected nodes horizontally/vertically.
3716 */
3717 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3718 {
3719 if (!nodepath || !nodepath->selected) return;
3721 if (g_list_length(nodepath->selected) == 1) {
3722 // flip handles of the single selected node
3723 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3724 double temp = n->p.pos[axis];
3725 n->p.pos[axis] = n->n.pos[axis];
3726 n->n.pos[axis] = temp;
3727 sp_node_update_handles(n, false);
3728 } else {
3729 // scale nodes as an "object":
3731 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3732 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3733 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3734 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3735 box.expandTo (n->pos); // contain all selected nodes
3736 }
3738 NR::Matrix t =
3739 NR::Matrix (NR::translate(-box.midpoint())) *
3740 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3741 NR::Matrix (NR::translate(box.midpoint()));
3743 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3744 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3745 n->pos *= t;
3746 n->n.pos *= t;
3747 n->p.pos *= t;
3748 sp_node_update_handles(n, false);
3749 }
3750 }
3752 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3753 }
3755 //-----------------------------------------------
3756 /**
3757 * Return new subpath under given nodepath.
3758 */
3759 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3760 {
3761 g_assert(nodepath);
3762 g_assert(nodepath->desktop);
3764 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3766 s->nodepath = nodepath;
3767 s->closed = FALSE;
3768 s->nodes = NULL;
3769 s->first = NULL;
3770 s->last = NULL;
3772 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3773 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3774 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3776 return s;
3777 }
3779 /**
3780 * Destroy nodes in subpath, then subpath itself.
3781 */
3782 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3783 {
3784 g_assert(subpath);
3785 g_assert(subpath->nodepath);
3786 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3788 while (subpath->nodes) {
3789 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3790 }
3792 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3794 g_free(subpath);
3795 }
3797 /**
3798 * Link head to tail in subpath.
3799 */
3800 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3801 {
3802 g_assert(!sp->closed);
3803 g_assert(sp->last != sp->first);
3804 g_assert(sp->first->code == NR_MOVETO);
3806 sp->closed = TRUE;
3808 //Link the head to the tail
3809 sp->first->p.other = sp->last;
3810 sp->last->n.other = sp->first;
3811 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3812 sp->first = sp->last;
3814 //Remove the extra end node
3815 sp_nodepath_node_destroy(sp->last->n.other);
3816 }
3818 /**
3819 * Open closed (loopy) subpath at node.
3820 */
3821 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3822 {
3823 g_assert(sp->closed);
3824 g_assert(n->subpath == sp);
3825 g_assert(sp->first == sp->last);
3827 /* We create new startpoint, current node will become last one */
3829 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3830 &n->pos, &n->pos, &n->n.pos);
3833 sp->closed = FALSE;
3835 //Unlink to make a head and tail
3836 sp->first = new_path;
3837 sp->last = n;
3838 n->n.other = NULL;
3839 new_path->p.other = NULL;
3840 }
3842 /**
3843 * Returns area in triangle given by points; may be negative.
3844 */
3845 inline double
3846 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3847 {
3848 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]);
3849 }
3851 /**
3852 * Return new node in subpath with given properties.
3853 * \param pos Position of node.
3854 * \param ppos Handle position in previous direction
3855 * \param npos Handle position in previous direction
3856 */
3857 Inkscape::NodePath::Node *
3858 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)
3859 {
3860 g_assert(sp);
3861 g_assert(sp->nodepath);
3862 g_assert(sp->nodepath->desktop);
3864 if (nodechunk == NULL)
3865 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3867 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3869 n->subpath = sp;
3871 if (type != Inkscape::NodePath::NODE_NONE) {
3872 // use the type from sodipodi:nodetypes
3873 n->type = type;
3874 } else {
3875 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3876 // points are (almost) collinear
3877 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3878 // endnode, or a node with a retracted handle
3879 n->type = Inkscape::NodePath::NODE_CUSP;
3880 } else {
3881 n->type = Inkscape::NodePath::NODE_SMOOTH;
3882 }
3883 } else {
3884 n->type = Inkscape::NodePath::NODE_CUSP;
3885 }
3886 }
3888 n->code = code;
3889 n->selected = FALSE;
3890 n->pos = *pos;
3891 n->p.pos = *ppos;
3892 n->n.pos = *npos;
3894 n->dragging_out = NULL;
3896 Inkscape::NodePath::Node *prev;
3897 if (next) {
3898 //g_assert(g_list_find(sp->nodes, next));
3899 prev = next->p.other;
3900 } else {
3901 prev = sp->last;
3902 }
3904 if (prev)
3905 prev->n.other = n;
3906 else
3907 sp->first = n;
3909 if (next)
3910 next->p.other = n;
3911 else
3912 sp->last = n;
3914 n->p.other = prev;
3915 n->n.other = next;
3917 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"));
3918 sp_knot_set_position(n->knot, pos, 0);
3920 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3921 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3922 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3923 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3924 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3925 sp_knot_update_ctrl(n->knot);
3927 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3928 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3929 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3930 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3931 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3932 sp_knot_show(n->knot);
3934 // We only create handle knots and lines on demand
3935 n->p.knot = NULL;
3936 n->p.line = NULL;
3937 n->n.knot = NULL;
3938 n->n.line = NULL;
3940 sp->nodes = g_list_prepend(sp->nodes, n);
3942 return n;
3943 }
3945 /**
3946 * Destroy node and its knots, link neighbors in subpath.
3947 */
3948 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3949 {
3950 g_assert(node);
3951 g_assert(node->subpath);
3952 g_assert(SP_IS_KNOT(node->knot));
3954 Inkscape::NodePath::SubPath *sp = node->subpath;
3956 if (node->selected) { // first, deselect
3957 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3958 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3959 }
3961 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3963 g_object_unref(G_OBJECT(node->knot));
3964 if (node->p.knot)
3965 g_object_unref(G_OBJECT(node->p.knot));
3966 if (node->n.knot)
3967 g_object_unref(G_OBJECT(node->n.knot));
3969 if (node->p.line)
3970 gtk_object_destroy(GTK_OBJECT(node->p.line));
3971 if (node->n.line)
3972 gtk_object_destroy(GTK_OBJECT(node->n.line));
3974 if (sp->nodes) { // there are others nodes on the subpath
3975 if (sp->closed) {
3976 if (sp->first == node) {
3977 g_assert(sp->last == node);
3978 sp->first = node->n.other;
3979 sp->last = sp->first;
3980 }
3981 node->p.other->n.other = node->n.other;
3982 node->n.other->p.other = node->p.other;
3983 } else {
3984 if (sp->first == node) {
3985 sp->first = node->n.other;
3986 sp->first->code = NR_MOVETO;
3987 }
3988 if (sp->last == node) sp->last = node->p.other;
3989 if (node->p.other) node->p.other->n.other = node->n.other;
3990 if (node->n.other) node->n.other->p.other = node->p.other;
3991 }
3992 } else { // this was the last node on subpath
3993 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3994 }
3996 g_mem_chunk_free(nodechunk, node);
3997 }
3999 /**
4000 * Returns one of the node's two sides.
4001 * \param which Indicates which side.
4002 * \return Pointer to previous node side if which==-1, next if which==1.
4003 */
4004 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4005 {
4006 g_assert(node);
4008 switch (which) {
4009 case -1:
4010 return &node->p;
4011 case 1:
4012 return &node->n;
4013 default:
4014 break;
4015 }
4017 g_assert_not_reached();
4019 return NULL;
4020 }
4022 /**
4023 * Return the other side of the node, given one of its sides.
4024 */
4025 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4026 {
4027 g_assert(node);
4029 if (me == &node->p) return &node->n;
4030 if (me == &node->n) return &node->p;
4032 g_assert_not_reached();
4034 return NULL;
4035 }
4037 /**
4038 * Return NRPathcode on the given side of the node.
4039 */
4040 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4041 {
4042 g_assert(node);
4044 if (me == &node->p) {
4045 if (node->p.other) return (NRPathcode)node->code;
4046 return NR_MOVETO;
4047 }
4049 if (me == &node->n) {
4050 if (node->n.other) return (NRPathcode)node->n.other->code;
4051 return NR_MOVETO;
4052 }
4054 g_assert_not_reached();
4056 return NR_END;
4057 }
4059 /**
4060 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4061 */
4062 Inkscape::NodePath::Node *
4063 sp_nodepath_get_node_by_index(int index)
4064 {
4065 Inkscape::NodePath::Node *e = NULL;
4067 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4068 if (!nodepath) {
4069 return e;
4070 }
4072 //find segment
4073 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4075 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4076 int n = g_list_length(sp->nodes);
4077 if (sp->closed) {
4078 n++;
4079 }
4081 //if the piece belongs to this subpath grab it
4082 //otherwise move onto the next subpath
4083 if (index < n) {
4084 e = sp->first;
4085 for (int i = 0; i < index; ++i) {
4086 e = e->n.other;
4087 }
4088 break;
4089 } else {
4090 if (sp->closed) {
4091 index -= (n+1);
4092 } else {
4093 index -= n;
4094 }
4095 }
4096 }
4098 return e;
4099 }
4101 /**
4102 * Returns plain text meaning of node type.
4103 */
4104 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4105 {
4106 unsigned retracted = 0;
4107 bool endnode = false;
4109 for (int which = -1; which <= 1; which += 2) {
4110 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4111 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4112 retracted ++;
4113 if (!side->other)
4114 endnode = true;
4115 }
4117 if (retracted == 0) {
4118 if (endnode) {
4119 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4120 return _("end node");
4121 } else {
4122 switch (node->type) {
4123 case Inkscape::NodePath::NODE_CUSP:
4124 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4125 return _("cusp");
4126 case Inkscape::NodePath::NODE_SMOOTH:
4127 // TRANSLATORS: "smooth" is an adjective here
4128 return _("smooth");
4129 case Inkscape::NodePath::NODE_SYMM:
4130 return _("symmetric");
4131 }
4132 }
4133 } else if (retracted == 1) {
4134 if (endnode) {
4135 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4136 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4137 } else {
4138 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4139 }
4140 } else {
4141 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4142 }
4144 return NULL;
4145 }
4147 /**
4148 * Handles content of statusbar as long as node tool is active.
4149 */
4150 void
4151 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
4152 {
4153 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");
4154 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4156 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4157 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4158 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4159 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4161 SPDesktop *desktop = NULL;
4162 if (nodepath) {
4163 desktop = nodepath->desktop;
4164 } else {
4165 desktop = SP_ACTIVE_DESKTOP;
4166 }
4168 SPEventContext *ec = desktop->event_context;
4169 if (!ec) return;
4170 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4171 if (!mc) return;
4173 if (selected_nodes == 0) {
4174 Inkscape::Selection *sel = desktop->selection;
4175 if (!sel || sel->isEmpty()) {
4176 mc->setF(Inkscape::NORMAL_MESSAGE,
4177 _("Select a single object to edit its nodes or handles."));
4178 } else {
4179 if (nodepath) {
4180 mc->setF(Inkscape::NORMAL_MESSAGE,
4181 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.",
4182 "<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.",
4183 total_nodes),
4184 total_nodes);
4185 } else {
4186 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4187 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4188 } else {
4189 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4190 }
4191 }
4192 }
4193 } else if (nodepath && selected_nodes == 1) {
4194 mc->setF(Inkscape::NORMAL_MESSAGE,
4195 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4196 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4197 total_nodes),
4198 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4199 } else {
4200 if (selected_subpaths > 1) {
4201 mc->setF(Inkscape::NORMAL_MESSAGE,
4202 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4203 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4204 total_nodes),
4205 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4206 } else {
4207 mc->setF(Inkscape::NORMAL_MESSAGE,
4208 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4209 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4210 total_nodes),
4211 selected_nodes, total_nodes, when_selected);
4212 }
4213 }
4214 }
4217 /*
4218 Local Variables:
4219 mode:c++
4220 c-file-style:"stroustrup"
4221 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4222 indent-tabs-mode:nil
4223 fill-column:99
4224 End:
4225 */
4226 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :