f6f9ef0d199cca4e6f17d551e67fa337f0c8bbae
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/canvas-bpath.h"
19 #include "display/curve.h"
20 #include "display/sp-ctrlline.h"
21 #include "display/sodipodi-ctrl.h"
22 #include "display/sp-canvas-util.h"
23 #include <glibmm/i18n.h>
24 #include <2geom/pathvector.h>
25 #include <2geom/sbasis-to-bezier.h>
26 #include <2geom/bezier-curve.h>
27 #include <2geom/hvlinesegment.h>
28 #include "helper/units.h"
29 #include "knot.h"
30 #include "inkscape.h"
31 #include "document.h"
32 #include "sp-namedview.h"
33 #include "desktop.h"
34 #include "desktop-handles.h"
35 #include "snap.h"
36 #include "message-stack.h"
37 #include "message-context.h"
38 #include "node-context.h"
39 #include "shape-editor.h"
40 #include "selection-chemistry.h"
41 #include "selection.h"
42 #include "xml/repr.h"
43 #include "prefs-utils.h"
44 #include "sp-metrics.h"
45 #include "sp-path.h"
46 #include "libnr/nr-matrix-ops.h"
47 #include "svg/svg.h"
48 #include "verbs.h"
49 #include "display/bezier-utils.h"
50 #include <vector>
51 #include <algorithm>
52 #include <cstring>
53 #include <cmath>
54 #include <string>
55 #include "live_effects/lpeobject.h"
56 #include "live_effects/lpeobject-reference.h"
57 #include "live_effects/effect.h"
58 #include "live_effects/parameter/parameter.h"
59 #include "live_effects/parameter/path.h"
60 #include "util/mathfns.h"
61 #include "display/snap-indicator.h"
62 #include "snapped-point.h"
64 class NR::Matrix;
66 /// \todo
67 /// evil evil evil. FIXME: conflict of two different Path classes!
68 /// There is a conflict in the namespace between two classes named Path.
69 /// #include "sp-flowtext.h"
70 /// #include "sp-flowregion.h"
72 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
73 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
74 GType sp_flowregion_get_type (void);
75 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
76 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
77 GType sp_flowtext_get_type (void);
78 // end evil workaround
80 #include "helper/stlport.h"
83 /// \todo fixme: Implement these via preferences */
85 #define NODE_FILL 0xbfbfbf00
86 #define NODE_STROKE 0x000000ff
87 #define NODE_FILL_HI 0xff000000
88 #define NODE_STROKE_HI 0x000000ff
89 #define NODE_FILL_SEL 0x0000ffff
90 #define NODE_STROKE_SEL 0x000000ff
91 #define NODE_FILL_SEL_HI 0xff000000
92 #define NODE_STROKE_SEL_HI 0x000000ff
93 #define KNOT_FILL 0xffffffff
94 #define KNOT_STROKE 0x000000ff
95 #define KNOT_FILL_HI 0xff000000
96 #define KNOT_STROKE_HI 0x000000ff
98 static GMemChunk *nodechunk = NULL;
100 /* Creation from object */
102 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t);
103 static void add_curve_to_subpath( Inkscape::NodePath::Path *np, Inkscape::NodePath::SubPath *sp, Geom::Curve const & c,
104 Inkscape::NodePath::NodeType const *t, guint & i, NR::Point & ppos, NRPathcode & pcode );
105 static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length);
107 /* Object updating */
109 static void stamp_repr(Inkscape::NodePath::Path *np);
110 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
111 static gchar *create_typestr(Inkscape::NodePath::Path *np);
113 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
115 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
117 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
119 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
121 /* Adjust handle placement, if the node or the other handle is moved */
122 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
123 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
125 /* Node event callbacks */
126 static void node_clicked(SPKnot *knot, guint state, gpointer data);
127 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
128 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
129 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
131 /* Handle event callbacks */
132 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
133 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
134 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
135 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
136 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
137 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
139 /* Constructors and destructors */
141 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
142 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
143 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
144 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
145 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
146 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
147 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
149 /* Helpers */
151 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
152 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
153 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
155 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
156 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
158 // active_node indicates mouseover node
159 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
161 static SPCanvasItem *
162 sp_nodepath_make_helper_item(Inkscape::NodePath::Path *np, /*SPDesktop *desktop, */const SPCurve *curve, bool show = false) {
163 g_print ("sp_nodepath_make_helper_item()\n");
164 SPCurve *helper_curve = curve->copy();
165 helper_curve->transform(to_2geom(np->i2d));
166 SPCanvasItem *helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
167 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
168 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(helper_path), 0, SP_WIND_RULE_NONZERO);
169 sp_canvas_item_move_to_z(helper_path, 0);
170 if (show) {
171 sp_canvas_item_show(helper_path);
172 }
173 helper_curve->unref();
174 return helper_path;
175 }
177 static SPCanvasItem *
178 canvasitem_from_pathvec(Inkscape::NodePath::Path *np, Geom::PathVector const &pathv, bool show) {
179 g_print ("canvasitem_from_pathvec()\n");
180 SPCurve *helper_curve = new SPCurve(pathv);
181 return sp_nodepath_make_helper_item(np, helper_curve, show);
182 }
184 static void
185 sp_nodepath_create_helperpaths(Inkscape::NodePath::Path *np) {
186 g_print ("sp_nodepath_create_helperpaths()\n");
187 //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> >* helper_path_vec;
188 if (np->item) {
189 g_print ("np->item: %s\n", SP_OBJECT_REPR(np->item)->attribute("id"));
190 } else {
191 g_print ("np->item == NULL!\n");
192 }
194 if (!SP_IS_LPE_ITEM(np->item)) {
195 g_print ("Only LPEItems can have helperpaths!\n");
196 return;
197 }
199 SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
200 PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
201 for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
202 Inkscape::LivePathEffect::LPEObjectReference *lperef = (*i);
203 Inkscape::LivePathEffect::Effect *lpe = lperef->lpeobject->lpe;
204 g_print ("Processing LPE %s\n", SP_OBJECT_REPR(lperef->lpeobject)->attribute("id"));
205 // create new canvas items from the effect's helper paths
206 std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
207 for (std::vector<Geom::PathVector>::iterator j = hpaths.begin(); j != hpaths.end(); ++j) {
208 g_print (" ... creating helper path\n");
209 (*np->helper_path_vec)[lpe].push_back(canvasitem_from_pathvec(np, *j, true));
210 }
211 }
212 g_print ("\n");
213 }
215 static void
216 sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np) {
217 g_print ("sp_nodepath_update_helperpaths()\n");
218 //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> >* helper_path_vec;
219 if (!SP_IS_LPE_ITEM(np->item)) {
220 g_print ("Only LPEItems can have helperpaths!\n");
221 return;
222 }
224 SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
225 PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
226 for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
227 Inkscape::LivePathEffect::Effect *lpe = (*i)->lpeobject->lpe;
228 g_print ("Processing LPE %s\n", SP_OBJECT_REPR((*i)->lpeobject)->attribute("id"));
229 /* update canvas items from the effect's helper paths; note that this code relies on the
230 * fact that getHelperPaths() will always return the same number of helperpaths in the same
231 * order as during their creation in sp_nodepath_create_helperpaths
232 */
233 std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
234 for (unsigned int j = 0; j < hpaths.size(); ++j) {
235 g_print (" ... updating helper path\n");
236 SPCurve *curve = new SPCurve(hpaths[j]);
237 curve->transform(to_2geom(np->i2d));
238 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(((*np->helper_path_vec)[lpe])[j]), curve);
239 curve = curve->unref();
240 }
241 }
242 g_print ("\n");
243 }
245 static void
246 sp_nodepath_destroy_helperpaths(Inkscape::NodePath::Path *np) {
247 g_print ("sp_nodepath_destroy_helperpaths()\n");
248 for (HelperPathList::iterator i = np->helper_path_vec->begin(); i != np->helper_path_vec->end(); ++i) {
249 g_print ("processing helper paths for LPE %s\n", SP_OBJECT_REPR((*i).first->getLPEObj())->attribute("id"));
250 for (std::vector<SPCanvasItem *>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j) {
251 g_print (" ... destroying helper path\n");
252 GtkObject *temp = *j;
253 *j = NULL;
254 gtk_object_destroy(temp);
255 }
256 }
257 g_print ("\n");
258 }
261 /**
262 * \brief Creates new nodepath from item
263 */
264 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
265 {
266 Inkscape::XML::Node *repr = object->repr;
268 /** \todo
269 * FIXME: remove this. We don't want to edit paths inside flowtext.
270 * Instead we will build our flowtext with cloned paths, so that the
271 * real paths are outside the flowtext and thus editable as usual.
272 */
273 if (SP_IS_FLOWTEXT(object)) {
274 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
275 if SP_IS_FLOWREGION(child) {
276 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
277 if (grandchild && SP_IS_PATH(grandchild)) {
278 object = SP_ITEM(grandchild);
279 break;
280 }
281 }
282 }
283 }
285 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
287 if (curve == NULL)
288 return NULL;
290 if (curve->get_segment_count() < 1) {
291 curve->unref();
292 return NULL; // prevent crash for one-node paths
293 }
295 //Create new nodepath
296 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
297 if (!np) {
298 curve->unref();
299 return NULL;
300 }
302 // Set defaults
303 np->desktop = desktop;
304 np->object = object;
305 np->subpaths = NULL;
306 np->selected = NULL;
307 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
308 np->local_change = 0;
309 np->show_handles = show_handles;
310 np->helper_path = NULL;
311 np->helper_path_vec = new HelperPathList;
312 np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
313 np->helperpath_width = 1.0;
314 np->curve = curve->copy();
315 np->show_helperpath = (prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1);
316 if (SP_IS_LPE_ITEM(object)) {
317 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
318 if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
319 np->show_helperpath = true;
320 }
321 }
322 np->straight_path = false;
323 if (IS_LIVEPATHEFFECT(object) && item) {
324 np->item = item;
325 } else {
326 np->item = SP_ITEM(object);
327 }
329 // we need to update item's transform from the repr here,
330 // because they may be out of sync when we respond
331 // to a change in repr by regenerating nodepath --bb
332 sp_object_read_attr(SP_OBJECT(np->item), "transform");
334 np->i2d = from_2geom(sp_item_i2d_affine(np->item));
335 np->d2i = np->i2d.inverse();
337 np->repr = repr;
338 if (repr_key_in) { // apparantly the object is an LPEObject
339 np->repr_key = g_strdup(repr_key_in);
340 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
341 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
342 if (lpeparam) {
343 lpeparam->param_setup_nodepath(np);
344 }
345 } else {
346 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
347 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
348 np->repr_key = g_strdup("inkscape:original-d");
350 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
351 if (lpe) {
352 lpe->setup_nodepath(np);
353 }
354 } else {
355 np->repr_key = g_strdup("d");
356 }
357 }
359 /* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice.
360 * So for example a closed rectangle has a nodetypestring of length 5.
361 * To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */
362 Geom::PathVector const &pathv = curve->get_pathvector();
363 guint length = curve->get_segment_count();
364 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
365 length += pit->empty() ? 0 : 1;
366 }
368 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
369 Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
371 // create the subpath(s) from the bpath
372 subpaths_from_pathvector(np, pathv, typestr);
374 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
375 np->subpaths = g_list_reverse(np->subpaths);
377 delete[] typestr;
378 curve->unref();
380 // Draw helper curve
381 if (np->show_helperpath) {
382 np->helper_path = sp_nodepath_make_helper_item(np, /*desktop, */np->curve, true);
383 }
385 sp_nodepath_create_helperpaths(np);
387 return np;
388 }
390 /**
391 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
392 */
393 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
395 if (!np) //soft fail, like delete
396 return;
398 while (np->subpaths) {
399 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
400 }
402 //Inform the ShapeEditor that made me, if any, that I am gone.
403 if (np->shape_editor)
404 np->shape_editor->nodepath_destroyed();
406 g_assert(!np->selected);
408 if (np->helper_path) {
409 GtkObject *temp = np->helper_path;
410 np->helper_path = NULL;
411 gtk_object_destroy(temp);
412 }
413 if (np->curve) {
414 np->curve->unref();
415 np->curve = NULL;
416 }
418 if (np->repr_key) {
419 g_free(np->repr_key);
420 np->repr_key = NULL;
421 }
422 if (np->repr_nodetypes_key) {
423 g_free(np->repr_nodetypes_key);
424 np->repr_nodetypes_key = NULL;
425 }
427 sp_nodepath_destroy_helperpaths(np);
428 delete np->helper_path_vec;
429 np->helper_path_vec = NULL;
431 np->desktop = NULL;
433 g_free(np);
434 }
436 /**
437 * Return the node count of a given NodeSubPath.
438 */
439 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
440 {
441 if (!subpath)
442 return 0;
443 gint nodeCount = g_list_length(subpath->nodes);
444 return nodeCount;
445 }
447 /**
448 * Return the node count of a given NodePath.
449 */
450 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
451 {
452 if (!np)
453 return 0;
454 gint nodeCount = 0;
455 for (GList *item = np->subpaths ; item ; item=item->next) {
456 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
457 nodeCount += g_list_length(subpath->nodes);
458 }
459 return nodeCount;
460 }
462 /**
463 * Return the subpath count of a given NodePath.
464 */
465 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
466 {
467 if (!np)
468 return 0;
469 return g_list_length (np->subpaths);
470 }
472 /**
473 * Return the selected node count of a given NodePath.
474 */
475 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
476 {
477 if (!np)
478 return 0;
479 return g_list_length (np->selected);
480 }
482 /**
483 * Return the number of subpaths where nodes are selected in a given NodePath.
484 */
485 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
486 {
487 if (!np)
488 return 0;
489 if (!np->selected)
490 return 0;
491 if (!np->selected->next)
492 return 1;
493 gint count = 0;
494 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
495 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
496 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
497 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
498 if (node->selected) {
499 count ++;
500 break;
501 }
502 }
503 }
504 return count;
505 }
507 /**
508 * Clean up a nodepath after editing.
509 *
510 * Currently we are deleting trivial subpaths.
511 */
512 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
513 {
514 GList *badSubPaths = NULL;
516 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
517 for (GList *l = nodepath->subpaths; l ; l=l->next) {
518 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
519 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
520 badSubPaths = g_list_append(badSubPaths, sp);
521 }
523 //Delete them. This second step is because sp_nodepath_subpath_destroy()
524 //also removes the subpath from nodepath->subpaths
525 for (GList *l = badSubPaths; l ; l=l->next) {
526 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
527 sp_nodepath_subpath_destroy(sp);
528 }
530 g_list_free(badSubPaths);
531 }
533 /**
534 * Create new nodepaths from pathvector, make it subpaths of np.
535 * \param t The node type array.
536 */
537 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
538 {
539 guint i = 0; // index into node type array
540 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
541 if (pit->empty())
542 continue; // don't add single knot paths
544 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
546 NR::Point ppos = from_2geom(pit->initialPoint()) * np->i2d;
547 NRPathcode pcode = NR_MOVETO;
549 for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
550 add_curve_to_subpath(np, sp, *cit, t, i, ppos, pcode);
551 }
553 if (pit->closed()) {
554 // Add last knot (because sp_nodepath_subpath_close kills the last knot)
555 /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
556 * If the length is zero, don't add it to the nodepath. */
557 Geom::Curve const &closing_seg = pit->back_closed();
558 if ( ! closing_seg.isDegenerate() ) {
559 NR::Point pos = from_2geom(closing_seg.finalPoint()) * np->i2d;
560 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
561 }
563 sp_nodepath_subpath_close(sp);
564 }
565 }
566 }
567 // should add initial point of curve with type of previous curve:
568 static void add_curve_to_subpath(Inkscape::NodePath::Path *np, Inkscape::NodePath::SubPath *sp, Geom::Curve const & c, Inkscape::NodePath::NodeType const *t, guint & i,
569 NR::Point & ppos, NRPathcode & pcode)
570 {
571 if( dynamic_cast<Geom::LineSegment const*>(&c) ||
572 dynamic_cast<Geom::HLineSegment const*>(&c) ||
573 dynamic_cast<Geom::VLineSegment const*>(&c) )
574 {
575 NR::Point pos = from_2geom(c.initialPoint()) * np->i2d;
576 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
577 ppos = from_2geom(c.finalPoint());
578 pcode = NR_LINETO;
579 }
580 else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&c)) {
581 std::vector<Geom::Point> points = cubic_bezier->points();
582 NR::Point pos = from_2geom(points[0]) * np->i2d;
583 NR::Point npos = from_2geom(points[1]) * np->i2d;
584 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
585 ppos = from_2geom(points[2]) * np->i2d;
586 pcode = NR_CURVETO;
587 }
588 else {
589 //this case handles sbasis as well as all other curve types
590 Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
592 for(Geom::Path::iterator iter = sbasis_path.begin(); iter != sbasis_path.end(); ++iter) {
593 add_curve_to_subpath(np, sp, *iter, t, i, ppos, pcode);
594 }
595 }
596 }
599 /**
600 * Convert from sodipodi:nodetypes to new style type array.
601 */
602 static
603 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
604 {
605 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
607 guint pos = 0;
609 if (types) {
610 for (guint i = 0; types[i] && ( i < length ); i++) {
611 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
612 if (types[i] != '\0') {
613 switch (types[i]) {
614 case 's':
615 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
616 break;
617 case 'z':
618 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
619 break;
620 case 'c':
621 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
622 break;
623 default:
624 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
625 break;
626 }
627 }
628 }
629 }
631 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
633 return typestr;
634 }
636 /**
637 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
638 * updated but repr is not (for speed). Used during curve and node drag.
639 */
640 static void update_object(Inkscape::NodePath::Path *np)
641 {
642 g_assert(np);
644 np->curve->unref();
645 np->curve = create_curve(np);
647 sp_nodepath_set_curve(np, np->curve);
649 if (np->show_helperpath) {
650 SPCurve * helper_curve = np->curve->copy();
651 helper_curve->transform(to_2geom(np->i2d));
652 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
653 helper_curve->unref();
654 }
656 sp_nodepath_update_helperpaths(np);
658 // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
659 // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
660 np->shape_editor->update_knotholder();
661 }
663 /**
664 * Update XML path node with data from path object.
665 */
666 static void update_repr_internal(Inkscape::NodePath::Path *np)
667 {
668 g_assert(np);
670 Inkscape::XML::Node *repr = np->object->repr;
672 np->curve->unref();
673 np->curve = create_curve(np);
675 gchar *typestr = create_typestr(np);
676 gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
678 // determine if path has an effect applied and write to correct "d" attribute.
679 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
680 np->local_change++;
681 repr->setAttribute(np->repr_key, svgpath);
682 }
684 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
685 np->local_change++;
686 repr->setAttribute(np->repr_nodetypes_key, typestr);
687 }
689 g_free(svgpath);
690 g_free(typestr);
692 if (np->show_helperpath) {
693 SPCurve * helper_curve = np->curve->copy();
694 helper_curve->transform(to_2geom(np->i2d));
695 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
696 helper_curve->unref();
697 }
699 // TODO: do we need this call here? after all, update_object() should have been called just before
700 //sp_nodepath_update_helperpaths(np);
701 }
703 /**
704 * Update XML path node with data from path object, commit changes forever.
705 */
706 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
707 {
708 //fixme: np can be NULL, so check before proceeding
709 g_return_if_fail(np != NULL);
711 update_repr_internal(np);
712 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
714 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
715 annotation);
716 }
718 /**
719 * Update XML path node with data from path object, commit changes with undo.
720 */
721 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
722 {
723 update_repr_internal(np);
724 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
725 annotation);
726 }
728 /**
729 * Make duplicate of path, replace corresponding XML node in tree, commit.
730 */
731 static void stamp_repr(Inkscape::NodePath::Path *np)
732 {
733 g_assert(np);
735 Inkscape::XML::Node *old_repr = np->object->repr;
736 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
738 // remember the position of the item
739 gint pos = old_repr->position();
740 // remember parent
741 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
743 SPCurve *curve = create_curve(np);
744 gchar *typestr = create_typestr(np);
746 gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
748 new_repr->setAttribute(np->repr_key, svgpath);
749 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
751 // add the new repr to the parent
752 parent->appendChild(new_repr);
753 // move to the saved position
754 new_repr->setPosition(pos > 0 ? pos : 0);
756 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
757 _("Stamp"));
759 Inkscape::GC::release(new_repr);
760 g_free(svgpath);
761 g_free(typestr);
762 curve->unref();
763 }
765 /**
766 * Create curve from path.
767 */
768 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
769 {
770 SPCurve *curve = new SPCurve();
772 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
773 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
774 curve->moveto(sp->first->pos * np->d2i);
775 Inkscape::NodePath::Node *n = sp->first->n.other;
776 while (n) {
777 NR::Point const end_pt = n->pos * np->d2i;
778 switch (n->code) {
779 case NR_LINETO:
780 curve->lineto(end_pt);
781 break;
782 case NR_CURVETO:
783 curve->curveto(n->p.other->n.pos * np->d2i,
784 n->p.pos * np->d2i,
785 end_pt);
786 break;
787 default:
788 g_assert_not_reached();
789 break;
790 }
791 if (n != sp->last) {
792 n = n->n.other;
793 } else {
794 n = NULL;
795 }
796 }
797 if (sp->closed) {
798 curve->closepath();
799 }
800 }
802 return curve;
803 }
805 /**
806 * Convert path type string to sodipodi:nodetypes style.
807 */
808 static gchar *create_typestr(Inkscape::NodePath::Path *np)
809 {
810 gchar *typestr = g_new(gchar, 32);
811 gint len = 32;
812 gint pos = 0;
814 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
815 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
817 if (pos >= len) {
818 typestr = g_renew(gchar, typestr, len + 32);
819 len += 32;
820 }
822 typestr[pos++] = 'c';
824 Inkscape::NodePath::Node *n;
825 n = sp->first->n.other;
826 while (n) {
827 gchar code;
829 switch (n->type) {
830 case Inkscape::NodePath::NODE_CUSP:
831 code = 'c';
832 break;
833 case Inkscape::NodePath::NODE_SMOOTH:
834 code = 's';
835 break;
836 case Inkscape::NodePath::NODE_SYMM:
837 code = 'z';
838 break;
839 default:
840 g_assert_not_reached();
841 code = '\0';
842 break;
843 }
845 if (pos >= len) {
846 typestr = g_renew(gchar, typestr, len + 32);
847 len += 32;
848 }
850 typestr[pos++] = code;
852 if (n != sp->last) {
853 n = n->n.other;
854 } else {
855 n = NULL;
856 }
857 }
858 }
860 if (pos >= len) {
861 typestr = g_renew(gchar, typestr, len + 1);
862 len += 1;
863 }
865 typestr[pos++] = '\0';
867 return typestr;
868 }
870 /**
871 * Returns current path in context. // later eliminate this function at all!
872 */
873 static Inkscape::NodePath::Path *sp_nodepath_current()
874 {
875 if (!SP_ACTIVE_DESKTOP) {
876 return NULL;
877 }
879 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
881 if (!SP_IS_NODE_CONTEXT(event_context)) {
882 return NULL;
883 }
885 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
886 }
890 /**
891 \brief Fills node and handle positions for three nodes, splitting line
892 marked by end at distance t.
893 */
894 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
895 {
896 g_assert(new_path != NULL);
897 g_assert(end != NULL);
899 g_assert(end->p.other == new_path);
900 Inkscape::NodePath::Node *start = new_path->p.other;
901 g_assert(start);
903 if (end->code == NR_LINETO) {
904 new_path->type =Inkscape::NodePath::NODE_CUSP;
905 new_path->code = NR_LINETO;
906 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
907 } else {
908 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
909 new_path->code = NR_CURVETO;
910 gdouble s = 1 - t;
911 for (int dim = 0; dim < 2; dim++) {
912 NR::Coord const f000 = start->pos[dim];
913 NR::Coord const f001 = start->n.pos[dim];
914 NR::Coord const f011 = end->p.pos[dim];
915 NR::Coord const f111 = end->pos[dim];
916 NR::Coord const f00t = s * f000 + t * f001;
917 NR::Coord const f01t = s * f001 + t * f011;
918 NR::Coord const f11t = s * f011 + t * f111;
919 NR::Coord const f0tt = s * f00t + t * f01t;
920 NR::Coord const f1tt = s * f01t + t * f11t;
921 NR::Coord const fttt = s * f0tt + t * f1tt;
922 start->n.pos[dim] = f00t;
923 new_path->p.pos[dim] = f0tt;
924 new_path->pos[dim] = fttt;
925 new_path->n.pos[dim] = f1tt;
926 end->p.pos[dim] = f11t;
927 }
928 }
929 }
931 /**
932 * Adds new node on direct line between two nodes, activates handles of all
933 * three nodes.
934 */
935 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
936 {
937 g_assert(end);
938 g_assert(end->subpath);
939 g_assert(g_list_find(end->subpath->nodes, end));
941 Inkscape::NodePath::Node *start = end->p.other;
942 g_assert( start->n.other == end );
943 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
944 end,
945 (NRPathcode)end->code == NR_LINETO?
946 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
947 (NRPathcode)end->code,
948 &start->pos, &start->pos, &start->n.pos);
949 sp_nodepath_line_midpoint(newnode, end, t);
951 sp_node_adjust_handles(start);
952 sp_node_update_handles(start);
953 sp_node_update_handles(newnode);
954 sp_node_adjust_handles(end);
955 sp_node_update_handles(end);
957 return newnode;
958 }
960 /**
961 \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
962 */
963 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
964 {
965 g_assert(node);
966 g_assert(node->subpath);
967 g_assert(g_list_find(node->subpath->nodes, node));
969 Inkscape::NodePath::SubPath *sp = node->subpath;
970 Inkscape::NodePath::Path *np = sp->nodepath;
972 if (sp->closed) {
973 sp_nodepath_subpath_open(sp, node);
974 return sp->first;
975 } else {
976 // no break for end nodes
977 if (node == sp->first) return NULL;
978 if (node == sp->last ) return NULL;
980 // create a new subpath
981 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
983 // duplicate the break node as start of the new subpath
984 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
986 // attach rest of curve to new node
987 g_assert(node->n.other);
988 newnode->n.other = node->n.other; node->n.other = NULL;
989 newnode->n.other->p.other = newnode;
990 newsubpath->last = sp->last;
991 sp->last = node;
992 node = newnode;
993 while (node->n.other) {
994 node = node->n.other;
995 node->subpath = newsubpath;
996 sp->nodes = g_list_remove(sp->nodes, node);
997 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
998 }
1001 return newnode;
1002 }
1003 }
1005 /**
1006 * Duplicate node and connect to neighbours.
1007 */
1008 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
1009 {
1010 g_assert(node);
1011 g_assert(node->subpath);
1012 g_assert(g_list_find(node->subpath->nodes, node));
1014 Inkscape::NodePath::SubPath *sp = node->subpath;
1016 NRPathcode code = (NRPathcode) node->code;
1017 if (code == NR_MOVETO) { // if node is the endnode,
1018 node->code = NR_LINETO; // new one is inserted before it, so change that to line
1019 }
1021 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
1023 if (!node->n.other || !node->p.other) // if node is an endnode, select it
1024 return node;
1025 else
1026 return newnode; // otherwise select the newly created node
1027 }
1029 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
1030 {
1031 node->p.pos = (node->pos + (node->pos - node->n.pos));
1032 }
1034 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1035 {
1036 node->n.pos = (node->pos + (node->pos - node->p.pos));
1037 }
1039 /**
1040 * Change line type at node, with side effects on neighbours.
1041 */
1042 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1043 {
1044 g_assert(end);
1045 g_assert(end->subpath);
1046 g_assert(end->p.other);
1048 if (end->code == static_cast< guint > ( code ) )
1049 return;
1051 Inkscape::NodePath::Node *start = end->p.other;
1053 end->code = code;
1055 if (code == NR_LINETO) {
1056 if (start->code == NR_LINETO) {
1057 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
1058 }
1059 if (end->n.other) {
1060 if (end->n.other->code == NR_LINETO) {
1061 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
1062 }
1063 }
1064 } else {
1065 NR::Point delta = end->pos - start->pos;
1066 start->n.pos = start->pos + delta / 3;
1067 end->p.pos = end->pos - delta / 3;
1068 sp_node_adjust_handle(start, 1);
1069 sp_node_adjust_handle(end, -1);
1070 }
1072 sp_node_update_handles(start);
1073 sp_node_update_handles(end);
1074 }
1076 /**
1077 * Change node type, and its handles accordingly.
1078 */
1079 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1080 {
1081 g_assert(node);
1082 g_assert(node->subpath);
1084 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1085 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1086 type =Inkscape::NodePath::NODE_CUSP;
1087 }
1088 }
1090 node->type = type;
1092 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1093 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1094 node->knot->setSize (node->selected? 11 : 9);
1095 sp_knot_update_ctrl(node->knot);
1096 } else {
1097 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1098 node->knot->setSize (node->selected? 9 : 7);
1099 sp_knot_update_ctrl(node->knot);
1100 }
1102 // if one of handles is mouseovered, preserve its position
1103 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1104 sp_node_adjust_handle(node, 1);
1105 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1106 sp_node_adjust_handle(node, -1);
1107 } else {
1108 sp_node_adjust_handles(node);
1109 }
1111 sp_node_update_handles(node);
1113 sp_nodepath_update_statusbar(node->subpath->nodepath);
1115 return node;
1116 }
1118 bool
1119 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1120 {
1121 Inkscape::NodePath::Node *othernode = side->other;
1122 if (!othernode)
1123 return false;
1124 NRPathcode const code = sp_node_path_code_from_side(node, side);
1125 if (code == NR_LINETO)
1126 return true;
1127 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1128 if (&node->p == side) {
1129 other_to_me = &othernode->n;
1130 } else if (&node->n == side) {
1131 other_to_me = &othernode->p;
1132 }
1133 if (!other_to_me)
1134 return false;
1135 bool is_line =
1136 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1137 NR::L2(node->pos - side->pos) < 1e-6);
1138 return is_line;
1139 }
1141 /**
1142 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1143 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1144 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1145 * If already cusp and set to cusp, retracts handles.
1146 */
1147 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1148 {
1149 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1151 /*
1152 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1154 if (two_handles) {
1155 // do nothing, adjust_handles called via set_node_type will line them up
1156 } else if (one_handle) {
1157 if (opposite_to_handle_is_line) {
1158 if (lined_up) {
1159 // already half-smooth; pull opposite handle too making it fully smooth
1160 } else {
1161 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1162 }
1163 } else {
1164 // pull opposite handle in line with the existing one
1165 }
1166 } else if (no_handles) {
1167 if (both_segments_are_lines OR both_segments_are_curves) {
1168 //pull both handles
1169 } else {
1170 // pull the handle opposite to line segment, making node half-smooth
1171 }
1172 }
1173 */
1174 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1175 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1176 bool p_is_line = sp_node_side_is_line(node, &node->p);
1177 bool n_is_line = sp_node_side_is_line(node, &node->n);
1179 if (p_has_handle && n_has_handle) {
1180 // do nothing, adjust_handles will line them up
1181 } else if (p_has_handle || n_has_handle) {
1182 if (p_has_handle && n_is_line) {
1183 Radial line (node->n.other->pos - node->pos);
1184 Radial handle (node->pos - node->p.pos);
1185 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1186 // already half-smooth; pull opposite handle too making it fully smooth
1187 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1188 } else {
1189 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1190 }
1191 } else if (n_has_handle && p_is_line) {
1192 Radial line (node->p.other->pos - node->pos);
1193 Radial handle (node->pos - node->n.pos);
1194 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1195 // already half-smooth; pull opposite handle too making it fully smooth
1196 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1197 } else {
1198 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1199 }
1200 } else if (p_has_handle && node->n.other) {
1201 // pull n handle
1202 node->n.other->code = NR_CURVETO;
1203 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1204 NR::L2(node->p.pos - node->pos) :
1205 NR::L2(node->n.other->pos - node->pos) / 3;
1206 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1207 } else if (n_has_handle && node->p.other) {
1208 // pull p handle
1209 node->code = NR_CURVETO;
1210 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1211 NR::L2(node->n.pos - node->pos) :
1212 NR::L2(node->p.other->pos - node->pos) / 3;
1213 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1214 }
1215 } else if (!p_has_handle && !n_has_handle) {
1216 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1217 // no handles, but both segments are either lnes or curves:
1218 //pull both handles
1220 // convert both to curves:
1221 node->code = NR_CURVETO;
1222 node->n.other->code = NR_CURVETO;
1224 NR::Point leg_prev = node->pos - node->p.other->pos;
1225 NR::Point leg_next = node->pos - node->n.other->pos;
1227 double norm_leg_prev = L2(leg_prev);
1228 double norm_leg_next = L2(leg_next);
1230 NR::Point delta;
1231 if (norm_leg_next > 0.0) {
1232 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1233 (&delta)->normalize();
1234 }
1236 if (type == Inkscape::NodePath::NODE_SYMM) {
1237 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1238 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1239 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1240 } else {
1241 // length of handle is proportional to distance to adjacent node
1242 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1243 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1244 }
1246 } else {
1247 // pull the handle opposite to line segment, making it half-smooth
1248 if (p_is_line && node->n.other) {
1249 if (type != Inkscape::NodePath::NODE_SYMM) {
1250 // pull n handle
1251 node->n.other->code = NR_CURVETO;
1252 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1253 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1254 }
1255 } else if (n_is_line && node->p.other) {
1256 if (type != Inkscape::NodePath::NODE_SYMM) {
1257 // pull p handle
1258 node->code = NR_CURVETO;
1259 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1260 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1261 }
1262 }
1263 }
1264 }
1265 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1266 // cusping a cusp: retract nodes
1267 node->p.pos = node->pos;
1268 node->n.pos = node->pos;
1269 }
1271 sp_nodepath_set_node_type (node, type);
1272 }
1274 /**
1275 * Move node to point, and adjust its and neighbouring handles.
1276 */
1277 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1278 {
1279 NR::Point delta = p - node->pos;
1280 node->pos = p;
1282 node->p.pos += delta;
1283 node->n.pos += delta;
1285 Inkscape::NodePath::Node *node_p = NULL;
1286 Inkscape::NodePath::Node *node_n = NULL;
1288 if (node->p.other) {
1289 if (node->code == NR_LINETO) {
1290 sp_node_adjust_handle(node, 1);
1291 sp_node_adjust_handle(node->p.other, -1);
1292 node_p = node->p.other;
1293 }
1294 }
1295 if (node->n.other) {
1296 if (node->n.other->code == NR_LINETO) {
1297 sp_node_adjust_handle(node, -1);
1298 sp_node_adjust_handle(node->n.other, 1);
1299 node_n = node->n.other;
1300 }
1301 }
1303 // this function is only called from batch movers that will update display at the end
1304 // themselves, so here we just move all the knots without emitting move signals, for speed
1305 sp_node_update_handles(node, false);
1306 if (node_n) {
1307 sp_node_update_handles(node_n, false);
1308 }
1309 if (node_p) {
1310 sp_node_update_handles(node_p, false);
1311 }
1312 }
1314 /**
1315 * Call sp_node_moveto() for node selection and handle possible snapping.
1316 */
1317 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1318 bool const snap, bool constrained = false,
1319 Inkscape::Snapper::ConstraintLine const &constraint = NR::Point())
1320 {
1321 NR::Coord best = NR_HUGE;
1322 NR::Point delta(dx, dy);
1323 NR::Point best_pt = delta;
1324 Inkscape::SnappedPoint best_abs;
1326 if (snap) {
1327 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1328 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1329 * must provide that information. */
1331 // Build a list of the unselected nodes to which the snapper should snap
1332 std::vector<NR::Point> unselected_nodes;
1333 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1334 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1335 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1336 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1337 if (!node->selected) {
1338 unselected_nodes.push_back(node->pos);
1339 }
1340 }
1341 }
1343 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1345 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1346 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1347 m.setup(NULL, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1348 Inkscape::SnappedPoint s;
1349 if (constrained) {
1350 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1351 dedicated_constraint.setPoint(n->pos);
1352 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, dedicated_constraint);
1353 } else {
1354 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1355 }
1356 if (s.getSnapped() && (s.getDistance() < best)) {
1357 best = s.getDistance();
1358 best_abs = s;
1359 best_pt = s.getPoint() - n->pos;
1360 }
1361 }
1363 if (best_abs.getSnapped()) {
1364 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1365 } else {
1366 nodepath->desktop->snapindicator->remove_snappoint();
1367 }
1368 }
1370 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1371 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1372 sp_node_moveto(n, n->pos + best_pt);
1373 }
1375 // do not update repr here so that node dragging is acceptably fast
1376 update_object(nodepath);
1377 }
1379 /**
1380 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1381 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1382 near x = 0.
1383 */
1384 double
1385 sculpt_profile (double x, double alpha, guint profile)
1386 {
1387 if (x >= 1)
1388 return 0;
1389 if (x <= 0)
1390 return 1;
1392 switch (profile) {
1393 case SCULPT_PROFILE_LINEAR:
1394 return 1 - x;
1395 case SCULPT_PROFILE_BELL:
1396 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1397 case SCULPT_PROFILE_ELLIPTIC:
1398 return sqrt(1 - x*x);
1399 }
1401 return 1;
1402 }
1404 double
1405 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1406 {
1407 // extremely primitive for now, don't have time to look for the real one
1408 double lower = NR::L2(b - a);
1409 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1410 return (lower + upper)/2;
1411 }
1413 void
1414 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1415 {
1416 n->pos = n->origin + delta;
1417 n->n.pos = n->n.origin + delta_n;
1418 n->p.pos = n->p.origin + delta_p;
1419 sp_node_adjust_handles(n);
1420 sp_node_update_handles(n, false);
1421 }
1423 /**
1424 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1425 * on how far they are from the dragged node n.
1426 */
1427 static void
1428 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1429 {
1430 g_assert (n);
1431 g_assert (nodepath);
1432 g_assert (n->subpath->nodepath == nodepath);
1434 double pressure = n->knot->pressure;
1435 if (pressure == 0)
1436 pressure = 0.5; // default
1437 pressure = CLAMP (pressure, 0.2, 0.8);
1439 // map pressure to alpha = 1/5 ... 5
1440 double alpha = 1 - 2 * fabs(pressure - 0.5);
1441 if (pressure > 0.5)
1442 alpha = 1/alpha;
1444 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1446 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1447 // Only one subpath has selected nodes:
1448 // use linear mode, where the distance from n to node being dragged is calculated along the path
1450 double n_sel_range = 0, p_sel_range = 0;
1451 guint n_nodes = 0, p_nodes = 0;
1452 guint n_sel_nodes = 0, p_sel_nodes = 0;
1454 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1455 {
1456 double n_range = 0, p_range = 0;
1457 bool n_going = true, p_going = true;
1458 Inkscape::NodePath::Node *n_node = n;
1459 Inkscape::NodePath::Node *p_node = n;
1460 do {
1461 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1462 if (n_node && n_going)
1463 n_node = n_node->n.other;
1464 if (n_node == NULL) {
1465 n_going = false;
1466 } else {
1467 n_nodes ++;
1468 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1469 if (n_node->selected) {
1470 n_sel_nodes ++;
1471 n_sel_range = n_range;
1472 }
1473 if (n_node == p_node) {
1474 n_going = false;
1475 p_going = false;
1476 }
1477 }
1478 if (p_node && p_going)
1479 p_node = p_node->p.other;
1480 if (p_node == NULL) {
1481 p_going = false;
1482 } else {
1483 p_nodes ++;
1484 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1485 if (p_node->selected) {
1486 p_sel_nodes ++;
1487 p_sel_range = p_range;
1488 }
1489 if (p_node == n_node) {
1490 n_going = false;
1491 p_going = false;
1492 }
1493 }
1494 } while (n_going || p_going);
1495 }
1497 // Second pass: actually move nodes in this subpath
1498 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1499 {
1500 double n_range = 0, p_range = 0;
1501 bool n_going = true, p_going = true;
1502 Inkscape::NodePath::Node *n_node = n;
1503 Inkscape::NodePath::Node *p_node = n;
1504 do {
1505 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1506 if (n_node && n_going)
1507 n_node = n_node->n.other;
1508 if (n_node == NULL) {
1509 n_going = false;
1510 } else {
1511 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1512 if (n_node->selected) {
1513 sp_nodepath_move_node_and_handles (n_node,
1514 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1515 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1516 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1517 }
1518 if (n_node == p_node) {
1519 n_going = false;
1520 p_going = false;
1521 }
1522 }
1523 if (p_node && p_going)
1524 p_node = p_node->p.other;
1525 if (p_node == NULL) {
1526 p_going = false;
1527 } else {
1528 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1529 if (p_node->selected) {
1530 sp_nodepath_move_node_and_handles (p_node,
1531 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1532 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1533 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1534 }
1535 if (p_node == n_node) {
1536 n_going = false;
1537 p_going = false;
1538 }
1539 }
1540 } while (n_going || p_going);
1541 }
1543 } else {
1544 // Multiple subpaths have selected nodes:
1545 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1546 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1547 // fix the pear-like shape when sculpting e.g. a ring
1549 // First pass: calculate range
1550 gdouble direct_range = 0;
1551 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1552 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1553 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1554 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1555 if (node->selected) {
1556 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1557 }
1558 }
1559 }
1561 // Second pass: actually move nodes
1562 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1563 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1564 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1565 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1566 if (node->selected) {
1567 if (direct_range > 1e-6) {
1568 sp_nodepath_move_node_and_handles (node,
1569 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1570 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1571 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1572 } else {
1573 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1574 }
1576 }
1577 }
1578 }
1579 }
1581 // do not update repr here so that node dragging is acceptably fast
1582 update_object(nodepath);
1583 }
1586 /**
1587 * Move node selection to point, adjust its and neighbouring handles,
1588 * handle possible snapping, and commit the change with possible undo.
1589 */
1590 void
1591 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1592 {
1593 if (!nodepath) return;
1595 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1597 if (dx == 0) {
1598 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1599 } else if (dy == 0) {
1600 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1601 } else {
1602 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1603 }
1604 }
1606 /**
1607 * Move node selection off screen and commit the change.
1608 */
1609 void
1610 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1611 {
1612 // borrowed from sp_selection_move_screen in selection-chemistry.c
1613 // we find out the current zoom factor and divide deltas by it
1614 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1616 gdouble zoom = desktop->current_zoom();
1617 gdouble zdx = dx / zoom;
1618 gdouble zdy = dy / zoom;
1620 if (!nodepath) return;
1622 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1624 if (dx == 0) {
1625 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1626 } else if (dy == 0) {
1627 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1628 } else {
1629 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1630 }
1631 }
1633 /**
1634 * Move selected nodes to the absolute position given
1635 */
1636 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1637 {
1638 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1639 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1640 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1641 sp_node_moveto(n, npos);
1642 }
1644 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1645 }
1647 /**
1648 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1649 */
1650 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1651 {
1652 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1653 g_return_val_if_fail(nodepath->selected, no_coord);
1655 // determine coordinate of first selected node
1656 GList *nsel = nodepath->selected;
1657 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1658 NR::Coord coord = n->pos[axis];
1659 bool coincide = true;
1661 // compare it to the coordinates of all the other selected nodes
1662 for (GList *l = nsel->next; l != NULL; l = l->next) {
1663 n = (Inkscape::NodePath::Node *) l->data;
1664 if (n->pos[axis] != coord) {
1665 coincide = false;
1666 }
1667 }
1668 if (coincide) {
1669 return coord;
1670 } else {
1671 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1672 // currently we return the coordinate of the bounding box midpoint because I don't know how
1673 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1674 return bbox.midpoint()[axis];
1675 }
1676 }
1678 /** If they don't yet exist, creates knot and line for the given side of the node */
1679 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1680 {
1681 if (!side->knot) {
1682 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"));
1684 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1685 side->knot->setSize (7);
1686 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1687 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1688 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1689 sp_knot_update_ctrl(side->knot);
1691 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1692 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1693 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1694 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1695 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1696 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1697 }
1699 if (!side->line) {
1700 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1701 SP_TYPE_CTRLLINE, NULL);
1702 }
1703 }
1705 /**
1706 * Ensure the given handle of the node is visible/invisible, update its screen position
1707 */
1708 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1709 {
1710 g_assert(node != NULL);
1712 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1713 NRPathcode code = sp_node_path_code_from_side(node, side);
1715 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1717 if (show_handle) {
1718 if (!side->knot) { // No handle knot at all
1719 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1720 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1721 side->knot->pos = side->pos;
1722 if (side->knot->item)
1723 SP_CTRL(side->knot->item)->moveto(side->pos);
1724 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1725 sp_knot_show(side->knot);
1726 } else {
1727 if (side->knot->pos != side->pos) { // only if it's really moved
1728 if (fire_move_signals) {
1729 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1730 } else {
1731 sp_knot_moveto(side->knot, &side->pos);
1732 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1733 }
1734 }
1735 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1736 sp_knot_show(side->knot);
1737 }
1738 }
1739 sp_canvas_item_show(side->line);
1740 } else {
1741 if (side->knot) {
1742 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1743 sp_knot_hide(side->knot);
1744 }
1745 }
1746 if (side->line) {
1747 sp_canvas_item_hide(side->line);
1748 }
1749 }
1750 }
1752 /**
1753 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1754 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1755 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1756 * updated; otherwise, just move the knots silently (used in batch moves).
1757 */
1758 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1759 {
1760 g_assert(node != NULL);
1762 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1763 sp_knot_show(node->knot);
1764 }
1766 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1767 if (fire_move_signals)
1768 sp_knot_set_position(node->knot, &node->pos, 0);
1769 else
1770 sp_knot_moveto(node->knot, &node->pos);
1771 }
1773 gboolean show_handles = node->selected;
1774 if (node->p.other != NULL) {
1775 if (node->p.other->selected) show_handles = TRUE;
1776 }
1777 if (node->n.other != NULL) {
1778 if (node->n.other->selected) show_handles = TRUE;
1779 }
1781 if (node->subpath->nodepath->show_handles == false)
1782 show_handles = FALSE;
1784 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1785 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1786 }
1788 /**
1789 * Call sp_node_update_handles() for all nodes on subpath.
1790 */
1791 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1792 {
1793 g_assert(subpath != NULL);
1795 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1796 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1797 }
1798 }
1800 /**
1801 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1802 */
1803 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1804 {
1805 g_assert(nodepath != NULL);
1807 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1808 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1809 }
1810 }
1812 void
1813 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1814 {
1815 if (nodepath == NULL) return;
1817 nodepath->show_handles = show;
1818 sp_nodepath_update_handles(nodepath);
1819 }
1821 /**
1822 * Adds all selected nodes in nodepath to list.
1823 */
1824 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1825 {
1826 StlConv<Node *>::list(l, selected);
1827 /// \todo this adds a copying, rework when the selection becomes a stl list
1828 }
1830 /**
1831 * Align selected nodes on the specified axis.
1832 */
1833 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1834 {
1835 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1836 return;
1837 }
1839 if ( !nodepath->selected->next ) { // only one node selected
1840 return;
1841 }
1842 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1843 NR::Point dest(pNode->pos);
1844 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1845 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1846 if (pNode) {
1847 dest[axis] = pNode->pos[axis];
1848 sp_node_moveto(pNode, dest);
1849 }
1850 }
1852 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1853 }
1855 /// Helper struct.
1856 struct NodeSort
1857 {
1858 Inkscape::NodePath::Node *_node;
1859 NR::Coord _coord;
1860 /// \todo use vectorof pointers instead of calling copy ctor
1861 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1862 _node(node), _coord(node->pos[axis])
1863 {}
1865 };
1867 static bool operator<(NodeSort const &a, NodeSort const &b)
1868 {
1869 return (a._coord < b._coord);
1870 }
1872 /**
1873 * Distribute selected nodes on the specified axis.
1874 */
1875 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1876 {
1877 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1878 return;
1879 }
1881 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1882 return;
1883 }
1885 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1886 std::vector<NodeSort> sorted;
1887 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1888 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1889 if (pNode) {
1890 NodeSort n(pNode, axis);
1891 sorted.push_back(n);
1892 //dest[axis] = pNode->pos[axis];
1893 //sp_node_moveto(pNode, dest);
1894 }
1895 }
1896 std::sort(sorted.begin(), sorted.end());
1897 unsigned int len = sorted.size();
1898 //overall bboxes span
1899 float dist = (sorted.back()._coord -
1900 sorted.front()._coord);
1901 //new distance between each bbox
1902 float step = (dist) / (len - 1);
1903 float pos = sorted.front()._coord;
1904 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1905 it < sorted.end();
1906 it ++ )
1907 {
1908 NR::Point dest((*it)._node->pos);
1909 dest[axis] = pos;
1910 sp_node_moveto((*it)._node, dest);
1911 pos += step;
1912 }
1914 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1915 }
1918 /**
1919 * Call sp_nodepath_line_add_node() for all selected segments.
1920 */
1921 void
1922 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1923 {
1924 if (!nodepath) {
1925 return;
1926 }
1928 GList *nl = NULL;
1930 int n_added = 0;
1932 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1933 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1934 g_assert(t->selected);
1935 if (t->p.other && t->p.other->selected) {
1936 nl = g_list_prepend(nl, t);
1937 }
1938 }
1940 while (nl) {
1941 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1942 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1943 sp_nodepath_node_select(n, TRUE, FALSE);
1944 n_added ++;
1945 nl = g_list_remove(nl, t);
1946 }
1948 /** \todo fixme: adjust ? */
1949 sp_nodepath_update_handles(nodepath);
1951 if (n_added > 1) {
1952 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1953 } else if (n_added > 0) {
1954 sp_nodepath_update_repr(nodepath, _("Add node"));
1955 }
1957 sp_nodepath_update_statusbar(nodepath);
1958 }
1960 /**
1961 * Select segment nearest to point
1962 */
1963 void
1964 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1965 {
1966 if (!nodepath) {
1967 return;
1968 }
1970 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1971 Geom::PathVector const &pathv = curve->get_pathvector();
1972 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1974 // calculate index for nodepath's representation.
1975 unsigned int segment_index = floor(pvpos.t) + 1;
1976 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1977 segment_index += pathv[i].size() + 1;
1978 if (pathv[i].closed()) {
1979 segment_index += 1;
1980 }
1981 }
1983 curve->unref();
1985 //find segment to segment
1986 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(segment_index);
1988 //fixme: this can return NULL, so check before proceeding.
1989 g_return_if_fail(e != NULL);
1991 gboolean force = FALSE;
1992 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1993 force = TRUE;
1994 }
1995 sp_nodepath_node_select(e, (gboolean) toggle, force);
1996 if (e->p.other)
1997 sp_nodepath_node_select(e->p.other, TRUE, force);
1999 sp_nodepath_update_handles(nodepath);
2001 sp_nodepath_update_statusbar(nodepath);
2002 }
2004 /**
2005 * Add a node nearest to point
2006 */
2007 void
2008 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
2009 {
2010 if (!nodepath) {
2011 return;
2012 }
2014 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2015 Geom::PathVector const &pathv = curve->get_pathvector();
2016 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
2018 // calculate index for nodepath's representation.
2019 double int_part;
2020 double t = std::modf(pvpos.t, &int_part);
2021 unsigned int segment_index = (unsigned int)int_part + 1;
2022 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
2023 segment_index += pathv[i].size() + 1;
2024 if (pathv[i].closed()) {
2025 segment_index += 1;
2026 }
2027 }
2029 curve->unref();
2031 //find segment to split
2032 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(segment_index);
2034 //don't know why but t seems to flip for lines
2035 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2036 t = 1.0 - t;
2037 }
2039 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2040 sp_nodepath_node_select(n, FALSE, TRUE);
2042 /* fixme: adjust ? */
2043 sp_nodepath_update_handles(nodepath);
2045 sp_nodepath_update_repr(nodepath, _("Add node"));
2047 sp_nodepath_update_statusbar(nodepath);
2048 }
2050 /*
2051 * Adjusts a segment so that t moves by a certain delta for dragging
2052 * converts lines to curves
2053 *
2054 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2055 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2056 */
2057 void
2058 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
2059 {
2060 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
2062 //fixme: e and e->p can be NULL, so check for those before proceeding
2063 g_return_if_fail(e != NULL);
2064 g_return_if_fail(&e->p != NULL);
2066 /* feel good is an arbitrary parameter that distributes the delta between handles
2067 * if t of the drag point is less than 1/6 distance form the endpoint only
2068 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2069 */
2070 double feel_good;
2071 if (t <= 1.0 / 6.0)
2072 feel_good = 0;
2073 else if (t <= 0.5)
2074 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2075 else if (t <= 5.0 / 6.0)
2076 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2077 else
2078 feel_good = 1;
2080 //if we're dragging a line convert it to a curve
2081 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2082 sp_nodepath_set_line_type(e, NR_CURVETO);
2083 }
2085 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2086 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2087 e->p.other->n.pos += offsetcoord0;
2088 e->p.pos += offsetcoord1;
2090 // adjust handles of adjacent nodes where necessary
2091 sp_node_adjust_handle(e,1);
2092 sp_node_adjust_handle(e->p.other,-1);
2094 sp_nodepath_update_handles(e->subpath->nodepath);
2096 update_object(e->subpath->nodepath);
2098 sp_nodepath_update_statusbar(e->subpath->nodepath);
2099 }
2102 /**
2103 * Call sp_nodepath_break() for all selected segments.
2104 */
2105 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2106 {
2107 if (!nodepath) return;
2109 GList *tempin = g_list_copy(nodepath->selected);
2110 GList *temp = NULL;
2111 for (GList *l = tempin; l != NULL; l = l->next) {
2112 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2113 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2114 if (nn == NULL) continue; // no break, no new node
2115 temp = g_list_prepend(temp, nn);
2116 }
2117 g_list_free(tempin);
2119 if (temp) {
2120 sp_nodepath_deselect(nodepath);
2121 }
2122 for (GList *l = temp; l != NULL; l = l->next) {
2123 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2124 }
2126 sp_nodepath_update_handles(nodepath);
2128 sp_nodepath_update_repr(nodepath, _("Break path"));
2129 }
2131 /**
2132 * Duplicate the selected node(s).
2133 */
2134 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2135 {
2136 if (!nodepath) {
2137 return;
2138 }
2140 GList *temp = NULL;
2141 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2142 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2143 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2144 if (nn == NULL) continue; // could not duplicate
2145 temp = g_list_prepend(temp, nn);
2146 }
2148 if (temp) {
2149 sp_nodepath_deselect(nodepath);
2150 }
2151 for (GList *l = temp; l != NULL; l = l->next) {
2152 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2153 }
2155 sp_nodepath_update_handles(nodepath);
2157 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2158 }
2160 /**
2161 * Internal function to join two nodes by merging them into one.
2162 */
2163 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2164 {
2165 /* a and b are endpoints */
2167 // if one of the two nodes is mouseovered, fix its position
2168 NR::Point c;
2169 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2170 c = a->pos;
2171 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2172 c = b->pos;
2173 } else {
2174 // otherwise, move joined node to the midpoint
2175 c = (a->pos + b->pos) / 2;
2176 }
2178 if (a->subpath == b->subpath) {
2179 Inkscape::NodePath::SubPath *sp = a->subpath;
2180 sp_nodepath_subpath_close(sp);
2181 sp_node_moveto (sp->first, c);
2183 sp_nodepath_update_handles(sp->nodepath);
2184 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2185 return;
2186 }
2188 /* a and b are separate subpaths */
2189 Inkscape::NodePath::SubPath *sa = a->subpath;
2190 Inkscape::NodePath::SubPath *sb = b->subpath;
2191 NR::Point p;
2192 Inkscape::NodePath::Node *n;
2193 NRPathcode code;
2194 if (a == sa->first) {
2195 // we will now reverse sa, so that a is its last node, not first, and drop that node
2196 p = sa->first->n.pos;
2197 code = (NRPathcode)sa->first->n.other->code;
2198 // create new subpath
2199 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2200 // create a first moveto node on it
2201 n = sa->last;
2202 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2203 n = n->p.other;
2204 if (n == sa->first) n = NULL;
2205 while (n) {
2206 // copy the rest of the nodes from sa to t, going backwards
2207 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2208 n = n->p.other;
2209 if (n == sa->first) n = NULL;
2210 }
2211 // replace sa with t
2212 sp_nodepath_subpath_destroy(sa);
2213 sa = t;
2214 } else if (a == sa->last) {
2215 // a is already last, just drop it
2216 p = sa->last->p.pos;
2217 code = (NRPathcode)sa->last->code;
2218 sp_nodepath_node_destroy(sa->last);
2219 } else {
2220 code = NR_END;
2221 g_assert_not_reached();
2222 }
2224 if (b == sb->first) {
2225 // copy all nodes from b to a, forward
2226 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2227 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2228 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2229 }
2230 } else if (b == sb->last) {
2231 // copy all nodes from b to a, backward
2232 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2233 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2234 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2235 }
2236 } else {
2237 g_assert_not_reached();
2238 }
2239 /* and now destroy sb */
2241 sp_nodepath_subpath_destroy(sb);
2243 sp_nodepath_update_handles(sa->nodepath);
2245 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2247 sp_nodepath_update_statusbar(nodepath);
2248 }
2250 /**
2251 * Internal function to join two nodes by adding a segment between them.
2252 */
2253 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2254 {
2255 if (a->subpath == b->subpath) {
2256 Inkscape::NodePath::SubPath *sp = a->subpath;
2258 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2259 sp->closed = TRUE;
2261 sp->first->p.other = sp->last;
2262 sp->last->n.other = sp->first;
2264 sp_node_handle_mirror_p_to_n(sp->last);
2265 sp_node_handle_mirror_n_to_p(sp->first);
2267 sp->first->code = sp->last->code;
2268 sp->first = sp->last;
2270 sp_nodepath_update_handles(sp->nodepath);
2272 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2274 return;
2275 }
2277 /* a and b are separate subpaths */
2278 Inkscape::NodePath::SubPath *sa = a->subpath;
2279 Inkscape::NodePath::SubPath *sb = b->subpath;
2281 Inkscape::NodePath::Node *n;
2282 NR::Point p;
2283 NRPathcode code;
2284 if (a == sa->first) {
2285 code = (NRPathcode) sa->first->n.other->code;
2286 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2287 n = sa->last;
2288 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2289 for (n = n->p.other; n != NULL; n = n->p.other) {
2290 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2291 }
2292 sp_nodepath_subpath_destroy(sa);
2293 sa = t;
2294 } else if (a == sa->last) {
2295 code = (NRPathcode)sa->last->code;
2296 } else {
2297 code = NR_END;
2298 g_assert_not_reached();
2299 }
2301 if (b == sb->first) {
2302 n = sb->first;
2303 sp_node_handle_mirror_p_to_n(sa->last);
2304 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2305 sp_node_handle_mirror_n_to_p(sa->last);
2306 for (n = n->n.other; n != NULL; n = n->n.other) {
2307 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2308 }
2309 } else if (b == sb->last) {
2310 n = sb->last;
2311 sp_node_handle_mirror_p_to_n(sa->last);
2312 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2313 sp_node_handle_mirror_n_to_p(sa->last);
2314 for (n = n->p.other; n != NULL; n = n->p.other) {
2315 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2316 }
2317 } else {
2318 g_assert_not_reached();
2319 }
2320 /* and now destroy sb */
2322 sp_nodepath_subpath_destroy(sb);
2324 sp_nodepath_update_handles(sa->nodepath);
2326 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2327 }
2329 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2331 /**
2332 * Internal function to handle joining two nodes.
2333 */
2334 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2335 {
2336 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2338 if (g_list_length(nodepath->selected) != 2) {
2339 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2340 return;
2341 }
2343 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2344 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2346 g_assert(a != b);
2347 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2348 // someone tried to join an orphan node (i.e. a single-node subpath).
2349 // this is not worth an error message, just fail silently.
2350 return;
2351 }
2353 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2354 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2355 return;
2356 }
2358 switch(mode) {
2359 case NODE_JOIN_ENDPOINTS:
2360 do_node_selected_join(nodepath, a, b);
2361 break;
2362 case NODE_JOIN_SEGMENT:
2363 do_node_selected_join_segment(nodepath, a, b);
2364 break;
2365 }
2366 }
2368 /**
2369 * Join two nodes by merging them into one.
2370 */
2371 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2372 {
2373 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2374 }
2376 /**
2377 * Join two nodes by adding a segment between them.
2378 */
2379 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2380 {
2381 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2382 }
2384 /**
2385 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2386 */
2387 void sp_node_delete_preserve(GList *nodes_to_delete)
2388 {
2389 GSList *nodepaths = NULL;
2391 while (nodes_to_delete) {
2392 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2393 Inkscape::NodePath::SubPath *sp = node->subpath;
2394 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2395 Inkscape::NodePath::Node *sample_cursor = NULL;
2396 Inkscape::NodePath::Node *sample_end = NULL;
2397 Inkscape::NodePath::Node *delete_cursor = node;
2398 bool just_delete = false;
2400 //find the start of this contiguous selection
2401 //move left to the first node that is not selected
2402 //or the start of the non-closed path
2403 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2404 delete_cursor = curr;
2405 }
2407 //just delete at the beginning of an open path
2408 if (!delete_cursor->p.other) {
2409 sample_cursor = delete_cursor;
2410 just_delete = true;
2411 } else {
2412 sample_cursor = delete_cursor->p.other;
2413 }
2415 //calculate points for each segment
2416 int rate = 5;
2417 float period = 1.0 / rate;
2418 std::vector<NR::Point> data;
2419 if (!just_delete) {
2420 data.push_back(sample_cursor->pos);
2421 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2422 //just delete at the end of an open path
2423 if (!sp->closed && curr == sp->last) {
2424 just_delete = true;
2425 break;
2426 }
2428 //sample points on the contiguous selected segment
2429 NR::Point *bez;
2430 bez = new NR::Point [4];
2431 bez[0] = curr->pos;
2432 bez[1] = curr->n.pos;
2433 bez[2] = curr->n.other->p.pos;
2434 bez[3] = curr->n.other->pos;
2435 for (int i=1; i<rate; i++) {
2436 gdouble t = i * period;
2437 NR::Point p = bezier_pt(3, bez, t);
2438 data.push_back(p);
2439 }
2440 data.push_back(curr->n.other->pos);
2442 sample_end = curr->n.other;
2443 //break if we've come full circle or hit the end of the selection
2444 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2445 break;
2446 }
2447 }
2448 }
2450 if (!just_delete) {
2451 //calculate the best fitting single segment and adjust the endpoints
2452 NR::Point *adata;
2453 adata = new NR::Point [data.size()];
2454 copy(data.begin(), data.end(), adata);
2456 NR::Point *bez;
2457 bez = new NR::Point [4];
2458 //would decreasing error create a better fitting approximation?
2459 gdouble error = 1.0;
2460 gint ret;
2461 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2463 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2464 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2465 //the resulting nodes behave as expected.
2466 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2467 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2468 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2469 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2471 //adjust endpoints
2472 sample_cursor->n.pos = bez[1];
2473 sample_end->p.pos = bez[2];
2474 }
2476 //destroy this contiguous selection
2477 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2478 Inkscape::NodePath::Node *temp = delete_cursor;
2479 if (delete_cursor->n.other == delete_cursor) {
2480 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2481 delete_cursor = NULL;
2482 } else {
2483 delete_cursor = delete_cursor->n.other;
2484 }
2485 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2486 sp_nodepath_node_destroy(temp);
2487 }
2489 sp_nodepath_update_handles(nodepath);
2491 if (!g_slist_find(nodepaths, nodepath))
2492 nodepaths = g_slist_prepend (nodepaths, nodepath);
2493 }
2495 for (GSList *i = nodepaths; i; i = i->next) {
2496 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2497 // different nodepaths will give us one undo event per nodepath
2498 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2500 // if the entire nodepath is removed, delete the selected object.
2501 if (nodepath->subpaths == NULL ||
2502 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2503 //at least 2
2504 sp_nodepath_get_node_count(nodepath) < 2) {
2505 SPDocument *document = sp_desktop_document (nodepath->desktop);
2506 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2507 //delete this nodepath's object, not the entire selection! (though at this time, this
2508 //does not matter)
2509 sp_selection_delete();
2510 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2511 _("Delete nodes"));
2512 } else {
2513 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2514 sp_nodepath_update_statusbar(nodepath);
2515 }
2516 }
2518 g_slist_free (nodepaths);
2519 }
2521 /**
2522 * Delete one or more selected nodes.
2523 */
2524 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2525 {
2526 if (!nodepath) return;
2527 if (!nodepath->selected) return;
2529 /** \todo fixme: do it the right way */
2530 while (nodepath->selected) {
2531 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2532 sp_nodepath_node_destroy(node);
2533 }
2536 //clean up the nodepath (such as for trivial subpaths)
2537 sp_nodepath_cleanup(nodepath);
2539 sp_nodepath_update_handles(nodepath);
2541 // if the entire nodepath is removed, delete the selected object.
2542 if (nodepath->subpaths == NULL ||
2543 sp_nodepath_get_node_count(nodepath) < 2) {
2544 SPDocument *document = sp_desktop_document (nodepath->desktop);
2545 sp_selection_delete();
2546 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2547 _("Delete nodes"));
2548 return;
2549 }
2551 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2553 sp_nodepath_update_statusbar(nodepath);
2554 }
2556 /**
2557 * Delete one or more segments between two selected nodes.
2558 * This is the code for 'split'.
2559 */
2560 void
2561 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2562 {
2563 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2564 Inkscape::NodePath::Node *curr, *next; //Iterators
2566 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2568 if (g_list_length(nodepath->selected) != 2) {
2569 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2570 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2571 return;
2572 }
2574 //Selected nodes, not inclusive
2575 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2576 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2578 if ( ( a==b) || //same node
2579 (a->subpath != b->subpath ) || //not the same path
2580 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2581 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2582 {
2583 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2584 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2585 return;
2586 }
2588 //###########################################
2589 //# BEGIN EDITS
2590 //###########################################
2591 //##################################
2592 //# CLOSED PATH
2593 //##################################
2594 if (a->subpath->closed) {
2597 gboolean reversed = FALSE;
2599 //Since we can go in a circle, we need to find the shorter distance.
2600 // a->b or b->a
2601 start = end = NULL;
2602 int distance = 0;
2603 int minDistance = 0;
2604 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2605 if (curr==b) {
2606 //printf("a to b:%d\n", distance);
2607 start = a;//go from a to b
2608 end = b;
2609 minDistance = distance;
2610 //printf("A to B :\n");
2611 break;
2612 }
2613 distance++;
2614 }
2616 //try again, the other direction
2617 distance = 0;
2618 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2619 if (curr==a) {
2620 //printf("b to a:%d\n", distance);
2621 if (distance < minDistance) {
2622 start = b; //we go from b to a
2623 end = a;
2624 reversed = TRUE;
2625 //printf("B to A\n");
2626 }
2627 break;
2628 }
2629 distance++;
2630 }
2633 //Copy everything from 'end' to 'start' to a new subpath
2634 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2635 for (curr=end ; curr ; curr=curr->n.other) {
2636 NRPathcode code = (NRPathcode) curr->code;
2637 if (curr == end)
2638 code = NR_MOVETO;
2639 sp_nodepath_node_new(t, NULL,
2640 (Inkscape::NodePath::NodeType)curr->type, code,
2641 &curr->p.pos, &curr->pos, &curr->n.pos);
2642 if (curr == start)
2643 break;
2644 }
2645 sp_nodepath_subpath_destroy(a->subpath);
2648 }
2652 //##################################
2653 //# OPEN PATH
2654 //##################################
2655 else {
2657 //We need to get the direction of the list between A and B
2658 //Can we walk from a to b?
2659 start = end = NULL;
2660 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2661 if (curr==b) {
2662 start = a; //did it! we go from a to b
2663 end = b;
2664 //printf("A to B\n");
2665 break;
2666 }
2667 }
2668 if (!start) {//didn't work? let's try the other direction
2669 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2670 if (curr==a) {
2671 start = b; //did it! we go from b to a
2672 end = a;
2673 //printf("B to A\n");
2674 break;
2675 }
2676 }
2677 }
2678 if (!start) {
2679 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2680 _("Cannot find path between nodes."));
2681 return;
2682 }
2686 //Copy everything after 'end' to a new subpath
2687 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2688 for (curr=end ; curr ; curr=curr->n.other) {
2689 NRPathcode code = (NRPathcode) curr->code;
2690 if (curr == end)
2691 code = NR_MOVETO;
2692 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2693 &curr->p.pos, &curr->pos, &curr->n.pos);
2694 }
2696 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2697 for (curr = start->n.other ; curr ; curr=next) {
2698 next = curr->n.other;
2699 sp_nodepath_node_destroy(curr);
2700 }
2702 }
2703 //###########################################
2704 //# END EDITS
2705 //###########################################
2707 //clean up the nodepath (such as for trivial subpaths)
2708 sp_nodepath_cleanup(nodepath);
2710 sp_nodepath_update_handles(nodepath);
2712 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2714 sp_nodepath_update_statusbar(nodepath);
2715 }
2717 /**
2718 * Call sp_nodepath_set_line() for all selected segments.
2719 */
2720 void
2721 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2722 {
2723 if (nodepath == NULL) return;
2725 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2726 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2727 g_assert(n->selected);
2728 if (n->p.other && n->p.other->selected) {
2729 sp_nodepath_set_line_type(n, code);
2730 }
2731 }
2733 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2734 }
2736 /**
2737 * Call sp_nodepath_convert_node_type() for all selected nodes.
2738 */
2739 void
2740 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2741 {
2742 if (nodepath == NULL) return;
2744 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2746 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2747 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2748 }
2750 sp_nodepath_update_repr(nodepath, _("Change node type"));
2751 }
2753 /**
2754 * Change select status of node, update its own and neighbour handles.
2755 */
2756 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2757 {
2758 node->selected = selected;
2760 if (selected) {
2761 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2762 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2763 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2764 sp_knot_update_ctrl(node->knot);
2765 } else {
2766 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2767 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2768 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2769 sp_knot_update_ctrl(node->knot);
2770 }
2772 sp_node_update_handles(node);
2773 if (node->n.other) sp_node_update_handles(node->n.other);
2774 if (node->p.other) sp_node_update_handles(node->p.other);
2775 }
2777 /**
2778 \brief Select a node
2779 \param node The node to select
2780 \param incremental If true, add to selection, otherwise deselect others
2781 \param override If true, always select this node, otherwise toggle selected status
2782 */
2783 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2784 {
2785 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2787 if (incremental) {
2788 if (override) {
2789 if (!g_list_find(nodepath->selected, node)) {
2790 nodepath->selected = g_list_prepend(nodepath->selected, node);
2791 }
2792 sp_node_set_selected(node, TRUE);
2793 } else { // toggle
2794 if (node->selected) {
2795 g_assert(g_list_find(nodepath->selected, node));
2796 nodepath->selected = g_list_remove(nodepath->selected, node);
2797 } else {
2798 g_assert(!g_list_find(nodepath->selected, node));
2799 nodepath->selected = g_list_prepend(nodepath->selected, node);
2800 }
2801 sp_node_set_selected(node, !node->selected);
2802 }
2803 } else {
2804 sp_nodepath_deselect(nodepath);
2805 nodepath->selected = g_list_prepend(nodepath->selected, node);
2806 sp_node_set_selected(node, TRUE);
2807 }
2809 sp_nodepath_update_statusbar(nodepath);
2810 }
2813 /**
2814 \brief Deselect all nodes in the nodepath
2815 */
2816 void
2817 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2818 {
2819 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2821 while (nodepath->selected) {
2822 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2823 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2824 }
2825 sp_nodepath_update_statusbar(nodepath);
2826 }
2828 /**
2829 \brief Select or invert selection of all nodes in the nodepath
2830 */
2831 void
2832 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2833 {
2834 if (!nodepath) return;
2836 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2837 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2838 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2839 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2840 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2841 }
2842 }
2843 }
2845 /**
2846 * If nothing selected, does the same as sp_nodepath_select_all();
2847 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2848 * (i.e., similar to "select all in layer", with the "selected" subpaths
2849 * being treated as "layers" in the path).
2850 */
2851 void
2852 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2853 {
2854 if (!nodepath) return;
2856 if (g_list_length (nodepath->selected) == 0) {
2857 sp_nodepath_select_all (nodepath, invert);
2858 return;
2859 }
2861 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2862 GSList *subpaths = NULL;
2864 for (GList *l = copy; l != NULL; l = l->next) {
2865 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2866 Inkscape::NodePath::SubPath *subpath = n->subpath;
2867 if (!g_slist_find (subpaths, subpath))
2868 subpaths = g_slist_prepend (subpaths, subpath);
2869 }
2871 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2872 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2873 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2874 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2875 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2876 }
2877 }
2879 g_slist_free (subpaths);
2880 g_list_free (copy);
2881 }
2883 /**
2884 * \brief Select the node after the last selected; if none is selected,
2885 * select the first within path.
2886 */
2887 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2888 {
2889 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2891 Inkscape::NodePath::Node *last = NULL;
2892 if (nodepath->selected) {
2893 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2894 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2895 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2896 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2897 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2898 if (node->selected) {
2899 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2900 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2901 if (spl->next) { // there's a next subpath
2902 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2903 last = subpath_next->first;
2904 } else if (spl->prev) { // there's a previous subpath
2905 last = NULL; // to be set later to the first node of first subpath
2906 } else {
2907 last = node->n.other;
2908 }
2909 } else {
2910 last = node->n.other;
2911 }
2912 } else {
2913 if (node->n.other) {
2914 last = node->n.other;
2915 } else {
2916 if (spl->next) { // there's a next subpath
2917 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2918 last = subpath_next->first;
2919 } else if (spl->prev) { // there's a previous subpath
2920 last = NULL; // to be set later to the first node of first subpath
2921 } else {
2922 last = (Inkscape::NodePath::Node *) subpath->first;
2923 }
2924 }
2925 }
2926 }
2927 }
2928 }
2929 sp_nodepath_deselect(nodepath);
2930 }
2932 if (last) { // there's at least one more node after selected
2933 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2934 } else { // no more nodes, select the first one in first subpath
2935 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2936 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2937 }
2938 }
2940 /**
2941 * \brief Select the node before the first selected; if none is selected,
2942 * select the last within path
2943 */
2944 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2945 {
2946 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2948 Inkscape::NodePath::Node *last = NULL;
2949 if (nodepath->selected) {
2950 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2951 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2952 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2953 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2954 if (node->selected) {
2955 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2956 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2957 if (spl->prev) { // there's a prev subpath
2958 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2959 last = subpath_prev->last;
2960 } else if (spl->next) { // there's a next subpath
2961 last = NULL; // to be set later to the last node of last subpath
2962 } else {
2963 last = node->p.other;
2964 }
2965 } else {
2966 last = node->p.other;
2967 }
2968 } else {
2969 if (node->p.other) {
2970 last = node->p.other;
2971 } else {
2972 if (spl->prev) { // there's a prev subpath
2973 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2974 last = subpath_prev->last;
2975 } else if (spl->next) { // there's a next subpath
2976 last = NULL; // to be set later to the last node of last subpath
2977 } else {
2978 last = (Inkscape::NodePath::Node *) subpath->last;
2979 }
2980 }
2981 }
2982 }
2983 }
2984 }
2985 sp_nodepath_deselect(nodepath);
2986 }
2988 if (last) { // there's at least one more node before selected
2989 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2990 } else { // no more nodes, select the last one in last subpath
2991 GList *spl = g_list_last(nodepath->subpaths);
2992 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2993 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2994 }
2995 }
2997 /**
2998 * \brief Select all nodes that are within the rectangle.
2999 */
3000 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
3001 {
3002 if (!incremental) {
3003 sp_nodepath_deselect(nodepath);
3004 }
3006 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3007 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3008 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3009 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3011 if (b.contains(node->pos)) {
3012 sp_nodepath_node_select(node, TRUE, TRUE);
3013 }
3014 }
3015 }
3016 }
3019 void
3020 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3021 {
3022 g_assert (n);
3023 g_assert (nodepath);
3024 g_assert (n->subpath->nodepath == nodepath);
3026 if (g_list_length (nodepath->selected) == 0) {
3027 if (grow > 0) {
3028 sp_nodepath_node_select(n, TRUE, TRUE);
3029 }
3030 return;
3031 }
3033 if (g_list_length (nodepath->selected) == 1) {
3034 if (grow < 0) {
3035 sp_nodepath_deselect (nodepath);
3036 return;
3037 }
3038 }
3040 double n_sel_range = 0, p_sel_range = 0;
3041 Inkscape::NodePath::Node *farthest_n_node = n;
3042 Inkscape::NodePath::Node *farthest_p_node = n;
3044 // Calculate ranges
3045 {
3046 double n_range = 0, p_range = 0;
3047 bool n_going = true, p_going = true;
3048 Inkscape::NodePath::Node *n_node = n;
3049 Inkscape::NodePath::Node *p_node = n;
3050 do {
3051 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3052 if (n_node && n_going)
3053 n_node = n_node->n.other;
3054 if (n_node == NULL) {
3055 n_going = false;
3056 } else {
3057 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3058 if (n_node->selected) {
3059 n_sel_range = n_range;
3060 farthest_n_node = n_node;
3061 }
3062 if (n_node == p_node) {
3063 n_going = false;
3064 p_going = false;
3065 }
3066 }
3067 if (p_node && p_going)
3068 p_node = p_node->p.other;
3069 if (p_node == NULL) {
3070 p_going = false;
3071 } else {
3072 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3073 if (p_node->selected) {
3074 p_sel_range = p_range;
3075 farthest_p_node = p_node;
3076 }
3077 if (p_node == n_node) {
3078 n_going = false;
3079 p_going = false;
3080 }
3081 }
3082 } while (n_going || p_going);
3083 }
3085 if (grow > 0) {
3086 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3087 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3088 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3089 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3090 }
3091 } else {
3092 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3093 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3094 } else if (farthest_p_node && farthest_p_node->selected) {
3095 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3096 }
3097 }
3098 }
3100 void
3101 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3102 {
3103 g_assert (n);
3104 g_assert (nodepath);
3105 g_assert (n->subpath->nodepath == nodepath);
3107 if (g_list_length (nodepath->selected) == 0) {
3108 if (grow > 0) {
3109 sp_nodepath_node_select(n, TRUE, TRUE);
3110 }
3111 return;
3112 }
3114 if (g_list_length (nodepath->selected) == 1) {
3115 if (grow < 0) {
3116 sp_nodepath_deselect (nodepath);
3117 return;
3118 }
3119 }
3121 Inkscape::NodePath::Node *farthest_selected = NULL;
3122 double farthest_dist = 0;
3124 Inkscape::NodePath::Node *closest_unselected = NULL;
3125 double closest_dist = NR_HUGE;
3127 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3128 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3129 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3130 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3131 if (node == n)
3132 continue;
3133 if (node->selected) {
3134 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3135 farthest_dist = NR::L2(node->pos - n->pos);
3136 farthest_selected = node;
3137 }
3138 } else {
3139 if (NR::L2(node->pos - n->pos) < closest_dist) {
3140 closest_dist = NR::L2(node->pos - n->pos);
3141 closest_unselected = node;
3142 }
3143 }
3144 }
3145 }
3147 if (grow > 0) {
3148 if (closest_unselected) {
3149 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3150 }
3151 } else {
3152 if (farthest_selected) {
3153 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3154 }
3155 }
3156 }
3159 /**
3160 \brief Saves all nodes' and handles' current positions in their origin members
3161 */
3162 void
3163 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3164 {
3165 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3166 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3167 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3168 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3169 n->origin = n->pos;
3170 n->p.origin = n->p.pos;
3171 n->n.origin = n->n.pos;
3172 }
3173 }
3174 }
3176 /**
3177 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3178 */
3179 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3180 {
3181 if (!nodepath->selected) {
3182 return NULL;
3183 }
3185 GList *r = NULL;
3186 guint i = 0;
3187 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3188 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3189 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3190 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3191 i++;
3192 if (node->selected) {
3193 r = g_list_append(r, GINT_TO_POINTER(i));
3194 }
3195 }
3196 }
3197 return r;
3198 }
3200 /**
3201 \brief Restores selection by selecting nodes whose positions are in the list
3202 */
3203 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3204 {
3205 sp_nodepath_deselect(nodepath);
3207 guint i = 0;
3208 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3209 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3210 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3211 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3212 i++;
3213 if (g_list_find(r, GINT_TO_POINTER(i))) {
3214 sp_nodepath_node_select(node, TRUE, TRUE);
3215 }
3216 }
3217 }
3218 }
3221 /**
3222 \brief Adjusts handle according to node type and line code.
3223 */
3224 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3225 {
3226 g_assert(node);
3228 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3229 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3231 // nothing to do if we are an end node
3232 if (me->other == NULL) return;
3233 if (other->other == NULL) return;
3235 // nothing to do if we are a cusp node
3236 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3238 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3239 NRPathcode mecode;
3240 if (which_adjust == 1) {
3241 mecode = (NRPathcode)me->other->code;
3242 } else {
3243 mecode = (NRPathcode)node->code;
3244 }
3245 if (mecode == NR_LINETO) return;
3247 if (sp_node_side_is_line(node, other)) {
3248 // other is a line, and we are either smooth or symm
3249 Inkscape::NodePath::Node *othernode = other->other;
3250 double len = NR::L2(me->pos - node->pos);
3251 NR::Point delta = node->pos - othernode->pos;
3252 double linelen = NR::L2(delta);
3253 if (linelen < 1e-18)
3254 return;
3255 me->pos = node->pos + (len / linelen)*delta;
3256 return;
3257 }
3259 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3260 // symmetrize
3261 me->pos = 2 * node->pos - other->pos;
3262 return;
3263 } else {
3264 // smoothify
3265 double len = NR::L2(me->pos - node->pos);
3266 NR::Point delta = other->pos - node->pos;
3267 double otherlen = NR::L2(delta);
3268 if (otherlen < 1e-18) return;
3269 me->pos = node->pos - (len / otherlen) * delta;
3270 }
3271 }
3273 /**
3274 \brief Adjusts both handles according to node type and line code
3275 */
3276 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3277 {
3278 g_assert(node);
3280 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3282 /* we are either smooth or symm */
3284 if (node->p.other == NULL) return;
3285 if (node->n.other == NULL) return;
3287 if (sp_node_side_is_line(node, &node->p)) {
3288 sp_node_adjust_handle(node, 1);
3289 return;
3290 }
3292 if (sp_node_side_is_line(node, &node->n)) {
3293 sp_node_adjust_handle(node, -1);
3294 return;
3295 }
3297 /* both are curves */
3298 NR::Point const delta( node->n.pos - node->p.pos );
3300 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3301 node->p.pos = node->pos - delta / 2;
3302 node->n.pos = node->pos + delta / 2;
3303 return;
3304 }
3306 /* We are smooth */
3307 double plen = NR::L2(node->p.pos - node->pos);
3308 if (plen < 1e-18) return;
3309 double nlen = NR::L2(node->n.pos - node->pos);
3310 if (nlen < 1e-18) return;
3311 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3312 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3313 }
3315 /**
3316 * Node event callback.
3317 */
3318 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3319 {
3320 gboolean ret = FALSE;
3321 switch (event->type) {
3322 case GDK_ENTER_NOTIFY:
3323 Inkscape::NodePath::Path::active_node = n;
3324 break;
3325 case GDK_LEAVE_NOTIFY:
3326 Inkscape::NodePath::Path::active_node = NULL;
3327 break;
3328 case GDK_SCROLL:
3329 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3330 switch (event->scroll.direction) {
3331 case GDK_SCROLL_UP:
3332 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3333 break;
3334 case GDK_SCROLL_DOWN:
3335 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3336 break;
3337 default:
3338 break;
3339 }
3340 ret = TRUE;
3341 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3342 switch (event->scroll.direction) {
3343 case GDK_SCROLL_UP:
3344 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3345 break;
3346 case GDK_SCROLL_DOWN:
3347 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3348 break;
3349 default:
3350 break;
3351 }
3352 ret = TRUE;
3353 }
3354 break;
3355 case GDK_KEY_PRESS:
3356 switch (get_group0_keyval (&event->key)) {
3357 case GDK_space:
3358 if (event->key.state & GDK_BUTTON1_MASK) {
3359 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3360 stamp_repr(nodepath);
3361 ret = TRUE;
3362 }
3363 break;
3364 case GDK_Page_Up:
3365 if (event->key.state & GDK_CONTROL_MASK) {
3366 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3367 } else {
3368 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3369 }
3370 break;
3371 case GDK_Page_Down:
3372 if (event->key.state & GDK_CONTROL_MASK) {
3373 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3374 } else {
3375 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3376 }
3377 break;
3378 default:
3379 break;
3380 }
3381 break;
3382 default:
3383 break;
3384 }
3386 return ret;
3387 }
3389 /**
3390 * Handle keypress on node; directly called.
3391 */
3392 gboolean node_key(GdkEvent *event)
3393 {
3394 Inkscape::NodePath::Path *np;
3396 // there is no way to verify nodes so set active_node to nil when deleting!!
3397 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3399 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3400 gint ret = FALSE;
3401 switch (get_group0_keyval (&event->key)) {
3402 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3403 case GDK_BackSpace:
3404 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3405 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3406 sp_nodepath_update_repr(np, _("Delete node"));
3407 Inkscape::NodePath::Path::active_node = NULL;
3408 ret = TRUE;
3409 break;
3410 case GDK_c:
3411 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3412 ret = TRUE;
3413 break;
3414 case GDK_s:
3415 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3416 ret = TRUE;
3417 break;
3418 case GDK_y:
3419 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3420 ret = TRUE;
3421 break;
3422 case GDK_b:
3423 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3424 ret = TRUE;
3425 break;
3426 }
3427 return ret;
3428 }
3429 return FALSE;
3430 }
3432 /**
3433 * Mouseclick on node callback.
3434 */
3435 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3436 {
3437 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3439 if (state & GDK_CONTROL_MASK) {
3440 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3442 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3443 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3444 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3445 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3446 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3447 } else {
3448 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3449 }
3450 sp_nodepath_update_repr(nodepath, _("Change node type"));
3451 sp_nodepath_update_statusbar(nodepath);
3453 } else { //ctrl+alt+click: delete node
3454 GList *node_to_delete = NULL;
3455 node_to_delete = g_list_append(node_to_delete, n);
3456 sp_node_delete_preserve(node_to_delete);
3457 }
3459 } else {
3460 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3461 }
3462 }
3464 /**
3465 * Mouse grabbed node callback.
3466 */
3467 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3468 {
3469 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3471 if (!n->selected) {
3472 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3473 }
3475 n->is_dragging = true;
3476 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3478 sp_nodepath_remember_origins (n->subpath->nodepath);
3479 }
3481 /**
3482 * Mouse ungrabbed node callback.
3483 */
3484 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3485 {
3486 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3488 n->dragging_out = NULL;
3489 n->is_dragging = false;
3490 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3492 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3493 }
3495 /**
3496 * The point on a line, given by its angle, closest to the given point.
3497 * \param p A point.
3498 * \param a Angle of the line; it is assumed to go through coordinate origin.
3499 * \param closest Pointer to the point struct where the result is stored.
3500 * \todo FIXME: use dot product perhaps?
3501 */
3502 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3503 {
3504 if (a == HUGE_VAL) { // vertical
3505 *closest = NR::Point(0, (*p)[NR::Y]);
3506 } else {
3507 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3508 (*closest)[NR::Y] = a * (*closest)[NR::X];
3509 }
3510 }
3512 /**
3513 * Distance from the point to a line given by its angle.
3514 * \param p A point.
3515 * \param a Angle of the line; it is assumed to go through coordinate origin.
3516 */
3517 static double point_line_distance(NR::Point *p, double a)
3518 {
3519 NR::Point c;
3520 point_line_closest(p, a, &c);
3521 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]));
3522 }
3524 /**
3525 * Callback for node "request" signal.
3526 * \todo fixme: This goes to "moved" event? (lauris)
3527 */
3528 static gboolean
3529 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3530 {
3531 double yn, xn, yp, xp;
3532 double an, ap, na, pa;
3533 double d_an, d_ap, d_na, d_pa;
3534 gboolean collinear = FALSE;
3535 NR::Point c;
3536 NR::Point pr;
3538 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3540 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3542 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3543 if ( (!n->subpath->nodepath->straight_path) &&
3544 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3545 || n->dragging_out ) )
3546 {
3547 NR::Point mouse = (*p);
3549 if (!n->dragging_out) {
3550 // This is the first drag-out event; find out which handle to drag out
3551 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3552 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3554 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3555 return FALSE;
3557 Inkscape::NodePath::NodeSide *opposite;
3558 if (appr_p > appr_n) { // closer to p
3559 n->dragging_out = &n->p;
3560 opposite = &n->n;
3561 n->code = NR_CURVETO;
3562 } else if (appr_p < appr_n) { // closer to n
3563 n->dragging_out = &n->n;
3564 opposite = &n->p;
3565 n->n.other->code = NR_CURVETO;
3566 } else { // p and n nodes are the same
3567 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3568 n->dragging_out = &n->p;
3569 opposite = &n->n;
3570 n->code = NR_CURVETO;
3571 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3572 n->dragging_out = &n->n;
3573 opposite = &n->p;
3574 n->n.other->code = NR_CURVETO;
3575 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3576 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);
3577 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);
3578 if (appr_other_p > appr_other_n) { // closer to other's p handle
3579 n->dragging_out = &n->n;
3580 opposite = &n->p;
3581 n->n.other->code = NR_CURVETO;
3582 } else { // closer to other's n handle
3583 n->dragging_out = &n->p;
3584 opposite = &n->n;
3585 n->code = NR_CURVETO;
3586 }
3587 }
3588 }
3590 // if there's another handle, make sure the one we drag out starts parallel to it
3591 if (opposite->pos != n->pos) {
3592 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3593 }
3595 // knots might not be created yet!
3596 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3597 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3598 }
3600 // pass this on to the handle-moved callback
3601 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3602 sp_node_update_handles(n);
3603 return TRUE;
3604 }
3606 if (state & GDK_CONTROL_MASK) { // constrained motion
3608 // calculate relative distances of handles
3609 // n handle:
3610 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3611 xn = n->n.pos[NR::X] - n->pos[NR::X];
3612 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3613 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3614 if (n->n.other) { // if there is the next point
3615 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3616 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3617 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3618 }
3619 }
3620 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3621 if (yn < 0) { xn = -xn; yn = -yn; }
3623 // p handle:
3624 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3625 xp = n->p.pos[NR::X] - n->pos[NR::X];
3626 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3627 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3628 if (n->p.other) {
3629 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3630 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3631 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3632 }
3633 }
3634 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3635 if (yp < 0) { xp = -xp; yp = -yp; }
3637 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3638 // sliding on handles, only if at least one of the handles is non-vertical
3639 // (otherwise it's the same as ctrl+drag anyway)
3641 // calculate angles of the handles
3642 if (xn == 0) {
3643 if (yn == 0) { // no handle, consider it the continuation of the other one
3644 an = 0;
3645 collinear = TRUE;
3646 }
3647 else an = 0; // vertical; set the angle to horizontal
3648 } else an = yn/xn;
3650 if (xp == 0) {
3651 if (yp == 0) { // no handle, consider it the continuation of the other one
3652 ap = an;
3653 }
3654 else ap = 0; // vertical; set the angle to horizontal
3655 } else ap = yp/xp;
3657 if (collinear) an = ap;
3659 // angles of the perpendiculars; HUGE_VAL means vertical
3660 if (an == 0) na = HUGE_VAL; else na = -1/an;
3661 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3663 // mouse point relative to the node's original pos
3664 pr = (*p) - n->origin;
3666 // distances to the four lines (two handles and two perpendiculars)
3667 d_an = point_line_distance(&pr, an);
3668 d_na = point_line_distance(&pr, na);
3669 d_ap = point_line_distance(&pr, ap);
3670 d_pa = point_line_distance(&pr, pa);
3672 // find out which line is the closest, save its closest point in c
3673 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3674 point_line_closest(&pr, an, &c);
3675 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3676 point_line_closest(&pr, ap, &c);
3677 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3678 point_line_closest(&pr, na, &c);
3679 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3680 point_line_closest(&pr, pa, &c);
3681 }
3683 // move the node to the closest point
3684 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3685 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3686 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3687 true);
3689 } else { // constraining to hor/vert
3691 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3692 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3693 (*p)[NR::X] - n->pos[NR::X],
3694 n->origin[NR::Y] - n->pos[NR::Y],
3695 true,
3696 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3697 } else { // snap to vert
3698 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3699 n->origin[NR::X] - n->pos[NR::X],
3700 (*p)[NR::Y] - n->pos[NR::Y],
3701 true,
3702 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3703 }
3704 }
3705 } else { // move freely
3706 if (n->is_dragging) {
3707 if (state & GDK_MOD1_MASK) { // sculpt
3708 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3709 } else {
3710 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3711 (*p)[NR::X] - n->pos[NR::X],
3712 (*p)[NR::Y] - n->pos[NR::Y],
3713 (state & GDK_SHIFT_MASK) == 0);
3714 }
3715 }
3716 }
3718 n->subpath->nodepath->desktop->scroll_to_point(p);
3720 return TRUE;
3721 }
3723 /**
3724 * Node handle clicked callback.
3725 */
3726 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3727 {
3728 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3730 if (state & GDK_CONTROL_MASK) { // "delete" handle
3731 if (n->p.knot == knot) {
3732 n->p.pos = n->pos;
3733 } else if (n->n.knot == knot) {
3734 n->n.pos = n->pos;
3735 }
3736 sp_node_update_handles(n);
3737 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3738 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3739 sp_nodepath_update_statusbar(nodepath);
3741 } else { // just select or add to selection, depending in Shift
3742 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3743 }
3744 }
3746 /**
3747 * Node handle grabbed callback.
3748 */
3749 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3750 {
3751 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3753 if (!n->selected) {
3754 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3755 }
3757 // remember the origin point of the handle
3758 if (n->p.knot == knot) {
3759 n->p.origin_radial = n->p.pos - n->pos;
3760 } else if (n->n.knot == knot) {
3761 n->n.origin_radial = n->n.pos - n->pos;
3762 } else {
3763 g_assert_not_reached();
3764 }
3766 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3767 }
3769 /**
3770 * Node handle ungrabbed callback.
3771 */
3772 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3773 {
3774 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3776 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3777 if (n->p.knot == knot) {
3778 n->p.origin_radial.a = 0;
3779 sp_knot_set_position(knot, &n->p.pos, state);
3780 } else if (n->n.knot == knot) {
3781 n->n.origin_radial.a = 0;
3782 sp_knot_set_position(knot, &n->n.pos, state);
3783 } else {
3784 g_assert_not_reached();
3785 }
3787 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3788 }
3790 /**
3791 * Node handle "request" signal callback.
3792 */
3793 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3794 {
3795 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3797 Inkscape::NodePath::NodeSide *me, *opposite;
3798 gint which;
3799 if (n->p.knot == knot) {
3800 me = &n->p;
3801 opposite = &n->n;
3802 which = -1;
3803 } else if (n->n.knot == knot) {
3804 me = &n->n;
3805 opposite = &n->p;
3806 which = 1;
3807 } else {
3808 me = opposite = NULL;
3809 which = 0;
3810 g_assert_not_reached();
3811 }
3813 SPDesktop *desktop = n->subpath->nodepath->desktop;
3814 SnapManager &m = desktop->namedview->snap_manager;
3815 m.setup(desktop, n->subpath->nodepath->item);
3816 Inkscape::SnappedPoint s;
3818 if ((state & GDK_SHIFT_MASK) != 0) {
3819 // We will not try to snap when the shift-key is pressed
3820 // so remove the old snap indicator and don't wait for it to time-out
3821 desktop->snapindicator->remove_snappoint();
3822 }
3824 Inkscape::NodePath::Node *othernode = opposite->other;
3825 if (othernode) {
3826 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3827 /* We are smooth node adjacent with line */
3828 NR::Point const delta = *p - n->pos;
3829 NR::Coord const len = NR::L2(delta);
3830 Inkscape::NodePath::Node *othernode = opposite->other;
3831 NR::Point const ndelta = n->pos - othernode->pos;
3832 NR::Coord const linelen = NR::L2(ndelta);
3833 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3834 NR::Coord const scal = dot(delta, ndelta) / linelen;
3835 (*p) = n->pos + (scal / linelen) * ndelta;
3836 }
3837 if ((state & GDK_SHIFT_MASK) == 0) {
3838 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3839 }
3840 } else {
3841 if ((state & GDK_SHIFT_MASK) == 0) {
3842 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3843 }
3844 }
3845 } else {
3846 if ((state & GDK_SHIFT_MASK) == 0) {
3847 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3848 }
3849 }
3851 s.getPoint(*p);
3853 sp_node_adjust_handle(n, -which);
3855 return FALSE;
3856 }
3858 /**
3859 * Node handle moved callback.
3860 */
3861 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3862 {
3863 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3865 Inkscape::NodePath::NodeSide *me;
3866 Inkscape::NodePath::NodeSide *other;
3867 if (n->p.knot == knot) {
3868 me = &n->p;
3869 other = &n->n;
3870 } else if (n->n.knot == knot) {
3871 me = &n->n;
3872 other = &n->p;
3873 } else {
3874 me = NULL;
3875 other = NULL;
3876 g_assert_not_reached();
3877 }
3879 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3880 Radial rme(me->pos - n->pos);
3881 Radial rother(other->pos - n->pos);
3882 Radial rnew(*p - n->pos);
3884 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3885 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3886 /* 0 interpreted as "no snapping". */
3888 // 1. Snap to the closest PI/snaps angle, starting from zero.
3889 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3891 // 2. Snap to the original angle, its opposite and perpendiculars
3892 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3893 /* The closest PI/2 angle, starting from original angle */
3894 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3896 // Snap to the closest.
3897 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3898 ? a_snapped
3899 : a_ortho );
3900 }
3902 // 3. Snap to the angle of the opposite line, if any
3903 Inkscape::NodePath::Node *othernode = other->other;
3904 if (othernode) {
3905 NR::Point other_to_snap(0,0);
3906 if (sp_node_side_is_line(n, other)) {
3907 other_to_snap = othernode->pos - n->pos;
3908 } else {
3909 other_to_snap = other->pos - n->pos;
3910 }
3911 if (NR::L2(other_to_snap) > 1e-3) {
3912 Radial rother_to_snap(other_to_snap);
3913 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3914 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3916 // Snap to the closest.
3917 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3918 ? a_snapped
3919 : a_oppo );
3920 }
3921 }
3923 rnew.a = a_snapped;
3924 }
3926 if (state & GDK_MOD1_MASK) {
3927 // lock handle length
3928 rnew.r = me->origin_radial.r;
3929 }
3931 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3932 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3933 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3934 rother.a += rnew.a - rme.a;
3935 other->pos = NR::Point(rother) + n->pos;
3936 if (other->knot) {
3937 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3938 sp_knot_moveto(other->knot, &other->pos);
3939 }
3940 }
3942 me->pos = NR::Point(rnew) + n->pos;
3943 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3945 // move knot, but without emitting the signal:
3946 // we cannot emit a "moved" signal because we're now processing it
3947 sp_knot_moveto(me->knot, &(me->pos));
3949 update_object(n->subpath->nodepath);
3951 /* status text */
3952 SPDesktop *desktop = n->subpath->nodepath->desktop;
3953 if (!desktop) return;
3954 SPEventContext *ec = desktop->event_context;
3955 if (!ec) return;
3956 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3957 if (!mc) return;
3959 double degrees = 180 / M_PI * rnew.a;
3960 if (degrees > 180) degrees -= 360;
3961 if (degrees < -180) degrees += 360;
3962 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3963 degrees = angle_to_compass (degrees);
3965 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3967 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3968 _("<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);
3970 g_string_free(length, TRUE);
3971 }
3973 /**
3974 * Node handle event callback.
3975 */
3976 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3977 {
3978 gboolean ret = FALSE;
3979 switch (event->type) {
3980 case GDK_KEY_PRESS:
3981 switch (get_group0_keyval (&event->key)) {
3982 case GDK_space:
3983 if (event->key.state & GDK_BUTTON1_MASK) {
3984 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3985 stamp_repr(nodepath);
3986 ret = TRUE;
3987 }
3988 break;
3989 default:
3990 break;
3991 }
3992 break;
3993 case GDK_ENTER_NOTIFY:
3994 // we use an experimentally determined threshold that seems to work fine
3995 if (NR::L2(n->pos - knot->pos) < 0.75)
3996 Inkscape::NodePath::Path::active_node = n;
3997 break;
3998 case GDK_LEAVE_NOTIFY:
3999 // we use an experimentally determined threshold that seems to work fine
4000 if (NR::L2(n->pos - knot->pos) < 0.75)
4001 Inkscape::NodePath::Path::active_node = NULL;
4002 break;
4003 default:
4004 break;
4005 }
4007 return ret;
4008 }
4010 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
4011 Radial &rme, Radial &rother, gboolean const both)
4012 {
4013 rme.a += angle;
4014 if ( both
4015 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4016 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4017 {
4018 rother.a += angle;
4019 }
4020 }
4022 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
4023 Radial &rme, Radial &rother, gboolean const both)
4024 {
4025 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4027 gdouble r;
4028 if ( both
4029 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4030 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4031 {
4032 r = MAX(rme.r, rother.r);
4033 } else {
4034 r = rme.r;
4035 }
4037 gdouble const weird_angle = atan2(norm_angle, r);
4038 /* Bulia says norm_angle is just the visible distance that the
4039 * object's end must travel on the screen. Left as 'angle' for want of
4040 * a better name.*/
4042 rme.a += weird_angle;
4043 if ( both
4044 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4045 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4046 {
4047 rother.a += weird_angle;
4048 }
4049 }
4051 /**
4052 * Rotate one node.
4053 */
4054 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4055 {
4056 Inkscape::NodePath::NodeSide *me, *other;
4057 bool both = false;
4059 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4060 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4062 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4063 me = &(n->p);
4064 other = &(n->n);
4065 } else if (!n->p.other) {
4066 me = &(n->n);
4067 other = &(n->p);
4068 } else {
4069 if (which > 0) { // right handle
4070 if (xn > xp) {
4071 me = &(n->n);
4072 other = &(n->p);
4073 } else {
4074 me = &(n->p);
4075 other = &(n->n);
4076 }
4077 } else if (which < 0){ // left handle
4078 if (xn <= xp) {
4079 me = &(n->n);
4080 other = &(n->p);
4081 } else {
4082 me = &(n->p);
4083 other = &(n->n);
4084 }
4085 } else { // both handles
4086 me = &(n->n);
4087 other = &(n->p);
4088 both = true;
4089 }
4090 }
4092 Radial rme(me->pos - n->pos);
4093 Radial rother(other->pos - n->pos);
4095 if (screen) {
4096 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4097 } else {
4098 node_rotate_one_internal (*n, angle, rme, rother, both);
4099 }
4101 me->pos = n->pos + NR::Point(rme);
4103 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4104 other->pos = n->pos + NR::Point(rother);
4105 }
4107 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4108 // so here we just move all the knots without emitting move signals, for speed
4109 sp_node_update_handles(n, false);
4110 }
4112 /**
4113 * Rotate selected nodes.
4114 */
4115 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4116 {
4117 if (!nodepath || !nodepath->selected) return;
4119 if (g_list_length(nodepath->selected) == 1) {
4120 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4121 node_rotate_one (n, angle, which, screen);
4122 } else {
4123 // rotate as an object:
4125 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4126 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4127 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4128 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4129 box.expandTo (n->pos); // contain all selected nodes
4130 }
4132 gdouble rot;
4133 if (screen) {
4134 gdouble const zoom = nodepath->desktop->current_zoom();
4135 gdouble const zmove = angle / zoom;
4136 gdouble const r = NR::L2(box.max() - box.midpoint());
4137 rot = atan2(zmove, r);
4138 } else {
4139 rot = angle;
4140 }
4142 NR::Point rot_center;
4143 if (Inkscape::NodePath::Path::active_node == NULL)
4144 rot_center = box.midpoint();
4145 else
4146 rot_center = Inkscape::NodePath::Path::active_node->pos;
4148 NR::Matrix t =
4149 NR::Matrix (NR::translate(-rot_center)) *
4150 NR::Matrix (NR::rotate(rot)) *
4151 NR::Matrix (NR::translate(rot_center));
4153 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4154 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4155 n->pos *= t;
4156 n->n.pos *= t;
4157 n->p.pos *= t;
4158 sp_node_update_handles(n, false);
4159 }
4160 }
4162 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4163 }
4165 /**
4166 * Scale one node.
4167 */
4168 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4169 {
4170 bool both = false;
4171 Inkscape::NodePath::NodeSide *me, *other;
4173 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4174 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4176 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4177 me = &(n->p);
4178 other = &(n->n);
4179 n->code = NR_CURVETO;
4180 } else if (!n->p.other) {
4181 me = &(n->n);
4182 other = &(n->p);
4183 if (n->n.other)
4184 n->n.other->code = NR_CURVETO;
4185 } else {
4186 if (which > 0) { // right handle
4187 if (xn > xp) {
4188 me = &(n->n);
4189 other = &(n->p);
4190 if (n->n.other)
4191 n->n.other->code = NR_CURVETO;
4192 } else {
4193 me = &(n->p);
4194 other = &(n->n);
4195 n->code = NR_CURVETO;
4196 }
4197 } else if (which < 0){ // left handle
4198 if (xn <= xp) {
4199 me = &(n->n);
4200 other = &(n->p);
4201 if (n->n.other)
4202 n->n.other->code = NR_CURVETO;
4203 } else {
4204 me = &(n->p);
4205 other = &(n->n);
4206 n->code = NR_CURVETO;
4207 }
4208 } else { // both handles
4209 me = &(n->n);
4210 other = &(n->p);
4211 both = true;
4212 n->code = NR_CURVETO;
4213 if (n->n.other)
4214 n->n.other->code = NR_CURVETO;
4215 }
4216 }
4218 Radial rme(me->pos - n->pos);
4219 Radial rother(other->pos - n->pos);
4221 rme.r += grow;
4222 if (rme.r < 0) rme.r = 0;
4223 if (rme.a == HUGE_VAL) {
4224 if (me->other) { // if direction is unknown, initialize it towards the next node
4225 Radial rme_next(me->other->pos - n->pos);
4226 rme.a = rme_next.a;
4227 } else { // if there's no next, initialize to 0
4228 rme.a = 0;
4229 }
4230 }
4231 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4232 rother.r += grow;
4233 if (rother.r < 0) rother.r = 0;
4234 if (rother.a == HUGE_VAL) {
4235 rother.a = rme.a + M_PI;
4236 }
4237 }
4239 me->pos = n->pos + NR::Point(rme);
4241 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4242 other->pos = n->pos + NR::Point(rother);
4243 }
4245 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4246 // so here we just move all the knots without emitting move signals, for speed
4247 sp_node_update_handles(n, false);
4248 }
4250 /**
4251 * Scale selected nodes.
4252 */
4253 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4254 {
4255 if (!nodepath || !nodepath->selected) return;
4257 if (g_list_length(nodepath->selected) == 1) {
4258 // scale handles of the single selected node
4259 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4260 node_scale_one (n, grow, which);
4261 } else {
4262 // scale nodes as an "object":
4264 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4265 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4266 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4267 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4268 box.expandTo (n->pos); // contain all selected nodes
4269 }
4271 double scale = (box.maxExtent() + grow)/box.maxExtent();
4273 NR::Point scale_center;
4274 if (Inkscape::NodePath::Path::active_node == NULL)
4275 scale_center = box.midpoint();
4276 else
4277 scale_center = Inkscape::NodePath::Path::active_node->pos;
4279 NR::Matrix t =
4280 NR::Matrix (NR::translate(-scale_center)) *
4281 NR::Matrix (NR::scale(scale, scale)) *
4282 NR::Matrix (NR::translate(scale_center));
4284 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4285 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4286 n->pos *= t;
4287 n->n.pos *= t;
4288 n->p.pos *= t;
4289 sp_node_update_handles(n, false);
4290 }
4291 }
4293 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4294 }
4296 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4297 {
4298 if (!nodepath) return;
4299 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4300 }
4302 /**
4303 * Flip selected nodes horizontally/vertically.
4304 */
4305 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4306 {
4307 if (!nodepath || !nodepath->selected) return;
4309 if (g_list_length(nodepath->selected) == 1 && !center) {
4310 // flip handles of the single selected node
4311 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4312 double temp = n->p.pos[axis];
4313 n->p.pos[axis] = n->n.pos[axis];
4314 n->n.pos[axis] = temp;
4315 sp_node_update_handles(n, false);
4316 } else {
4317 // scale nodes as an "object":
4319 NR::Rect box = sp_node_selected_bbox (nodepath);
4320 if (!center) {
4321 center = box.midpoint();
4322 }
4323 NR::Matrix t =
4324 NR::Matrix (NR::translate(- *center)) *
4325 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4326 NR::Matrix (NR::translate(*center));
4328 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4329 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4330 n->pos *= t;
4331 n->n.pos *= t;
4332 n->p.pos *= t;
4333 sp_node_update_handles(n, false);
4334 }
4335 }
4337 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4338 }
4340 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4341 {
4342 g_assert (nodepath->selected);
4344 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4345 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4346 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4347 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4348 box.expandTo (n->pos); // contain all selected nodes
4349 }
4350 return box;
4351 }
4353 //-----------------------------------------------
4354 /**
4355 * Return new subpath under given nodepath.
4356 */
4357 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4358 {
4359 g_assert(nodepath);
4360 g_assert(nodepath->desktop);
4362 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4364 s->nodepath = nodepath;
4365 s->closed = FALSE;
4366 s->nodes = NULL;
4367 s->first = NULL;
4368 s->last = NULL;
4370 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4371 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4372 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4374 return s;
4375 }
4377 /**
4378 * Destroy nodes in subpath, then subpath itself.
4379 */
4380 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4381 {
4382 g_assert(subpath);
4383 g_assert(subpath->nodepath);
4384 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4386 while (subpath->nodes) {
4387 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4388 }
4390 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4392 g_free(subpath);
4393 }
4395 /**
4396 * Link head to tail in subpath.
4397 */
4398 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4399 {
4400 g_assert(!sp->closed);
4401 g_assert(sp->last != sp->first);
4402 g_assert(sp->first->code == NR_MOVETO);
4404 sp->closed = TRUE;
4406 //Link the head to the tail
4407 sp->first->p.other = sp->last;
4408 sp->last->n.other = sp->first;
4409 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4410 sp->first = sp->last;
4412 //Remove the extra end node
4413 sp_nodepath_node_destroy(sp->last->n.other);
4414 }
4416 /**
4417 * Open closed (loopy) subpath at node.
4418 */
4419 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4420 {
4421 g_assert(sp->closed);
4422 g_assert(n->subpath == sp);
4423 g_assert(sp->first == sp->last);
4425 /* We create new startpoint, current node will become last one */
4427 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4428 &n->pos, &n->pos, &n->n.pos);
4431 sp->closed = FALSE;
4433 //Unlink to make a head and tail
4434 sp->first = new_path;
4435 sp->last = n;
4436 n->n.other = NULL;
4437 new_path->p.other = NULL;
4438 }
4440 /**
4441 * Return new node in subpath with given properties.
4442 * \param pos Position of node.
4443 * \param ppos Handle position in previous direction
4444 * \param npos Handle position in previous direction
4445 */
4446 Inkscape::NodePath::Node *
4447 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)
4448 {
4449 g_assert(sp);
4450 g_assert(sp->nodepath);
4451 g_assert(sp->nodepath->desktop);
4453 if (nodechunk == NULL)
4454 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4456 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4458 n->subpath = sp;
4460 if (type != Inkscape::NodePath::NODE_NONE) {
4461 // use the type from sodipodi:nodetypes
4462 n->type = type;
4463 } else {
4464 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4465 // points are (almost) collinear
4466 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4467 // endnode, or a node with a retracted handle
4468 n->type = Inkscape::NodePath::NODE_CUSP;
4469 } else {
4470 n->type = Inkscape::NodePath::NODE_SMOOTH;
4471 }
4472 } else {
4473 n->type = Inkscape::NodePath::NODE_CUSP;
4474 }
4475 }
4477 n->code = code;
4478 n->selected = FALSE;
4479 n->pos = *pos;
4480 n->p.pos = *ppos;
4481 n->n.pos = *npos;
4483 n->dragging_out = NULL;
4485 Inkscape::NodePath::Node *prev;
4486 if (next) {
4487 //g_assert(g_list_find(sp->nodes, next));
4488 prev = next->p.other;
4489 } else {
4490 prev = sp->last;
4491 }
4493 if (prev)
4494 prev->n.other = n;
4495 else
4496 sp->first = n;
4498 if (next)
4499 next->p.other = n;
4500 else
4501 sp->last = n;
4503 n->p.other = prev;
4504 n->n.other = next;
4506 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"));
4507 sp_knot_set_position(n->knot, pos, 0);
4509 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4510 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4511 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4512 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4513 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4514 sp_knot_update_ctrl(n->knot);
4516 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4517 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4518 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4519 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4520 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4521 sp_knot_show(n->knot);
4523 // We only create handle knots and lines on demand
4524 n->p.knot = NULL;
4525 n->p.line = NULL;
4526 n->n.knot = NULL;
4527 n->n.line = NULL;
4529 sp->nodes = g_list_prepend(sp->nodes, n);
4531 return n;
4532 }
4534 /**
4535 * Destroy node and its knots, link neighbors in subpath.
4536 */
4537 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4538 {
4539 g_assert(node);
4540 g_assert(node->subpath);
4541 g_assert(SP_IS_KNOT(node->knot));
4543 Inkscape::NodePath::SubPath *sp = node->subpath;
4545 if (node->selected) { // first, deselect
4546 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4547 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4548 }
4550 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4552 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4553 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4554 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4555 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4556 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4557 g_object_unref(G_OBJECT(node->knot));
4559 if (node->p.knot) {
4560 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4561 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4562 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4563 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4564 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4565 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4566 g_object_unref(G_OBJECT(node->p.knot));
4567 node->p.knot = NULL;
4568 }
4570 if (node->n.knot) {
4571 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4572 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4573 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4574 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4575 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4576 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4577 g_object_unref(G_OBJECT(node->n.knot));
4578 node->n.knot = NULL;
4579 }
4581 if (node->p.line)
4582 gtk_object_destroy(GTK_OBJECT(node->p.line));
4583 if (node->n.line)
4584 gtk_object_destroy(GTK_OBJECT(node->n.line));
4586 if (sp->nodes) { // there are others nodes on the subpath
4587 if (sp->closed) {
4588 if (sp->first == node) {
4589 g_assert(sp->last == node);
4590 sp->first = node->n.other;
4591 sp->last = sp->first;
4592 }
4593 node->p.other->n.other = node->n.other;
4594 node->n.other->p.other = node->p.other;
4595 } else {
4596 if (sp->first == node) {
4597 sp->first = node->n.other;
4598 sp->first->code = NR_MOVETO;
4599 }
4600 if (sp->last == node) sp->last = node->p.other;
4601 if (node->p.other) node->p.other->n.other = node->n.other;
4602 if (node->n.other) node->n.other->p.other = node->p.other;
4603 }
4604 } else { // this was the last node on subpath
4605 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4606 }
4608 g_mem_chunk_free(nodechunk, node);
4609 }
4611 /**
4612 * Returns one of the node's two sides.
4613 * \param which Indicates which side.
4614 * \return Pointer to previous node side if which==-1, next if which==1.
4615 */
4616 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4617 {
4618 g_assert(node);
4620 switch (which) {
4621 case -1:
4622 return &node->p;
4623 case 1:
4624 return &node->n;
4625 default:
4626 break;
4627 }
4629 g_assert_not_reached();
4631 return NULL;
4632 }
4634 /**
4635 * Return the other side of the node, given one of its sides.
4636 */
4637 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4638 {
4639 g_assert(node);
4641 if (me == &node->p) return &node->n;
4642 if (me == &node->n) return &node->p;
4644 g_assert_not_reached();
4646 return NULL;
4647 }
4649 /**
4650 * Return NRPathcode on the given side of the node.
4651 */
4652 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4653 {
4654 g_assert(node);
4656 if (me == &node->p) {
4657 if (node->p.other) return (NRPathcode)node->code;
4658 return NR_MOVETO;
4659 }
4661 if (me == &node->n) {
4662 if (node->n.other) return (NRPathcode)node->n.other->code;
4663 return NR_MOVETO;
4664 }
4666 g_assert_not_reached();
4668 return NR_END;
4669 }
4671 /**
4672 * Return node with the given index
4673 */
4674 Inkscape::NodePath::Node *
4675 sp_nodepath_get_node_by_index(int index)
4676 {
4677 Inkscape::NodePath::Node *e = NULL;
4679 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4680 if (!nodepath) {
4681 return e;
4682 }
4684 //find segment
4685 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4687 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4688 int n = g_list_length(sp->nodes);
4689 if (sp->closed) {
4690 n++;
4691 }
4693 //if the piece belongs to this subpath grab it
4694 //otherwise move onto the next subpath
4695 if (index < n) {
4696 e = sp->first;
4697 for (int i = 0; i < index; ++i) {
4698 e = e->n.other;
4699 }
4700 break;
4701 } else {
4702 if (sp->closed) {
4703 index -= (n+1);
4704 } else {
4705 index -= n;
4706 }
4707 }
4708 }
4710 return e;
4711 }
4713 /**
4714 * Returns plain text meaning of node type.
4715 */
4716 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4717 {
4718 unsigned retracted = 0;
4719 bool endnode = false;
4721 for (int which = -1; which <= 1; which += 2) {
4722 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4723 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4724 retracted ++;
4725 if (!side->other)
4726 endnode = true;
4727 }
4729 if (retracted == 0) {
4730 if (endnode) {
4731 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4732 return _("end node");
4733 } else {
4734 switch (node->type) {
4735 case Inkscape::NodePath::NODE_CUSP:
4736 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4737 return _("cusp");
4738 case Inkscape::NodePath::NODE_SMOOTH:
4739 // TRANSLATORS: "smooth" is an adjective here
4740 return _("smooth");
4741 case Inkscape::NodePath::NODE_SYMM:
4742 return _("symmetric");
4743 }
4744 }
4745 } else if (retracted == 1) {
4746 if (endnode) {
4747 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4748 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4749 } else {
4750 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4751 }
4752 } else {
4753 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4754 }
4756 return NULL;
4757 }
4759 /**
4760 * Handles content of statusbar as long as node tool is active.
4761 */
4762 void
4763 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4764 {
4765 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");
4766 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4768 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4769 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4770 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4771 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4773 SPDesktop *desktop = NULL;
4774 if (nodepath) {
4775 desktop = nodepath->desktop;
4776 } else {
4777 desktop = SP_ACTIVE_DESKTOP;
4778 }
4780 SPEventContext *ec = desktop->event_context;
4781 if (!ec) return;
4782 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4783 if (!mc) return;
4785 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4787 if (selected_nodes == 0) {
4788 Inkscape::Selection *sel = desktop->selection;
4789 if (!sel || sel->isEmpty()) {
4790 mc->setF(Inkscape::NORMAL_MESSAGE,
4791 _("Select a single object to edit its nodes or handles."));
4792 } else {
4793 if (nodepath) {
4794 mc->setF(Inkscape::NORMAL_MESSAGE,
4795 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.",
4796 "<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.",
4797 total_nodes),
4798 total_nodes);
4799 } else {
4800 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4801 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4802 } else {
4803 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4804 }
4805 }
4806 }
4807 } else if (nodepath && selected_nodes == 1) {
4808 mc->setF(Inkscape::NORMAL_MESSAGE,
4809 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4810 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4811 total_nodes),
4812 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4813 } else {
4814 if (selected_subpaths > 1) {
4815 mc->setF(Inkscape::NORMAL_MESSAGE,
4816 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4817 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4818 total_nodes),
4819 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4820 } else {
4821 mc->setF(Inkscape::NORMAL_MESSAGE,
4822 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4823 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4824 total_nodes),
4825 selected_nodes, total_nodes, when_selected);
4826 }
4827 }
4828 }
4830 /*
4831 * returns a *copy* of the curve of that object.
4832 */
4833 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4834 if (!object)
4835 return NULL;
4837 SPCurve *curve = NULL;
4838 if (SP_IS_PATH(object)) {
4839 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4840 curve = curve_new->copy();
4841 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4842 const gchar *svgd = object->repr->attribute(key);
4843 if (svgd) {
4844 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4845 SPCurve *curve_new = new SPCurve(pv);
4846 if (curve_new) {
4847 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4848 }
4849 }
4850 }
4852 return curve;
4853 }
4855 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4856 if (!np || !np->object || !curve)
4857 return;
4859 if (SP_IS_PATH(np->object)) {
4860 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4861 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4862 } else {
4863 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4864 }
4865 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4866 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( LIVEPATHEFFECT(np->object)->lpe->getParameter(np->repr_key) );
4867 if (pathparam) {
4868 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
4869 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4870 }
4871 }
4872 }
4874 /**
4875 SPCanvasItem *
4876 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
4877 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
4878 }
4879 **/
4881 /**
4882 SPCanvasItem *
4883 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4884 SPCurve *flash_curve = curve->copy();
4885 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4886 flash_curve->transform(i2d);
4887 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4888 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4889 // unless we also flash the nodes...
4890 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4891 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4892 sp_canvas_item_show(canvasitem);
4893 flash_curve->unref();
4894 return canvasitem;
4895 }
4897 SPCanvasItem *
4898 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4899 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4900 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4901 }
4902 **/
4904 SPCanvasItem *
4905 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
4906 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
4907 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
4908 flash_curve->transform(i2d);
4909 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4910 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4911 // unless we also flash the nodes...
4912 guint32 color = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
4913 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4914 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4915 sp_canvas_item_show(canvasitem);
4916 flash_curve->unref();
4917 return canvasitem;
4918 }
4920 // TODO: Merge this with sp_nodepath_make_helper_item()!
4921 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4922 np->show_helperpath = show;
4924 if (show) {
4925 SPCurve *helper_curve = np->curve->copy();
4926 helper_curve->transform(to_2geom(np->i2d));
4927 if (!np->helper_path) {
4928 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
4930 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4931 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4932 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4933 sp_canvas_item_move_to_z(np->helper_path, 0);
4934 sp_canvas_item_show(np->helper_path);
4935 } else {
4936 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4937 }
4938 helper_curve->unref();
4939 } else {
4940 if (np->helper_path) {
4941 GtkObject *temp = np->helper_path;
4942 np->helper_path = NULL;
4943 gtk_object_destroy(temp);
4944 }
4945 }
4946 }
4948 /* sp_nodepath_make_straight_path:
4949 * Prevents user from curving the path by dragging a segment or activating handles etc.
4950 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4951 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4952 */
4953 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4954 np->straight_path = true;
4955 np->show_handles = false;
4956 g_message("add code to make the path straight.");
4957 // do sp_nodepath_convert_node_type on all nodes?
4958 // coding tip: search for this text : "Make selected segments lines"
4959 }
4962 /*
4963 Local Variables:
4964 mode:c++
4965 c-file-style:"stroustrup"
4966 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4967 indent-tabs-mode:nil
4968 fill-column:99
4969 End:
4970 */
4971 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :