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