3f76a6548e1a603db04ecdb15f8f917fe5344d53
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, bool incremental, bool override);
99 static void sp_node_set_selected(Inkscape::NodePath::Node *node, bool 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 bool 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 bool 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 bool 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 //fixme: np can be NULL, so check before proceeding
483 g_return_if_fail(np != NULL);
485 update_repr_internal(np);
486 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
487 annotation);
489 if (np->livarot_path) {
490 delete np->livarot_path;
491 np->livarot_path = NULL;
492 }
494 if (np->path && SP_IS_ITEM(np->path)) {
495 np->livarot_path = Path_for_item (np->path, true, true);
496 if (np->livarot_path)
497 np->livarot_path->ConvertWithBackData(0.01);
498 }
500 }
502 /**
503 * Update XML path node with data from path object, commit changes with undo.
504 */
505 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
506 {
507 update_repr_internal(np);
508 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
509 annotation);
511 if (np->livarot_path) {
512 delete np->livarot_path;
513 np->livarot_path = NULL;
514 }
516 if (np->path && SP_IS_ITEM(np->path)) {
517 np->livarot_path = Path_for_item (np->path, true, true);
518 if (np->livarot_path)
519 np->livarot_path->ConvertWithBackData(0.01);
520 }
521 }
523 /**
524 * Make duplicate of path, replace corresponding XML node in tree, commit.
525 */
526 static void stamp_repr(Inkscape::NodePath::Path *np)
527 {
528 g_assert(np);
530 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
531 Inkscape::XML::Node *new_repr = old_repr->duplicate();
533 // remember the position of the item
534 gint pos = old_repr->position();
535 // remember parent
536 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
538 SPCurve *curve = create_curve(np);
539 gchar *typestr = create_typestr(np);
541 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
543 new_repr->setAttribute("d", svgpath);
544 new_repr->setAttribute("sodipodi:nodetypes", typestr);
546 // add the new repr to the parent
547 parent->appendChild(new_repr);
548 // move to the saved position
549 new_repr->setPosition(pos > 0 ? pos : 0);
551 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
552 _("Stamp"));
554 Inkscape::GC::release(new_repr);
555 g_free(svgpath);
556 g_free(typestr);
557 sp_curve_unref(curve);
558 }
560 /**
561 * Create curve from path.
562 */
563 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
564 {
565 SPCurve *curve = sp_curve_new();
567 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
568 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
569 sp_curve_moveto(curve,
570 sp->first->pos * np->d2i);
571 Inkscape::NodePath::Node *n = sp->first->n.other;
572 while (n) {
573 NR::Point const end_pt = n->pos * np->d2i;
574 switch (n->code) {
575 case NR_LINETO:
576 sp_curve_lineto(curve, end_pt);
577 break;
578 case NR_CURVETO:
579 sp_curve_curveto(curve,
580 n->p.other->n.pos * np->d2i,
581 n->p.pos * np->d2i,
582 end_pt);
583 break;
584 default:
585 g_assert_not_reached();
586 break;
587 }
588 if (n != sp->last) {
589 n = n->n.other;
590 } else {
591 n = NULL;
592 }
593 }
594 if (sp->closed) {
595 sp_curve_closepath(curve);
596 }
597 }
599 return curve;
600 }
602 /**
603 * Convert path type string to sodipodi:nodetypes style.
604 */
605 static gchar *create_typestr(Inkscape::NodePath::Path *np)
606 {
607 gchar *typestr = g_new(gchar, 32);
608 gint len = 32;
609 gint pos = 0;
611 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
612 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
614 if (pos >= len) {
615 typestr = g_renew(gchar, typestr, len + 32);
616 len += 32;
617 }
619 typestr[pos++] = 'c';
621 Inkscape::NodePath::Node *n;
622 n = sp->first->n.other;
623 while (n) {
624 gchar code;
626 switch (n->type) {
627 case Inkscape::NodePath::NODE_CUSP:
628 code = 'c';
629 break;
630 case Inkscape::NodePath::NODE_SMOOTH:
631 code = 's';
632 break;
633 case Inkscape::NodePath::NODE_SYMM:
634 code = 'z';
635 break;
636 default:
637 g_assert_not_reached();
638 code = '\0';
639 break;
640 }
642 if (pos >= len) {
643 typestr = g_renew(gchar, typestr, len + 32);
644 len += 32;
645 }
647 typestr[pos++] = code;
649 if (n != sp->last) {
650 n = n->n.other;
651 } else {
652 n = NULL;
653 }
654 }
655 }
657 if (pos >= len) {
658 typestr = g_renew(gchar, typestr, len + 1);
659 len += 1;
660 }
662 typestr[pos++] = '\0';
664 return typestr;
665 }
667 /**
668 * Returns current path in context.
669 */
670 static Inkscape::NodePath::Path *sp_nodepath_current()
671 {
672 if (!SP_ACTIVE_DESKTOP) {
673 return NULL;
674 }
676 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
678 if (!SP_IS_NODE_CONTEXT(event_context)) {
679 return NULL;
680 }
682 return SP_NODE_CONTEXT(event_context)->nodepath;
683 }
687 /**
688 \brief Fills node and handle positions for three nodes, splitting line
689 marked by end at distance t.
690 */
691 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
692 {
693 g_assert(new_path != NULL);
694 g_assert(end != NULL);
696 g_assert(end->p.other == new_path);
697 Inkscape::NodePath::Node *start = new_path->p.other;
698 g_assert(start);
700 if (end->code == NR_LINETO) {
701 new_path->type =Inkscape::NodePath::NODE_CUSP;
702 new_path->code = NR_LINETO;
703 new_path->pos = (t * start->pos + (1 - t) * end->pos);
704 } else {
705 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
706 new_path->code = NR_CURVETO;
707 gdouble s = 1 - t;
708 for (int dim = 0; dim < 2; dim++) {
709 NR::Coord const f000 = start->pos[dim];
710 NR::Coord const f001 = start->n.pos[dim];
711 NR::Coord const f011 = end->p.pos[dim];
712 NR::Coord const f111 = end->pos[dim];
713 NR::Coord const f00t = s * f000 + t * f001;
714 NR::Coord const f01t = s * f001 + t * f011;
715 NR::Coord const f11t = s * f011 + t * f111;
716 NR::Coord const f0tt = s * f00t + t * f01t;
717 NR::Coord const f1tt = s * f01t + t * f11t;
718 NR::Coord const fttt = s * f0tt + t * f1tt;
719 start->n.pos[dim] = f00t;
720 new_path->p.pos[dim] = f0tt;
721 new_path->pos[dim] = fttt;
722 new_path->n.pos[dim] = f1tt;
723 end->p.pos[dim] = f11t;
724 }
725 }
726 }
728 /**
729 * Adds new node on direct line between two nodes, activates handles of all
730 * three nodes.
731 */
732 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
733 {
734 g_assert(end);
735 g_assert(end->subpath);
736 g_assert(g_list_find(end->subpath->nodes, end));
738 Inkscape::NodePath::Node *start = end->p.other;
739 g_assert( start->n.other == end );
740 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
741 end,
742 Inkscape::NodePath::NODE_SMOOTH,
743 (NRPathcode)end->code,
744 &start->pos, &start->pos, &start->n.pos);
745 sp_nodepath_line_midpoint(newnode, end, t);
747 sp_node_update_handles(start);
748 sp_node_update_handles(newnode);
749 sp_node_update_handles(end);
751 return newnode;
752 }
754 /**
755 \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
756 */
757 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
758 {
759 g_assert(node);
760 g_assert(node->subpath);
761 g_assert(g_list_find(node->subpath->nodes, node));
763 Inkscape::NodePath::SubPath *sp = node->subpath;
764 Inkscape::NodePath::Path *np = sp->nodepath;
766 if (sp->closed) {
767 sp_nodepath_subpath_open(sp, node);
768 return sp->first;
769 } else {
770 // no break for end nodes
771 if (node == sp->first) return NULL;
772 if (node == sp->last ) return NULL;
774 // create a new subpath
775 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
777 // duplicate the break node as start of the new subpath
778 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
780 while (node->n.other) { // copy the remaining nodes into the new subpath
781 Inkscape::NodePath::Node *n = node->n.other;
782 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);
783 if (n->selected) {
784 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
785 }
786 sp_nodepath_node_destroy(n); // remove the point on the original subpath
787 }
789 return newnode;
790 }
791 }
793 /**
794 * Duplicate node and connect to neighbours.
795 */
796 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
797 {
798 g_assert(node);
799 g_assert(node->subpath);
800 g_assert(g_list_find(node->subpath->nodes, node));
802 Inkscape::NodePath::SubPath *sp = node->subpath;
804 NRPathcode code = (NRPathcode) node->code;
805 if (code == NR_MOVETO) { // if node is the endnode,
806 node->code = NR_LINETO; // new one is inserted before it, so change that to line
807 }
809 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
811 if (!node->n.other || !node->p.other) // if node is an endnode, select it
812 return node;
813 else
814 return newnode; // otherwise select the newly created node
815 }
817 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
818 {
819 node->p.pos = (node->pos + (node->pos - node->n.pos));
820 }
822 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
823 {
824 node->n.pos = (node->pos + (node->pos - node->p.pos));
825 }
827 /**
828 * Change line type at node, with side effects on neighbours.
829 */
830 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
831 {
832 g_assert(end);
833 g_assert(end->subpath);
834 g_assert(end->p.other);
836 if (end->code == static_cast< guint > ( code ) )
837 return;
839 Inkscape::NodePath::Node *start = end->p.other;
841 end->code = code;
843 if (code == NR_LINETO) {
844 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
845 if (end->n.other) {
846 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
847 }
848 sp_node_adjust_handle(start, -1);
849 sp_node_adjust_handle(end, 1);
850 } else {
851 NR::Point delta = end->pos - start->pos;
852 start->n.pos = start->pos + delta / 3;
853 end->p.pos = end->pos - delta / 3;
854 sp_node_adjust_handle(start, 1);
855 sp_node_adjust_handle(end, -1);
856 }
858 sp_node_update_handles(start);
859 sp_node_update_handles(end);
860 }
862 /**
863 * Change node type, and its handles accordingly.
864 */
865 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
866 {
867 g_assert(node);
868 g_assert(node->subpath);
870 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
871 return node;
873 if ((node->p.other != NULL) && (node->n.other != NULL)) {
874 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
875 type =Inkscape::NodePath::NODE_CUSP;
876 }
877 }
879 node->type = type;
881 if (node->type == Inkscape::NodePath::NODE_CUSP) {
882 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
883 node->knot->setSize (node->selected? 11 : 9);
884 sp_knot_update_ctrl(node->knot);
885 } else {
886 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
887 node->knot->setSize (node->selected? 9 : 7);
888 sp_knot_update_ctrl(node->knot);
889 }
891 // if one of handles is mouseovered, preserve its position
892 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
893 sp_node_adjust_handle(node, 1);
894 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
895 sp_node_adjust_handle(node, -1);
896 } else {
897 sp_node_adjust_handles(node);
898 }
900 sp_node_update_handles(node);
902 sp_nodepath_update_statusbar(node->subpath->nodepath);
904 return node;
905 }
907 /**
908 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
909 * adjacent segments from lines to curves.
910 */
911 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
912 {
913 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
914 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
915 // convert adjacent segment BEFORE to curve
916 node->code = NR_CURVETO;
917 NR::Point delta;
918 if (node->n.other != NULL)
919 delta = node->n.other->pos - node->p.other->pos;
920 else
921 delta = node->pos - node->p.other->pos;
922 node->p.pos = node->pos - delta / 4;
923 sp_node_update_handles(node);
924 }
926 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
927 // convert adjacent segment AFTER to curve
928 node->n.other->code = NR_CURVETO;
929 NR::Point delta;
930 if (node->p.other != NULL)
931 delta = node->p.other->pos - node->n.other->pos;
932 else
933 delta = node->pos - node->n.other->pos;
934 node->n.pos = node->pos - delta / 4;
935 sp_node_update_handles(node);
936 }
937 }
939 sp_nodepath_set_node_type (node, type);
940 }
942 /**
943 * Move node to point, and adjust its and neighbouring handles.
944 */
945 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
946 {
947 NR::Point delta = p - node->pos;
948 node->pos = p;
950 node->p.pos += delta;
951 node->n.pos += delta;
953 if (node->p.other) {
954 if (node->code == NR_LINETO) {
955 sp_node_adjust_handle(node, 1);
956 sp_node_adjust_handle(node->p.other, -1);
957 }
958 }
959 if (node->n.other) {
960 if (node->n.other->code == NR_LINETO) {
961 sp_node_adjust_handle(node, -1);
962 sp_node_adjust_handle(node->n.other, 1);
963 }
964 }
966 // this function is only called from batch movers that will update display at the end
967 // themselves, so here we just move all the knots without emitting move signals, for speed
968 sp_node_update_handles(node, false);
969 }
971 /**
972 * Call sp_node_moveto() for node selection and handle possible snapping.
973 */
974 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
975 bool const snap = true)
976 {
977 NR::Coord best = NR_HUGE;
978 NR::Point delta(dx, dy);
979 NR::Point best_pt = delta;
981 if (snap) {
982 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
984 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
985 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
986 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
987 if (s.getDistance() < best) {
988 best = s.getDistance();
989 best_pt = s.getPoint() - n->pos;
990 }
991 }
992 }
994 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
995 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
996 sp_node_moveto(n, n->pos + best_pt);
997 }
999 // do not update repr here so that node dragging is acceptably fast
1000 update_object(nodepath);
1001 }
1003 /**
1004 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1005 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1006 near x = 0.
1007 */
1008 double
1009 sculpt_profile (double x, double alpha, guint profile)
1010 {
1011 if (x >= 1)
1012 return 0;
1013 if (x <= 0)
1014 return 1;
1016 switch (profile) {
1017 case SCULPT_PROFILE_LINEAR:
1018 return 1 - x;
1019 case SCULPT_PROFILE_BELL:
1020 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1021 case SCULPT_PROFILE_ELLIPTIC:
1022 return sqrt(1 - x*x);
1023 }
1025 return 1;
1026 }
1028 double
1029 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1030 {
1031 // extremely primitive for now, don't have time to look for the real one
1032 double lower = NR::L2(b - a);
1033 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1034 return (lower + upper)/2;
1035 }
1037 void
1038 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1039 {
1040 n->pos = n->origin + delta;
1041 n->n.pos = n->n.origin + delta_n;
1042 n->p.pos = n->p.origin + delta_p;
1043 sp_node_adjust_handles(n);
1044 sp_node_update_handles(n, false);
1045 }
1047 /**
1048 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1049 * on how far they are from the dragged node n.
1050 */
1051 static void
1052 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1053 {
1054 g_assert (n);
1055 g_assert (nodepath);
1056 g_assert (n->subpath->nodepath == nodepath);
1058 double pressure = n->knot->pressure;
1059 if (pressure == 0)
1060 pressure = 0.5; // default
1061 pressure = CLAMP (pressure, 0.2, 0.8);
1063 // map pressure to alpha = 1/5 ... 5
1064 double alpha = 1 - 2 * fabs(pressure - 0.5);
1065 if (pressure > 0.5)
1066 alpha = 1/alpha;
1068 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1070 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1071 // Only one subpath has selected nodes:
1072 // use linear mode, where the distance from n to node being dragged is calculated along the path
1074 double n_sel_range = 0, p_sel_range = 0;
1075 guint n_nodes = 0, p_nodes = 0;
1076 guint n_sel_nodes = 0, p_sel_nodes = 0;
1078 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1079 {
1080 double n_range = 0, p_range = 0;
1081 bool n_going = true, p_going = true;
1082 Inkscape::NodePath::Node *n_node = n;
1083 Inkscape::NodePath::Node *p_node = n;
1084 do {
1085 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1086 if (n_node && n_going)
1087 n_node = n_node->n.other;
1088 if (n_node == NULL) {
1089 n_going = false;
1090 } else {
1091 n_nodes ++;
1092 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1093 if (n_node->selected) {
1094 n_sel_nodes ++;
1095 n_sel_range = n_range;
1096 }
1097 if (n_node == p_node) {
1098 n_going = false;
1099 p_going = false;
1100 }
1101 }
1102 if (p_node && p_going)
1103 p_node = p_node->p.other;
1104 if (p_node == NULL) {
1105 p_going = false;
1106 } else {
1107 p_nodes ++;
1108 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1109 if (p_node->selected) {
1110 p_sel_nodes ++;
1111 p_sel_range = p_range;
1112 }
1113 if (p_node == n_node) {
1114 n_going = false;
1115 p_going = false;
1116 }
1117 }
1118 } while (n_going || p_going);
1119 }
1121 // Second pass: actually move nodes in this subpath
1122 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1123 {
1124 double n_range = 0, p_range = 0;
1125 bool n_going = true, p_going = true;
1126 Inkscape::NodePath::Node *n_node = n;
1127 Inkscape::NodePath::Node *p_node = n;
1128 do {
1129 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1130 if (n_node && n_going)
1131 n_node = n_node->n.other;
1132 if (n_node == NULL) {
1133 n_going = false;
1134 } else {
1135 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1136 if (n_node->selected) {
1137 sp_nodepath_move_node_and_handles (n_node,
1138 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1139 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1140 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1141 }
1142 if (n_node == p_node) {
1143 n_going = false;
1144 p_going = false;
1145 }
1146 }
1147 if (p_node && p_going)
1148 p_node = p_node->p.other;
1149 if (p_node == NULL) {
1150 p_going = false;
1151 } else {
1152 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1153 if (p_node->selected) {
1154 sp_nodepath_move_node_and_handles (p_node,
1155 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1156 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1157 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1158 }
1159 if (p_node == n_node) {
1160 n_going = false;
1161 p_going = false;
1162 }
1163 }
1164 } while (n_going || p_going);
1165 }
1167 } else {
1168 // Multiple subpaths have selected nodes:
1169 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1170 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1171 // fix the pear-like shape when sculpting e.g. a ring
1173 // First pass: calculate range
1174 gdouble direct_range = 0;
1175 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1176 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1177 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1178 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1179 if (node->selected) {
1180 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1181 }
1182 }
1183 }
1185 // Second pass: actually move nodes
1186 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1187 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1188 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1189 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1190 if (node->selected) {
1191 if (direct_range > 1e-6) {
1192 sp_nodepath_move_node_and_handles (node,
1193 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1194 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1195 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1196 } else {
1197 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1198 }
1200 }
1201 }
1202 }
1203 }
1205 // do not update repr here so that node dragging is acceptably fast
1206 update_object(nodepath);
1207 }
1210 /**
1211 * Move node selection to point, adjust its and neighbouring handles,
1212 * handle possible snapping, and commit the change with possible undo.
1213 */
1214 void
1215 sp_node_selected_move(gdouble dx, gdouble dy)
1216 {
1217 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1218 if (!nodepath) return;
1220 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1222 if (dx == 0) {
1223 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1224 } else if (dy == 0) {
1225 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1226 } else {
1227 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1228 }
1229 }
1231 /**
1232 * Move node selection off screen and commit the change.
1233 */
1234 void
1235 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1236 {
1237 // borrowed from sp_selection_move_screen in selection-chemistry.c
1238 // we find out the current zoom factor and divide deltas by it
1239 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1241 gdouble zoom = desktop->current_zoom();
1242 gdouble zdx = dx / zoom;
1243 gdouble zdy = dy / zoom;
1245 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1246 if (!nodepath) return;
1248 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1250 if (dx == 0) {
1251 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1252 } else if (dy == 0) {
1253 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1254 } else {
1255 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1256 }
1257 }
1259 /** If they don't yet exist, creates knot and line for the given side of the node */
1260 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1261 {
1262 if (!side->knot) {
1263 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"));
1265 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1266 side->knot->setSize (7);
1267 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1268 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1269 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1270 sp_knot_update_ctrl(side->knot);
1272 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1273 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1274 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1275 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1276 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1277 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1278 }
1280 if (!side->line) {
1281 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1282 SP_TYPE_CTRLLINE, NULL);
1283 }
1284 }
1286 /**
1287 * Ensure the given handle of the node is visible/invisible, update its screen position
1288 */
1289 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, bool show_handle, bool fire_move_signals)
1290 {
1291 g_assert(node != NULL);
1293 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1294 NRPathcode code = sp_node_path_code_from_side(node, side);
1296 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1298 if (show_handle) {
1299 if (!side->knot) { // No handle knot at all
1300 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1301 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1302 side->knot->pos = side->pos;
1303 if (side->knot->item)
1304 SP_CTRL(side->knot->item)->moveto(side->pos);
1305 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1306 sp_knot_show(side->knot);
1307 } else {
1308 if (side->knot->pos != side->pos) { // only if it's really moved
1309 if (fire_move_signals) {
1310 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1311 } else {
1312 sp_knot_moveto(side->knot, &side->pos);
1313 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1314 }
1315 }
1316 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1317 sp_knot_show(side->knot);
1318 }
1319 }
1320 sp_canvas_item_show(side->line);
1321 } else {
1322 if (side->knot) {
1323 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1324 sp_knot_hide(side->knot);
1325 }
1326 }
1327 if (side->line) {
1328 sp_canvas_item_hide(side->line);
1329 }
1330 }
1331 }
1333 /**
1334 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1335 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1336 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1337 * updated; otherwise, just move the knots silently (used in batch moves).
1338 */
1339 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1340 {
1341 g_assert(node != NULL);
1343 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1344 sp_knot_show(node->knot);
1345 }
1347 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1348 if (fire_move_signals)
1349 sp_knot_set_position(node->knot, &node->pos, 0);
1350 else
1351 sp_knot_moveto(node->knot, &node->pos);
1352 }
1354 bool show_handles = node->selected;
1355 if (node->p.other != NULL) {
1356 if (node->p.other->selected) show_handles = TRUE;
1357 }
1358 if (node->n.other != NULL) {
1359 if (node->n.other->selected) show_handles = TRUE;
1360 }
1362 if (node->subpath->nodepath->show_handles == false)
1363 show_handles = FALSE;
1365 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1366 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1367 }
1369 /**
1370 * Call sp_node_update_handles() for all nodes on subpath.
1371 */
1372 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1373 {
1374 g_assert(subpath != NULL);
1376 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1377 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1378 }
1379 }
1381 /**
1382 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1383 */
1384 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1385 {
1386 g_assert(nodepath != NULL);
1388 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1389 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1390 }
1391 }
1393 void
1394 sp_nodepath_show_handles(bool show)
1395 {
1396 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1397 if (nodepath == NULL) return;
1399 nodepath->show_handles = show;
1400 sp_nodepath_update_handles(nodepath);
1401 }
1403 /**
1404 * Adds all selected nodes in nodepath to list.
1405 */
1406 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1407 {
1408 StlConv<Node *>::list(l, selected);
1409 /// \todo this adds a copying, rework when the selection becomes a stl list
1410 }
1412 /**
1413 * Align selected nodes on the specified axis.
1414 */
1415 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1416 {
1417 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1418 return;
1419 }
1421 if ( !nodepath->selected->next ) { // only one node selected
1422 return;
1423 }
1424 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1425 NR::Point dest(pNode->pos);
1426 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1427 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1428 if (pNode) {
1429 dest[axis] = pNode->pos[axis];
1430 sp_node_moveto(pNode, dest);
1431 }
1432 }
1434 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1435 }
1437 /// Helper struct.
1438 struct NodeSort
1439 {
1440 Inkscape::NodePath::Node *_node;
1441 NR::Coord _coord;
1442 /// \todo use vectorof pointers instead of calling copy ctor
1443 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1444 _node(node), _coord(node->pos[axis])
1445 {}
1447 };
1449 static bool operator<(NodeSort const &a, NodeSort const &b)
1450 {
1451 return (a._coord < b._coord);
1452 }
1454 /**
1455 * Distribute selected nodes on the specified axis.
1456 */
1457 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1458 {
1459 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1460 return;
1461 }
1463 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1464 return;
1465 }
1467 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1468 std::vector<NodeSort> sorted;
1469 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1470 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1471 if (pNode) {
1472 NodeSort n(pNode, axis);
1473 sorted.push_back(n);
1474 //dest[axis] = pNode->pos[axis];
1475 //sp_node_moveto(pNode, dest);
1476 }
1477 }
1478 std::sort(sorted.begin(), sorted.end());
1479 unsigned int len = sorted.size();
1480 //overall bboxes span
1481 float dist = (sorted.back()._coord -
1482 sorted.front()._coord);
1483 //new distance between each bbox
1484 float step = (dist) / (len - 1);
1485 float pos = sorted.front()._coord;
1486 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1487 it < sorted.end();
1488 it ++ )
1489 {
1490 NR::Point dest((*it)._node->pos);
1491 dest[axis] = pos;
1492 sp_node_moveto((*it)._node, dest);
1493 pos += step;
1494 }
1496 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1497 }
1500 /**
1501 * Call sp_nodepath_line_add_node() for all selected segments.
1502 */
1503 void
1504 sp_node_selected_add_node(void)
1505 {
1506 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1507 if (!nodepath) {
1508 return;
1509 }
1511 GList *nl = NULL;
1513 int n_added = 0;
1515 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1516 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1517 g_assert(t->selected);
1518 if (t->p.other && t->p.other->selected) {
1519 nl = g_list_prepend(nl, t);
1520 }
1521 }
1523 while (nl) {
1524 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1525 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1526 sp_nodepath_node_select(n, TRUE, FALSE);
1527 n_added ++;
1528 nl = g_list_remove(nl, t);
1529 }
1531 /** \todo fixme: adjust ? */
1532 sp_nodepath_update_handles(nodepath);
1534 if (n_added > 1) {
1535 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1536 } else if (n_added > 0) {
1537 sp_nodepath_update_repr(nodepath, _("Add node"));
1538 }
1540 sp_nodepath_update_statusbar(nodepath);
1541 }
1543 /**
1544 * Select segment nearest to point
1545 */
1546 void
1547 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1548 {
1549 if (!nodepath) {
1550 return;
1551 }
1553 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1555 //find segment to segment
1556 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1558 //fixme: this can return NULL, so check before proceeding.
1559 g_return_if_fail(e != NULL);
1561 bool force = FALSE;
1562 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1563 force = TRUE;
1564 }
1565 sp_nodepath_node_select(e, (bool) toggle, force);
1566 if (e->p.other)
1567 sp_nodepath_node_select(e->p.other, TRUE, force);
1569 sp_nodepath_update_handles(nodepath);
1571 sp_nodepath_update_statusbar(nodepath);
1572 }
1574 /**
1575 * Add a node nearest to point
1576 */
1577 void
1578 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1579 {
1580 if (!nodepath) {
1581 return;
1582 }
1584 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1586 //find segment to split
1587 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1589 //don't know why but t seems to flip for lines
1590 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1591 position.t = 1.0 - position.t;
1592 }
1593 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1594 sp_nodepath_node_select(n, FALSE, TRUE);
1596 /* fixme: adjust ? */
1597 sp_nodepath_update_handles(nodepath);
1599 sp_nodepath_update_repr(nodepath, _("Add node"));
1601 sp_nodepath_update_statusbar(nodepath);
1602 }
1604 /*
1605 * Adjusts a segment so that t moves by a certain delta for dragging
1606 * converts lines to curves
1607 *
1608 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1609 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1610 */
1611 void
1612 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1613 {
1614 //fixme: e and e->p can be NULL, so check for those before proceeding
1615 g_return_if_fail(e != NULL);
1616 g_return_if_fail(&e->p != NULL);
1618 /* feel good is an arbitrary parameter that distributes the delta between handles
1619 * if t of the drag point is less than 1/6 distance form the endpoint only
1620 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1621 */
1622 double feel_good;
1623 if (t <= 1.0 / 6.0)
1624 feel_good = 0;
1625 else if (t <= 0.5)
1626 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1627 else if (t <= 5.0 / 6.0)
1628 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1629 else
1630 feel_good = 1;
1632 //if we're dragging a line convert it to a curve
1633 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1634 sp_nodepath_set_line_type(e, NR_CURVETO);
1635 }
1637 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1638 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1639 e->p.other->n.pos += offsetcoord0;
1640 e->p.pos += offsetcoord1;
1642 // adjust handles of adjacent nodes where necessary
1643 sp_node_adjust_handle(e,1);
1644 sp_node_adjust_handle(e->p.other,-1);
1646 sp_nodepath_update_handles(e->subpath->nodepath);
1648 update_object(e->subpath->nodepath);
1650 sp_nodepath_update_statusbar(e->subpath->nodepath);
1651 }
1654 /**
1655 * Call sp_nodepath_break() for all selected segments.
1656 */
1657 void sp_node_selected_break()
1658 {
1659 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1660 if (!nodepath) return;
1662 GList *temp = NULL;
1663 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1664 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1665 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1666 if (nn == NULL) continue; // no break, no new node
1667 temp = g_list_prepend(temp, nn);
1668 }
1670 if (temp) {
1671 sp_nodepath_deselect(nodepath);
1672 }
1673 for (GList *l = temp; l != NULL; l = l->next) {
1674 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1675 }
1677 sp_nodepath_update_handles(nodepath);
1679 sp_nodepath_update_repr(nodepath, _("Break path"));
1680 }
1682 /**
1683 * Duplicate the selected node(s).
1684 */
1685 void sp_node_selected_duplicate()
1686 {
1687 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1688 if (!nodepath) {
1689 return;
1690 }
1692 GList *temp = NULL;
1693 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1694 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1695 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1696 if (nn == NULL) continue; // could not duplicate
1697 temp = g_list_prepend(temp, nn);
1698 }
1700 if (temp) {
1701 sp_nodepath_deselect(nodepath);
1702 }
1703 for (GList *l = temp; l != NULL; l = l->next) {
1704 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1705 }
1707 sp_nodepath_update_handles(nodepath);
1709 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1710 }
1712 /**
1713 * Join two nodes by merging them into one.
1714 */
1715 void sp_node_selected_join()
1716 {
1717 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1718 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1720 if (g_list_length(nodepath->selected) != 2) {
1721 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1722 return;
1723 }
1725 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1726 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1728 g_assert(a != b);
1729 g_assert(a->p.other || a->n.other);
1730 g_assert(b->p.other || b->n.other);
1732 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1733 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1734 return;
1735 }
1737 /* a and b are endpoints */
1739 NR::Point c;
1740 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1741 c = a->pos;
1742 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1743 c = b->pos;
1744 } else {
1745 c = (a->pos + b->pos) / 2;
1746 }
1748 if (a->subpath == b->subpath) {
1749 Inkscape::NodePath::SubPath *sp = a->subpath;
1750 sp_nodepath_subpath_close(sp);
1751 sp_node_moveto (sp->first, c);
1753 sp_nodepath_update_handles(sp->nodepath);
1754 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1755 return;
1756 }
1758 /* a and b are separate subpaths */
1759 Inkscape::NodePath::SubPath *sa = a->subpath;
1760 Inkscape::NodePath::SubPath *sb = b->subpath;
1761 NR::Point p;
1762 Inkscape::NodePath::Node *n;
1763 NRPathcode code;
1764 if (a == sa->first) {
1765 p = sa->first->n.pos;
1766 code = (NRPathcode)sa->first->n.other->code;
1767 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1768 n = sa->last;
1769 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1770 n = n->p.other;
1771 while (n) {
1772 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1773 n = n->p.other;
1774 if (n == sa->first) n = NULL;
1775 }
1776 sp_nodepath_subpath_destroy(sa);
1777 sa = t;
1778 } else if (a == sa->last) {
1779 p = sa->last->p.pos;
1780 code = (NRPathcode)sa->last->code;
1781 sp_nodepath_node_destroy(sa->last);
1782 } else {
1783 code = NR_END;
1784 g_assert_not_reached();
1785 }
1787 if (b == sb->first) {
1788 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1789 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1790 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1791 }
1792 } else if (b == sb->last) {
1793 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1794 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1795 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1796 }
1797 } else {
1798 g_assert_not_reached();
1799 }
1800 /* and now destroy sb */
1802 sp_nodepath_subpath_destroy(sb);
1804 sp_nodepath_update_handles(sa->nodepath);
1806 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1808 sp_nodepath_update_statusbar(nodepath);
1809 }
1811 /**
1812 * Join two nodes by adding a segment between them.
1813 */
1814 void sp_node_selected_join_segment()
1815 {
1816 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1817 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1819 if (g_list_length(nodepath->selected) != 2) {
1820 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1821 return;
1822 }
1824 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1825 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1827 g_assert(a != b);
1828 g_assert(a->p.other || a->n.other);
1829 g_assert(b->p.other || b->n.other);
1831 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1832 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1833 return;
1834 }
1836 if (a->subpath == b->subpath) {
1837 Inkscape::NodePath::SubPath *sp = a->subpath;
1839 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1840 sp->closed = TRUE;
1842 sp->first->p.other = sp->last;
1843 sp->last->n.other = sp->first;
1845 sp_node_handle_mirror_p_to_n(sp->last);
1846 sp_node_handle_mirror_n_to_p(sp->first);
1848 sp->first->code = sp->last->code;
1849 sp->first = sp->last;
1851 sp_nodepath_update_handles(sp->nodepath);
1853 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1855 return;
1856 }
1858 /* a and b are separate subpaths */
1859 Inkscape::NodePath::SubPath *sa = a->subpath;
1860 Inkscape::NodePath::SubPath *sb = b->subpath;
1862 Inkscape::NodePath::Node *n;
1863 NR::Point p;
1864 NRPathcode code;
1865 if (a == sa->first) {
1866 code = (NRPathcode) sa->first->n.other->code;
1867 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1868 n = sa->last;
1869 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1870 for (n = n->p.other; n != NULL; n = n->p.other) {
1871 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1872 }
1873 sp_nodepath_subpath_destroy(sa);
1874 sa = t;
1875 } else if (a == sa->last) {
1876 code = (NRPathcode)sa->last->code;
1877 } else {
1878 code = NR_END;
1879 g_assert_not_reached();
1880 }
1882 if (b == sb->first) {
1883 n = sb->first;
1884 sp_node_handle_mirror_p_to_n(sa->last);
1885 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1886 sp_node_handle_mirror_n_to_p(sa->last);
1887 for (n = n->n.other; n != NULL; n = n->n.other) {
1888 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1889 }
1890 } else if (b == sb->last) {
1891 n = sb->last;
1892 sp_node_handle_mirror_p_to_n(sa->last);
1893 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1894 sp_node_handle_mirror_n_to_p(sa->last);
1895 for (n = n->p.other; n != NULL; n = n->p.other) {
1896 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1897 }
1898 } else {
1899 g_assert_not_reached();
1900 }
1901 /* and now destroy sb */
1903 sp_nodepath_subpath_destroy(sb);
1905 sp_nodepath_update_handles(sa->nodepath);
1907 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1908 }
1910 /**
1911 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1912 */
1913 void sp_node_delete_preserve(GList *nodes_to_delete)
1914 {
1915 GSList *nodepaths = NULL;
1917 while (nodes_to_delete) {
1918 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1919 Inkscape::NodePath::SubPath *sp = node->subpath;
1920 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1921 Inkscape::NodePath::Node *sample_cursor = NULL;
1922 Inkscape::NodePath::Node *sample_end = NULL;
1923 Inkscape::NodePath::Node *delete_cursor = node;
1924 bool just_delete = false;
1926 //find the start of this contiguous selection
1927 //move left to the first node that is not selected
1928 //or the start of the non-closed path
1929 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1930 delete_cursor = curr;
1931 }
1933 //just delete at the beginning of an open path
1934 if (!delete_cursor->p.other) {
1935 sample_cursor = delete_cursor;
1936 just_delete = true;
1937 } else {
1938 sample_cursor = delete_cursor->p.other;
1939 }
1941 //calculate points for each segment
1942 int rate = 5;
1943 float period = 1.0 / rate;
1944 std::vector<NR::Point> data;
1945 if (!just_delete) {
1946 data.push_back(sample_cursor->pos);
1947 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1948 //just delete at the end of an open path
1949 if (!sp->closed && curr == sp->last) {
1950 just_delete = true;
1951 break;
1952 }
1954 //sample points on the contiguous selected segment
1955 NR::Point *bez;
1956 bez = new NR::Point [4];
1957 bez[0] = curr->pos;
1958 bez[1] = curr->n.pos;
1959 bez[2] = curr->n.other->p.pos;
1960 bez[3] = curr->n.other->pos;
1961 for (int i=1; i<rate; i++) {
1962 gdouble t = i * period;
1963 NR::Point p = bezier_pt(3, bez, t);
1964 data.push_back(p);
1965 }
1966 data.push_back(curr->n.other->pos);
1968 sample_end = curr->n.other;
1969 //break if we've come full circle or hit the end of the selection
1970 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1971 break;
1972 }
1973 }
1974 }
1976 if (!just_delete) {
1977 //calculate the best fitting single segment and adjust the endpoints
1978 NR::Point *adata;
1979 adata = new NR::Point [data.size()];
1980 copy(data.begin(), data.end(), adata);
1982 NR::Point *bez;
1983 bez = new NR::Point [4];
1984 //would decreasing error create a better fitting approximation?
1985 gdouble error = 1.0;
1986 gint ret;
1987 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1989 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
1990 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
1991 //the resulting nodes behave as expected.
1992 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
1993 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
1995 //adjust endpoints
1996 sample_cursor->n.pos = bez[1];
1997 sample_end->p.pos = bez[2];
1998 }
2000 //destroy this contiguous selection
2001 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2002 Inkscape::NodePath::Node *temp = delete_cursor;
2003 if (delete_cursor->n.other == delete_cursor) {
2004 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2005 delete_cursor = NULL;
2006 } else {
2007 delete_cursor = delete_cursor->n.other;
2008 }
2009 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2010 sp_nodepath_node_destroy(temp);
2011 }
2013 sp_nodepath_update_handles(nodepath);
2015 if (!g_slist_find(nodepaths, nodepath))
2016 nodepaths = g_slist_prepend (nodepaths, nodepath);
2017 }
2019 for (GSList *i = nodepaths; i; i = i->next) {
2020 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2021 // different nodepaths will give us one undo event per nodepath
2022 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2024 // if the entire nodepath is removed, delete the selected object.
2025 if (nodepath->subpaths == NULL ||
2026 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2027 //at least 2
2028 sp_nodepath_get_node_count(nodepath) < 2) {
2029 SPDocument *document = sp_desktop_document (nodepath->desktop);
2030 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2031 //delete this nodepath's object, not the entire selection! (though at this time, this
2032 //does not matter)
2033 sp_selection_delete();
2034 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2035 _("Delete nodes"));
2036 } else {
2037 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2038 sp_nodepath_update_statusbar(nodepath);
2039 }
2040 }
2042 g_slist_free (nodepaths);
2043 }
2045 /**
2046 * Delete one or more selected nodes.
2047 */
2048 void sp_node_selected_delete()
2049 {
2050 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2051 if (!nodepath) return;
2052 if (!nodepath->selected) return;
2054 /** \todo fixme: do it the right way */
2055 while (nodepath->selected) {
2056 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2057 sp_nodepath_node_destroy(node);
2058 }
2061 //clean up the nodepath (such as for trivial subpaths)
2062 sp_nodepath_cleanup(nodepath);
2064 sp_nodepath_update_handles(nodepath);
2066 // if the entire nodepath is removed, delete the selected object.
2067 if (nodepath->subpaths == NULL ||
2068 sp_nodepath_get_node_count(nodepath) < 2) {
2069 SPDocument *document = sp_desktop_document (nodepath->desktop);
2070 sp_selection_delete();
2071 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2072 _("Delete nodes"));
2073 return;
2074 }
2076 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2078 sp_nodepath_update_statusbar(nodepath);
2079 }
2081 /**
2082 * Delete one or more segments between two selected nodes.
2083 * This is the code for 'split'.
2084 */
2085 void
2086 sp_node_selected_delete_segment(void)
2087 {
2088 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2089 Inkscape::NodePath::Node *curr, *next; //Iterators
2091 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2092 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2094 if (g_list_length(nodepath->selected) != 2) {
2095 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2096 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2097 return;
2098 }
2100 //Selected nodes, not inclusive
2101 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2102 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2104 if ( ( a==b) || //same node
2105 (a->subpath != b->subpath ) || //not the same path
2106 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2107 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2108 {
2109 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2110 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2111 return;
2112 }
2114 //###########################################
2115 //# BEGIN EDITS
2116 //###########################################
2117 //##################################
2118 //# CLOSED PATH
2119 //##################################
2120 if (a->subpath->closed) {
2123 bool reversed = FALSE;
2125 //Since we can go in a circle, we need to find the shorter distance.
2126 // a->b or b->a
2127 start = end = NULL;
2128 int distance = 0;
2129 int minDistance = 0;
2130 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2131 if (curr==b) {
2132 //printf("a to b:%d\n", distance);
2133 start = a;//go from a to b
2134 end = b;
2135 minDistance = distance;
2136 //printf("A to B :\n");
2137 break;
2138 }
2139 distance++;
2140 }
2142 //try again, the other direction
2143 distance = 0;
2144 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2145 if (curr==a) {
2146 //printf("b to a:%d\n", distance);
2147 if (distance < minDistance) {
2148 start = b; //we go from b to a
2149 end = a;
2150 reversed = TRUE;
2151 //printf("B to A\n");
2152 }
2153 break;
2154 }
2155 distance++;
2156 }
2159 //Copy everything from 'end' to 'start' to a new subpath
2160 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2161 for (curr=end ; curr ; curr=curr->n.other) {
2162 NRPathcode code = (NRPathcode) curr->code;
2163 if (curr == end)
2164 code = NR_MOVETO;
2165 sp_nodepath_node_new(t, NULL,
2166 (Inkscape::NodePath::NodeType)curr->type, code,
2167 &curr->p.pos, &curr->pos, &curr->n.pos);
2168 if (curr == start)
2169 break;
2170 }
2171 sp_nodepath_subpath_destroy(a->subpath);
2174 }
2178 //##################################
2179 //# OPEN PATH
2180 //##################################
2181 else {
2183 //We need to get the direction of the list between A and B
2184 //Can we walk from a to b?
2185 start = end = NULL;
2186 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2187 if (curr==b) {
2188 start = a; //did it! we go from a to b
2189 end = b;
2190 //printf("A to B\n");
2191 break;
2192 }
2193 }
2194 if (!start) {//didn't work? let's try the other direction
2195 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2196 if (curr==a) {
2197 start = b; //did it! we go from b to a
2198 end = a;
2199 //printf("B to A\n");
2200 break;
2201 }
2202 }
2203 }
2204 if (!start) {
2205 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2206 _("Cannot find path between nodes."));
2207 return;
2208 }
2212 //Copy everything after 'end' to a new subpath
2213 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2214 for (curr=end ; curr ; curr=curr->n.other) {
2215 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2216 &curr->p.pos, &curr->pos, &curr->n.pos);
2217 }
2219 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2220 for (curr = start->n.other ; curr ; curr=next) {
2221 next = curr->n.other;
2222 sp_nodepath_node_destroy(curr);
2223 }
2225 }
2226 //###########################################
2227 //# END EDITS
2228 //###########################################
2230 //clean up the nodepath (such as for trivial subpaths)
2231 sp_nodepath_cleanup(nodepath);
2233 sp_nodepath_update_handles(nodepath);
2235 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2237 sp_nodepath_update_statusbar(nodepath);
2238 }
2240 /**
2241 * Call sp_nodepath_set_line() for all selected segments.
2242 */
2243 void
2244 sp_node_selected_set_line_type(NRPathcode code)
2245 {
2246 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2247 if (nodepath == NULL) return;
2249 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2250 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2251 g_assert(n->selected);
2252 if (n->p.other && n->p.other->selected) {
2253 sp_nodepath_set_line_type(n, code);
2254 }
2255 }
2257 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2258 }
2260 /**
2261 * Call sp_nodepath_convert_node_type() for all selected nodes.
2262 */
2263 void
2264 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2265 {
2266 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2267 if (nodepath == NULL) return;
2269 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2270 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2271 }
2273 sp_nodepath_update_repr(nodepath, _("Change node type"));
2274 }
2276 /**
2277 * Change select status of node, update its own and neighbour handles.
2278 */
2279 static void sp_node_set_selected(Inkscape::NodePath::Node *node, bool selected)
2280 {
2281 node->selected = selected;
2283 if (selected) {
2284 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2285 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2286 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2287 sp_knot_update_ctrl(node->knot);
2288 } else {
2289 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2290 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2291 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2292 sp_knot_update_ctrl(node->knot);
2293 }
2295 sp_node_update_handles(node);
2296 if (node->n.other) sp_node_update_handles(node->n.other);
2297 if (node->p.other) sp_node_update_handles(node->p.other);
2298 }
2300 /**
2301 \brief Select a node
2302 \param node The node to select
2303 \param incremental If true, add to selection, otherwise deselect others
2304 \param override If true, always select this node, otherwise toggle selected status
2305 */
2306 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, bool incremental, bool override)
2307 {
2308 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2310 if (incremental) {
2311 if (override) {
2312 if (!g_list_find(nodepath->selected, node)) {
2313 nodepath->selected = g_list_prepend(nodepath->selected, node);
2314 }
2315 sp_node_set_selected(node, TRUE);
2316 } else { // toggle
2317 if (node->selected) {
2318 g_assert(g_list_find(nodepath->selected, node));
2319 nodepath->selected = g_list_remove(nodepath->selected, node);
2320 } else {
2321 g_assert(!g_list_find(nodepath->selected, node));
2322 nodepath->selected = g_list_prepend(nodepath->selected, node);
2323 }
2324 sp_node_set_selected(node, !node->selected);
2325 }
2326 } else {
2327 sp_nodepath_deselect(nodepath);
2328 nodepath->selected = g_list_prepend(nodepath->selected, node);
2329 sp_node_set_selected(node, TRUE);
2330 }
2332 sp_nodepath_update_statusbar(nodepath);
2333 }
2336 /**
2337 \brief Deselect all nodes in the nodepath
2338 */
2339 void
2340 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2341 {
2342 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2344 while (nodepath->selected) {
2345 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2346 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2347 }
2348 sp_nodepath_update_statusbar(nodepath);
2349 }
2351 /**
2352 \brief Select or invert selection of all nodes in the nodepath
2353 */
2354 void
2355 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2356 {
2357 if (!nodepath) return;
2359 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2360 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2361 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2362 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2363 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2364 }
2365 }
2366 }
2368 /**
2369 * If nothing selected, does the same as sp_nodepath_select_all();
2370 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2371 * (i.e., similar to "select all in layer", with the "selected" subpaths
2372 * being treated as "layers" in the path).
2373 */
2374 void
2375 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2376 {
2377 if (!nodepath) return;
2379 if (g_list_length (nodepath->selected) == 0) {
2380 sp_nodepath_select_all (nodepath, invert);
2381 return;
2382 }
2384 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2385 GSList *subpaths = NULL;
2387 for (GList *l = copy; l != NULL; l = l->next) {
2388 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2389 Inkscape::NodePath::SubPath *subpath = n->subpath;
2390 if (!g_slist_find (subpaths, subpath))
2391 subpaths = g_slist_prepend (subpaths, subpath);
2392 }
2394 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2395 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2396 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2397 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2398 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2399 }
2400 }
2402 g_slist_free (subpaths);
2403 g_list_free (copy);
2404 }
2406 /**
2407 * \brief Select the node after the last selected; if none is selected,
2408 * select the first within path.
2409 */
2410 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2411 {
2412 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2414 Inkscape::NodePath::Node *last = NULL;
2415 if (nodepath->selected) {
2416 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2417 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2418 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2419 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2420 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2421 if (node->selected) {
2422 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2423 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2424 if (spl->next) { // there's a next subpath
2425 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2426 last = subpath_next->first;
2427 } else if (spl->prev) { // there's a previous subpath
2428 last = NULL; // to be set later to the first node of first subpath
2429 } else {
2430 last = node->n.other;
2431 }
2432 } else {
2433 last = node->n.other;
2434 }
2435 } else {
2436 if (node->n.other) {
2437 last = node->n.other;
2438 } else {
2439 if (spl->next) { // there's a next subpath
2440 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2441 last = subpath_next->first;
2442 } else if (spl->prev) { // there's a previous subpath
2443 last = NULL; // to be set later to the first node of first subpath
2444 } else {
2445 last = (Inkscape::NodePath::Node *) subpath->first;
2446 }
2447 }
2448 }
2449 }
2450 }
2451 }
2452 sp_nodepath_deselect(nodepath);
2453 }
2455 if (last) { // there's at least one more node after selected
2456 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2457 } else { // no more nodes, select the first one in first subpath
2458 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2459 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2460 }
2461 }
2463 /**
2464 * \brief Select the node before the first selected; if none is selected,
2465 * select the last within path
2466 */
2467 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2468 {
2469 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2471 Inkscape::NodePath::Node *last = NULL;
2472 if (nodepath->selected) {
2473 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2474 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2475 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2476 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2477 if (node->selected) {
2478 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2479 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2480 if (spl->prev) { // there's a prev subpath
2481 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2482 last = subpath_prev->last;
2483 } else if (spl->next) { // there's a next subpath
2484 last = NULL; // to be set later to the last node of last subpath
2485 } else {
2486 last = node->p.other;
2487 }
2488 } else {
2489 last = node->p.other;
2490 }
2491 } else {
2492 if (node->p.other) {
2493 last = node->p.other;
2494 } else {
2495 if (spl->prev) { // there's a prev subpath
2496 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2497 last = subpath_prev->last;
2498 } else if (spl->next) { // there's a next subpath
2499 last = NULL; // to be set later to the last node of last subpath
2500 } else {
2501 last = (Inkscape::NodePath::Node *) subpath->last;
2502 }
2503 }
2504 }
2505 }
2506 }
2507 }
2508 sp_nodepath_deselect(nodepath);
2509 }
2511 if (last) { // there's at least one more node before selected
2512 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2513 } else { // no more nodes, select the last one in last subpath
2514 GList *spl = g_list_last(nodepath->subpaths);
2515 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2516 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2517 }
2518 }
2520 /**
2521 * \brief Select all nodes that are within the rectangle.
2522 */
2523 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, bool incremental)
2524 {
2525 if (!incremental) {
2526 sp_nodepath_deselect(nodepath);
2527 }
2529 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2530 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2531 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2532 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2534 if (b.contains(node->pos)) {
2535 sp_nodepath_node_select(node, TRUE, TRUE);
2536 }
2537 }
2538 }
2539 }
2542 void
2543 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2544 {
2545 g_assert (n);
2546 g_assert (nodepath);
2547 g_assert (n->subpath->nodepath == nodepath);
2549 if (g_list_length (nodepath->selected) == 0) {
2550 if (grow > 0) {
2551 sp_nodepath_node_select(n, TRUE, TRUE);
2552 }
2553 return;
2554 }
2556 if (g_list_length (nodepath->selected) == 1) {
2557 if (grow < 0) {
2558 sp_nodepath_deselect (nodepath);
2559 return;
2560 }
2561 }
2563 double n_sel_range = 0, p_sel_range = 0;
2564 Inkscape::NodePath::Node *farthest_n_node = n;
2565 Inkscape::NodePath::Node *farthest_p_node = n;
2567 // Calculate ranges
2568 {
2569 double n_range = 0, p_range = 0;
2570 bool n_going = true, p_going = true;
2571 Inkscape::NodePath::Node *n_node = n;
2572 Inkscape::NodePath::Node *p_node = n;
2573 do {
2574 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2575 if (n_node && n_going)
2576 n_node = n_node->n.other;
2577 if (n_node == NULL) {
2578 n_going = false;
2579 } else {
2580 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2581 if (n_node->selected) {
2582 n_sel_range = n_range;
2583 farthest_n_node = n_node;
2584 }
2585 if (n_node == p_node) {
2586 n_going = false;
2587 p_going = false;
2588 }
2589 }
2590 if (p_node && p_going)
2591 p_node = p_node->p.other;
2592 if (p_node == NULL) {
2593 p_going = false;
2594 } else {
2595 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2596 if (p_node->selected) {
2597 p_sel_range = p_range;
2598 farthest_p_node = p_node;
2599 }
2600 if (p_node == n_node) {
2601 n_going = false;
2602 p_going = false;
2603 }
2604 }
2605 } while (n_going || p_going);
2606 }
2608 if (grow > 0) {
2609 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2610 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2611 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2612 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2613 }
2614 } else {
2615 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2616 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2617 } else if (farthest_p_node && farthest_p_node->selected) {
2618 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2619 }
2620 }
2621 }
2623 void
2624 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2625 {
2626 g_assert (n);
2627 g_assert (nodepath);
2628 g_assert (n->subpath->nodepath == nodepath);
2630 if (g_list_length (nodepath->selected) == 0) {
2631 if (grow > 0) {
2632 sp_nodepath_node_select(n, TRUE, TRUE);
2633 }
2634 return;
2635 }
2637 if (g_list_length (nodepath->selected) == 1) {
2638 if (grow < 0) {
2639 sp_nodepath_deselect (nodepath);
2640 return;
2641 }
2642 }
2644 Inkscape::NodePath::Node *farthest_selected = NULL;
2645 double farthest_dist = 0;
2647 Inkscape::NodePath::Node *closest_unselected = NULL;
2648 double closest_dist = NR_HUGE;
2650 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2651 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2652 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2653 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2654 if (node == n)
2655 continue;
2656 if (node->selected) {
2657 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2658 farthest_dist = NR::L2(node->pos - n->pos);
2659 farthest_selected = node;
2660 }
2661 } else {
2662 if (NR::L2(node->pos - n->pos) < closest_dist) {
2663 closest_dist = NR::L2(node->pos - n->pos);
2664 closest_unselected = node;
2665 }
2666 }
2667 }
2668 }
2670 if (grow > 0) {
2671 if (closest_unselected) {
2672 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2673 }
2674 } else {
2675 if (farthest_selected) {
2676 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2677 }
2678 }
2679 }
2682 /**
2683 \brief Saves all nodes' and handles' current positions in their origin members
2684 */
2685 void
2686 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2687 {
2688 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2689 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2690 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2691 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2692 n->origin = n->pos;
2693 n->p.origin = n->p.pos;
2694 n->n.origin = n->n.pos;
2695 }
2696 }
2697 }
2699 /**
2700 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2701 */
2702 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2703 {
2704 if (!nodepath->selected) {
2705 return NULL;
2706 }
2708 GList *r = NULL;
2709 guint i = 0;
2710 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2711 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2712 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2713 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2714 i++;
2715 if (node->selected) {
2716 r = g_list_append(r, GINT_TO_POINTER(i));
2717 }
2718 }
2719 }
2720 return r;
2721 }
2723 /**
2724 \brief Restores selection by selecting nodes whose positions are in the list
2725 */
2726 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2727 {
2728 sp_nodepath_deselect(nodepath);
2730 guint i = 0;
2731 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2732 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2733 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2734 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2735 i++;
2736 if (g_list_find(r, GINT_TO_POINTER(i))) {
2737 sp_nodepath_node_select(node, TRUE, TRUE);
2738 }
2739 }
2740 }
2742 }
2744 /**
2745 \brief Adjusts handle according to node type and line code.
2746 */
2747 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2748 {
2749 double len, otherlen, linelen;
2751 g_assert(node);
2753 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2754 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2756 /** \todo fixme: */
2757 if (me->other == NULL) return;
2758 if (other->other == NULL) return;
2760 /* I have line */
2762 NRPathcode mecode, ocode;
2763 if (which_adjust == 1) {
2764 mecode = (NRPathcode)me->other->code;
2765 ocode = (NRPathcode)node->code;
2766 } else {
2767 mecode = (NRPathcode)node->code;
2768 ocode = (NRPathcode)other->other->code;
2769 }
2771 if (mecode == NR_LINETO) return;
2773 /* I am curve */
2775 if (other->other == NULL) return;
2777 /* Other has line */
2779 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2781 NR::Point delta;
2782 if (ocode == NR_LINETO) {
2783 /* other is lineto, we are either smooth or symm */
2784 Inkscape::NodePath::Node *othernode = other->other;
2785 len = NR::L2(me->pos - node->pos);
2786 delta = node->pos - othernode->pos;
2787 linelen = NR::L2(delta);
2788 if (linelen < 1e-18)
2789 return;
2790 me->pos = node->pos + (len / linelen)*delta;
2791 return;
2792 }
2794 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2796 me->pos = 2 * node->pos - other->pos;
2797 return;
2798 }
2800 /* We are smooth */
2802 len = NR::L2(me->pos - node->pos);
2803 delta = other->pos - node->pos;
2804 otherlen = NR::L2(delta);
2805 if (otherlen < 1e-18) return;
2807 me->pos = node->pos - (len / otherlen) * delta;
2808 }
2810 /**
2811 \brief Adjusts both handles according to node type and line code
2812 */
2813 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2814 {
2815 g_assert(node);
2817 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2819 /* we are either smooth or symm */
2821 if (node->p.other == NULL) return;
2823 if (node->n.other == NULL) return;
2825 if (node->code == NR_LINETO) {
2826 if (node->n.other->code == NR_LINETO) return;
2827 sp_node_adjust_handle(node, 1);
2828 return;
2829 }
2831 if (node->n.other->code == NR_LINETO) {
2832 if (node->code == NR_LINETO) return;
2833 sp_node_adjust_handle(node, -1);
2834 return;
2835 }
2837 /* both are curves */
2838 NR::Point const delta( node->n.pos - node->p.pos );
2840 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2841 node->p.pos = node->pos - delta / 2;
2842 node->n.pos = node->pos + delta / 2;
2843 return;
2844 }
2846 /* We are smooth */
2847 double plen = NR::L2(node->p.pos - node->pos);
2848 if (plen < 1e-18) return;
2849 double nlen = NR::L2(node->n.pos - node->pos);
2850 if (nlen < 1e-18) return;
2851 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2852 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2853 }
2855 /**
2856 * Node event callback.
2857 */
2858 static bool node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2859 {
2860 bool ret = FALSE;
2861 switch (event->type) {
2862 case GDK_ENTER_NOTIFY:
2863 active_node = n;
2864 break;
2865 case GDK_LEAVE_NOTIFY:
2866 active_node = NULL;
2867 break;
2868 case GDK_KEY_PRESS:
2869 switch (get_group0_keyval (&event->key)) {
2870 case GDK_space:
2871 if (event->key.state & GDK_BUTTON1_MASK) {
2872 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2873 stamp_repr(nodepath);
2874 ret = TRUE;
2875 }
2876 break;
2877 case GDK_Page_Up:
2878 if (event->key.state & GDK_CONTROL_MASK) {
2879 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2880 } else {
2881 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2882 }
2883 break;
2884 case GDK_Page_Down:
2885 if (event->key.state & GDK_CONTROL_MASK) {
2886 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2887 } else {
2888 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2889 }
2890 break;
2891 default:
2892 break;
2893 }
2894 break;
2895 default:
2896 break;
2897 }
2899 return ret;
2900 }
2902 /**
2903 * Handle keypress on node; directly called.
2904 */
2905 bool node_key(GdkEvent *event)
2906 {
2907 Inkscape::NodePath::Path *np;
2909 // there is no way to verify nodes so set active_node to nil when deleting!!
2910 if (active_node == NULL) return FALSE;
2912 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2913 gint ret = FALSE;
2914 switch (get_group0_keyval (&event->key)) {
2915 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2916 case GDK_BackSpace:
2917 np = active_node->subpath->nodepath;
2918 sp_nodepath_node_destroy(active_node);
2919 sp_nodepath_update_repr(np, _("Delete node"));
2920 active_node = NULL;
2921 ret = TRUE;
2922 break;
2923 case GDK_c:
2924 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2925 ret = TRUE;
2926 break;
2927 case GDK_s:
2928 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2929 ret = TRUE;
2930 break;
2931 case GDK_y:
2932 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2933 ret = TRUE;
2934 break;
2935 case GDK_b:
2936 sp_nodepath_node_break(active_node);
2937 ret = TRUE;
2938 break;
2939 }
2940 return ret;
2941 }
2942 return FALSE;
2943 }
2945 /**
2946 * Mouseclick on node callback.
2947 */
2948 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2949 {
2950 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2952 if (state & GDK_CONTROL_MASK) {
2953 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2955 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2956 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2957 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2958 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2959 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2960 } else {
2961 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2962 }
2963 sp_nodepath_update_repr(nodepath, _("Change node type"));
2964 sp_nodepath_update_statusbar(nodepath);
2966 } else { //ctrl+alt+click: delete node
2967 GList *node_to_delete = NULL;
2968 node_to_delete = g_list_append(node_to_delete, n);
2969 sp_node_delete_preserve(node_to_delete);
2970 }
2972 } else {
2973 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2974 }
2975 }
2977 /**
2978 * Mouse grabbed node callback.
2979 */
2980 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2981 {
2982 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2984 if (!n->selected) {
2985 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2986 }
2988 sp_nodepath_remember_origins (n->subpath->nodepath);
2989 }
2991 /**
2992 * Mouse ungrabbed node callback.
2993 */
2994 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2995 {
2996 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2998 n->dragging_out = NULL;
3000 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3001 }
3003 /**
3004 * The point on a line, given by its angle, closest to the given point.
3005 * \param p A point.
3006 * \param a Angle of the line; it is assumed to go through coordinate origin.
3007 * \param closest Pointer to the point struct where the result is stored.
3008 * \todo FIXME: use dot product perhaps?
3009 */
3010 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3011 {
3012 if (a == HUGE_VAL) { // vertical
3013 *closest = NR::Point(0, (*p)[NR::Y]);
3014 } else {
3015 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3016 (*closest)[NR::Y] = a * (*closest)[NR::X];
3017 }
3018 }
3020 /**
3021 * Distance from the point to a line given by its angle.
3022 * \param p A point.
3023 * \param a Angle of the line; it is assumed to go through coordinate origin.
3024 */
3025 static double point_line_distance(NR::Point *p, double a)
3026 {
3027 NR::Point c;
3028 point_line_closest(p, a, &c);
3029 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]));
3030 }
3032 /**
3033 * Callback for node "request" signal.
3034 * \todo fixme: This goes to "moved" event? (lauris)
3035 */
3036 static bool
3037 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3038 {
3039 double yn, xn, yp, xp;
3040 double an, ap, na, pa;
3041 double d_an, d_ap, d_na, d_pa;
3042 bool collinear = FALSE;
3043 NR::Point c;
3044 NR::Point pr;
3046 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3048 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3049 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3051 NR::Point mouse = (*p);
3053 if (!n->dragging_out) {
3054 // This is the first drag-out event; find out which handle to drag out
3055 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3056 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3058 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3059 return FALSE;
3061 Inkscape::NodePath::NodeSide *opposite;
3062 if (appr_p > appr_n) { // closer to p
3063 n->dragging_out = &n->p;
3064 opposite = &n->n;
3065 n->code = NR_CURVETO;
3066 } else if (appr_p < appr_n) { // closer to n
3067 n->dragging_out = &n->n;
3068 opposite = &n->p;
3069 n->n.other->code = NR_CURVETO;
3070 } else { // p and n nodes are the same
3071 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3072 n->dragging_out = &n->p;
3073 opposite = &n->n;
3074 n->code = NR_CURVETO;
3075 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3076 n->dragging_out = &n->n;
3077 opposite = &n->p;
3078 n->n.other->code = NR_CURVETO;
3079 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3080 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);
3081 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);
3082 if (appr_other_p > appr_other_n) { // closer to other's p handle
3083 n->dragging_out = &n->n;
3084 opposite = &n->p;
3085 n->n.other->code = NR_CURVETO;
3086 } else { // closer to other's n handle
3087 n->dragging_out = &n->p;
3088 opposite = &n->n;
3089 n->code = NR_CURVETO;
3090 }
3091 }
3092 }
3094 // if there's another handle, make sure the one we drag out starts parallel to it
3095 if (opposite->pos != n->pos) {
3096 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3097 }
3099 // knots might not be created yet!
3100 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3101 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3102 }
3104 // pass this on to the handle-moved callback
3105 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3106 sp_node_update_handles(n);
3107 return TRUE;
3108 }
3110 if (state & GDK_CONTROL_MASK) { // constrained motion
3112 // calculate relative distances of handles
3113 // n handle:
3114 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3115 xn = n->n.pos[NR::X] - n->pos[NR::X];
3116 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3117 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3118 if (n->n.other) { // if there is the next point
3119 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3120 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3121 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3122 }
3123 }
3124 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3125 if (yn < 0) { xn = -xn; yn = -yn; }
3127 // p handle:
3128 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3129 xp = n->p.pos[NR::X] - n->pos[NR::X];
3130 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3131 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3132 if (n->p.other) {
3133 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3134 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3135 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3136 }
3137 }
3138 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3139 if (yp < 0) { xp = -xp; yp = -yp; }
3141 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3142 // sliding on handles, only if at least one of the handles is non-vertical
3143 // (otherwise it's the same as ctrl+drag anyway)
3145 // calculate angles of the handles
3146 if (xn == 0) {
3147 if (yn == 0) { // no handle, consider it the continuation of the other one
3148 an = 0;
3149 collinear = TRUE;
3150 }
3151 else an = 0; // vertical; set the angle to horizontal
3152 } else an = yn/xn;
3154 if (xp == 0) {
3155 if (yp == 0) { // no handle, consider it the continuation of the other one
3156 ap = an;
3157 }
3158 else ap = 0; // vertical; set the angle to horizontal
3159 } else ap = yp/xp;
3161 if (collinear) an = ap;
3163 // angles of the perpendiculars; HUGE_VAL means vertical
3164 if (an == 0) na = HUGE_VAL; else na = -1/an;
3165 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3167 // mouse point relative to the node's original pos
3168 pr = (*p) - n->origin;
3170 // distances to the four lines (two handles and two perpendiculars)
3171 d_an = point_line_distance(&pr, an);
3172 d_na = point_line_distance(&pr, na);
3173 d_ap = point_line_distance(&pr, ap);
3174 d_pa = point_line_distance(&pr, pa);
3176 // find out which line is the closest, save its closest point in c
3177 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3178 point_line_closest(&pr, an, &c);
3179 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3180 point_line_closest(&pr, ap, &c);
3181 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3182 point_line_closest(&pr, na, &c);
3183 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3184 point_line_closest(&pr, pa, &c);
3185 }
3187 // move the node to the closest point
3188 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3189 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3190 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3192 } else { // constraining to hor/vert
3194 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3195 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3196 } else { // snap to vert
3197 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3198 }
3199 }
3200 } else { // move freely
3201 if (state & GDK_MOD1_MASK) { // sculpt
3202 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3203 } else {
3204 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3205 (*p)[NR::X] - n->pos[NR::X],
3206 (*p)[NR::Y] - n->pos[NR::Y],
3207 (state & GDK_SHIFT_MASK) == 0);
3208 }
3209 }
3211 n->subpath->nodepath->desktop->scroll_to_point(p);
3213 return TRUE;
3214 }
3216 /**
3217 * Node handle clicked callback.
3218 */
3219 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3220 {
3221 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3223 if (state & GDK_CONTROL_MASK) { // "delete" handle
3224 if (n->p.knot == knot) {
3225 n->p.pos = n->pos;
3226 } else if (n->n.knot == knot) {
3227 n->n.pos = n->pos;
3228 }
3229 sp_node_update_handles(n);
3230 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3231 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3232 sp_nodepath_update_statusbar(nodepath);
3234 } else { // just select or add to selection, depending in Shift
3235 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3236 }
3237 }
3239 /**
3240 * Node handle grabbed callback.
3241 */
3242 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3243 {
3244 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3246 if (!n->selected) {
3247 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3248 }
3250 // remember the origin point of the handle
3251 if (n->p.knot == knot) {
3252 n->p.origin_radial = n->p.pos - n->pos;
3253 } else if (n->n.knot == knot) {
3254 n->n.origin_radial = n->n.pos - n->pos;
3255 } else {
3256 g_assert_not_reached();
3257 }
3259 }
3261 /**
3262 * Node handle ungrabbed callback.
3263 */
3264 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3265 {
3266 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3268 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3269 if (n->p.knot == knot) {
3270 n->p.origin_radial.a = 0;
3271 sp_knot_set_position(knot, &n->p.pos, state);
3272 } else if (n->n.knot == knot) {
3273 n->n.origin_radial.a = 0;
3274 sp_knot_set_position(knot, &n->n.pos, state);
3275 } else {
3276 g_assert_not_reached();
3277 }
3279 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3280 }
3282 /**
3283 * Node handle "request" signal callback.
3284 */
3285 static bool node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3286 {
3287 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3289 Inkscape::NodePath::NodeSide *me, *opposite;
3290 gint which;
3291 if (n->p.knot == knot) {
3292 me = &n->p;
3293 opposite = &n->n;
3294 which = -1;
3295 } else if (n->n.knot == knot) {
3296 me = &n->n;
3297 opposite = &n->p;
3298 which = 1;
3299 } else {
3300 me = opposite = NULL;
3301 which = 0;
3302 g_assert_not_reached();
3303 }
3305 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3307 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3309 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3310 /* We are smooth node adjacent with line */
3311 NR::Point const delta = *p - n->pos;
3312 NR::Coord const len = NR::L2(delta);
3313 Inkscape::NodePath::Node *othernode = opposite->other;
3314 NR::Point const ndelta = n->pos - othernode->pos;
3315 NR::Coord const linelen = NR::L2(ndelta);
3316 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3317 NR::Coord const scal = dot(delta, ndelta) / linelen;
3318 (*p) = n->pos + (scal / linelen) * ndelta;
3319 }
3320 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3321 } else {
3322 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3323 }
3325 sp_node_adjust_handle(n, -which);
3327 return FALSE;
3328 }
3330 /**
3331 * Node handle moved callback.
3332 */
3333 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3334 {
3335 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3337 Inkscape::NodePath::NodeSide *me;
3338 Inkscape::NodePath::NodeSide *other;
3339 if (n->p.knot == knot) {
3340 me = &n->p;
3341 other = &n->n;
3342 } else if (n->n.knot == knot) {
3343 me = &n->n;
3344 other = &n->p;
3345 } else {
3346 me = NULL;
3347 other = NULL;
3348 g_assert_not_reached();
3349 }
3351 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3352 Radial rme(me->pos - n->pos);
3353 Radial rother(other->pos - n->pos);
3354 Radial rnew(*p - n->pos);
3356 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3357 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3358 /* 0 interpreted as "no snapping". */
3360 // The closest PI/snaps angle, starting from zero.
3361 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3362 if (me->origin_radial.a == HUGE_VAL) {
3363 // ortho doesn't exist: original handle was zero length.
3364 rnew.a = a_snapped;
3365 } else {
3366 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3367 * its opposite and perpendiculars). */
3368 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3370 // Snap to the closest.
3371 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3372 ? a_snapped
3373 : a_ortho );
3374 }
3375 }
3377 if (state & GDK_MOD1_MASK) {
3378 // lock handle length
3379 rnew.r = me->origin_radial.r;
3380 }
3382 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3383 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3384 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3385 rother.a += rnew.a - rme.a;
3386 other->pos = NR::Point(rother) + n->pos;
3387 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3388 sp_knot_set_position(other->knot, &other->pos, 0);
3389 }
3391 me->pos = NR::Point(rnew) + n->pos;
3392 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3394 // this is what sp_knot_set_position does, but without emitting the signal:
3395 // we cannot emit a "moved" signal because we're now processing it
3396 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3398 knot->desktop->set_coordinate_status(me->pos);
3400 update_object(n->subpath->nodepath);
3402 /* status text */
3403 SPDesktop *desktop = n->subpath->nodepath->desktop;
3404 if (!desktop) return;
3405 SPEventContext *ec = desktop->event_context;
3406 if (!ec) return;
3407 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3408 if (!mc) return;
3410 double degrees = 180 / M_PI * rnew.a;
3411 if (degrees > 180) degrees -= 360;
3412 if (degrees < -180) degrees += 360;
3413 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3414 degrees = angle_to_compass (degrees);
3416 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3418 mc->setF(Inkscape::NORMAL_MESSAGE,
3419 _("<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);
3421 g_string_free(length, TRUE);
3422 }
3424 /**
3425 * Node handle event callback.
3426 */
3427 static bool node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3428 {
3429 bool ret = FALSE;
3430 switch (event->type) {
3431 case GDK_KEY_PRESS:
3432 switch (get_group0_keyval (&event->key)) {
3433 case GDK_space:
3434 if (event->key.state & GDK_BUTTON1_MASK) {
3435 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3436 stamp_repr(nodepath);
3437 ret = TRUE;
3438 }
3439 break;
3440 default:
3441 break;
3442 }
3443 break;
3444 default:
3445 break;
3446 }
3448 return ret;
3449 }
3451 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3452 Radial &rme, Radial &rother, bool const both)
3453 {
3454 rme.a += angle;
3455 if ( both
3456 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3457 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3458 {
3459 rother.a += angle;
3460 }
3461 }
3463 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3464 Radial &rme, Radial &rother, bool const both)
3465 {
3466 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3468 gdouble r;
3469 if ( both
3470 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3471 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3472 {
3473 r = MAX(rme.r, rother.r);
3474 } else {
3475 r = rme.r;
3476 }
3478 gdouble const weird_angle = atan2(norm_angle, r);
3479 /* Bulia says norm_angle is just the visible distance that the
3480 * object's end must travel on the screen. Left as 'angle' for want of
3481 * a better name.*/
3483 rme.a += weird_angle;
3484 if ( both
3485 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3486 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3487 {
3488 rother.a += weird_angle;
3489 }
3490 }
3492 /**
3493 * Rotate one node.
3494 */
3495 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, bool screen)
3496 {
3497 Inkscape::NodePath::NodeSide *me, *other;
3498 bool both = false;
3500 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3501 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3503 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3504 me = &(n->p);
3505 other = &(n->n);
3506 } else if (!n->p.other) {
3507 me = &(n->n);
3508 other = &(n->p);
3509 } else {
3510 if (which > 0) { // right handle
3511 if (xn > xp) {
3512 me = &(n->n);
3513 other = &(n->p);
3514 } else {
3515 me = &(n->p);
3516 other = &(n->n);
3517 }
3518 } else if (which < 0){ // left handle
3519 if (xn <= xp) {
3520 me = &(n->n);
3521 other = &(n->p);
3522 } else {
3523 me = &(n->p);
3524 other = &(n->n);
3525 }
3526 } else { // both handles
3527 me = &(n->n);
3528 other = &(n->p);
3529 both = true;
3530 }
3531 }
3533 Radial rme(me->pos - n->pos);
3534 Radial rother(other->pos - n->pos);
3536 if (screen) {
3537 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3538 } else {
3539 node_rotate_one_internal (*n, angle, rme, rother, both);
3540 }
3542 me->pos = n->pos + NR::Point(rme);
3544 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3545 other->pos = n->pos + NR::Point(rother);
3546 }
3548 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3549 // so here we just move all the knots without emitting move signals, for speed
3550 sp_node_update_handles(n, false);
3551 }
3553 /**
3554 * Rotate selected nodes.
3555 */
3556 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3557 {
3558 if (!nodepath || !nodepath->selected) return;
3560 if (g_list_length(nodepath->selected) == 1) {
3561 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3562 node_rotate_one (n, angle, which, screen);
3563 } else {
3564 // rotate as an object:
3566 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3567 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3568 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3569 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3570 box.expandTo (n->pos); // contain all selected nodes
3571 }
3573 gdouble rot;
3574 if (screen) {
3575 gdouble const zoom = nodepath->desktop->current_zoom();
3576 gdouble const zmove = angle / zoom;
3577 gdouble const r = NR::L2(box.max() - box.midpoint());
3578 rot = atan2(zmove, r);
3579 } else {
3580 rot = angle;
3581 }
3583 NR::Matrix t =
3584 NR::Matrix (NR::translate(-box.midpoint())) *
3585 NR::Matrix (NR::rotate(rot)) *
3586 NR::Matrix (NR::translate(box.midpoint()));
3588 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3589 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3590 n->pos *= t;
3591 n->n.pos *= t;
3592 n->p.pos *= t;
3593 sp_node_update_handles(n, false);
3594 }
3595 }
3597 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3598 }
3600 /**
3601 * Scale one node.
3602 */
3603 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3604 {
3605 bool both = false;
3606 Inkscape::NodePath::NodeSide *me, *other;
3608 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3609 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3611 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3612 me = &(n->p);
3613 other = &(n->n);
3614 n->code = NR_CURVETO;
3615 } else if (!n->p.other) {
3616 me = &(n->n);
3617 other = &(n->p);
3618 if (n->n.other)
3619 n->n.other->code = NR_CURVETO;
3620 } else {
3621 if (which > 0) { // right handle
3622 if (xn > xp) {
3623 me = &(n->n);
3624 other = &(n->p);
3625 if (n->n.other)
3626 n->n.other->code = NR_CURVETO;
3627 } else {
3628 me = &(n->p);
3629 other = &(n->n);
3630 n->code = NR_CURVETO;
3631 }
3632 } else if (which < 0){ // left handle
3633 if (xn <= xp) {
3634 me = &(n->n);
3635 other = &(n->p);
3636 if (n->n.other)
3637 n->n.other->code = NR_CURVETO;
3638 } else {
3639 me = &(n->p);
3640 other = &(n->n);
3641 n->code = NR_CURVETO;
3642 }
3643 } else { // both handles
3644 me = &(n->n);
3645 other = &(n->p);
3646 both = true;
3647 n->code = NR_CURVETO;
3648 if (n->n.other)
3649 n->n.other->code = NR_CURVETO;
3650 }
3651 }
3653 Radial rme(me->pos - n->pos);
3654 Radial rother(other->pos - n->pos);
3656 rme.r += grow;
3657 if (rme.r < 0) rme.r = 0;
3658 if (rme.a == HUGE_VAL) {
3659 if (me->other) { // if direction is unknown, initialize it towards the next node
3660 Radial rme_next(me->other->pos - n->pos);
3661 rme.a = rme_next.a;
3662 } else { // if there's no next, initialize to 0
3663 rme.a = 0;
3664 }
3665 }
3666 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3667 rother.r += grow;
3668 if (rother.r < 0) rother.r = 0;
3669 if (rother.a == HUGE_VAL) {
3670 rother.a = rme.a + M_PI;
3671 }
3672 }
3674 me->pos = n->pos + NR::Point(rme);
3676 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3677 other->pos = n->pos + NR::Point(rother);
3678 }
3680 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3681 // so here we just move all the knots without emitting move signals, for speed
3682 sp_node_update_handles(n, false);
3683 }
3685 /**
3686 * Scale selected nodes.
3687 */
3688 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3689 {
3690 if (!nodepath || !nodepath->selected) return;
3692 if (g_list_length(nodepath->selected) == 1) {
3693 // scale handles of the single selected node
3694 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3695 node_scale_one (n, grow, which);
3696 } else {
3697 // scale nodes as an "object":
3699 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3700 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3701 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3702 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3703 box.expandTo (n->pos); // contain all selected nodes
3704 }
3706 double scale = (box.maxExtent() + grow)/box.maxExtent();
3708 NR::Matrix t =
3709 NR::Matrix (NR::translate(-box.midpoint())) *
3710 NR::Matrix (NR::scale(scale, scale)) *
3711 NR::Matrix (NR::translate(box.midpoint()));
3713 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3714 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3715 n->pos *= t;
3716 n->n.pos *= t;
3717 n->p.pos *= t;
3718 sp_node_update_handles(n, false);
3719 }
3720 }
3722 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3723 }
3725 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3726 {
3727 if (!nodepath) return;
3728 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3729 }
3731 /**
3732 * Flip selected nodes horizontally/vertically.
3733 */
3734 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3735 {
3736 if (!nodepath || !nodepath->selected) return;
3738 if (g_list_length(nodepath->selected) == 1) {
3739 // flip handles of the single selected node
3740 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3741 double temp = n->p.pos[axis];
3742 n->p.pos[axis] = n->n.pos[axis];
3743 n->n.pos[axis] = temp;
3744 sp_node_update_handles(n, false);
3745 } else {
3746 // scale nodes as an "object":
3748 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3749 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3750 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3751 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3752 box.expandTo (n->pos); // contain all selected nodes
3753 }
3755 NR::Matrix t =
3756 NR::Matrix (NR::translate(-box.midpoint())) *
3757 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3758 NR::Matrix (NR::translate(box.midpoint()));
3760 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3761 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3762 n->pos *= t;
3763 n->n.pos *= t;
3764 n->p.pos *= t;
3765 sp_node_update_handles(n, false);
3766 }
3767 }
3769 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3770 }
3772 //-----------------------------------------------
3773 /**
3774 * Return new subpath under given nodepath.
3775 */
3776 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3777 {
3778 g_assert(nodepath);
3779 g_assert(nodepath->desktop);
3781 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3783 s->nodepath = nodepath;
3784 s->closed = FALSE;
3785 s->nodes = NULL;
3786 s->first = NULL;
3787 s->last = NULL;
3789 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3790 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3791 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3793 return s;
3794 }
3796 /**
3797 * Destroy nodes in subpath, then subpath itself.
3798 */
3799 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3800 {
3801 g_assert(subpath);
3802 g_assert(subpath->nodepath);
3803 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3805 while (subpath->nodes) {
3806 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3807 }
3809 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3811 g_free(subpath);
3812 }
3814 /**
3815 * Link head to tail in subpath.
3816 */
3817 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3818 {
3819 g_assert(!sp->closed);
3820 g_assert(sp->last != sp->first);
3821 g_assert(sp->first->code == NR_MOVETO);
3823 sp->closed = TRUE;
3825 //Link the head to the tail
3826 sp->first->p.other = sp->last;
3827 sp->last->n.other = sp->first;
3828 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3829 sp->first = sp->last;
3831 //Remove the extra end node
3832 sp_nodepath_node_destroy(sp->last->n.other);
3833 }
3835 /**
3836 * Open closed (loopy) subpath at node.
3837 */
3838 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3839 {
3840 g_assert(sp->closed);
3841 g_assert(n->subpath == sp);
3842 g_assert(sp->first == sp->last);
3844 /* We create new startpoint, current node will become last one */
3846 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3847 &n->pos, &n->pos, &n->n.pos);
3850 sp->closed = FALSE;
3852 //Unlink to make a head and tail
3853 sp->first = new_path;
3854 sp->last = n;
3855 n->n.other = NULL;
3856 new_path->p.other = NULL;
3857 }
3859 /**
3860 * Returns area in triangle given by points; may be negative.
3861 */
3862 inline double
3863 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3864 {
3865 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]);
3866 }
3868 /**
3869 * Return new node in subpath with given properties.
3870 * \param pos Position of node.
3871 * \param ppos Handle position in previous direction
3872 * \param npos Handle position in previous direction
3873 */
3874 Inkscape::NodePath::Node *
3875 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)
3876 {
3877 g_assert(sp);
3878 g_assert(sp->nodepath);
3879 g_assert(sp->nodepath->desktop);
3881 if (nodechunk == NULL)
3882 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3884 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3886 n->subpath = sp;
3888 if (type != Inkscape::NodePath::NODE_NONE) {
3889 // use the type from sodipodi:nodetypes
3890 n->type = type;
3891 } else {
3892 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3893 // points are (almost) collinear
3894 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3895 // endnode, or a node with a retracted handle
3896 n->type = Inkscape::NodePath::NODE_CUSP;
3897 } else {
3898 n->type = Inkscape::NodePath::NODE_SMOOTH;
3899 }
3900 } else {
3901 n->type = Inkscape::NodePath::NODE_CUSP;
3902 }
3903 }
3905 n->code = code;
3906 n->selected = FALSE;
3907 n->pos = *pos;
3908 n->p.pos = *ppos;
3909 n->n.pos = *npos;
3911 n->dragging_out = NULL;
3913 Inkscape::NodePath::Node *prev;
3914 if (next) {
3915 //g_assert(g_list_find(sp->nodes, next));
3916 prev = next->p.other;
3917 } else {
3918 prev = sp->last;
3919 }
3921 if (prev)
3922 prev->n.other = n;
3923 else
3924 sp->first = n;
3926 if (next)
3927 next->p.other = n;
3928 else
3929 sp->last = n;
3931 n->p.other = prev;
3932 n->n.other = next;
3934 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"));
3935 sp_knot_set_position(n->knot, pos, 0);
3937 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3938 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3939 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3940 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3941 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3942 sp_knot_update_ctrl(n->knot);
3944 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3945 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3946 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3947 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3948 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3949 sp_knot_show(n->knot);
3951 // We only create handle knots and lines on demand
3952 n->p.knot = NULL;
3953 n->p.line = NULL;
3954 n->n.knot = NULL;
3955 n->n.line = NULL;
3957 sp->nodes = g_list_prepend(sp->nodes, n);
3959 return n;
3960 }
3962 /**
3963 * Destroy node and its knots, link neighbors in subpath.
3964 */
3965 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3966 {
3967 g_assert(node);
3968 g_assert(node->subpath);
3969 g_assert(SP_IS_KNOT(node->knot));
3971 Inkscape::NodePath::SubPath *sp = node->subpath;
3973 if (node->selected) { // first, deselect
3974 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3975 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3976 }
3978 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3980 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
3981 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
3982 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
3983 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
3984 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
3985 g_object_unref(G_OBJECT(node->knot));
3987 if (node->p.knot) {
3988 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
3989 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
3990 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
3991 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
3992 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
3993 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
3994 g_object_unref(G_OBJECT(node->p.knot));
3995 }
3997 if (node->n.knot) {
3998 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
3999 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4000 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4001 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4002 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4003 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4004 g_object_unref(G_OBJECT(node->n.knot));
4005 }
4007 if (node->p.line)
4008 gtk_object_destroy(GTK_OBJECT(node->p.line));
4009 if (node->n.line)
4010 gtk_object_destroy(GTK_OBJECT(node->n.line));
4012 if (sp->nodes) { // there are others nodes on the subpath
4013 if (sp->closed) {
4014 if (sp->first == node) {
4015 g_assert(sp->last == node);
4016 sp->first = node->n.other;
4017 sp->last = sp->first;
4018 }
4019 node->p.other->n.other = node->n.other;
4020 node->n.other->p.other = node->p.other;
4021 } else {
4022 if (sp->first == node) {
4023 sp->first = node->n.other;
4024 sp->first->code = NR_MOVETO;
4025 }
4026 if (sp->last == node) sp->last = node->p.other;
4027 if (node->p.other) node->p.other->n.other = node->n.other;
4028 if (node->n.other) node->n.other->p.other = node->p.other;
4029 }
4030 } else { // this was the last node on subpath
4031 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4032 }
4034 g_mem_chunk_free(nodechunk, node);
4035 }
4037 /**
4038 * Returns one of the node's two sides.
4039 * \param which Indicates which side.
4040 * \return Pointer to previous node side if which==-1, next if which==1.
4041 */
4042 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4043 {
4044 g_assert(node);
4046 switch (which) {
4047 case -1:
4048 return &node->p;
4049 case 1:
4050 return &node->n;
4051 default:
4052 break;
4053 }
4055 g_assert_not_reached();
4057 return NULL;
4058 }
4060 /**
4061 * Return the other side of the node, given one of its sides.
4062 */
4063 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4064 {
4065 g_assert(node);
4067 if (me == &node->p) return &node->n;
4068 if (me == &node->n) return &node->p;
4070 g_assert_not_reached();
4072 return NULL;
4073 }
4075 /**
4076 * Return NRPathcode on the given side of the node.
4077 */
4078 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4079 {
4080 g_assert(node);
4082 if (me == &node->p) {
4083 if (node->p.other) return (NRPathcode)node->code;
4084 return NR_MOVETO;
4085 }
4087 if (me == &node->n) {
4088 if (node->n.other) return (NRPathcode)node->n.other->code;
4089 return NR_MOVETO;
4090 }
4092 g_assert_not_reached();
4094 return NR_END;
4095 }
4097 /**
4098 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4099 */
4100 Inkscape::NodePath::Node *
4101 sp_nodepath_get_node_by_index(int index)
4102 {
4103 Inkscape::NodePath::Node *e = NULL;
4105 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4106 if (!nodepath) {
4107 return e;
4108 }
4110 //find segment
4111 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4113 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4114 int n = g_list_length(sp->nodes);
4115 if (sp->closed) {
4116 n++;
4117 }
4119 //if the piece belongs to this subpath grab it
4120 //otherwise move onto the next subpath
4121 if (index < n) {
4122 e = sp->first;
4123 for (int i = 0; i < index; ++i) {
4124 e = e->n.other;
4125 }
4126 break;
4127 } else {
4128 if (sp->closed) {
4129 index -= (n+1);
4130 } else {
4131 index -= n;
4132 }
4133 }
4134 }
4136 return e;
4137 }
4139 /**
4140 * Returns plain text meaning of node type.
4141 */
4142 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4143 {
4144 unsigned retracted = 0;
4145 bool endnode = false;
4147 for (int which = -1; which <= 1; which += 2) {
4148 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4149 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4150 retracted ++;
4151 if (!side->other)
4152 endnode = true;
4153 }
4155 if (retracted == 0) {
4156 if (endnode) {
4157 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4158 return _("end node");
4159 } else {
4160 switch (node->type) {
4161 case Inkscape::NodePath::NODE_CUSP:
4162 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4163 return _("cusp");
4164 case Inkscape::NodePath::NODE_SMOOTH:
4165 // TRANSLATORS: "smooth" is an adjective here
4166 return _("smooth");
4167 case Inkscape::NodePath::NODE_SYMM:
4168 return _("symmetric");
4169 }
4170 }
4171 } else if (retracted == 1) {
4172 if (endnode) {
4173 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4174 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4175 } else {
4176 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4177 }
4178 } else {
4179 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4180 }
4182 return NULL;
4183 }
4185 /**
4186 * Handles content of statusbar as long as node tool is active.
4187 */
4188 void
4189 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
4190 {
4191 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");
4192 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4194 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4195 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4196 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4197 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4199 SPDesktop *desktop = NULL;
4200 if (nodepath) {
4201 desktop = nodepath->desktop;
4202 } else {
4203 desktop = SP_ACTIVE_DESKTOP;
4204 }
4206 SPEventContext *ec = desktop->event_context;
4207 if (!ec) return;
4208 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4209 if (!mc) return;
4211 if (selected_nodes == 0) {
4212 Inkscape::Selection *sel = desktop->selection;
4213 if (!sel || sel->isEmpty()) {
4214 mc->setF(Inkscape::NORMAL_MESSAGE,
4215 _("Select a single object to edit its nodes or handles."));
4216 } else {
4217 if (nodepath) {
4218 mc->setF(Inkscape::NORMAL_MESSAGE,
4219 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.",
4220 "<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.",
4221 total_nodes),
4222 total_nodes);
4223 } else {
4224 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4225 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4226 } else {
4227 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4228 }
4229 }
4230 }
4231 } else if (nodepath && selected_nodes == 1) {
4232 mc->setF(Inkscape::NORMAL_MESSAGE,
4233 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4234 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4235 total_nodes),
4236 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4237 } else {
4238 if (selected_subpaths > 1) {
4239 mc->setF(Inkscape::NORMAL_MESSAGE,
4240 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4241 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4242 total_nodes),
4243 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4244 } else {
4245 mc->setF(Inkscape::NORMAL_MESSAGE,
4246 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4247 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4248 total_nodes),
4249 selected_nodes, total_nodes, when_selected);
4250 }
4251 }
4252 }
4255 /*
4256 Local Variables:
4257 mode:c++
4258 c-file-style:"stroustrup"
4259 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4260 indent-tabs-mode:nil
4261 fill-column:99
4262 End:
4263 */
4264 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :