ec3066cfc2ff9c22e40a55b8dc49293f24db7b42
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 "shape-editor.h"
35 #include "selection-chemistry.h"
36 #include "selection.h"
37 #include "xml/repr.h"
38 #include "prefs-utils.h"
39 #include "sp-metrics.h"
40 #include "sp-path.h"
41 #include "libnr/nr-matrix-ops.h"
42 #include "splivarot.h"
43 #include "svg/svg.h"
44 #include "verbs.h"
45 #include "display/bezier-utils.h"
46 #include <vector>
47 #include <algorithm>
49 class NR::Matrix;
51 /// \todo
52 /// evil evil evil. FIXME: conflict of two different Path classes!
53 /// There is a conflict in the namespace between two classes named Path.
54 /// #include "sp-flowtext.h"
55 /// #include "sp-flowregion.h"
57 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
58 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
59 GType sp_flowregion_get_type (void);
60 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
61 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
62 GType sp_flowtext_get_type (void);
63 // end evil workaround
65 #include "helper/stlport.h"
68 /// \todo fixme: Implement these via preferences */
70 #define NODE_FILL 0xbfbfbf00
71 #define NODE_STROKE 0x000000ff
72 #define NODE_FILL_HI 0xff000000
73 #define NODE_STROKE_HI 0x000000ff
74 #define NODE_FILL_SEL 0x0000ffff
75 #define NODE_STROKE_SEL 0x000000ff
76 #define NODE_FILL_SEL_HI 0xff000000
77 #define NODE_STROKE_SEL_HI 0x000000ff
78 #define KNOT_FILL 0xffffffff
79 #define KNOT_STROKE 0x000000ff
80 #define KNOT_FILL_HI 0xff000000
81 #define KNOT_STROKE_HI 0x000000ff
83 static GMemChunk *nodechunk = NULL;
85 /* Creation from object */
87 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
88 static gchar *parse_nodetypes(gchar const *types, gint length);
90 /* Object updating */
92 static void stamp_repr(Inkscape::NodePath::Path *np);
93 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
94 static gchar *create_typestr(Inkscape::NodePath::Path *np);
96 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
98 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
100 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
102 /* Adjust handle placement, if the node or the other handle is moved */
103 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
104 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
106 /* Node event callbacks */
107 static void node_clicked(SPKnot *knot, guint state, gpointer data);
108 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
109 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
110 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
112 /* Handle event callbacks */
113 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
114 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
115 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
116 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
117 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
118 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
120 /* Constructors and destructors */
122 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
123 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
124 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
125 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
126 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
127 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
128 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
130 /* Helpers */
132 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
133 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
134 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
136 // active_node indicates mouseover node
137 static Inkscape::NodePath::Node *active_node = NULL;
139 /**
140 * \brief Creates new nodepath from item
141 */
142 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item, bool show_handles)
143 {
144 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
146 /** \todo
147 * FIXME: remove this. We don't want to edit paths inside flowtext.
148 * Instead we will build our flowtext with cloned paths, so that the
149 * real paths are outside the flowtext and thus editable as usual.
150 */
151 if (SP_IS_FLOWTEXT(item)) {
152 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
153 if SP_IS_FLOWREGION(child) {
154 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
155 if (grandchild && SP_IS_PATH(grandchild)) {
156 item = SP_ITEM(grandchild);
157 break;
158 }
159 }
160 }
161 }
163 if (!SP_IS_PATH(item))
164 return NULL;
165 SPPath *path = SP_PATH(item);
166 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
167 if (curve == NULL)
168 return NULL;
170 NArtBpath *bpath = sp_curve_first_bpath(curve);
171 gint length = curve->end;
172 if (length == 0)
173 return NULL; // prevent crash for one-node paths
175 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
176 gchar *typestr = parse_nodetypes(nodetypes, length);
178 //Create new nodepath
179 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
180 if (!np)
181 return NULL;
183 // Set defaults
184 np->desktop = desktop;
185 np->path = path;
186 np->subpaths = NULL;
187 np->selected = NULL;
188 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
189 np->livarot_path = NULL;
190 np->local_change = 0;
191 np->show_handles = show_handles;
193 // we need to update item's transform from the repr here,
194 // because they may be out of sync when we respond
195 // to a change in repr by regenerating nodepath --bb
196 sp_object_read_attr(SP_OBJECT(item), "transform");
198 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
199 np->d2i = np->i2d.inverse();
200 np->repr = repr;
202 // create the subpath(s) from the bpath
203 NArtBpath *b = bpath;
204 while (b->code != NR_END) {
205 b = subpath_from_bpath(np, b, typestr + (b - bpath));
206 }
208 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
209 np->subpaths = g_list_reverse(np->subpaths);
211 g_free(typestr);
212 sp_curve_unref(curve);
214 // create the livarot representation from the same item
215 sp_nodepath_ensure_livarot_path(np);
217 return np;
218 }
220 /**
221 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
222 */
223 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
225 if (!np) //soft fail, like delete
226 return;
228 while (np->subpaths) {
229 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
230 }
232 //Inform the ShapeEditor that made me, if any, that I am gone.
233 if (np->shape_editor)
234 np->shape_editor->nodepath_destroyed();
236 g_assert(!np->selected);
238 if (np->livarot_path) {
239 delete np->livarot_path;
240 np->livarot_path = NULL;
241 }
243 np->desktop = NULL;
245 g_free(np);
246 }
249 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
250 {
251 if (np && np->livarot_path == NULL && np->path && SP_IS_ITEM(np->path)) {
252 np->livarot_path = Path_for_item (np->path, true, true);
253 if (np->livarot_path)
254 np->livarot_path->ConvertWithBackData(0.01);
255 }
256 }
259 /**
260 * Return the node count of a given NodeSubPath.
261 */
262 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
263 {
264 if (!subpath)
265 return 0;
266 gint nodeCount = g_list_length(subpath->nodes);
267 return nodeCount;
268 }
270 /**
271 * Return the node count of a given NodePath.
272 */
273 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
274 {
275 if (!np)
276 return 0;
277 gint nodeCount = 0;
278 for (GList *item = np->subpaths ; item ; item=item->next) {
279 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
280 nodeCount += g_list_length(subpath->nodes);
281 }
282 return nodeCount;
283 }
285 /**
286 * Return the subpath count of a given NodePath.
287 */
288 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
289 {
290 if (!np)
291 return 0;
292 return g_list_length (np->subpaths);
293 }
295 /**
296 * Return the selected node count of a given NodePath.
297 */
298 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
299 {
300 if (!np)
301 return 0;
302 return g_list_length (np->selected);
303 }
305 /**
306 * Return the number of subpaths where nodes are selected in a given NodePath.
307 */
308 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
309 {
310 if (!np)
311 return 0;
312 if (!np->selected)
313 return 0;
314 if (!np->selected->next)
315 return 1;
316 gint count = 0;
317 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
318 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
319 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
320 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
321 if (node->selected) {
322 count ++;
323 break;
324 }
325 }
326 }
327 return count;
328 }
330 /**
331 * Clean up a nodepath after editing.
332 *
333 * Currently we are deleting trivial subpaths.
334 */
335 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
336 {
337 GList *badSubPaths = NULL;
339 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
340 for (GList *l = nodepath->subpaths; l ; l=l->next) {
341 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
342 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
343 badSubPaths = g_list_append(badSubPaths, sp);
344 }
346 //Delete them. This second step is because sp_nodepath_subpath_destroy()
347 //also removes the subpath from nodepath->subpaths
348 for (GList *l = badSubPaths; l ; l=l->next) {
349 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
350 sp_nodepath_subpath_destroy(sp);
351 }
353 g_list_free(badSubPaths);
354 }
356 /**
357 * Create new nodepath from b, make it subpath of np.
358 * \param t The node type.
359 * \todo Fixme: t should be a proper type, rather than gchar
360 */
361 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
362 {
363 NR::Point ppos, pos, npos;
365 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
367 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
368 bool const closed = (b->code == NR_MOVETO);
370 pos = NR::Point(b->x3, b->y3) * np->i2d;
371 if (b[1].code == NR_CURVETO) {
372 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
373 } else {
374 npos = pos;
375 }
376 Inkscape::NodePath::Node *n;
377 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
378 g_assert(sp->first == n);
379 g_assert(sp->last == n);
381 b++;
382 t++;
383 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
384 pos = NR::Point(b->x3, b->y3) * np->i2d;
385 if (b->code == NR_CURVETO) {
386 ppos = NR::Point(b->x2, b->y2) * np->i2d;
387 } else {
388 ppos = pos;
389 }
390 if (b[1].code == NR_CURVETO) {
391 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
392 } else {
393 npos = pos;
394 }
395 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
396 b++;
397 t++;
398 }
400 if (closed) sp_nodepath_subpath_close(sp);
402 return b;
403 }
405 /**
406 * Convert from sodipodi:nodetypes to new style type string.
407 */
408 static gchar *parse_nodetypes(gchar const *types, gint length)
409 {
410 g_assert(length > 0);
412 gchar *typestr = g_new(gchar, length + 1);
414 gint pos = 0;
416 if (types) {
417 for (gint i = 0; types[i] && ( i < length ); i++) {
418 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
419 if (types[i] != '\0') {
420 switch (types[i]) {
421 case 's':
422 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
423 break;
424 case 'z':
425 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
426 break;
427 case 'c':
428 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
429 break;
430 default:
431 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
432 break;
433 }
434 }
435 }
436 }
438 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
440 return typestr;
441 }
443 /**
444 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
445 * updated but repr is not (for speed). Used during curve and node drag.
446 */
447 static void update_object(Inkscape::NodePath::Path *np)
448 {
449 g_assert(np);
451 SPCurve *curve = create_curve(np);
453 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
455 sp_curve_unref(curve);
456 }
458 /**
459 * Update XML path node with data from path object.
460 */
461 static void update_repr_internal(Inkscape::NodePath::Path *np)
462 {
463 g_assert(np);
465 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
467 SPCurve *curve = create_curve(np);
468 gchar *typestr = create_typestr(np);
469 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
471 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
472 np->local_change++;
473 repr->setAttribute("d", svgpath);
474 }
476 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
477 np->local_change++;
478 repr->setAttribute("sodipodi:nodetypes", typestr);
479 }
481 g_free(svgpath);
482 g_free(typestr);
483 sp_curve_unref(curve);
484 }
486 /**
487 * Update XML path node with data from path object, commit changes forever.
488 */
489 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
490 {
491 //fixme: np can be NULL, so check before proceeding
492 g_return_if_fail(np != NULL);
494 if (np->livarot_path) {
495 delete np->livarot_path;
496 np->livarot_path = NULL;
497 }
499 update_repr_internal(np);
500 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
502 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
503 annotation);
504 }
506 /**
507 * Update XML path node with data from path object, commit changes with undo.
508 */
509 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
510 {
511 if (np->livarot_path) {
512 delete np->livarot_path;
513 np->livarot_path = NULL;
514 }
516 update_repr_internal(np);
517 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
518 annotation);
519 }
521 /**
522 * Make duplicate of path, replace corresponding XML node in tree, commit.
523 */
524 static void stamp_repr(Inkscape::NodePath::Path *np)
525 {
526 g_assert(np);
528 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
529 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
531 // remember the position of the item
532 gint pos = old_repr->position();
533 // remember parent
534 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
536 SPCurve *curve = create_curve(np);
537 gchar *typestr = create_typestr(np);
539 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
541 new_repr->setAttribute("d", svgpath);
542 new_repr->setAttribute("sodipodi:nodetypes", typestr);
544 // add the new repr to the parent
545 parent->appendChild(new_repr);
546 // move to the saved position
547 new_repr->setPosition(pos > 0 ? pos : 0);
549 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
550 _("Stamp"));
552 Inkscape::GC::release(new_repr);
553 g_free(svgpath);
554 g_free(typestr);
555 sp_curve_unref(curve);
556 }
558 /**
559 * Create curve from path.
560 */
561 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
562 {
563 SPCurve *curve = sp_curve_new();
565 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
566 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
567 sp_curve_moveto(curve,
568 sp->first->pos * np->d2i);
569 Inkscape::NodePath::Node *n = sp->first->n.other;
570 while (n) {
571 NR::Point const end_pt = n->pos * np->d2i;
572 switch (n->code) {
573 case NR_LINETO:
574 sp_curve_lineto(curve, end_pt);
575 break;
576 case NR_CURVETO:
577 sp_curve_curveto(curve,
578 n->p.other->n.pos * np->d2i,
579 n->p.pos * np->d2i,
580 end_pt);
581 break;
582 default:
583 g_assert_not_reached();
584 break;
585 }
586 if (n != sp->last) {
587 n = n->n.other;
588 } else {
589 n = NULL;
590 }
591 }
592 if (sp->closed) {
593 sp_curve_closepath(curve);
594 }
595 }
597 return curve;
598 }
600 /**
601 * Convert path type string to sodipodi:nodetypes style.
602 */
603 static gchar *create_typestr(Inkscape::NodePath::Path *np)
604 {
605 gchar *typestr = g_new(gchar, 32);
606 gint len = 32;
607 gint pos = 0;
609 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
610 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
612 if (pos >= len) {
613 typestr = g_renew(gchar, typestr, len + 32);
614 len += 32;
615 }
617 typestr[pos++] = 'c';
619 Inkscape::NodePath::Node *n;
620 n = sp->first->n.other;
621 while (n) {
622 gchar code;
624 switch (n->type) {
625 case Inkscape::NodePath::NODE_CUSP:
626 code = 'c';
627 break;
628 case Inkscape::NodePath::NODE_SMOOTH:
629 code = 's';
630 break;
631 case Inkscape::NodePath::NODE_SYMM:
632 code = 'z';
633 break;
634 default:
635 g_assert_not_reached();
636 code = '\0';
637 break;
638 }
640 if (pos >= len) {
641 typestr = g_renew(gchar, typestr, len + 32);
642 len += 32;
643 }
645 typestr[pos++] = code;
647 if (n != sp->last) {
648 n = n->n.other;
649 } else {
650 n = NULL;
651 }
652 }
653 }
655 if (pos >= len) {
656 typestr = g_renew(gchar, typestr, len + 1);
657 len += 1;
658 }
660 typestr[pos++] = '\0';
662 return typestr;
663 }
665 /**
666 * Returns current path in context. // later eliminate this function at all!
667 */
668 static Inkscape::NodePath::Path *sp_nodepath_current()
669 {
670 if (!SP_ACTIVE_DESKTOP) {
671 return NULL;
672 }
674 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
676 if (!SP_IS_NODE_CONTEXT(event_context)) {
677 return NULL;
678 }
680 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
681 }
685 /**
686 \brief Fills node and handle positions for three nodes, splitting line
687 marked by end at distance t.
688 */
689 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
690 {
691 g_assert(new_path != NULL);
692 g_assert(end != NULL);
694 g_assert(end->p.other == new_path);
695 Inkscape::NodePath::Node *start = new_path->p.other;
696 g_assert(start);
698 if (end->code == NR_LINETO) {
699 new_path->type =Inkscape::NodePath::NODE_CUSP;
700 new_path->code = NR_LINETO;
701 new_path->pos = (t * start->pos + (1 - t) * end->pos);
702 } else {
703 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
704 new_path->code = NR_CURVETO;
705 gdouble s = 1 - t;
706 for (int dim = 0; dim < 2; dim++) {
707 NR::Coord const f000 = start->pos[dim];
708 NR::Coord const f001 = start->n.pos[dim];
709 NR::Coord const f011 = end->p.pos[dim];
710 NR::Coord const f111 = end->pos[dim];
711 NR::Coord const f00t = s * f000 + t * f001;
712 NR::Coord const f01t = s * f001 + t * f011;
713 NR::Coord const f11t = s * f011 + t * f111;
714 NR::Coord const f0tt = s * f00t + t * f01t;
715 NR::Coord const f1tt = s * f01t + t * f11t;
716 NR::Coord const fttt = s * f0tt + t * f1tt;
717 start->n.pos[dim] = f00t;
718 new_path->p.pos[dim] = f0tt;
719 new_path->pos[dim] = fttt;
720 new_path->n.pos[dim] = f1tt;
721 end->p.pos[dim] = f11t;
722 }
723 }
724 }
726 /**
727 * Adds new node on direct line between two nodes, activates handles of all
728 * three nodes.
729 */
730 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
731 {
732 g_assert(end);
733 g_assert(end->subpath);
734 g_assert(g_list_find(end->subpath->nodes, end));
736 Inkscape::NodePath::Node *start = end->p.other;
737 g_assert( start->n.other == end );
738 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
739 end,
740 Inkscape::NodePath::NODE_SMOOTH,
741 (NRPathcode)end->code,
742 &start->pos, &start->pos, &start->n.pos);
743 sp_nodepath_line_midpoint(newnode, end, t);
745 sp_node_update_handles(start);
746 sp_node_update_handles(newnode);
747 sp_node_update_handles(end);
749 return newnode;
750 }
752 /**
753 \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
754 */
755 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
756 {
757 g_assert(node);
758 g_assert(node->subpath);
759 g_assert(g_list_find(node->subpath->nodes, node));
761 Inkscape::NodePath::SubPath *sp = node->subpath;
762 Inkscape::NodePath::Path *np = sp->nodepath;
764 if (sp->closed) {
765 sp_nodepath_subpath_open(sp, node);
766 return sp->first;
767 } else {
768 // no break for end nodes
769 if (node == sp->first) return NULL;
770 if (node == sp->last ) return NULL;
772 // create a new subpath
773 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
775 // duplicate the break node as start of the new subpath
776 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
778 while (node->n.other) { // copy the remaining nodes into the new subpath
779 Inkscape::NodePath::Node *n = node->n.other;
780 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);
781 if (n->selected) {
782 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
783 }
784 sp_nodepath_node_destroy(n); // remove the point on the original subpath
785 }
787 return newnode;
788 }
789 }
791 /**
792 * Duplicate node and connect to neighbours.
793 */
794 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
795 {
796 g_assert(node);
797 g_assert(node->subpath);
798 g_assert(g_list_find(node->subpath->nodes, node));
800 Inkscape::NodePath::SubPath *sp = node->subpath;
802 NRPathcode code = (NRPathcode) node->code;
803 if (code == NR_MOVETO) { // if node is the endnode,
804 node->code = NR_LINETO; // new one is inserted before it, so change that to line
805 }
807 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
809 if (!node->n.other || !node->p.other) // if node is an endnode, select it
810 return node;
811 else
812 return newnode; // otherwise select the newly created node
813 }
815 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
816 {
817 node->p.pos = (node->pos + (node->pos - node->n.pos));
818 }
820 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
821 {
822 node->n.pos = (node->pos + (node->pos - node->p.pos));
823 }
825 /**
826 * Change line type at node, with side effects on neighbours.
827 */
828 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
829 {
830 g_assert(end);
831 g_assert(end->subpath);
832 g_assert(end->p.other);
834 if (end->code == static_cast< guint > ( code ) )
835 return;
837 Inkscape::NodePath::Node *start = end->p.other;
839 end->code = code;
841 if (code == NR_LINETO) {
842 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
843 if (end->n.other) {
844 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
845 }
846 sp_node_adjust_handle(start, -1);
847 sp_node_adjust_handle(end, 1);
848 } else {
849 NR::Point delta = end->pos - start->pos;
850 start->n.pos = start->pos + delta / 3;
851 end->p.pos = end->pos - delta / 3;
852 sp_node_adjust_handle(start, 1);
853 sp_node_adjust_handle(end, -1);
854 }
856 sp_node_update_handles(start);
857 sp_node_update_handles(end);
858 }
860 /**
861 * Change node type, and its handles accordingly.
862 */
863 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
864 {
865 g_assert(node);
866 g_assert(node->subpath);
868 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
869 return node;
871 if ((node->p.other != NULL) && (node->n.other != NULL)) {
872 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
873 type =Inkscape::NodePath::NODE_CUSP;
874 }
875 }
877 node->type = type;
879 if (node->type == Inkscape::NodePath::NODE_CUSP) {
880 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
881 node->knot->setSize (node->selected? 11 : 9);
882 sp_knot_update_ctrl(node->knot);
883 } else {
884 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
885 node->knot->setSize (node->selected? 9 : 7);
886 sp_knot_update_ctrl(node->knot);
887 }
889 // if one of handles is mouseovered, preserve its position
890 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
891 sp_node_adjust_handle(node, 1);
892 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
893 sp_node_adjust_handle(node, -1);
894 } else {
895 sp_node_adjust_handles(node);
896 }
898 sp_node_update_handles(node);
900 sp_nodepath_update_statusbar(node->subpath->nodepath);
902 return node;
903 }
905 /**
906 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
907 * adjacent segments from lines to curves.
908 */
909 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
910 {
911 bool p_line = (node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos);
912 bool n_line = (node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos);
914 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
915 if (p_line && n_line) {
916 // only if both adjacent segments are lines,
917 // convert both to curves:
919 // BEFORE:
920 {
921 node->code = NR_CURVETO;
922 NR::Point delta;
923 if (node->n.other != NULL)
924 delta = node->n.other->pos - node->p.other->pos;
925 else
926 delta = node->pos - node->p.other->pos;
927 node->p.pos = node->pos - delta / 4;
928 sp_node_update_handles(node);
929 }
931 // AFTER:
932 {
933 node->n.other->code = NR_CURVETO;
934 NR::Point delta;
935 if (node->p.other != NULL)
936 delta = node->p.other->pos - node->n.other->pos;
937 else
938 delta = node->pos - node->n.other->pos;
939 node->n.pos = node->pos - delta / 4;
940 sp_node_update_handles(node);
941 }
942 }
943 }
945 sp_nodepath_set_node_type (node, type);
946 }
948 /**
949 * Move node to point, and adjust its and neighbouring handles.
950 */
951 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
952 {
953 NR::Point delta = p - node->pos;
954 node->pos = p;
956 node->p.pos += delta;
957 node->n.pos += delta;
959 Inkscape::NodePath::Node *node_p = NULL;
960 Inkscape::NodePath::Node *node_n = NULL;
962 if (node->p.other) {
963 if (node->code == NR_LINETO) {
964 sp_node_adjust_handle(node, 1);
965 sp_node_adjust_handle(node->p.other, -1);
966 node_p = node->p.other;
967 }
968 }
969 if (node->n.other) {
970 if (node->n.other->code == NR_LINETO) {
971 sp_node_adjust_handle(node, -1);
972 sp_node_adjust_handle(node->n.other, 1);
973 node_n = node->n.other;
974 }
975 }
977 // this function is only called from batch movers that will update display at the end
978 // themselves, so here we just move all the knots without emitting move signals, for speed
979 sp_node_update_handles(node, false);
980 if (node_n) {
981 sp_node_update_handles(node_n, false);
982 }
983 if (node_p) {
984 sp_node_update_handles(node_p, false);
985 }
986 }
988 /**
989 * Call sp_node_moveto() for node selection and handle possible snapping.
990 */
991 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
992 bool const snap = true)
993 {
994 NR::Coord best = NR_HUGE;
995 NR::Point delta(dx, dy);
996 NR::Point best_pt = delta;
998 if (snap) {
999 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
1001 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1002 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1003 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
1004 if (s.getDistance() < best) {
1005 best = s.getDistance();
1006 best_pt = s.getPoint() - n->pos;
1007 }
1008 }
1009 }
1011 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1012 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1013 sp_node_moveto(n, n->pos + best_pt);
1014 }
1016 // do not update repr here so that node dragging is acceptably fast
1017 update_object(nodepath);
1018 }
1020 /**
1021 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1022 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1023 near x = 0.
1024 */
1025 double
1026 sculpt_profile (double x, double alpha, guint profile)
1027 {
1028 if (x >= 1)
1029 return 0;
1030 if (x <= 0)
1031 return 1;
1033 switch (profile) {
1034 case SCULPT_PROFILE_LINEAR:
1035 return 1 - x;
1036 case SCULPT_PROFILE_BELL:
1037 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1038 case SCULPT_PROFILE_ELLIPTIC:
1039 return sqrt(1 - x*x);
1040 }
1042 return 1;
1043 }
1045 double
1046 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1047 {
1048 // extremely primitive for now, don't have time to look for the real one
1049 double lower = NR::L2(b - a);
1050 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1051 return (lower + upper)/2;
1052 }
1054 void
1055 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1056 {
1057 n->pos = n->origin + delta;
1058 n->n.pos = n->n.origin + delta_n;
1059 n->p.pos = n->p.origin + delta_p;
1060 sp_node_adjust_handles(n);
1061 sp_node_update_handles(n, false);
1062 }
1064 /**
1065 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1066 * on how far they are from the dragged node n.
1067 */
1068 static void
1069 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1070 {
1071 g_assert (n);
1072 g_assert (nodepath);
1073 g_assert (n->subpath->nodepath == nodepath);
1075 double pressure = n->knot->pressure;
1076 if (pressure == 0)
1077 pressure = 0.5; // default
1078 pressure = CLAMP (pressure, 0.2, 0.8);
1080 // map pressure to alpha = 1/5 ... 5
1081 double alpha = 1 - 2 * fabs(pressure - 0.5);
1082 if (pressure > 0.5)
1083 alpha = 1/alpha;
1085 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1087 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1088 // Only one subpath has selected nodes:
1089 // use linear mode, where the distance from n to node being dragged is calculated along the path
1091 double n_sel_range = 0, p_sel_range = 0;
1092 guint n_nodes = 0, p_nodes = 0;
1093 guint n_sel_nodes = 0, p_sel_nodes = 0;
1095 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1096 {
1097 double n_range = 0, p_range = 0;
1098 bool n_going = true, p_going = true;
1099 Inkscape::NodePath::Node *n_node = n;
1100 Inkscape::NodePath::Node *p_node = n;
1101 do {
1102 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1103 if (n_node && n_going)
1104 n_node = n_node->n.other;
1105 if (n_node == NULL) {
1106 n_going = false;
1107 } else {
1108 n_nodes ++;
1109 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1110 if (n_node->selected) {
1111 n_sel_nodes ++;
1112 n_sel_range = n_range;
1113 }
1114 if (n_node == p_node) {
1115 n_going = false;
1116 p_going = false;
1117 }
1118 }
1119 if (p_node && p_going)
1120 p_node = p_node->p.other;
1121 if (p_node == NULL) {
1122 p_going = false;
1123 } else {
1124 p_nodes ++;
1125 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1126 if (p_node->selected) {
1127 p_sel_nodes ++;
1128 p_sel_range = p_range;
1129 }
1130 if (p_node == n_node) {
1131 n_going = false;
1132 p_going = false;
1133 }
1134 }
1135 } while (n_going || p_going);
1136 }
1138 // Second pass: actually move nodes in this subpath
1139 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1140 {
1141 double n_range = 0, p_range = 0;
1142 bool n_going = true, p_going = true;
1143 Inkscape::NodePath::Node *n_node = n;
1144 Inkscape::NodePath::Node *p_node = n;
1145 do {
1146 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1147 if (n_node && n_going)
1148 n_node = n_node->n.other;
1149 if (n_node == NULL) {
1150 n_going = false;
1151 } else {
1152 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1153 if (n_node->selected) {
1154 sp_nodepath_move_node_and_handles (n_node,
1155 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1156 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1157 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1158 }
1159 if (n_node == p_node) {
1160 n_going = false;
1161 p_going = false;
1162 }
1163 }
1164 if (p_node && p_going)
1165 p_node = p_node->p.other;
1166 if (p_node == NULL) {
1167 p_going = false;
1168 } else {
1169 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1170 if (p_node->selected) {
1171 sp_nodepath_move_node_and_handles (p_node,
1172 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1173 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1174 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1175 }
1176 if (p_node == n_node) {
1177 n_going = false;
1178 p_going = false;
1179 }
1180 }
1181 } while (n_going || p_going);
1182 }
1184 } else {
1185 // Multiple subpaths have selected nodes:
1186 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1187 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1188 // fix the pear-like shape when sculpting e.g. a ring
1190 // First pass: calculate range
1191 gdouble direct_range = 0;
1192 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1193 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1194 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1195 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1196 if (node->selected) {
1197 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1198 }
1199 }
1200 }
1202 // Second pass: actually move nodes
1203 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1204 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1205 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1206 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1207 if (node->selected) {
1208 if (direct_range > 1e-6) {
1209 sp_nodepath_move_node_and_handles (node,
1210 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1211 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1212 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1213 } else {
1214 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1215 }
1217 }
1218 }
1219 }
1220 }
1222 // do not update repr here so that node dragging is acceptably fast
1223 update_object(nodepath);
1224 }
1227 /**
1228 * Move node selection to point, adjust its and neighbouring handles,
1229 * handle possible snapping, and commit the change with possible undo.
1230 */
1231 void
1232 sp_node_selected_move(gdouble dx, gdouble dy)
1233 {
1234 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1235 if (!nodepath) return;
1237 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1239 if (dx == 0) {
1240 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1241 } else if (dy == 0) {
1242 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1243 } else {
1244 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1245 }
1246 }
1248 /**
1249 * Move node selection off screen and commit the change.
1250 */
1251 void
1252 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1253 {
1254 // borrowed from sp_selection_move_screen in selection-chemistry.c
1255 // we find out the current zoom factor and divide deltas by it
1256 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1258 gdouble zoom = desktop->current_zoom();
1259 gdouble zdx = dx / zoom;
1260 gdouble zdy = dy / zoom;
1262 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1263 if (!nodepath) return;
1265 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1267 if (dx == 0) {
1268 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1269 } else if (dy == 0) {
1270 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1271 } else {
1272 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1273 }
1274 }
1276 /** If they don't yet exist, creates knot and line for the given side of the node */
1277 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1278 {
1279 if (!side->knot) {
1280 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"));
1282 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1283 side->knot->setSize (7);
1284 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1285 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1286 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1287 sp_knot_update_ctrl(side->knot);
1289 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1290 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1291 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1292 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1293 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1294 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1295 }
1297 if (!side->line) {
1298 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1299 SP_TYPE_CTRLLINE, NULL);
1300 }
1301 }
1303 /**
1304 * Ensure the given handle of the node is visible/invisible, update its screen position
1305 */
1306 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1307 {
1308 g_assert(node != NULL);
1310 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1311 NRPathcode code = sp_node_path_code_from_side(node, side);
1313 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1315 if (show_handle) {
1316 if (!side->knot) { // No handle knot at all
1317 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1318 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1319 side->knot->pos = side->pos;
1320 if (side->knot->item)
1321 SP_CTRL(side->knot->item)->moveto(side->pos);
1322 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1323 sp_knot_show(side->knot);
1324 } else {
1325 if (side->knot->pos != side->pos) { // only if it's really moved
1326 if (fire_move_signals) {
1327 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1328 } else {
1329 sp_knot_moveto(side->knot, &side->pos);
1330 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1331 }
1332 }
1333 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1334 sp_knot_show(side->knot);
1335 }
1336 }
1337 sp_canvas_item_show(side->line);
1338 } else {
1339 if (side->knot) {
1340 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1341 sp_knot_hide(side->knot);
1342 }
1343 }
1344 if (side->line) {
1345 sp_canvas_item_hide(side->line);
1346 }
1347 }
1348 }
1350 /**
1351 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1352 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1353 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1354 * updated; otherwise, just move the knots silently (used in batch moves).
1355 */
1356 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1357 {
1358 g_assert(node != NULL);
1360 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1361 sp_knot_show(node->knot);
1362 }
1364 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1365 if (fire_move_signals)
1366 sp_knot_set_position(node->knot, &node->pos, 0);
1367 else
1368 sp_knot_moveto(node->knot, &node->pos);
1369 }
1371 gboolean show_handles = node->selected;
1372 if (node->p.other != NULL) {
1373 if (node->p.other->selected) show_handles = TRUE;
1374 }
1375 if (node->n.other != NULL) {
1376 if (node->n.other->selected) show_handles = TRUE;
1377 }
1379 if (node->subpath->nodepath->show_handles == false)
1380 show_handles = FALSE;
1382 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1383 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1384 }
1386 /**
1387 * Call sp_node_update_handles() for all nodes on subpath.
1388 */
1389 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1390 {
1391 g_assert(subpath != NULL);
1393 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1394 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1395 }
1396 }
1398 /**
1399 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1400 */
1401 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1402 {
1403 g_assert(nodepath != NULL);
1405 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1406 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1407 }
1408 }
1410 void
1411 sp_nodepath_show_handles(bool show)
1412 {
1413 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1414 if (nodepath == NULL) return;
1416 nodepath->show_handles = show;
1417 sp_nodepath_update_handles(nodepath);
1418 }
1420 /**
1421 * Adds all selected nodes in nodepath to list.
1422 */
1423 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1424 {
1425 StlConv<Node *>::list(l, selected);
1426 /// \todo this adds a copying, rework when the selection becomes a stl list
1427 }
1429 /**
1430 * Align selected nodes on the specified axis.
1431 */
1432 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1433 {
1434 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1435 return;
1436 }
1438 if ( !nodepath->selected->next ) { // only one node selected
1439 return;
1440 }
1441 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1442 NR::Point dest(pNode->pos);
1443 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1444 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1445 if (pNode) {
1446 dest[axis] = pNode->pos[axis];
1447 sp_node_moveto(pNode, dest);
1448 }
1449 }
1451 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1452 }
1454 /// Helper struct.
1455 struct NodeSort
1456 {
1457 Inkscape::NodePath::Node *_node;
1458 NR::Coord _coord;
1459 /// \todo use vectorof pointers instead of calling copy ctor
1460 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1461 _node(node), _coord(node->pos[axis])
1462 {}
1464 };
1466 static bool operator<(NodeSort const &a, NodeSort const &b)
1467 {
1468 return (a._coord < b._coord);
1469 }
1471 /**
1472 * Distribute selected nodes on the specified axis.
1473 */
1474 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1475 {
1476 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1477 return;
1478 }
1480 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1481 return;
1482 }
1484 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1485 std::vector<NodeSort> sorted;
1486 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1487 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1488 if (pNode) {
1489 NodeSort n(pNode, axis);
1490 sorted.push_back(n);
1491 //dest[axis] = pNode->pos[axis];
1492 //sp_node_moveto(pNode, dest);
1493 }
1494 }
1495 std::sort(sorted.begin(), sorted.end());
1496 unsigned int len = sorted.size();
1497 //overall bboxes span
1498 float dist = (sorted.back()._coord -
1499 sorted.front()._coord);
1500 //new distance between each bbox
1501 float step = (dist) / (len - 1);
1502 float pos = sorted.front()._coord;
1503 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1504 it < sorted.end();
1505 it ++ )
1506 {
1507 NR::Point dest((*it)._node->pos);
1508 dest[axis] = pos;
1509 sp_node_moveto((*it)._node, dest);
1510 pos += step;
1511 }
1513 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1514 }
1517 /**
1518 * Call sp_nodepath_line_add_node() for all selected segments.
1519 */
1520 void
1521 sp_node_selected_add_node(void)
1522 {
1523 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1524 if (!nodepath) {
1525 return;
1526 }
1528 GList *nl = NULL;
1530 int n_added = 0;
1532 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1533 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1534 g_assert(t->selected);
1535 if (t->p.other && t->p.other->selected) {
1536 nl = g_list_prepend(nl, t);
1537 }
1538 }
1540 while (nl) {
1541 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1542 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1543 sp_nodepath_node_select(n, TRUE, FALSE);
1544 n_added ++;
1545 nl = g_list_remove(nl, t);
1546 }
1548 /** \todo fixme: adjust ? */
1549 sp_nodepath_update_handles(nodepath);
1551 if (n_added > 1) {
1552 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1553 } else if (n_added > 0) {
1554 sp_nodepath_update_repr(nodepath, _("Add node"));
1555 }
1557 sp_nodepath_update_statusbar(nodepath);
1558 }
1560 /**
1561 * Select segment nearest to point
1562 */
1563 void
1564 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1565 {
1566 if (!nodepath) {
1567 return;
1568 }
1570 sp_nodepath_ensure_livarot_path(nodepath);
1571 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1572 if (!maybe_position) {
1573 return;
1574 }
1575 Path::cut_position position = *maybe_position;
1577 //find segment to segment
1578 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1580 //fixme: this can return NULL, so check before proceeding.
1581 g_return_if_fail(e != NULL);
1583 gboolean force = FALSE;
1584 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1585 force = TRUE;
1586 }
1587 sp_nodepath_node_select(e, (gboolean) toggle, force);
1588 if (e->p.other)
1589 sp_nodepath_node_select(e->p.other, TRUE, force);
1591 sp_nodepath_update_handles(nodepath);
1593 sp_nodepath_update_statusbar(nodepath);
1594 }
1596 /**
1597 * Add a node nearest to point
1598 */
1599 void
1600 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1601 {
1602 if (!nodepath) {
1603 return;
1604 }
1606 sp_nodepath_ensure_livarot_path(nodepath);
1607 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1608 if (!maybe_position) {
1609 return;
1610 }
1611 Path::cut_position position = *maybe_position;
1613 //find segment to split
1614 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1616 //don't know why but t seems to flip for lines
1617 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1618 position.t = 1.0 - position.t;
1619 }
1620 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1621 sp_nodepath_node_select(n, FALSE, TRUE);
1623 /* fixme: adjust ? */
1624 sp_nodepath_update_handles(nodepath);
1626 sp_nodepath_update_repr(nodepath, _("Add node"));
1628 sp_nodepath_update_statusbar(nodepath);
1629 }
1631 /*
1632 * Adjusts a segment so that t moves by a certain delta for dragging
1633 * converts lines to curves
1634 *
1635 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1636 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1637 */
1638 void
1639 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1640 {
1641 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1643 //fixme: e and e->p can be NULL, so check for those before proceeding
1644 g_return_if_fail(e != NULL);
1645 g_return_if_fail(&e->p != NULL);
1647 /* feel good is an arbitrary parameter that distributes the delta between handles
1648 * if t of the drag point is less than 1/6 distance form the endpoint only
1649 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1650 */
1651 double feel_good;
1652 if (t <= 1.0 / 6.0)
1653 feel_good = 0;
1654 else if (t <= 0.5)
1655 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1656 else if (t <= 5.0 / 6.0)
1657 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1658 else
1659 feel_good = 1;
1661 //if we're dragging a line convert it to a curve
1662 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1663 sp_nodepath_set_line_type(e, NR_CURVETO);
1664 }
1666 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1667 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1668 e->p.other->n.pos += offsetcoord0;
1669 e->p.pos += offsetcoord1;
1671 // adjust handles of adjacent nodes where necessary
1672 sp_node_adjust_handle(e,1);
1673 sp_node_adjust_handle(e->p.other,-1);
1675 sp_nodepath_update_handles(e->subpath->nodepath);
1677 update_object(e->subpath->nodepath);
1679 sp_nodepath_update_statusbar(e->subpath->nodepath);
1680 }
1683 /**
1684 * Call sp_nodepath_break() for all selected segments.
1685 */
1686 void sp_node_selected_break()
1687 {
1688 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1689 if (!nodepath) return;
1691 GList *temp = NULL;
1692 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1693 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1694 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1695 if (nn == NULL) continue; // no break, no new node
1696 temp = g_list_prepend(temp, nn);
1697 }
1699 if (temp) {
1700 sp_nodepath_deselect(nodepath);
1701 }
1702 for (GList *l = temp; l != NULL; l = l->next) {
1703 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1704 }
1706 sp_nodepath_update_handles(nodepath);
1708 sp_nodepath_update_repr(nodepath, _("Break path"));
1709 }
1711 /**
1712 * Duplicate the selected node(s).
1713 */
1714 void sp_node_selected_duplicate()
1715 {
1716 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1717 if (!nodepath) {
1718 return;
1719 }
1721 GList *temp = NULL;
1722 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1723 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1724 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1725 if (nn == NULL) continue; // could not duplicate
1726 temp = g_list_prepend(temp, nn);
1727 }
1729 if (temp) {
1730 sp_nodepath_deselect(nodepath);
1731 }
1732 for (GList *l = temp; l != NULL; l = l->next) {
1733 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1734 }
1736 sp_nodepath_update_handles(nodepath);
1738 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1739 }
1741 /**
1742 * Join two nodes by merging them into one.
1743 */
1744 void sp_node_selected_join()
1745 {
1746 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1747 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1749 if (g_list_length(nodepath->selected) != 2) {
1750 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1751 return;
1752 }
1754 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1755 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1757 g_assert(a != b);
1758 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1759 // someone tried to join an orphan node (i.e. a single-node subpath).
1760 // this is not worth an error message, just fail silently.
1761 return;
1762 }
1764 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1765 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1766 return;
1767 }
1769 /* a and b are endpoints */
1771 NR::Point c;
1772 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1773 c = a->pos;
1774 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1775 c = b->pos;
1776 } else {
1777 c = (a->pos + b->pos) / 2;
1778 }
1780 if (a->subpath == b->subpath) {
1781 Inkscape::NodePath::SubPath *sp = a->subpath;
1782 sp_nodepath_subpath_close(sp);
1783 sp_node_moveto (sp->first, c);
1785 sp_nodepath_update_handles(sp->nodepath);
1786 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1787 return;
1788 }
1790 /* a and b are separate subpaths */
1791 Inkscape::NodePath::SubPath *sa = a->subpath;
1792 Inkscape::NodePath::SubPath *sb = b->subpath;
1793 NR::Point p;
1794 Inkscape::NodePath::Node *n;
1795 NRPathcode code;
1796 if (a == sa->first) {
1797 p = sa->first->n.pos;
1798 code = (NRPathcode)sa->first->n.other->code;
1799 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1800 n = sa->last;
1801 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1802 n = n->p.other;
1803 while (n) {
1804 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1805 n = n->p.other;
1806 if (n == sa->first) n = NULL;
1807 }
1808 sp_nodepath_subpath_destroy(sa);
1809 sa = t;
1810 } else if (a == sa->last) {
1811 p = sa->last->p.pos;
1812 code = (NRPathcode)sa->last->code;
1813 sp_nodepath_node_destroy(sa->last);
1814 } else {
1815 code = NR_END;
1816 g_assert_not_reached();
1817 }
1819 if (b == sb->first) {
1820 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1821 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1822 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1823 }
1824 } else if (b == sb->last) {
1825 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1826 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1827 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1828 }
1829 } else {
1830 g_assert_not_reached();
1831 }
1832 /* and now destroy sb */
1834 sp_nodepath_subpath_destroy(sb);
1836 sp_nodepath_update_handles(sa->nodepath);
1838 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1840 sp_nodepath_update_statusbar(nodepath);
1841 }
1843 /**
1844 * Join two nodes by adding a segment between them.
1845 */
1846 void sp_node_selected_join_segment()
1847 {
1848 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1849 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1851 if (g_list_length(nodepath->selected) != 2) {
1852 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1853 return;
1854 }
1856 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1857 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1859 g_assert(a != b);
1860 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1861 // someone tried to join an orphan node (i.e. a single-node subpath).
1862 // this is not worth an error message, just fail silently.
1863 return;
1864 }
1866 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1867 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1868 return;
1869 }
1871 if (a->subpath == b->subpath) {
1872 Inkscape::NodePath::SubPath *sp = a->subpath;
1874 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1875 sp->closed = TRUE;
1877 sp->first->p.other = sp->last;
1878 sp->last->n.other = sp->first;
1880 sp_node_handle_mirror_p_to_n(sp->last);
1881 sp_node_handle_mirror_n_to_p(sp->first);
1883 sp->first->code = sp->last->code;
1884 sp->first = sp->last;
1886 sp_nodepath_update_handles(sp->nodepath);
1888 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1890 return;
1891 }
1893 /* a and b are separate subpaths */
1894 Inkscape::NodePath::SubPath *sa = a->subpath;
1895 Inkscape::NodePath::SubPath *sb = b->subpath;
1897 Inkscape::NodePath::Node *n;
1898 NR::Point p;
1899 NRPathcode code;
1900 if (a == sa->first) {
1901 code = (NRPathcode) sa->first->n.other->code;
1902 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1903 n = sa->last;
1904 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1905 for (n = n->p.other; n != NULL; n = n->p.other) {
1906 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1907 }
1908 sp_nodepath_subpath_destroy(sa);
1909 sa = t;
1910 } else if (a == sa->last) {
1911 code = (NRPathcode)sa->last->code;
1912 } else {
1913 code = NR_END;
1914 g_assert_not_reached();
1915 }
1917 if (b == sb->first) {
1918 n = sb->first;
1919 sp_node_handle_mirror_p_to_n(sa->last);
1920 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1921 sp_node_handle_mirror_n_to_p(sa->last);
1922 for (n = n->n.other; n != NULL; n = n->n.other) {
1923 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1924 }
1925 } else if (b == sb->last) {
1926 n = sb->last;
1927 sp_node_handle_mirror_p_to_n(sa->last);
1928 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1929 sp_node_handle_mirror_n_to_p(sa->last);
1930 for (n = n->p.other; n != NULL; n = n->p.other) {
1931 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1932 }
1933 } else {
1934 g_assert_not_reached();
1935 }
1936 /* and now destroy sb */
1938 sp_nodepath_subpath_destroy(sb);
1940 sp_nodepath_update_handles(sa->nodepath);
1942 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1943 }
1945 /**
1946 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1947 */
1948 void sp_node_delete_preserve(GList *nodes_to_delete)
1949 {
1950 GSList *nodepaths = NULL;
1952 while (nodes_to_delete) {
1953 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1954 Inkscape::NodePath::SubPath *sp = node->subpath;
1955 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1956 Inkscape::NodePath::Node *sample_cursor = NULL;
1957 Inkscape::NodePath::Node *sample_end = NULL;
1958 Inkscape::NodePath::Node *delete_cursor = node;
1959 bool just_delete = false;
1961 //find the start of this contiguous selection
1962 //move left to the first node that is not selected
1963 //or the start of the non-closed path
1964 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1965 delete_cursor = curr;
1966 }
1968 //just delete at the beginning of an open path
1969 if (!delete_cursor->p.other) {
1970 sample_cursor = delete_cursor;
1971 just_delete = true;
1972 } else {
1973 sample_cursor = delete_cursor->p.other;
1974 }
1976 //calculate points for each segment
1977 int rate = 5;
1978 float period = 1.0 / rate;
1979 std::vector<NR::Point> data;
1980 if (!just_delete) {
1981 data.push_back(sample_cursor->pos);
1982 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1983 //just delete at the end of an open path
1984 if (!sp->closed && curr == sp->last) {
1985 just_delete = true;
1986 break;
1987 }
1989 //sample points on the contiguous selected segment
1990 NR::Point *bez;
1991 bez = new NR::Point [4];
1992 bez[0] = curr->pos;
1993 bez[1] = curr->n.pos;
1994 bez[2] = curr->n.other->p.pos;
1995 bez[3] = curr->n.other->pos;
1996 for (int i=1; i<rate; i++) {
1997 gdouble t = i * period;
1998 NR::Point p = bezier_pt(3, bez, t);
1999 data.push_back(p);
2000 }
2001 data.push_back(curr->n.other->pos);
2003 sample_end = curr->n.other;
2004 //break if we've come full circle or hit the end of the selection
2005 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2006 break;
2007 }
2008 }
2009 }
2011 if (!just_delete) {
2012 //calculate the best fitting single segment and adjust the endpoints
2013 NR::Point *adata;
2014 adata = new NR::Point [data.size()];
2015 copy(data.begin(), data.end(), adata);
2017 NR::Point *bez;
2018 bez = new NR::Point [4];
2019 //would decreasing error create a better fitting approximation?
2020 gdouble error = 1.0;
2021 gint ret;
2022 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2024 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2025 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2026 //the resulting nodes behave as expected.
2027 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2028 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2030 //adjust endpoints
2031 sample_cursor->n.pos = bez[1];
2032 sample_end->p.pos = bez[2];
2033 }
2035 //destroy this contiguous selection
2036 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2037 Inkscape::NodePath::Node *temp = delete_cursor;
2038 if (delete_cursor->n.other == delete_cursor) {
2039 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2040 delete_cursor = NULL;
2041 } else {
2042 delete_cursor = delete_cursor->n.other;
2043 }
2044 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2045 sp_nodepath_node_destroy(temp);
2046 }
2048 sp_nodepath_update_handles(nodepath);
2050 if (!g_slist_find(nodepaths, nodepath))
2051 nodepaths = g_slist_prepend (nodepaths, nodepath);
2052 }
2054 for (GSList *i = nodepaths; i; i = i->next) {
2055 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2056 // different nodepaths will give us one undo event per nodepath
2057 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2059 // if the entire nodepath is removed, delete the selected object.
2060 if (nodepath->subpaths == NULL ||
2061 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2062 //at least 2
2063 sp_nodepath_get_node_count(nodepath) < 2) {
2064 SPDocument *document = sp_desktop_document (nodepath->desktop);
2065 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2066 //delete this nodepath's object, not the entire selection! (though at this time, this
2067 //does not matter)
2068 sp_selection_delete();
2069 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2070 _("Delete nodes"));
2071 } else {
2072 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2073 sp_nodepath_update_statusbar(nodepath);
2074 }
2075 }
2077 g_slist_free (nodepaths);
2078 }
2080 /**
2081 * Delete one or more selected nodes.
2082 */
2083 void sp_node_selected_delete()
2084 {
2085 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2086 if (!nodepath) return;
2087 if (!nodepath->selected) return;
2089 /** \todo fixme: do it the right way */
2090 while (nodepath->selected) {
2091 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2092 sp_nodepath_node_destroy(node);
2093 }
2096 //clean up the nodepath (such as for trivial subpaths)
2097 sp_nodepath_cleanup(nodepath);
2099 sp_nodepath_update_handles(nodepath);
2101 // if the entire nodepath is removed, delete the selected object.
2102 if (nodepath->subpaths == NULL ||
2103 sp_nodepath_get_node_count(nodepath) < 2) {
2104 SPDocument *document = sp_desktop_document (nodepath->desktop);
2105 sp_selection_delete();
2106 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2107 _("Delete nodes"));
2108 return;
2109 }
2111 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2113 sp_nodepath_update_statusbar(nodepath);
2114 }
2116 /**
2117 * Delete one or more segments between two selected nodes.
2118 * This is the code for 'split'.
2119 */
2120 void
2121 sp_node_selected_delete_segment(void)
2122 {
2123 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2124 Inkscape::NodePath::Node *curr, *next; //Iterators
2126 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2127 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2129 if (g_list_length(nodepath->selected) != 2) {
2130 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2131 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2132 return;
2133 }
2135 //Selected nodes, not inclusive
2136 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2137 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2139 if ( ( a==b) || //same node
2140 (a->subpath != b->subpath ) || //not the same path
2141 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2142 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2143 {
2144 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2145 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2146 return;
2147 }
2149 //###########################################
2150 //# BEGIN EDITS
2151 //###########################################
2152 //##################################
2153 //# CLOSED PATH
2154 //##################################
2155 if (a->subpath->closed) {
2158 gboolean reversed = FALSE;
2160 //Since we can go in a circle, we need to find the shorter distance.
2161 // a->b or b->a
2162 start = end = NULL;
2163 int distance = 0;
2164 int minDistance = 0;
2165 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2166 if (curr==b) {
2167 //printf("a to b:%d\n", distance);
2168 start = a;//go from a to b
2169 end = b;
2170 minDistance = distance;
2171 //printf("A to B :\n");
2172 break;
2173 }
2174 distance++;
2175 }
2177 //try again, the other direction
2178 distance = 0;
2179 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2180 if (curr==a) {
2181 //printf("b to a:%d\n", distance);
2182 if (distance < minDistance) {
2183 start = b; //we go from b to a
2184 end = a;
2185 reversed = TRUE;
2186 //printf("B to A\n");
2187 }
2188 break;
2189 }
2190 distance++;
2191 }
2194 //Copy everything from 'end' to 'start' to a new subpath
2195 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2196 for (curr=end ; curr ; curr=curr->n.other) {
2197 NRPathcode code = (NRPathcode) curr->code;
2198 if (curr == end)
2199 code = NR_MOVETO;
2200 sp_nodepath_node_new(t, NULL,
2201 (Inkscape::NodePath::NodeType)curr->type, code,
2202 &curr->p.pos, &curr->pos, &curr->n.pos);
2203 if (curr == start)
2204 break;
2205 }
2206 sp_nodepath_subpath_destroy(a->subpath);
2209 }
2213 //##################################
2214 //# OPEN PATH
2215 //##################################
2216 else {
2218 //We need to get the direction of the list between A and B
2219 //Can we walk from a to b?
2220 start = end = NULL;
2221 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2222 if (curr==b) {
2223 start = a; //did it! we go from a to b
2224 end = b;
2225 //printf("A to B\n");
2226 break;
2227 }
2228 }
2229 if (!start) {//didn't work? let's try the other direction
2230 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2231 if (curr==a) {
2232 start = b; //did it! we go from b to a
2233 end = a;
2234 //printf("B to A\n");
2235 break;
2236 }
2237 }
2238 }
2239 if (!start) {
2240 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2241 _("Cannot find path between nodes."));
2242 return;
2243 }
2247 //Copy everything after 'end' to a new subpath
2248 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2249 for (curr=end ; curr ; curr=curr->n.other) {
2250 NRPathcode code = (NRPathcode) curr->code;
2251 if (curr == end)
2252 code = NR_MOVETO;
2253 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2254 &curr->p.pos, &curr->pos, &curr->n.pos);
2255 }
2257 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2258 for (curr = start->n.other ; curr ; curr=next) {
2259 next = curr->n.other;
2260 sp_nodepath_node_destroy(curr);
2261 }
2263 }
2264 //###########################################
2265 //# END EDITS
2266 //###########################################
2268 //clean up the nodepath (such as for trivial subpaths)
2269 sp_nodepath_cleanup(nodepath);
2271 sp_nodepath_update_handles(nodepath);
2273 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2275 sp_nodepath_update_statusbar(nodepath);
2276 }
2278 /**
2279 * Call sp_nodepath_set_line() for all selected segments.
2280 */
2281 void
2282 sp_node_selected_set_line_type(NRPathcode code)
2283 {
2284 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2285 if (nodepath == NULL) return;
2287 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2288 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2289 g_assert(n->selected);
2290 if (n->p.other && n->p.other->selected) {
2291 sp_nodepath_set_line_type(n, code);
2292 }
2293 }
2295 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2296 }
2298 /**
2299 * Call sp_nodepath_convert_node_type() for all selected nodes.
2300 */
2301 void
2302 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2303 {
2304 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2305 if (nodepath == NULL) return;
2307 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2308 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2309 }
2311 sp_nodepath_update_repr(nodepath, _("Change node type"));
2312 }
2314 /**
2315 * Change select status of node, update its own and neighbour handles.
2316 */
2317 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2318 {
2319 node->selected = selected;
2321 if (selected) {
2322 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2323 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2324 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2325 sp_knot_update_ctrl(node->knot);
2326 } else {
2327 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2328 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2329 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2330 sp_knot_update_ctrl(node->knot);
2331 }
2333 sp_node_update_handles(node);
2334 if (node->n.other) sp_node_update_handles(node->n.other);
2335 if (node->p.other) sp_node_update_handles(node->p.other);
2336 }
2338 /**
2339 \brief Select a node
2340 \param node The node to select
2341 \param incremental If true, add to selection, otherwise deselect others
2342 \param override If true, always select this node, otherwise toggle selected status
2343 */
2344 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2345 {
2346 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2348 if (incremental) {
2349 if (override) {
2350 if (!g_list_find(nodepath->selected, node)) {
2351 nodepath->selected = g_list_prepend(nodepath->selected, node);
2352 }
2353 sp_node_set_selected(node, TRUE);
2354 } else { // toggle
2355 if (node->selected) {
2356 g_assert(g_list_find(nodepath->selected, node));
2357 nodepath->selected = g_list_remove(nodepath->selected, node);
2358 } else {
2359 g_assert(!g_list_find(nodepath->selected, node));
2360 nodepath->selected = g_list_prepend(nodepath->selected, node);
2361 }
2362 sp_node_set_selected(node, !node->selected);
2363 }
2364 } else {
2365 sp_nodepath_deselect(nodepath);
2366 nodepath->selected = g_list_prepend(nodepath->selected, node);
2367 sp_node_set_selected(node, TRUE);
2368 }
2370 sp_nodepath_update_statusbar(nodepath);
2371 }
2374 /**
2375 \brief Deselect all nodes in the nodepath
2376 */
2377 void
2378 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2379 {
2380 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2382 while (nodepath->selected) {
2383 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2384 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2385 }
2386 sp_nodepath_update_statusbar(nodepath);
2387 }
2389 /**
2390 \brief Select or invert selection of all nodes in the nodepath
2391 */
2392 void
2393 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2394 {
2395 if (!nodepath) return;
2397 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2398 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2399 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2400 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2401 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2402 }
2403 }
2404 }
2406 /**
2407 * If nothing selected, does the same as sp_nodepath_select_all();
2408 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2409 * (i.e., similar to "select all in layer", with the "selected" subpaths
2410 * being treated as "layers" in the path).
2411 */
2412 void
2413 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2414 {
2415 if (!nodepath) return;
2417 if (g_list_length (nodepath->selected) == 0) {
2418 sp_nodepath_select_all (nodepath, invert);
2419 return;
2420 }
2422 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2423 GSList *subpaths = NULL;
2425 for (GList *l = copy; l != NULL; l = l->next) {
2426 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2427 Inkscape::NodePath::SubPath *subpath = n->subpath;
2428 if (!g_slist_find (subpaths, subpath))
2429 subpaths = g_slist_prepend (subpaths, subpath);
2430 }
2432 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2433 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2434 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2435 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2436 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2437 }
2438 }
2440 g_slist_free (subpaths);
2441 g_list_free (copy);
2442 }
2444 /**
2445 * \brief Select the node after the last selected; if none is selected,
2446 * select the first within path.
2447 */
2448 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2449 {
2450 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2452 Inkscape::NodePath::Node *last = NULL;
2453 if (nodepath->selected) {
2454 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2455 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2456 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2457 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2458 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2459 if (node->selected) {
2460 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2461 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2462 if (spl->next) { // there's a next subpath
2463 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2464 last = subpath_next->first;
2465 } else if (spl->prev) { // there's a previous subpath
2466 last = NULL; // to be set later to the first node of first subpath
2467 } else {
2468 last = node->n.other;
2469 }
2470 } else {
2471 last = node->n.other;
2472 }
2473 } else {
2474 if (node->n.other) {
2475 last = node->n.other;
2476 } else {
2477 if (spl->next) { // there's a next subpath
2478 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2479 last = subpath_next->first;
2480 } else if (spl->prev) { // there's a previous subpath
2481 last = NULL; // to be set later to the first node of first subpath
2482 } else {
2483 last = (Inkscape::NodePath::Node *) subpath->first;
2484 }
2485 }
2486 }
2487 }
2488 }
2489 }
2490 sp_nodepath_deselect(nodepath);
2491 }
2493 if (last) { // there's at least one more node after selected
2494 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2495 } else { // no more nodes, select the first one in first subpath
2496 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2497 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2498 }
2499 }
2501 /**
2502 * \brief Select the node before the first selected; if none is selected,
2503 * select the last within path
2504 */
2505 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2506 {
2507 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2509 Inkscape::NodePath::Node *last = NULL;
2510 if (nodepath->selected) {
2511 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2512 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2513 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2514 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2515 if (node->selected) {
2516 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2517 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2518 if (spl->prev) { // there's a prev subpath
2519 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2520 last = subpath_prev->last;
2521 } else if (spl->next) { // there's a next subpath
2522 last = NULL; // to be set later to the last node of last subpath
2523 } else {
2524 last = node->p.other;
2525 }
2526 } else {
2527 last = node->p.other;
2528 }
2529 } else {
2530 if (node->p.other) {
2531 last = node->p.other;
2532 } else {
2533 if (spl->prev) { // there's a prev subpath
2534 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2535 last = subpath_prev->last;
2536 } else if (spl->next) { // there's a next subpath
2537 last = NULL; // to be set later to the last node of last subpath
2538 } else {
2539 last = (Inkscape::NodePath::Node *) subpath->last;
2540 }
2541 }
2542 }
2543 }
2544 }
2545 }
2546 sp_nodepath_deselect(nodepath);
2547 }
2549 if (last) { // there's at least one more node before selected
2550 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2551 } else { // no more nodes, select the last one in last subpath
2552 GList *spl = g_list_last(nodepath->subpaths);
2553 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2554 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2555 }
2556 }
2558 /**
2559 * \brief Select all nodes that are within the rectangle.
2560 */
2561 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2562 {
2563 if (!incremental) {
2564 sp_nodepath_deselect(nodepath);
2565 }
2567 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2568 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2569 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2570 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2572 if (b.contains(node->pos)) {
2573 sp_nodepath_node_select(node, TRUE, TRUE);
2574 }
2575 }
2576 }
2577 }
2580 void
2581 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2582 {
2583 g_assert (n);
2584 g_assert (nodepath);
2585 g_assert (n->subpath->nodepath == nodepath);
2587 if (g_list_length (nodepath->selected) == 0) {
2588 if (grow > 0) {
2589 sp_nodepath_node_select(n, TRUE, TRUE);
2590 }
2591 return;
2592 }
2594 if (g_list_length (nodepath->selected) == 1) {
2595 if (grow < 0) {
2596 sp_nodepath_deselect (nodepath);
2597 return;
2598 }
2599 }
2601 double n_sel_range = 0, p_sel_range = 0;
2602 Inkscape::NodePath::Node *farthest_n_node = n;
2603 Inkscape::NodePath::Node *farthest_p_node = n;
2605 // Calculate ranges
2606 {
2607 double n_range = 0, p_range = 0;
2608 bool n_going = true, p_going = true;
2609 Inkscape::NodePath::Node *n_node = n;
2610 Inkscape::NodePath::Node *p_node = n;
2611 do {
2612 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2613 if (n_node && n_going)
2614 n_node = n_node->n.other;
2615 if (n_node == NULL) {
2616 n_going = false;
2617 } else {
2618 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2619 if (n_node->selected) {
2620 n_sel_range = n_range;
2621 farthest_n_node = n_node;
2622 }
2623 if (n_node == p_node) {
2624 n_going = false;
2625 p_going = false;
2626 }
2627 }
2628 if (p_node && p_going)
2629 p_node = p_node->p.other;
2630 if (p_node == NULL) {
2631 p_going = false;
2632 } else {
2633 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2634 if (p_node->selected) {
2635 p_sel_range = p_range;
2636 farthest_p_node = p_node;
2637 }
2638 if (p_node == n_node) {
2639 n_going = false;
2640 p_going = false;
2641 }
2642 }
2643 } while (n_going || p_going);
2644 }
2646 if (grow > 0) {
2647 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2648 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2649 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2650 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2651 }
2652 } else {
2653 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2654 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2655 } else if (farthest_p_node && farthest_p_node->selected) {
2656 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2657 }
2658 }
2659 }
2661 void
2662 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2663 {
2664 g_assert (n);
2665 g_assert (nodepath);
2666 g_assert (n->subpath->nodepath == nodepath);
2668 if (g_list_length (nodepath->selected) == 0) {
2669 if (grow > 0) {
2670 sp_nodepath_node_select(n, TRUE, TRUE);
2671 }
2672 return;
2673 }
2675 if (g_list_length (nodepath->selected) == 1) {
2676 if (grow < 0) {
2677 sp_nodepath_deselect (nodepath);
2678 return;
2679 }
2680 }
2682 Inkscape::NodePath::Node *farthest_selected = NULL;
2683 double farthest_dist = 0;
2685 Inkscape::NodePath::Node *closest_unselected = NULL;
2686 double closest_dist = NR_HUGE;
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 *node = (Inkscape::NodePath::Node *) nl->data;
2692 if (node == n)
2693 continue;
2694 if (node->selected) {
2695 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2696 farthest_dist = NR::L2(node->pos - n->pos);
2697 farthest_selected = node;
2698 }
2699 } else {
2700 if (NR::L2(node->pos - n->pos) < closest_dist) {
2701 closest_dist = NR::L2(node->pos - n->pos);
2702 closest_unselected = node;
2703 }
2704 }
2705 }
2706 }
2708 if (grow > 0) {
2709 if (closest_unselected) {
2710 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2711 }
2712 } else {
2713 if (farthest_selected) {
2714 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2715 }
2716 }
2717 }
2720 /**
2721 \brief Saves all nodes' and handles' current positions in their origin members
2722 */
2723 void
2724 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2725 {
2726 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2727 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2728 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2729 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2730 n->origin = n->pos;
2731 n->p.origin = n->p.pos;
2732 n->n.origin = n->n.pos;
2733 }
2734 }
2735 }
2737 /**
2738 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2739 */
2740 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2741 {
2742 if (!nodepath->selected) {
2743 return NULL;
2744 }
2746 GList *r = NULL;
2747 guint i = 0;
2748 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2749 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2750 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2751 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2752 i++;
2753 if (node->selected) {
2754 r = g_list_append(r, GINT_TO_POINTER(i));
2755 }
2756 }
2757 }
2758 return r;
2759 }
2761 /**
2762 \brief Restores selection by selecting nodes whose positions are in the list
2763 */
2764 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2765 {
2766 sp_nodepath_deselect(nodepath);
2768 guint i = 0;
2769 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2770 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2771 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2772 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2773 i++;
2774 if (g_list_find(r, GINT_TO_POINTER(i))) {
2775 sp_nodepath_node_select(node, TRUE, TRUE);
2776 }
2777 }
2778 }
2780 }
2782 /**
2783 \brief Adjusts handle according to node type and line code.
2784 */
2785 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2786 {
2787 double len, otherlen, linelen;
2789 g_assert(node);
2791 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2792 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2794 /** \todo fixme: */
2795 if (me->other == NULL) return;
2796 if (other->other == NULL) return;
2798 /* I have line */
2800 NRPathcode mecode, ocode;
2801 if (which_adjust == 1) {
2802 mecode = (NRPathcode)me->other->code;
2803 ocode = (NRPathcode)node->code;
2804 } else {
2805 mecode = (NRPathcode)node->code;
2806 ocode = (NRPathcode)other->other->code;
2807 }
2809 if (mecode == NR_LINETO) return;
2811 /* I am curve */
2813 if (other->other == NULL) return;
2815 /* Other has line */
2817 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2819 NR::Point delta;
2820 if (ocode == NR_LINETO) {
2821 /* other is lineto, we are either smooth or symm */
2822 Inkscape::NodePath::Node *othernode = other->other;
2823 len = NR::L2(me->pos - node->pos);
2824 delta = node->pos - othernode->pos;
2825 linelen = NR::L2(delta);
2826 if (linelen < 1e-18)
2827 return;
2828 me->pos = node->pos + (len / linelen)*delta;
2829 return;
2830 }
2832 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2834 me->pos = 2 * node->pos - other->pos;
2835 return;
2836 }
2838 /* We are smooth */
2840 len = NR::L2(me->pos - node->pos);
2841 delta = other->pos - node->pos;
2842 otherlen = NR::L2(delta);
2843 if (otherlen < 1e-18) return;
2845 me->pos = node->pos - (len / otherlen) * delta;
2846 }
2848 /**
2849 \brief Adjusts both handles according to node type and line code
2850 */
2851 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2852 {
2853 g_assert(node);
2855 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2857 /* we are either smooth or symm */
2859 if (node->p.other == NULL) return;
2861 if (node->n.other == NULL) return;
2863 if (node->code == NR_LINETO) {
2864 if (node->n.other->code == NR_LINETO) return;
2865 sp_node_adjust_handle(node, 1);
2866 return;
2867 }
2869 if (node->n.other->code == NR_LINETO) {
2870 if (node->code == NR_LINETO) return;
2871 sp_node_adjust_handle(node, -1);
2872 return;
2873 }
2875 /* both are curves */
2876 NR::Point const delta( node->n.pos - node->p.pos );
2878 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2879 node->p.pos = node->pos - delta / 2;
2880 node->n.pos = node->pos + delta / 2;
2881 return;
2882 }
2884 /* We are smooth */
2885 double plen = NR::L2(node->p.pos - node->pos);
2886 if (plen < 1e-18) return;
2887 double nlen = NR::L2(node->n.pos - node->pos);
2888 if (nlen < 1e-18) return;
2889 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2890 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2891 }
2893 /**
2894 * Node event callback.
2895 */
2896 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2897 {
2898 gboolean ret = FALSE;
2899 switch (event->type) {
2900 case GDK_ENTER_NOTIFY:
2901 active_node = n;
2902 break;
2903 case GDK_LEAVE_NOTIFY:
2904 active_node = NULL;
2905 break;
2906 case GDK_SCROLL:
2907 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2908 switch (event->scroll.direction) {
2909 case GDK_SCROLL_UP:
2910 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2911 break;
2912 case GDK_SCROLL_DOWN:
2913 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2914 break;
2915 default:
2916 break;
2917 }
2918 ret = TRUE;
2919 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
2920 switch (event->scroll.direction) {
2921 case GDK_SCROLL_UP:
2922 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2923 break;
2924 case GDK_SCROLL_DOWN:
2925 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2926 break;
2927 default:
2928 break;
2929 }
2930 ret = TRUE;
2931 }
2932 break;
2933 case GDK_KEY_PRESS:
2934 switch (get_group0_keyval (&event->key)) {
2935 case GDK_space:
2936 if (event->key.state & GDK_BUTTON1_MASK) {
2937 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2938 stamp_repr(nodepath);
2939 ret = TRUE;
2940 }
2941 break;
2942 case GDK_Page_Up:
2943 if (event->key.state & GDK_CONTROL_MASK) {
2944 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2945 } else {
2946 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2947 }
2948 break;
2949 case GDK_Page_Down:
2950 if (event->key.state & GDK_CONTROL_MASK) {
2951 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2952 } else {
2953 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2954 }
2955 break;
2956 default:
2957 break;
2958 }
2959 break;
2960 default:
2961 break;
2962 }
2964 return ret;
2965 }
2967 /**
2968 * Handle keypress on node; directly called.
2969 */
2970 gboolean node_key(GdkEvent *event)
2971 {
2972 Inkscape::NodePath::Path *np;
2974 // there is no way to verify nodes so set active_node to nil when deleting!!
2975 if (active_node == NULL) return FALSE;
2977 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2978 gint ret = FALSE;
2979 switch (get_group0_keyval (&event->key)) {
2980 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2981 case GDK_BackSpace:
2982 np = active_node->subpath->nodepath;
2983 sp_nodepath_node_destroy(active_node);
2984 sp_nodepath_update_repr(np, _("Delete node"));
2985 active_node = NULL;
2986 ret = TRUE;
2987 break;
2988 case GDK_c:
2989 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2990 ret = TRUE;
2991 break;
2992 case GDK_s:
2993 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2994 ret = TRUE;
2995 break;
2996 case GDK_y:
2997 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2998 ret = TRUE;
2999 break;
3000 case GDK_b:
3001 sp_nodepath_node_break(active_node);
3002 ret = TRUE;
3003 break;
3004 }
3005 return ret;
3006 }
3007 return FALSE;
3008 }
3010 /**
3011 * Mouseclick on node callback.
3012 */
3013 static void node_clicked(SPKnot *knot, guint state, gpointer data)
3014 {
3015 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3017 if (state & GDK_CONTROL_MASK) {
3018 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3020 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3021 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3022 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3023 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3024 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3025 } else {
3026 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3027 }
3028 sp_nodepath_update_repr(nodepath, _("Change node type"));
3029 sp_nodepath_update_statusbar(nodepath);
3031 } else { //ctrl+alt+click: delete node
3032 GList *node_to_delete = NULL;
3033 node_to_delete = g_list_append(node_to_delete, n);
3034 sp_node_delete_preserve(node_to_delete);
3035 }
3037 } else {
3038 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3039 }
3040 }
3042 /**
3043 * Mouse grabbed node callback.
3044 */
3045 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3046 {
3047 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3049 if (!n->selected) {
3050 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3051 }
3053 n->is_dragging = true;
3054 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3056 sp_nodepath_remember_origins (n->subpath->nodepath);
3057 }
3059 /**
3060 * Mouse ungrabbed node callback.
3061 */
3062 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
3063 {
3064 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3066 n->dragging_out = NULL;
3067 n->is_dragging = false;
3068 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3070 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3071 }
3073 /**
3074 * The point on a line, given by its angle, closest to the given point.
3075 * \param p A point.
3076 * \param a Angle of the line; it is assumed to go through coordinate origin.
3077 * \param closest Pointer to the point struct where the result is stored.
3078 * \todo FIXME: use dot product perhaps?
3079 */
3080 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3081 {
3082 if (a == HUGE_VAL) { // vertical
3083 *closest = NR::Point(0, (*p)[NR::Y]);
3084 } else {
3085 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3086 (*closest)[NR::Y] = a * (*closest)[NR::X];
3087 }
3088 }
3090 /**
3091 * Distance from the point to a line given by its angle.
3092 * \param p A point.
3093 * \param a Angle of the line; it is assumed to go through coordinate origin.
3094 */
3095 static double point_line_distance(NR::Point *p, double a)
3096 {
3097 NR::Point c;
3098 point_line_closest(p, a, &c);
3099 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]));
3100 }
3102 /**
3103 * Callback for node "request" signal.
3104 * \todo fixme: This goes to "moved" event? (lauris)
3105 */
3106 static gboolean
3107 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3108 {
3109 double yn, xn, yp, xp;
3110 double an, ap, na, pa;
3111 double d_an, d_ap, d_na, d_pa;
3112 gboolean collinear = FALSE;
3113 NR::Point c;
3114 NR::Point pr;
3116 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3118 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3119 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3121 NR::Point mouse = (*p);
3123 if (!n->dragging_out) {
3124 // This is the first drag-out event; find out which handle to drag out
3125 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3126 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3128 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3129 return FALSE;
3131 Inkscape::NodePath::NodeSide *opposite;
3132 if (appr_p > appr_n) { // closer to p
3133 n->dragging_out = &n->p;
3134 opposite = &n->n;
3135 n->code = NR_CURVETO;
3136 } else if (appr_p < appr_n) { // closer to n
3137 n->dragging_out = &n->n;
3138 opposite = &n->p;
3139 n->n.other->code = NR_CURVETO;
3140 } else { // p and n nodes are the same
3141 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3142 n->dragging_out = &n->p;
3143 opposite = &n->n;
3144 n->code = NR_CURVETO;
3145 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3146 n->dragging_out = &n->n;
3147 opposite = &n->p;
3148 n->n.other->code = NR_CURVETO;
3149 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3150 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);
3151 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);
3152 if (appr_other_p > appr_other_n) { // closer to other's p handle
3153 n->dragging_out = &n->n;
3154 opposite = &n->p;
3155 n->n.other->code = NR_CURVETO;
3156 } else { // closer to other's n handle
3157 n->dragging_out = &n->p;
3158 opposite = &n->n;
3159 n->code = NR_CURVETO;
3160 }
3161 }
3162 }
3164 // if there's another handle, make sure the one we drag out starts parallel to it
3165 if (opposite->pos != n->pos) {
3166 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3167 }
3169 // knots might not be created yet!
3170 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3171 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3172 }
3174 // pass this on to the handle-moved callback
3175 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3176 sp_node_update_handles(n);
3177 return TRUE;
3178 }
3180 if (state & GDK_CONTROL_MASK) { // constrained motion
3182 // calculate relative distances of handles
3183 // n handle:
3184 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3185 xn = n->n.pos[NR::X] - n->pos[NR::X];
3186 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3187 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3188 if (n->n.other) { // if there is the next point
3189 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3190 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3191 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3192 }
3193 }
3194 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3195 if (yn < 0) { xn = -xn; yn = -yn; }
3197 // p handle:
3198 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3199 xp = n->p.pos[NR::X] - n->pos[NR::X];
3200 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3201 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3202 if (n->p.other) {
3203 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3204 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3205 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3206 }
3207 }
3208 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3209 if (yp < 0) { xp = -xp; yp = -yp; }
3211 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3212 // sliding on handles, only if at least one of the handles is non-vertical
3213 // (otherwise it's the same as ctrl+drag anyway)
3215 // calculate angles of the handles
3216 if (xn == 0) {
3217 if (yn == 0) { // no handle, consider it the continuation of the other one
3218 an = 0;
3219 collinear = TRUE;
3220 }
3221 else an = 0; // vertical; set the angle to horizontal
3222 } else an = yn/xn;
3224 if (xp == 0) {
3225 if (yp == 0) { // no handle, consider it the continuation of the other one
3226 ap = an;
3227 }
3228 else ap = 0; // vertical; set the angle to horizontal
3229 } else ap = yp/xp;
3231 if (collinear) an = ap;
3233 // angles of the perpendiculars; HUGE_VAL means vertical
3234 if (an == 0) na = HUGE_VAL; else na = -1/an;
3235 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3237 // mouse point relative to the node's original pos
3238 pr = (*p) - n->origin;
3240 // distances to the four lines (two handles and two perpendiculars)
3241 d_an = point_line_distance(&pr, an);
3242 d_na = point_line_distance(&pr, na);
3243 d_ap = point_line_distance(&pr, ap);
3244 d_pa = point_line_distance(&pr, pa);
3246 // find out which line is the closest, save its closest point in c
3247 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3248 point_line_closest(&pr, an, &c);
3249 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3250 point_line_closest(&pr, ap, &c);
3251 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3252 point_line_closest(&pr, na, &c);
3253 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3254 point_line_closest(&pr, pa, &c);
3255 }
3257 // move the node to the closest point
3258 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3259 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3260 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3262 } else { // constraining to hor/vert
3264 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3265 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3266 } else { // snap to vert
3267 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3268 }
3269 }
3270 } else { // move freely
3271 if (n->is_dragging) {
3272 if (state & GDK_MOD1_MASK) { // sculpt
3273 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3274 } else {
3275 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3276 (*p)[NR::X] - n->pos[NR::X],
3277 (*p)[NR::Y] - n->pos[NR::Y],
3278 (state & GDK_SHIFT_MASK) == 0);
3279 }
3280 }
3281 }
3283 n->subpath->nodepath->desktop->scroll_to_point(p);
3285 return TRUE;
3286 }
3288 /**
3289 * Node handle clicked callback.
3290 */
3291 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3292 {
3293 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3295 if (state & GDK_CONTROL_MASK) { // "delete" handle
3296 if (n->p.knot == knot) {
3297 n->p.pos = n->pos;
3298 } else if (n->n.knot == knot) {
3299 n->n.pos = n->pos;
3300 }
3301 sp_node_update_handles(n);
3302 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3303 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3304 sp_nodepath_update_statusbar(nodepath);
3306 } else { // just select or add to selection, depending in Shift
3307 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3308 }
3309 }
3311 /**
3312 * Node handle grabbed callback.
3313 */
3314 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3315 {
3316 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3318 if (!n->selected) {
3319 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3320 }
3322 // remember the origin point of the handle
3323 if (n->p.knot == knot) {
3324 n->p.origin_radial = n->p.pos - n->pos;
3325 } else if (n->n.knot == knot) {
3326 n->n.origin_radial = n->n.pos - n->pos;
3327 } else {
3328 g_assert_not_reached();
3329 }
3331 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3332 }
3334 /**
3335 * Node handle ungrabbed callback.
3336 */
3337 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3338 {
3339 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3341 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3342 if (n->p.knot == knot) {
3343 n->p.origin_radial.a = 0;
3344 sp_knot_set_position(knot, &n->p.pos, state);
3345 } else if (n->n.knot == knot) {
3346 n->n.origin_radial.a = 0;
3347 sp_knot_set_position(knot, &n->n.pos, state);
3348 } else {
3349 g_assert_not_reached();
3350 }
3352 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3353 }
3355 /**
3356 * Node handle "request" signal callback.
3357 */
3358 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3359 {
3360 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3362 Inkscape::NodePath::NodeSide *me, *opposite;
3363 gint which;
3364 if (n->p.knot == knot) {
3365 me = &n->p;
3366 opposite = &n->n;
3367 which = -1;
3368 } else if (n->n.knot == knot) {
3369 me = &n->n;
3370 opposite = &n->p;
3371 which = 1;
3372 } else {
3373 me = opposite = NULL;
3374 which = 0;
3375 g_assert_not_reached();
3376 }
3378 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3380 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3382 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3383 /* We are smooth node adjacent with line */
3384 NR::Point const delta = *p - n->pos;
3385 NR::Coord const len = NR::L2(delta);
3386 Inkscape::NodePath::Node *othernode = opposite->other;
3387 NR::Point const ndelta = n->pos - othernode->pos;
3388 NR::Coord const linelen = NR::L2(ndelta);
3389 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3390 NR::Coord const scal = dot(delta, ndelta) / linelen;
3391 (*p) = n->pos + (scal / linelen) * ndelta;
3392 }
3393 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3394 } else {
3395 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3396 }
3398 sp_node_adjust_handle(n, -which);
3400 return FALSE;
3401 }
3403 /**
3404 * Node handle moved callback.
3405 */
3406 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3407 {
3408 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3410 Inkscape::NodePath::NodeSide *me;
3411 Inkscape::NodePath::NodeSide *other;
3412 if (n->p.knot == knot) {
3413 me = &n->p;
3414 other = &n->n;
3415 } else if (n->n.knot == knot) {
3416 me = &n->n;
3417 other = &n->p;
3418 } else {
3419 me = NULL;
3420 other = NULL;
3421 g_assert_not_reached();
3422 }
3424 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3425 Radial rme(me->pos - n->pos);
3426 Radial rother(other->pos - n->pos);
3427 Radial rnew(*p - n->pos);
3429 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3430 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3431 /* 0 interpreted as "no snapping". */
3433 // The closest PI/snaps angle, starting from zero.
3434 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3435 if (me->origin_radial.a == HUGE_VAL) {
3436 // ortho doesn't exist: original handle was zero length.
3437 rnew.a = a_snapped;
3438 } else {
3439 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3440 * its opposite and perpendiculars). */
3441 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3443 // Snap to the closest.
3444 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3445 ? a_snapped
3446 : a_ortho );
3447 }
3448 }
3450 if (state & GDK_MOD1_MASK) {
3451 // lock handle length
3452 rnew.r = me->origin_radial.r;
3453 }
3455 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3456 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3457 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3458 rother.a += rnew.a - rme.a;
3459 other->pos = NR::Point(rother) + n->pos;
3460 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3461 sp_knot_set_position(other->knot, &other->pos, 0);
3462 }
3464 me->pos = NR::Point(rnew) + n->pos;
3465 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3467 // move knot, but without emitting the signal:
3468 // we cannot emit a "moved" signal because we're now processing it
3469 sp_knot_moveto(me->knot, &(me->pos));
3471 update_object(n->subpath->nodepath);
3473 /* status text */
3474 SPDesktop *desktop = n->subpath->nodepath->desktop;
3475 if (!desktop) return;
3476 SPEventContext *ec = desktop->event_context;
3477 if (!ec) return;
3478 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3479 if (!mc) return;
3481 double degrees = 180 / M_PI * rnew.a;
3482 if (degrees > 180) degrees -= 360;
3483 if (degrees < -180) degrees += 360;
3484 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3485 degrees = angle_to_compass (degrees);
3487 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3489 mc->setF(Inkscape::NORMAL_MESSAGE,
3490 _("<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);
3492 g_string_free(length, TRUE);
3493 }
3495 /**
3496 * Node handle event callback.
3497 */
3498 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3499 {
3500 gboolean ret = FALSE;
3501 switch (event->type) {
3502 case GDK_KEY_PRESS:
3503 switch (get_group0_keyval (&event->key)) {
3504 case GDK_space:
3505 if (event->key.state & GDK_BUTTON1_MASK) {
3506 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3507 stamp_repr(nodepath);
3508 ret = TRUE;
3509 }
3510 break;
3511 default:
3512 break;
3513 }
3514 break;
3515 default:
3516 break;
3517 }
3519 return ret;
3520 }
3522 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3523 Radial &rme, Radial &rother, gboolean const both)
3524 {
3525 rme.a += angle;
3526 if ( both
3527 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3528 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3529 {
3530 rother.a += angle;
3531 }
3532 }
3534 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3535 Radial &rme, Radial &rother, gboolean const both)
3536 {
3537 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3539 gdouble r;
3540 if ( both
3541 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3542 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3543 {
3544 r = MAX(rme.r, rother.r);
3545 } else {
3546 r = rme.r;
3547 }
3549 gdouble const weird_angle = atan2(norm_angle, r);
3550 /* Bulia says norm_angle is just the visible distance that the
3551 * object's end must travel on the screen. Left as 'angle' for want of
3552 * a better name.*/
3554 rme.a += weird_angle;
3555 if ( both
3556 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3557 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3558 {
3559 rother.a += weird_angle;
3560 }
3561 }
3563 /**
3564 * Rotate one node.
3565 */
3566 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3567 {
3568 Inkscape::NodePath::NodeSide *me, *other;
3569 bool both = false;
3571 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3572 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3574 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3575 me = &(n->p);
3576 other = &(n->n);
3577 } else if (!n->p.other) {
3578 me = &(n->n);
3579 other = &(n->p);
3580 } else {
3581 if (which > 0) { // right handle
3582 if (xn > xp) {
3583 me = &(n->n);
3584 other = &(n->p);
3585 } else {
3586 me = &(n->p);
3587 other = &(n->n);
3588 }
3589 } else if (which < 0){ // left handle
3590 if (xn <= xp) {
3591 me = &(n->n);
3592 other = &(n->p);
3593 } else {
3594 me = &(n->p);
3595 other = &(n->n);
3596 }
3597 } else { // both handles
3598 me = &(n->n);
3599 other = &(n->p);
3600 both = true;
3601 }
3602 }
3604 Radial rme(me->pos - n->pos);
3605 Radial rother(other->pos - n->pos);
3607 if (screen) {
3608 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3609 } else {
3610 node_rotate_one_internal (*n, angle, rme, rother, both);
3611 }
3613 me->pos = n->pos + NR::Point(rme);
3615 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3616 other->pos = n->pos + NR::Point(rother);
3617 }
3619 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3620 // so here we just move all the knots without emitting move signals, for speed
3621 sp_node_update_handles(n, false);
3622 }
3624 /**
3625 * Rotate selected nodes.
3626 */
3627 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3628 {
3629 if (!nodepath || !nodepath->selected) return;
3631 if (g_list_length(nodepath->selected) == 1) {
3632 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3633 node_rotate_one (n, angle, which, screen);
3634 } else {
3635 // rotate as an object:
3637 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3638 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3639 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3640 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3641 box.expandTo (n->pos); // contain all selected nodes
3642 }
3644 gdouble rot;
3645 if (screen) {
3646 gdouble const zoom = nodepath->desktop->current_zoom();
3647 gdouble const zmove = angle / zoom;
3648 gdouble const r = NR::L2(box.max() - box.midpoint());
3649 rot = atan2(zmove, r);
3650 } else {
3651 rot = angle;
3652 }
3654 NR::Matrix t =
3655 NR::Matrix (NR::translate(-box.midpoint())) *
3656 NR::Matrix (NR::rotate(rot)) *
3657 NR::Matrix (NR::translate(box.midpoint()));
3659 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3660 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3661 n->pos *= t;
3662 n->n.pos *= t;
3663 n->p.pos *= t;
3664 sp_node_update_handles(n, false);
3665 }
3666 }
3668 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3669 }
3671 /**
3672 * Scale one node.
3673 */
3674 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3675 {
3676 bool both = false;
3677 Inkscape::NodePath::NodeSide *me, *other;
3679 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3680 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3682 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3683 me = &(n->p);
3684 other = &(n->n);
3685 n->code = NR_CURVETO;
3686 } else if (!n->p.other) {
3687 me = &(n->n);
3688 other = &(n->p);
3689 if (n->n.other)
3690 n->n.other->code = NR_CURVETO;
3691 } else {
3692 if (which > 0) { // right handle
3693 if (xn > xp) {
3694 me = &(n->n);
3695 other = &(n->p);
3696 if (n->n.other)
3697 n->n.other->code = NR_CURVETO;
3698 } else {
3699 me = &(n->p);
3700 other = &(n->n);
3701 n->code = NR_CURVETO;
3702 }
3703 } else if (which < 0){ // left handle
3704 if (xn <= xp) {
3705 me = &(n->n);
3706 other = &(n->p);
3707 if (n->n.other)
3708 n->n.other->code = NR_CURVETO;
3709 } else {
3710 me = &(n->p);
3711 other = &(n->n);
3712 n->code = NR_CURVETO;
3713 }
3714 } else { // both handles
3715 me = &(n->n);
3716 other = &(n->p);
3717 both = true;
3718 n->code = NR_CURVETO;
3719 if (n->n.other)
3720 n->n.other->code = NR_CURVETO;
3721 }
3722 }
3724 Radial rme(me->pos - n->pos);
3725 Radial rother(other->pos - n->pos);
3727 rme.r += grow;
3728 if (rme.r < 0) rme.r = 0;
3729 if (rme.a == HUGE_VAL) {
3730 if (me->other) { // if direction is unknown, initialize it towards the next node
3731 Radial rme_next(me->other->pos - n->pos);
3732 rme.a = rme_next.a;
3733 } else { // if there's no next, initialize to 0
3734 rme.a = 0;
3735 }
3736 }
3737 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3738 rother.r += grow;
3739 if (rother.r < 0) rother.r = 0;
3740 if (rother.a == HUGE_VAL) {
3741 rother.a = rme.a + M_PI;
3742 }
3743 }
3745 me->pos = n->pos + NR::Point(rme);
3747 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3748 other->pos = n->pos + NR::Point(rother);
3749 }
3751 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3752 // so here we just move all the knots without emitting move signals, for speed
3753 sp_node_update_handles(n, false);
3754 }
3756 /**
3757 * Scale selected nodes.
3758 */
3759 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3760 {
3761 if (!nodepath || !nodepath->selected) return;
3763 if (g_list_length(nodepath->selected) == 1) {
3764 // scale handles of the single selected node
3765 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3766 node_scale_one (n, grow, which);
3767 } else {
3768 // scale nodes as an "object":
3770 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3771 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3772 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3773 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3774 box.expandTo (n->pos); // contain all selected nodes
3775 }
3777 double scale = (box.maxExtent() + grow)/box.maxExtent();
3779 NR::Matrix t =
3780 NR::Matrix (NR::translate(-box.midpoint())) *
3781 NR::Matrix (NR::scale(scale, scale)) *
3782 NR::Matrix (NR::translate(box.midpoint()));
3784 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3785 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3786 n->pos *= t;
3787 n->n.pos *= t;
3788 n->p.pos *= t;
3789 sp_node_update_handles(n, false);
3790 }
3791 }
3793 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3794 }
3796 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3797 {
3798 if (!nodepath) return;
3799 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3800 }
3802 /**
3803 * Flip selected nodes horizontally/vertically.
3804 */
3805 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3806 {
3807 if (!nodepath || !nodepath->selected) return;
3809 if (g_list_length(nodepath->selected) == 1) {
3810 // flip handles of the single selected node
3811 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3812 double temp = n->p.pos[axis];
3813 n->p.pos[axis] = n->n.pos[axis];
3814 n->n.pos[axis] = temp;
3815 sp_node_update_handles(n, false);
3816 } else {
3817 // scale nodes as an "object":
3819 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3820 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3821 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3822 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3823 box.expandTo (n->pos); // contain all selected nodes
3824 }
3826 NR::Matrix t =
3827 NR::Matrix (NR::translate(-box.midpoint())) *
3828 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3829 NR::Matrix (NR::translate(box.midpoint()));
3831 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3832 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3833 n->pos *= t;
3834 n->n.pos *= t;
3835 n->p.pos *= t;
3836 sp_node_update_handles(n, false);
3837 }
3838 }
3840 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3841 }
3843 //-----------------------------------------------
3844 /**
3845 * Return new subpath under given nodepath.
3846 */
3847 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3848 {
3849 g_assert(nodepath);
3850 g_assert(nodepath->desktop);
3852 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3854 s->nodepath = nodepath;
3855 s->closed = FALSE;
3856 s->nodes = NULL;
3857 s->first = NULL;
3858 s->last = NULL;
3860 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3861 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3862 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3864 return s;
3865 }
3867 /**
3868 * Destroy nodes in subpath, then subpath itself.
3869 */
3870 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3871 {
3872 g_assert(subpath);
3873 g_assert(subpath->nodepath);
3874 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3876 while (subpath->nodes) {
3877 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3878 }
3880 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3882 g_free(subpath);
3883 }
3885 /**
3886 * Link head to tail in subpath.
3887 */
3888 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3889 {
3890 g_assert(!sp->closed);
3891 g_assert(sp->last != sp->first);
3892 g_assert(sp->first->code == NR_MOVETO);
3894 sp->closed = TRUE;
3896 //Link the head to the tail
3897 sp->first->p.other = sp->last;
3898 sp->last->n.other = sp->first;
3899 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3900 sp->first = sp->last;
3902 //Remove the extra end node
3903 sp_nodepath_node_destroy(sp->last->n.other);
3904 }
3906 /**
3907 * Open closed (loopy) subpath at node.
3908 */
3909 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3910 {
3911 g_assert(sp->closed);
3912 g_assert(n->subpath == sp);
3913 g_assert(sp->first == sp->last);
3915 /* We create new startpoint, current node will become last one */
3917 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3918 &n->pos, &n->pos, &n->n.pos);
3921 sp->closed = FALSE;
3923 //Unlink to make a head and tail
3924 sp->first = new_path;
3925 sp->last = n;
3926 n->n.other = NULL;
3927 new_path->p.other = NULL;
3928 }
3930 /**
3931 * Returns area in triangle given by points; may be negative.
3932 */
3933 inline double
3934 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3935 {
3936 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]);
3937 }
3939 /**
3940 * Return new node in subpath with given properties.
3941 * \param pos Position of node.
3942 * \param ppos Handle position in previous direction
3943 * \param npos Handle position in previous direction
3944 */
3945 Inkscape::NodePath::Node *
3946 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)
3947 {
3948 g_assert(sp);
3949 g_assert(sp->nodepath);
3950 g_assert(sp->nodepath->desktop);
3952 if (nodechunk == NULL)
3953 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3955 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3957 n->subpath = sp;
3959 if (type != Inkscape::NodePath::NODE_NONE) {
3960 // use the type from sodipodi:nodetypes
3961 n->type = type;
3962 } else {
3963 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3964 // points are (almost) collinear
3965 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3966 // endnode, or a node with a retracted handle
3967 n->type = Inkscape::NodePath::NODE_CUSP;
3968 } else {
3969 n->type = Inkscape::NodePath::NODE_SMOOTH;
3970 }
3971 } else {
3972 n->type = Inkscape::NodePath::NODE_CUSP;
3973 }
3974 }
3976 n->code = code;
3977 n->selected = FALSE;
3978 n->pos = *pos;
3979 n->p.pos = *ppos;
3980 n->n.pos = *npos;
3982 n->dragging_out = NULL;
3984 Inkscape::NodePath::Node *prev;
3985 if (next) {
3986 //g_assert(g_list_find(sp->nodes, next));
3987 prev = next->p.other;
3988 } else {
3989 prev = sp->last;
3990 }
3992 if (prev)
3993 prev->n.other = n;
3994 else
3995 sp->first = n;
3997 if (next)
3998 next->p.other = n;
3999 else
4000 sp->last = n;
4002 n->p.other = prev;
4003 n->n.other = next;
4005 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"));
4006 sp_knot_set_position(n->knot, pos, 0);
4008 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4009 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4010 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4011 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4012 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4013 sp_knot_update_ctrl(n->knot);
4015 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4016 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4017 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4018 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4019 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4020 sp_knot_show(n->knot);
4022 // We only create handle knots and lines on demand
4023 n->p.knot = NULL;
4024 n->p.line = NULL;
4025 n->n.knot = NULL;
4026 n->n.line = NULL;
4028 sp->nodes = g_list_prepend(sp->nodes, n);
4030 return n;
4031 }
4033 /**
4034 * Destroy node and its knots, link neighbors in subpath.
4035 */
4036 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4037 {
4038 g_assert(node);
4039 g_assert(node->subpath);
4040 g_assert(SP_IS_KNOT(node->knot));
4042 Inkscape::NodePath::SubPath *sp = node->subpath;
4044 if (node->selected) { // first, deselect
4045 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4046 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4047 }
4049 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4051 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4052 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4053 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4054 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4055 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4056 g_object_unref(G_OBJECT(node->knot));
4058 if (node->p.knot) {
4059 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4060 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4061 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4062 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4063 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4064 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4065 g_object_unref(G_OBJECT(node->p.knot));
4066 node->p.knot = NULL;
4067 }
4069 if (node->n.knot) {
4070 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4071 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4072 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4073 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4074 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4075 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4076 g_object_unref(G_OBJECT(node->n.knot));
4077 node->n.knot = NULL;
4078 }
4080 if (node->p.line)
4081 gtk_object_destroy(GTK_OBJECT(node->p.line));
4082 if (node->n.line)
4083 gtk_object_destroy(GTK_OBJECT(node->n.line));
4085 if (sp->nodes) { // there are others nodes on the subpath
4086 if (sp->closed) {
4087 if (sp->first == node) {
4088 g_assert(sp->last == node);
4089 sp->first = node->n.other;
4090 sp->last = sp->first;
4091 }
4092 node->p.other->n.other = node->n.other;
4093 node->n.other->p.other = node->p.other;
4094 } else {
4095 if (sp->first == node) {
4096 sp->first = node->n.other;
4097 sp->first->code = NR_MOVETO;
4098 }
4099 if (sp->last == node) sp->last = node->p.other;
4100 if (node->p.other) node->p.other->n.other = node->n.other;
4101 if (node->n.other) node->n.other->p.other = node->p.other;
4102 }
4103 } else { // this was the last node on subpath
4104 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4105 }
4107 g_mem_chunk_free(nodechunk, node);
4108 }
4110 /**
4111 * Returns one of the node's two sides.
4112 * \param which Indicates which side.
4113 * \return Pointer to previous node side if which==-1, next if which==1.
4114 */
4115 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4116 {
4117 g_assert(node);
4119 switch (which) {
4120 case -1:
4121 return &node->p;
4122 case 1:
4123 return &node->n;
4124 default:
4125 break;
4126 }
4128 g_assert_not_reached();
4130 return NULL;
4131 }
4133 /**
4134 * Return the other side of the node, given one of its sides.
4135 */
4136 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4137 {
4138 g_assert(node);
4140 if (me == &node->p) return &node->n;
4141 if (me == &node->n) return &node->p;
4143 g_assert_not_reached();
4145 return NULL;
4146 }
4148 /**
4149 * Return NRPathcode on the given side of the node.
4150 */
4151 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4152 {
4153 g_assert(node);
4155 if (me == &node->p) {
4156 if (node->p.other) return (NRPathcode)node->code;
4157 return NR_MOVETO;
4158 }
4160 if (me == &node->n) {
4161 if (node->n.other) return (NRPathcode)node->n.other->code;
4162 return NR_MOVETO;
4163 }
4165 g_assert_not_reached();
4167 return NR_END;
4168 }
4170 /**
4171 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4172 */
4173 Inkscape::NodePath::Node *
4174 sp_nodepath_get_node_by_index(int index)
4175 {
4176 Inkscape::NodePath::Node *e = NULL;
4178 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4179 if (!nodepath) {
4180 return e;
4181 }
4183 //find segment
4184 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4186 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4187 int n = g_list_length(sp->nodes);
4188 if (sp->closed) {
4189 n++;
4190 }
4192 //if the piece belongs to this subpath grab it
4193 //otherwise move onto the next subpath
4194 if (index < n) {
4195 e = sp->first;
4196 for (int i = 0; i < index; ++i) {
4197 e = e->n.other;
4198 }
4199 break;
4200 } else {
4201 if (sp->closed) {
4202 index -= (n+1);
4203 } else {
4204 index -= n;
4205 }
4206 }
4207 }
4209 return e;
4210 }
4212 /**
4213 * Returns plain text meaning of node type.
4214 */
4215 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4216 {
4217 unsigned retracted = 0;
4218 bool endnode = false;
4220 for (int which = -1; which <= 1; which += 2) {
4221 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4222 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4223 retracted ++;
4224 if (!side->other)
4225 endnode = true;
4226 }
4228 if (retracted == 0) {
4229 if (endnode) {
4230 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4231 return _("end node");
4232 } else {
4233 switch (node->type) {
4234 case Inkscape::NodePath::NODE_CUSP:
4235 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4236 return _("cusp");
4237 case Inkscape::NodePath::NODE_SMOOTH:
4238 // TRANSLATORS: "smooth" is an adjective here
4239 return _("smooth");
4240 case Inkscape::NodePath::NODE_SYMM:
4241 return _("symmetric");
4242 }
4243 }
4244 } else if (retracted == 1) {
4245 if (endnode) {
4246 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4247 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4248 } else {
4249 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4250 }
4251 } else {
4252 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4253 }
4255 return NULL;
4256 }
4258 /**
4259 * Handles content of statusbar as long as node tool is active.
4260 */
4261 void
4262 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4263 {
4264 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");
4265 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4267 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4268 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4269 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4270 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4272 SPDesktop *desktop = NULL;
4273 if (nodepath) {
4274 desktop = nodepath->desktop;
4275 } else {
4276 desktop = SP_ACTIVE_DESKTOP;
4277 }
4279 SPEventContext *ec = desktop->event_context;
4280 if (!ec) return;
4281 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4282 if (!mc) return;
4284 if (selected_nodes == 0) {
4285 Inkscape::Selection *sel = desktop->selection;
4286 if (!sel || sel->isEmpty()) {
4287 mc->setF(Inkscape::NORMAL_MESSAGE,
4288 _("Select a single object to edit its nodes or handles."));
4289 } else {
4290 if (nodepath) {
4291 mc->setF(Inkscape::NORMAL_MESSAGE,
4292 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.",
4293 "<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.",
4294 total_nodes),
4295 total_nodes);
4296 } else {
4297 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4298 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4299 } else {
4300 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4301 }
4302 }
4303 }
4304 } else if (nodepath && selected_nodes == 1) {
4305 mc->setF(Inkscape::NORMAL_MESSAGE,
4306 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4307 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4308 total_nodes),
4309 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4310 } else {
4311 if (selected_subpaths > 1) {
4312 mc->setF(Inkscape::NORMAL_MESSAGE,
4313 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4314 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4315 total_nodes),
4316 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4317 } else {
4318 mc->setF(Inkscape::NORMAL_MESSAGE,
4319 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4320 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4321 total_nodes),
4322 selected_nodes, total_nodes, when_selected);
4323 }
4324 }
4325 }
4328 /*
4329 Local Variables:
4330 mode:c++
4331 c-file-style:"stroustrup"
4332 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4333 indent-tabs-mode:nil
4334 fill-column:99
4335 End:
4336 */
4337 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :