017544901a7717f6e51cc4d213aaa352afb0a28a
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(to_2geom(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(to_2geom(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 = from_2geom(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 = from_2geom(pit->initialPoint()) * 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 = from_2geom(cit->initialPoint()) * np->i2d;
536 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
538 ppos = from_2geom(cit->finalPoint()) * 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 = from_2geom(points[0]) * np->i2d;
544 NR::Point npos = from_2geom(points[1]) * np->i2d;
545 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
547 ppos = from_2geom(points[2]) * 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 = from_2geom(closing_seg.finalPoint()) * 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(to_2geom(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(to_2geom(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 * Returns current path in context. // later eliminate this function at all!
841 */
842 static Inkscape::NodePath::Path *sp_nodepath_current()
843 {
844 if (!SP_ACTIVE_DESKTOP) {
845 return NULL;
846 }
848 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
850 if (!SP_IS_NODE_CONTEXT(event_context)) {
851 return NULL;
852 }
854 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
855 }
859 /**
860 \brief Fills node and handle positions for three nodes, splitting line
861 marked by end at distance t.
862 */
863 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
864 {
865 g_assert(new_path != NULL);
866 g_assert(end != NULL);
868 g_assert(end->p.other == new_path);
869 Inkscape::NodePath::Node *start = new_path->p.other;
870 g_assert(start);
872 if (end->code == NR_LINETO) {
873 new_path->type =Inkscape::NodePath::NODE_CUSP;
874 new_path->code = NR_LINETO;
875 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
876 } else {
877 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
878 new_path->code = NR_CURVETO;
879 gdouble s = 1 - t;
880 for (int dim = 0; dim < 2; dim++) {
881 NR::Coord const f000 = start->pos[dim];
882 NR::Coord const f001 = start->n.pos[dim];
883 NR::Coord const f011 = end->p.pos[dim];
884 NR::Coord const f111 = end->pos[dim];
885 NR::Coord const f00t = s * f000 + t * f001;
886 NR::Coord const f01t = s * f001 + t * f011;
887 NR::Coord const f11t = s * f011 + t * f111;
888 NR::Coord const f0tt = s * f00t + t * f01t;
889 NR::Coord const f1tt = s * f01t + t * f11t;
890 NR::Coord const fttt = s * f0tt + t * f1tt;
891 start->n.pos[dim] = f00t;
892 new_path->p.pos[dim] = f0tt;
893 new_path->pos[dim] = fttt;
894 new_path->n.pos[dim] = f1tt;
895 end->p.pos[dim] = f11t;
896 }
897 }
898 }
900 /**
901 * Adds new node on direct line between two nodes, activates handles of all
902 * three nodes.
903 */
904 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
905 {
906 g_assert(end);
907 g_assert(end->subpath);
908 g_assert(g_list_find(end->subpath->nodes, end));
910 Inkscape::NodePath::Node *start = end->p.other;
911 g_assert( start->n.other == end );
912 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
913 end,
914 (NRPathcode)end->code == NR_LINETO?
915 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
916 (NRPathcode)end->code,
917 &start->pos, &start->pos, &start->n.pos);
918 sp_nodepath_line_midpoint(newnode, end, t);
920 sp_node_adjust_handles(start);
921 sp_node_update_handles(start);
922 sp_node_update_handles(newnode);
923 sp_node_adjust_handles(end);
924 sp_node_update_handles(end);
926 return newnode;
927 }
929 /**
930 \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
931 */
932 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
933 {
934 g_assert(node);
935 g_assert(node->subpath);
936 g_assert(g_list_find(node->subpath->nodes, node));
938 Inkscape::NodePath::SubPath *sp = node->subpath;
939 Inkscape::NodePath::Path *np = sp->nodepath;
941 if (sp->closed) {
942 sp_nodepath_subpath_open(sp, node);
943 return sp->first;
944 } else {
945 // no break for end nodes
946 if (node == sp->first) return NULL;
947 if (node == sp->last ) return NULL;
949 // create a new subpath
950 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
952 // duplicate the break node as start of the new subpath
953 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
955 // attach rest of curve to new node
956 g_assert(node->n.other);
957 newnode->n.other = node->n.other; node->n.other = NULL;
958 newnode->n.other->p.other = newnode;
959 newsubpath->last = sp->last;
960 sp->last = node;
961 node = newnode;
962 while (node->n.other) {
963 node = node->n.other;
964 node->subpath = newsubpath;
965 sp->nodes = g_list_remove(sp->nodes, node);
966 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
967 }
970 return newnode;
971 }
972 }
974 /**
975 * Duplicate node and connect to neighbours.
976 */
977 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
978 {
979 g_assert(node);
980 g_assert(node->subpath);
981 g_assert(g_list_find(node->subpath->nodes, node));
983 Inkscape::NodePath::SubPath *sp = node->subpath;
985 NRPathcode code = (NRPathcode) node->code;
986 if (code == NR_MOVETO) { // if node is the endnode,
987 node->code = NR_LINETO; // new one is inserted before it, so change that to line
988 }
990 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
992 if (!node->n.other || !node->p.other) // if node is an endnode, select it
993 return node;
994 else
995 return newnode; // otherwise select the newly created node
996 }
998 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
999 {
1000 node->p.pos = (node->pos + (node->pos - node->n.pos));
1001 }
1003 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1004 {
1005 node->n.pos = (node->pos + (node->pos - node->p.pos));
1006 }
1008 /**
1009 * Change line type at node, with side effects on neighbours.
1010 */
1011 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1012 {
1013 g_assert(end);
1014 g_assert(end->subpath);
1015 g_assert(end->p.other);
1017 if (end->code == static_cast< guint > ( code ) )
1018 return;
1020 Inkscape::NodePath::Node *start = end->p.other;
1022 end->code = code;
1024 if (code == NR_LINETO) {
1025 if (start->code == NR_LINETO) {
1026 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
1027 }
1028 if (end->n.other) {
1029 if (end->n.other->code == NR_LINETO) {
1030 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
1031 }
1032 }
1033 } else {
1034 NR::Point delta = end->pos - start->pos;
1035 start->n.pos = start->pos + delta / 3;
1036 end->p.pos = end->pos - delta / 3;
1037 sp_node_adjust_handle(start, 1);
1038 sp_node_adjust_handle(end, -1);
1039 }
1041 sp_node_update_handles(start);
1042 sp_node_update_handles(end);
1043 }
1045 /**
1046 * Change node type, and its handles accordingly.
1047 */
1048 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1049 {
1050 g_assert(node);
1051 g_assert(node->subpath);
1053 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1054 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1055 type =Inkscape::NodePath::NODE_CUSP;
1056 }
1057 }
1059 node->type = type;
1061 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1062 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1063 node->knot->setSize (node->selected? 11 : 9);
1064 sp_knot_update_ctrl(node->knot);
1065 } else {
1066 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1067 node->knot->setSize (node->selected? 9 : 7);
1068 sp_knot_update_ctrl(node->knot);
1069 }
1071 // if one of handles is mouseovered, preserve its position
1072 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1073 sp_node_adjust_handle(node, 1);
1074 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1075 sp_node_adjust_handle(node, -1);
1076 } else {
1077 sp_node_adjust_handles(node);
1078 }
1080 sp_node_update_handles(node);
1082 sp_nodepath_update_statusbar(node->subpath->nodepath);
1084 return node;
1085 }
1087 bool
1088 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1089 {
1090 Inkscape::NodePath::Node *othernode = side->other;
1091 if (!othernode)
1092 return false;
1093 NRPathcode const code = sp_node_path_code_from_side(node, side);
1094 if (code == NR_LINETO)
1095 return true;
1096 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1097 if (&node->p == side) {
1098 other_to_me = &othernode->n;
1099 } else if (&node->n == side) {
1100 other_to_me = &othernode->p;
1101 }
1102 if (!other_to_me)
1103 return false;
1104 bool is_line =
1105 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1106 NR::L2(node->pos - side->pos) < 1e-6);
1107 return is_line;
1108 }
1110 /**
1111 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1112 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1113 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1114 * If already cusp and set to cusp, retracts handles.
1115 */
1116 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1117 {
1118 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1120 /*
1121 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1123 if (two_handles) {
1124 // do nothing, adjust_handles called via set_node_type will line them up
1125 } else if (one_handle) {
1126 if (opposite_to_handle_is_line) {
1127 if (lined_up) {
1128 // already half-smooth; pull opposite handle too making it fully smooth
1129 } else {
1130 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1131 }
1132 } else {
1133 // pull opposite handle in line with the existing one
1134 }
1135 } else if (no_handles) {
1136 if (both_segments_are_lines OR both_segments_are_curves) {
1137 //pull both handles
1138 } else {
1139 // pull the handle opposite to line segment, making node half-smooth
1140 }
1141 }
1142 */
1143 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1144 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1145 bool p_is_line = sp_node_side_is_line(node, &node->p);
1146 bool n_is_line = sp_node_side_is_line(node, &node->n);
1148 if (p_has_handle && n_has_handle) {
1149 // do nothing, adjust_handles will line them up
1150 } else if (p_has_handle || n_has_handle) {
1151 if (p_has_handle && n_is_line) {
1152 Radial line (node->n.other->pos - node->pos);
1153 Radial handle (node->pos - node->p.pos);
1154 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1155 // already half-smooth; pull opposite handle too making it fully smooth
1156 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1157 } else {
1158 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1159 }
1160 } else if (n_has_handle && p_is_line) {
1161 Radial line (node->p.other->pos - node->pos);
1162 Radial handle (node->pos - node->n.pos);
1163 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1164 // already half-smooth; pull opposite handle too making it fully smooth
1165 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1166 } else {
1167 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1168 }
1169 } else if (p_has_handle && node->n.other) {
1170 // pull n handle
1171 node->n.other->code = NR_CURVETO;
1172 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1173 NR::L2(node->p.pos - node->pos) :
1174 NR::L2(node->n.other->pos - node->pos) / 3;
1175 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1176 } else if (n_has_handle && node->p.other) {
1177 // pull p handle
1178 node->code = NR_CURVETO;
1179 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1180 NR::L2(node->n.pos - node->pos) :
1181 NR::L2(node->p.other->pos - node->pos) / 3;
1182 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1183 }
1184 } else if (!p_has_handle && !n_has_handle) {
1185 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1186 // no handles, but both segments are either lnes or curves:
1187 //pull both handles
1189 // convert both to curves:
1190 node->code = NR_CURVETO;
1191 node->n.other->code = NR_CURVETO;
1193 NR::Point leg_prev = node->pos - node->p.other->pos;
1194 NR::Point leg_next = node->pos - node->n.other->pos;
1196 double norm_leg_prev = L2(leg_prev);
1197 double norm_leg_next = L2(leg_next);
1199 NR::Point delta;
1200 if (norm_leg_next > 0.0) {
1201 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1202 (&delta)->normalize();
1203 }
1205 if (type == Inkscape::NodePath::NODE_SYMM) {
1206 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1207 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1208 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1209 } else {
1210 // length of handle is proportional to distance to adjacent node
1211 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1212 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1213 }
1215 } else {
1216 // pull the handle opposite to line segment, making it half-smooth
1217 if (p_is_line && node->n.other) {
1218 if (type != Inkscape::NodePath::NODE_SYMM) {
1219 // pull n handle
1220 node->n.other->code = NR_CURVETO;
1221 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1222 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1223 }
1224 } else if (n_is_line && node->p.other) {
1225 if (type != Inkscape::NodePath::NODE_SYMM) {
1226 // pull p handle
1227 node->code = NR_CURVETO;
1228 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1229 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1230 }
1231 }
1232 }
1233 }
1234 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1235 // cusping a cusp: retract nodes
1236 node->p.pos = node->pos;
1237 node->n.pos = node->pos;
1238 }
1240 sp_nodepath_set_node_type (node, type);
1241 }
1243 /**
1244 * Move node to point, and adjust its and neighbouring handles.
1245 */
1246 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1247 {
1248 NR::Point delta = p - node->pos;
1249 node->pos = p;
1251 node->p.pos += delta;
1252 node->n.pos += delta;
1254 Inkscape::NodePath::Node *node_p = NULL;
1255 Inkscape::NodePath::Node *node_n = NULL;
1257 if (node->p.other) {
1258 if (node->code == NR_LINETO) {
1259 sp_node_adjust_handle(node, 1);
1260 sp_node_adjust_handle(node->p.other, -1);
1261 node_p = node->p.other;
1262 }
1263 }
1264 if (node->n.other) {
1265 if (node->n.other->code == NR_LINETO) {
1266 sp_node_adjust_handle(node, -1);
1267 sp_node_adjust_handle(node->n.other, 1);
1268 node_n = node->n.other;
1269 }
1270 }
1272 // this function is only called from batch movers that will update display at the end
1273 // themselves, so here we just move all the knots without emitting move signals, for speed
1274 sp_node_update_handles(node, false);
1275 if (node_n) {
1276 sp_node_update_handles(node_n, false);
1277 }
1278 if (node_p) {
1279 sp_node_update_handles(node_p, false);
1280 }
1281 }
1283 /**
1284 * Call sp_node_moveto() for node selection and handle possible snapping.
1285 */
1286 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1287 bool const snap, bool constrained = false,
1288 Inkscape::Snapper::ConstraintLine const &constraint = NR::Point())
1289 {
1290 NR::Coord best = NR_HUGE;
1291 NR::Point delta(dx, dy);
1292 NR::Point best_pt = delta;
1293 Inkscape::SnappedPoint best_abs;
1295 if (snap) {
1296 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1297 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1298 * must provide that information. */
1300 // Build a list of the unselected nodes to which the snapper should snap
1301 std::vector<NR::Point> unselected_nodes;
1302 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1303 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1304 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1305 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1306 if (!node->selected) {
1307 unselected_nodes.push_back(node->pos);
1308 }
1309 }
1310 }
1312 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1314 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1315 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1316 m.setup(NULL, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1317 Inkscape::SnappedPoint s;
1318 if (constrained) {
1319 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1320 dedicated_constraint.setPoint(n->pos);
1321 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, dedicated_constraint);
1322 } else {
1323 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1324 }
1325 if (s.getSnapped() && (s.getDistance() < best)) {
1326 best = s.getDistance();
1327 best_abs = s;
1328 best_pt = s.getPoint() - n->pos;
1329 }
1330 }
1332 if (best_abs.getSnapped()) {
1333 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1334 } else {
1335 nodepath->desktop->snapindicator->remove_snappoint();
1336 }
1337 }
1339 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1340 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1341 sp_node_moveto(n, n->pos + best_pt);
1342 }
1344 // do not update repr here so that node dragging is acceptably fast
1345 update_object(nodepath);
1346 }
1348 /**
1349 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1350 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1351 near x = 0.
1352 */
1353 double
1354 sculpt_profile (double x, double alpha, guint profile)
1355 {
1356 if (x >= 1)
1357 return 0;
1358 if (x <= 0)
1359 return 1;
1361 switch (profile) {
1362 case SCULPT_PROFILE_LINEAR:
1363 return 1 - x;
1364 case SCULPT_PROFILE_BELL:
1365 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1366 case SCULPT_PROFILE_ELLIPTIC:
1367 return sqrt(1 - x*x);
1368 }
1370 return 1;
1371 }
1373 double
1374 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1375 {
1376 // extremely primitive for now, don't have time to look for the real one
1377 double lower = NR::L2(b - a);
1378 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1379 return (lower + upper)/2;
1380 }
1382 void
1383 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1384 {
1385 n->pos = n->origin + delta;
1386 n->n.pos = n->n.origin + delta_n;
1387 n->p.pos = n->p.origin + delta_p;
1388 sp_node_adjust_handles(n);
1389 sp_node_update_handles(n, false);
1390 }
1392 /**
1393 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1394 * on how far they are from the dragged node n.
1395 */
1396 static void
1397 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1398 {
1399 g_assert (n);
1400 g_assert (nodepath);
1401 g_assert (n->subpath->nodepath == nodepath);
1403 double pressure = n->knot->pressure;
1404 if (pressure == 0)
1405 pressure = 0.5; // default
1406 pressure = CLAMP (pressure, 0.2, 0.8);
1408 // map pressure to alpha = 1/5 ... 5
1409 double alpha = 1 - 2 * fabs(pressure - 0.5);
1410 if (pressure > 0.5)
1411 alpha = 1/alpha;
1413 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1415 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1416 // Only one subpath has selected nodes:
1417 // use linear mode, where the distance from n to node being dragged is calculated along the path
1419 double n_sel_range = 0, p_sel_range = 0;
1420 guint n_nodes = 0, p_nodes = 0;
1421 guint n_sel_nodes = 0, p_sel_nodes = 0;
1423 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1424 {
1425 double n_range = 0, p_range = 0;
1426 bool n_going = true, p_going = true;
1427 Inkscape::NodePath::Node *n_node = n;
1428 Inkscape::NodePath::Node *p_node = n;
1429 do {
1430 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1431 if (n_node && n_going)
1432 n_node = n_node->n.other;
1433 if (n_node == NULL) {
1434 n_going = false;
1435 } else {
1436 n_nodes ++;
1437 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1438 if (n_node->selected) {
1439 n_sel_nodes ++;
1440 n_sel_range = n_range;
1441 }
1442 if (n_node == p_node) {
1443 n_going = false;
1444 p_going = false;
1445 }
1446 }
1447 if (p_node && p_going)
1448 p_node = p_node->p.other;
1449 if (p_node == NULL) {
1450 p_going = false;
1451 } else {
1452 p_nodes ++;
1453 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1454 if (p_node->selected) {
1455 p_sel_nodes ++;
1456 p_sel_range = p_range;
1457 }
1458 if (p_node == n_node) {
1459 n_going = false;
1460 p_going = false;
1461 }
1462 }
1463 } while (n_going || p_going);
1464 }
1466 // Second pass: actually move nodes in this subpath
1467 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1468 {
1469 double n_range = 0, p_range = 0;
1470 bool n_going = true, p_going = true;
1471 Inkscape::NodePath::Node *n_node = n;
1472 Inkscape::NodePath::Node *p_node = n;
1473 do {
1474 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1475 if (n_node && n_going)
1476 n_node = n_node->n.other;
1477 if (n_node == NULL) {
1478 n_going = false;
1479 } else {
1480 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1481 if (n_node->selected) {
1482 sp_nodepath_move_node_and_handles (n_node,
1483 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1484 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1485 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1486 }
1487 if (n_node == p_node) {
1488 n_going = false;
1489 p_going = false;
1490 }
1491 }
1492 if (p_node && p_going)
1493 p_node = p_node->p.other;
1494 if (p_node == NULL) {
1495 p_going = false;
1496 } else {
1497 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1498 if (p_node->selected) {
1499 sp_nodepath_move_node_and_handles (p_node,
1500 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1501 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1502 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1503 }
1504 if (p_node == n_node) {
1505 n_going = false;
1506 p_going = false;
1507 }
1508 }
1509 } while (n_going || p_going);
1510 }
1512 } else {
1513 // Multiple subpaths have selected nodes:
1514 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1515 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1516 // fix the pear-like shape when sculpting e.g. a ring
1518 // First pass: calculate range
1519 gdouble direct_range = 0;
1520 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1521 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1522 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1523 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1524 if (node->selected) {
1525 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1526 }
1527 }
1528 }
1530 // Second pass: actually move nodes
1531 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1532 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1533 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1534 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1535 if (node->selected) {
1536 if (direct_range > 1e-6) {
1537 sp_nodepath_move_node_and_handles (node,
1538 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1539 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1540 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1541 } else {
1542 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1543 }
1545 }
1546 }
1547 }
1548 }
1550 // do not update repr here so that node dragging is acceptably fast
1551 update_object(nodepath);
1552 }
1555 /**
1556 * Move node selection to point, adjust its and neighbouring handles,
1557 * handle possible snapping, and commit the change with possible undo.
1558 */
1559 void
1560 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1561 {
1562 if (!nodepath) return;
1564 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1566 if (dx == 0) {
1567 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1568 } else if (dy == 0) {
1569 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1570 } else {
1571 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1572 }
1573 }
1575 /**
1576 * Move node selection off screen and commit the change.
1577 */
1578 void
1579 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1580 {
1581 // borrowed from sp_selection_move_screen in selection-chemistry.c
1582 // we find out the current zoom factor and divide deltas by it
1583 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1585 gdouble zoom = desktop->current_zoom();
1586 gdouble zdx = dx / zoom;
1587 gdouble zdy = dy / zoom;
1589 if (!nodepath) return;
1591 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1593 if (dx == 0) {
1594 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1595 } else if (dy == 0) {
1596 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1597 } else {
1598 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1599 }
1600 }
1602 /**
1603 * Move selected nodes to the absolute position given
1604 */
1605 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
1606 {
1607 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1608 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1609 Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
1610 sp_node_moveto(n, npos);
1611 }
1613 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1614 }
1616 /**
1617 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1618 */
1619 boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1620 {
1621 boost::optional<Geom::Coord> no_coord;
1622 g_return_val_if_fail(nodepath->selected, no_coord);
1624 // determine coordinate of first selected node
1625 GList *nsel = nodepath->selected;
1626 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1627 NR::Coord coord = n->pos[axis];
1628 bool coincide = true;
1630 // compare it to the coordinates of all the other selected nodes
1631 for (GList *l = nsel->next; l != NULL; l = l->next) {
1632 n = (Inkscape::NodePath::Node *) l->data;
1633 if (n->pos[axis] != coord) {
1634 coincide = false;
1635 }
1636 }
1637 if (coincide) {
1638 return coord;
1639 } else {
1640 Geom::Rect bbox = sp_node_selected_bbox(nodepath);
1641 // currently we return the coordinate of the bounding box midpoint because I don't know how
1642 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1643 return bbox.midpoint()[axis];
1644 }
1645 }
1647 /** If they don't yet exist, creates knot and line for the given side of the node */
1648 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1649 {
1650 if (!side->knot) {
1651 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"));
1653 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1654 side->knot->setSize (7);
1655 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1656 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1657 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1658 sp_knot_update_ctrl(side->knot);
1660 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1661 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1662 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1663 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1664 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1665 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1666 }
1668 if (!side->line) {
1669 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1670 SP_TYPE_CTRLLINE, NULL);
1671 }
1672 }
1674 /**
1675 * Ensure the given handle of the node is visible/invisible, update its screen position
1676 */
1677 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1678 {
1679 g_assert(node != NULL);
1681 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1682 NRPathcode code = sp_node_path_code_from_side(node, side);
1684 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1686 if (show_handle) {
1687 if (!side->knot) { // No handle knot at all
1688 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1689 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1690 side->knot->pos = side->pos;
1691 if (side->knot->item)
1692 SP_CTRL(side->knot->item)->moveto(side->pos);
1693 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1694 sp_knot_show(side->knot);
1695 } else {
1696 if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1697 if (fire_move_signals) {
1698 sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1699 } else {
1700 sp_knot_moveto(side->knot, side->pos);
1701 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1702 }
1703 }
1704 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1705 sp_knot_show(side->knot);
1706 }
1707 }
1708 sp_canvas_item_show(side->line);
1709 } else {
1710 if (side->knot) {
1711 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1712 sp_knot_hide(side->knot);
1713 }
1714 }
1715 if (side->line) {
1716 sp_canvas_item_hide(side->line);
1717 }
1718 }
1719 }
1721 /**
1722 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1723 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1724 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1725 * updated; otherwise, just move the knots silently (used in batch moves).
1726 */
1727 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1728 {
1729 g_assert(node != NULL);
1731 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1732 sp_knot_show(node->knot);
1733 }
1735 if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1736 if (fire_move_signals)
1737 sp_knot_set_position(node->knot, node->pos, 0);
1738 else
1739 sp_knot_moveto(node->knot, node->pos);
1740 }
1742 gboolean show_handles = node->selected;
1743 if (node->p.other != NULL) {
1744 if (node->p.other->selected) show_handles = TRUE;
1745 }
1746 if (node->n.other != NULL) {
1747 if (node->n.other->selected) show_handles = TRUE;
1748 }
1750 if (node->subpath->nodepath->show_handles == false)
1751 show_handles = FALSE;
1753 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1754 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1755 }
1757 /**
1758 * Call sp_node_update_handles() for all nodes on subpath.
1759 */
1760 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1761 {
1762 g_assert(subpath != NULL);
1764 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1765 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1766 }
1767 }
1769 /**
1770 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1771 */
1772 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1773 {
1774 g_assert(nodepath != NULL);
1776 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1777 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1778 }
1779 }
1781 void
1782 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1783 {
1784 if (nodepath == NULL) return;
1786 nodepath->show_handles = show;
1787 sp_nodepath_update_handles(nodepath);
1788 }
1790 /**
1791 * Adds all selected nodes in nodepath to list.
1792 */
1793 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1794 {
1795 StlConv<Node *>::list(l, selected);
1796 /// \todo this adds a copying, rework when the selection becomes a stl list
1797 }
1799 /**
1800 * Align selected nodes on the specified axis.
1801 */
1802 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1803 {
1804 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1805 return;
1806 }
1808 if ( !nodepath->selected->next ) { // only one node selected
1809 return;
1810 }
1811 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1812 NR::Point dest(pNode->pos);
1813 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1814 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1815 if (pNode) {
1816 dest[axis] = pNode->pos[axis];
1817 sp_node_moveto(pNode, dest);
1818 }
1819 }
1821 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1822 }
1824 /// Helper struct.
1825 struct NodeSort
1826 {
1827 Inkscape::NodePath::Node *_node;
1828 NR::Coord _coord;
1829 /// \todo use vectorof pointers instead of calling copy ctor
1830 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1831 _node(node), _coord(node->pos[axis])
1832 {}
1834 };
1836 static bool operator<(NodeSort const &a, NodeSort const &b)
1837 {
1838 return (a._coord < b._coord);
1839 }
1841 /**
1842 * Distribute selected nodes on the specified axis.
1843 */
1844 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1845 {
1846 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1847 return;
1848 }
1850 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1851 return;
1852 }
1854 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1855 std::vector<NodeSort> sorted;
1856 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1857 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1858 if (pNode) {
1859 NodeSort n(pNode, axis);
1860 sorted.push_back(n);
1861 //dest[axis] = pNode->pos[axis];
1862 //sp_node_moveto(pNode, dest);
1863 }
1864 }
1865 std::sort(sorted.begin(), sorted.end());
1866 unsigned int len = sorted.size();
1867 //overall bboxes span
1868 float dist = (sorted.back()._coord -
1869 sorted.front()._coord);
1870 //new distance between each bbox
1871 float step = (dist) / (len - 1);
1872 float pos = sorted.front()._coord;
1873 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1874 it < sorted.end();
1875 it ++ )
1876 {
1877 NR::Point dest((*it)._node->pos);
1878 dest[axis] = pos;
1879 sp_node_moveto((*it)._node, dest);
1880 pos += step;
1881 }
1883 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1884 }
1887 /**
1888 * Call sp_nodepath_line_add_node() for all selected segments.
1889 */
1890 void
1891 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1892 {
1893 if (!nodepath) {
1894 return;
1895 }
1897 GList *nl = NULL;
1899 int n_added = 0;
1901 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1902 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1903 g_assert(t->selected);
1904 if (t->p.other && t->p.other->selected) {
1905 nl = g_list_prepend(nl, t);
1906 }
1907 }
1909 while (nl) {
1910 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1911 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1912 sp_nodepath_node_select(n, TRUE, FALSE);
1913 n_added ++;
1914 nl = g_list_remove(nl, t);
1915 }
1917 /** \todo fixme: adjust ? */
1918 sp_nodepath_update_handles(nodepath);
1920 if (n_added > 1) {
1921 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1922 } else if (n_added > 0) {
1923 sp_nodepath_update_repr(nodepath, _("Add node"));
1924 }
1926 sp_nodepath_update_statusbar(nodepath);
1927 }
1929 /**
1930 * Select segment nearest to point
1931 */
1932 void
1933 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1934 {
1935 if (!nodepath) {
1936 return;
1937 }
1939 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1940 Geom::PathVector const &pathv = curve->get_pathvector();
1941 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1943 // calculate index for nodepath's representation.
1944 unsigned int segment_index = floor(pvpos.t) + 1;
1945 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1946 segment_index += pathv[i].size() + 1;
1947 if (pathv[i].closed()) {
1948 segment_index += 1;
1949 }
1950 }
1952 curve->unref();
1954 //find segment to segment
1955 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(segment_index);
1957 //fixme: this can return NULL, so check before proceeding.
1958 g_return_if_fail(e != NULL);
1960 gboolean force = FALSE;
1961 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1962 force = TRUE;
1963 }
1964 sp_nodepath_node_select(e, (gboolean) toggle, force);
1965 if (e->p.other)
1966 sp_nodepath_node_select(e->p.other, TRUE, force);
1968 sp_nodepath_update_handles(nodepath);
1970 sp_nodepath_update_statusbar(nodepath);
1971 }
1973 /**
1974 * Add a node nearest to point
1975 */
1976 void
1977 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1978 {
1979 if (!nodepath) {
1980 return;
1981 }
1983 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1984 Geom::PathVector const &pathv = curve->get_pathvector();
1985 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1987 // calculate index for nodepath's representation.
1988 double int_part;
1989 double t = std::modf(pvpos.t, &int_part);
1990 unsigned int segment_index = (unsigned int)int_part + 1;
1991 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1992 segment_index += pathv[i].size() + 1;
1993 if (pathv[i].closed()) {
1994 segment_index += 1;
1995 }
1996 }
1998 curve->unref();
2000 //find segment to split
2001 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(segment_index);
2003 //don't know why but t seems to flip for lines
2004 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2005 t = 1.0 - t;
2006 }
2008 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2009 sp_nodepath_node_select(n, FALSE, TRUE);
2011 /* fixme: adjust ? */
2012 sp_nodepath_update_handles(nodepath);
2014 sp_nodepath_update_repr(nodepath, _("Add node"));
2016 sp_nodepath_update_statusbar(nodepath);
2017 }
2019 /*
2020 * Adjusts a segment so that t moves by a certain delta for dragging
2021 * converts lines to curves
2022 *
2023 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2024 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2025 */
2026 void
2027 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
2028 {
2029 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
2031 //fixme: e and e->p can be NULL, so check for those before proceeding
2032 g_return_if_fail(e != NULL);
2033 g_return_if_fail(&e->p != NULL);
2035 /* feel good is an arbitrary parameter that distributes the delta between handles
2036 * if t of the drag point is less than 1/6 distance form the endpoint only
2037 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2038 */
2039 double feel_good;
2040 if (t <= 1.0 / 6.0)
2041 feel_good = 0;
2042 else if (t <= 0.5)
2043 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2044 else if (t <= 5.0 / 6.0)
2045 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2046 else
2047 feel_good = 1;
2049 //if we're dragging a line convert it to a curve
2050 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2051 sp_nodepath_set_line_type(e, NR_CURVETO);
2052 }
2054 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2055 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2056 e->p.other->n.pos += offsetcoord0;
2057 e->p.pos += offsetcoord1;
2059 // adjust handles of adjacent nodes where necessary
2060 sp_node_adjust_handle(e,1);
2061 sp_node_adjust_handle(e->p.other,-1);
2063 sp_nodepath_update_handles(e->subpath->nodepath);
2065 update_object(e->subpath->nodepath);
2067 sp_nodepath_update_statusbar(e->subpath->nodepath);
2068 }
2071 /**
2072 * Call sp_nodepath_break() for all selected segments.
2073 */
2074 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2075 {
2076 if (!nodepath) return;
2078 GList *tempin = g_list_copy(nodepath->selected);
2079 GList *temp = NULL;
2080 for (GList *l = tempin; l != NULL; l = l->next) {
2081 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2082 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2083 if (nn == NULL) continue; // no break, no new node
2084 temp = g_list_prepend(temp, nn);
2085 }
2086 g_list_free(tempin);
2088 if (temp) {
2089 sp_nodepath_deselect(nodepath);
2090 }
2091 for (GList *l = temp; l != NULL; l = l->next) {
2092 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2093 }
2095 sp_nodepath_update_handles(nodepath);
2097 sp_nodepath_update_repr(nodepath, _("Break path"));
2098 }
2100 /**
2101 * Duplicate the selected node(s).
2102 */
2103 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2104 {
2105 if (!nodepath) {
2106 return;
2107 }
2109 GList *temp = NULL;
2110 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2111 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2112 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2113 if (nn == NULL) continue; // could not duplicate
2114 temp = g_list_prepend(temp, nn);
2115 }
2117 if (temp) {
2118 sp_nodepath_deselect(nodepath);
2119 }
2120 for (GList *l = temp; l != NULL; l = l->next) {
2121 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2122 }
2124 sp_nodepath_update_handles(nodepath);
2126 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2127 }
2129 /**
2130 * Internal function to join two nodes by merging them into one.
2131 */
2132 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2133 {
2134 /* a and b are endpoints */
2136 // if one of the two nodes is mouseovered, fix its position
2137 NR::Point c;
2138 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2139 c = a->pos;
2140 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2141 c = b->pos;
2142 } else {
2143 // otherwise, move joined node to the midpoint
2144 c = (a->pos + b->pos) / 2;
2145 }
2147 if (a->subpath == b->subpath) {
2148 Inkscape::NodePath::SubPath *sp = a->subpath;
2149 sp_nodepath_subpath_close(sp);
2150 sp_node_moveto (sp->first, c);
2152 sp_nodepath_update_handles(sp->nodepath);
2153 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2154 return;
2155 }
2157 /* a and b are separate subpaths */
2158 Inkscape::NodePath::SubPath *sa = a->subpath;
2159 Inkscape::NodePath::SubPath *sb = b->subpath;
2160 NR::Point p;
2161 Inkscape::NodePath::Node *n;
2162 NRPathcode code;
2163 if (a == sa->first) {
2164 // we will now reverse sa, so that a is its last node, not first, and drop that node
2165 p = sa->first->n.pos;
2166 code = (NRPathcode)sa->first->n.other->code;
2167 // create new subpath
2168 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2169 // create a first moveto node on it
2170 n = sa->last;
2171 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2172 n = n->p.other;
2173 if (n == sa->first) n = NULL;
2174 while (n) {
2175 // copy the rest of the nodes from sa to t, going backwards
2176 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2177 n = n->p.other;
2178 if (n == sa->first) n = NULL;
2179 }
2180 // replace sa with t
2181 sp_nodepath_subpath_destroy(sa);
2182 sa = t;
2183 } else if (a == sa->last) {
2184 // a is already last, just drop it
2185 p = sa->last->p.pos;
2186 code = (NRPathcode)sa->last->code;
2187 sp_nodepath_node_destroy(sa->last);
2188 } else {
2189 code = NR_END;
2190 g_assert_not_reached();
2191 }
2193 if (b == sb->first) {
2194 // copy all nodes from b to a, forward
2195 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2196 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2197 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2198 }
2199 } else if (b == sb->last) {
2200 // copy all nodes from b to a, backward
2201 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2202 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2203 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2204 }
2205 } else {
2206 g_assert_not_reached();
2207 }
2208 /* and now destroy sb */
2210 sp_nodepath_subpath_destroy(sb);
2212 sp_nodepath_update_handles(sa->nodepath);
2214 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2216 sp_nodepath_update_statusbar(nodepath);
2217 }
2219 /**
2220 * Internal function to join two nodes by adding a segment between them.
2221 */
2222 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2223 {
2224 if (a->subpath == b->subpath) {
2225 Inkscape::NodePath::SubPath *sp = a->subpath;
2227 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2228 sp->closed = TRUE;
2230 sp->first->p.other = sp->last;
2231 sp->last->n.other = sp->first;
2233 sp_node_handle_mirror_p_to_n(sp->last);
2234 sp_node_handle_mirror_n_to_p(sp->first);
2236 sp->first->code = sp->last->code;
2237 sp->first = sp->last;
2239 sp_nodepath_update_handles(sp->nodepath);
2241 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2243 return;
2244 }
2246 /* a and b are separate subpaths */
2247 Inkscape::NodePath::SubPath *sa = a->subpath;
2248 Inkscape::NodePath::SubPath *sb = b->subpath;
2250 Inkscape::NodePath::Node *n;
2251 NR::Point p;
2252 NRPathcode code;
2253 if (a == sa->first) {
2254 code = (NRPathcode) sa->first->n.other->code;
2255 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2256 n = sa->last;
2257 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2258 for (n = n->p.other; n != NULL; n = n->p.other) {
2259 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2260 }
2261 sp_nodepath_subpath_destroy(sa);
2262 sa = t;
2263 } else if (a == sa->last) {
2264 code = (NRPathcode)sa->last->code;
2265 } else {
2266 code = NR_END;
2267 g_assert_not_reached();
2268 }
2270 if (b == sb->first) {
2271 n = sb->first;
2272 sp_node_handle_mirror_p_to_n(sa->last);
2273 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2274 sp_node_handle_mirror_n_to_p(sa->last);
2275 for (n = n->n.other; n != NULL; n = n->n.other) {
2276 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2277 }
2278 } else if (b == sb->last) {
2279 n = sb->last;
2280 sp_node_handle_mirror_p_to_n(sa->last);
2281 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2282 sp_node_handle_mirror_n_to_p(sa->last);
2283 for (n = n->p.other; n != NULL; n = n->p.other) {
2284 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2285 }
2286 } else {
2287 g_assert_not_reached();
2288 }
2289 /* and now destroy sb */
2291 sp_nodepath_subpath_destroy(sb);
2293 sp_nodepath_update_handles(sa->nodepath);
2295 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2296 }
2298 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2300 /**
2301 * Internal function to handle joining two nodes.
2302 */
2303 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2304 {
2305 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2307 if (g_list_length(nodepath->selected) != 2) {
2308 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2309 return;
2310 }
2312 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2313 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2315 g_assert(a != b);
2316 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2317 // someone tried to join an orphan node (i.e. a single-node subpath).
2318 // this is not worth an error message, just fail silently.
2319 return;
2320 }
2322 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2323 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2324 return;
2325 }
2327 switch(mode) {
2328 case NODE_JOIN_ENDPOINTS:
2329 do_node_selected_join(nodepath, a, b);
2330 break;
2331 case NODE_JOIN_SEGMENT:
2332 do_node_selected_join_segment(nodepath, a, b);
2333 break;
2334 }
2335 }
2337 /**
2338 * Join two nodes by merging them into one.
2339 */
2340 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2341 {
2342 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2343 }
2345 /**
2346 * Join two nodes by adding a segment between them.
2347 */
2348 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2349 {
2350 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2351 }
2353 /**
2354 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2355 */
2356 void sp_node_delete_preserve(GList *nodes_to_delete)
2357 {
2358 GSList *nodepaths = NULL;
2360 while (nodes_to_delete) {
2361 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2362 Inkscape::NodePath::SubPath *sp = node->subpath;
2363 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2364 Inkscape::NodePath::Node *sample_cursor = NULL;
2365 Inkscape::NodePath::Node *sample_end = NULL;
2366 Inkscape::NodePath::Node *delete_cursor = node;
2367 bool just_delete = false;
2369 //find the start of this contiguous selection
2370 //move left to the first node that is not selected
2371 //or the start of the non-closed path
2372 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2373 delete_cursor = curr;
2374 }
2376 //just delete at the beginning of an open path
2377 if (!delete_cursor->p.other) {
2378 sample_cursor = delete_cursor;
2379 just_delete = true;
2380 } else {
2381 sample_cursor = delete_cursor->p.other;
2382 }
2384 //calculate points for each segment
2385 int rate = 5;
2386 float period = 1.0 / rate;
2387 std::vector<NR::Point> data;
2388 if (!just_delete) {
2389 data.push_back(sample_cursor->pos);
2390 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2391 //just delete at the end of an open path
2392 if (!sp->closed && curr == sp->last) {
2393 just_delete = true;
2394 break;
2395 }
2397 //sample points on the contiguous selected segment
2398 NR::Point *bez;
2399 bez = new NR::Point [4];
2400 bez[0] = curr->pos;
2401 bez[1] = curr->n.pos;
2402 bez[2] = curr->n.other->p.pos;
2403 bez[3] = curr->n.other->pos;
2404 for (int i=1; i<rate; i++) {
2405 gdouble t = i * period;
2406 NR::Point p = bezier_pt(3, bez, t);
2407 data.push_back(p);
2408 }
2409 data.push_back(curr->n.other->pos);
2411 sample_end = curr->n.other;
2412 //break if we've come full circle or hit the end of the selection
2413 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2414 break;
2415 }
2416 }
2417 }
2419 if (!just_delete) {
2420 //calculate the best fitting single segment and adjust the endpoints
2421 NR::Point *adata;
2422 adata = new NR::Point [data.size()];
2423 copy(data.begin(), data.end(), adata);
2425 NR::Point *bez;
2426 bez = new NR::Point [4];
2427 //would decreasing error create a better fitting approximation?
2428 gdouble error = 1.0;
2429 gint ret;
2430 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2432 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2433 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2434 //the resulting nodes behave as expected.
2435 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2436 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2437 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2438 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2440 //adjust endpoints
2441 sample_cursor->n.pos = bez[1];
2442 sample_end->p.pos = bez[2];
2443 }
2445 //destroy this contiguous selection
2446 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2447 Inkscape::NodePath::Node *temp = delete_cursor;
2448 if (delete_cursor->n.other == delete_cursor) {
2449 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2450 delete_cursor = NULL;
2451 } else {
2452 delete_cursor = delete_cursor->n.other;
2453 }
2454 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2455 sp_nodepath_node_destroy(temp);
2456 }
2458 sp_nodepath_update_handles(nodepath);
2460 if (!g_slist_find(nodepaths, nodepath))
2461 nodepaths = g_slist_prepend (nodepaths, nodepath);
2462 }
2464 for (GSList *i = nodepaths; i; i = i->next) {
2465 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2466 // different nodepaths will give us one undo event per nodepath
2467 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2469 // if the entire nodepath is removed, delete the selected object.
2470 if (nodepath->subpaths == NULL ||
2471 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2472 //at least 2
2473 sp_nodepath_get_node_count(nodepath) < 2) {
2474 SPDocument *document = sp_desktop_document (nodepath->desktop);
2475 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2476 //delete this nodepath's object, not the entire selection! (though at this time, this
2477 //does not matter)
2478 sp_selection_delete();
2479 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2480 _("Delete nodes"));
2481 } else {
2482 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2483 sp_nodepath_update_statusbar(nodepath);
2484 }
2485 }
2487 g_slist_free (nodepaths);
2488 }
2490 /**
2491 * Delete one or more selected nodes.
2492 */
2493 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2494 {
2495 if (!nodepath) return;
2496 if (!nodepath->selected) return;
2498 /** \todo fixme: do it the right way */
2499 while (nodepath->selected) {
2500 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2501 sp_nodepath_node_destroy(node);
2502 }
2505 //clean up the nodepath (such as for trivial subpaths)
2506 sp_nodepath_cleanup(nodepath);
2508 sp_nodepath_update_handles(nodepath);
2510 // if the entire nodepath is removed, delete the selected object.
2511 if (nodepath->subpaths == NULL ||
2512 sp_nodepath_get_node_count(nodepath) < 2) {
2513 SPDocument *document = sp_desktop_document (nodepath->desktop);
2514 sp_selection_delete();
2515 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2516 _("Delete nodes"));
2517 return;
2518 }
2520 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2522 sp_nodepath_update_statusbar(nodepath);
2523 }
2525 /**
2526 * Delete one or more segments between two selected nodes.
2527 * This is the code for 'split'.
2528 */
2529 void
2530 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2531 {
2532 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2533 Inkscape::NodePath::Node *curr, *next; //Iterators
2535 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2537 if (g_list_length(nodepath->selected) != 2) {
2538 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2539 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2540 return;
2541 }
2543 //Selected nodes, not inclusive
2544 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2545 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2547 if ( ( a==b) || //same node
2548 (a->subpath != b->subpath ) || //not the same path
2549 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2550 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2551 {
2552 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2553 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2554 return;
2555 }
2557 //###########################################
2558 //# BEGIN EDITS
2559 //###########################################
2560 //##################################
2561 //# CLOSED PATH
2562 //##################################
2563 if (a->subpath->closed) {
2566 gboolean reversed = FALSE;
2568 //Since we can go in a circle, we need to find the shorter distance.
2569 // a->b or b->a
2570 start = end = NULL;
2571 int distance = 0;
2572 int minDistance = 0;
2573 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2574 if (curr==b) {
2575 //printf("a to b:%d\n", distance);
2576 start = a;//go from a to b
2577 end = b;
2578 minDistance = distance;
2579 //printf("A to B :\n");
2580 break;
2581 }
2582 distance++;
2583 }
2585 //try again, the other direction
2586 distance = 0;
2587 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2588 if (curr==a) {
2589 //printf("b to a:%d\n", distance);
2590 if (distance < minDistance) {
2591 start = b; //we go from b to a
2592 end = a;
2593 reversed = TRUE;
2594 //printf("B to A\n");
2595 }
2596 break;
2597 }
2598 distance++;
2599 }
2602 //Copy everything from 'end' to 'start' to a new subpath
2603 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2604 for (curr=end ; curr ; curr=curr->n.other) {
2605 NRPathcode code = (NRPathcode) curr->code;
2606 if (curr == end)
2607 code = NR_MOVETO;
2608 sp_nodepath_node_new(t, NULL,
2609 (Inkscape::NodePath::NodeType)curr->type, code,
2610 &curr->p.pos, &curr->pos, &curr->n.pos);
2611 if (curr == start)
2612 break;
2613 }
2614 sp_nodepath_subpath_destroy(a->subpath);
2617 }
2621 //##################################
2622 //# OPEN PATH
2623 //##################################
2624 else {
2626 //We need to get the direction of the list between A and B
2627 //Can we walk from a to b?
2628 start = end = NULL;
2629 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2630 if (curr==b) {
2631 start = a; //did it! we go from a to b
2632 end = b;
2633 //printf("A to B\n");
2634 break;
2635 }
2636 }
2637 if (!start) {//didn't work? let's try the other direction
2638 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2639 if (curr==a) {
2640 start = b; //did it! we go from b to a
2641 end = a;
2642 //printf("B to A\n");
2643 break;
2644 }
2645 }
2646 }
2647 if (!start) {
2648 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2649 _("Cannot find path between nodes."));
2650 return;
2651 }
2655 //Copy everything after 'end' to a new subpath
2656 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2657 for (curr=end ; curr ; curr=curr->n.other) {
2658 NRPathcode code = (NRPathcode) curr->code;
2659 if (curr == end)
2660 code = NR_MOVETO;
2661 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2662 &curr->p.pos, &curr->pos, &curr->n.pos);
2663 }
2665 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2666 for (curr = start->n.other ; curr ; curr=next) {
2667 next = curr->n.other;
2668 sp_nodepath_node_destroy(curr);
2669 }
2671 }
2672 //###########################################
2673 //# END EDITS
2674 //###########################################
2676 //clean up the nodepath (such as for trivial subpaths)
2677 sp_nodepath_cleanup(nodepath);
2679 sp_nodepath_update_handles(nodepath);
2681 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2683 sp_nodepath_update_statusbar(nodepath);
2684 }
2686 /**
2687 * Call sp_nodepath_set_line() for all selected segments.
2688 */
2689 void
2690 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2691 {
2692 if (nodepath == NULL) return;
2694 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2695 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2696 g_assert(n->selected);
2697 if (n->p.other && n->p.other->selected) {
2698 sp_nodepath_set_line_type(n, code);
2699 }
2700 }
2702 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2703 }
2705 /**
2706 * Call sp_nodepath_convert_node_type() for all selected nodes.
2707 */
2708 void
2709 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2710 {
2711 if (nodepath == NULL) return;
2713 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2715 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2716 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2717 }
2719 sp_nodepath_update_repr(nodepath, _("Change node type"));
2720 }
2722 /**
2723 * Change select status of node, update its own and neighbour handles.
2724 */
2725 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2726 {
2727 node->selected = selected;
2729 if (selected) {
2730 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2731 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2732 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2733 sp_knot_update_ctrl(node->knot);
2734 } else {
2735 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2736 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2737 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2738 sp_knot_update_ctrl(node->knot);
2739 }
2741 sp_node_update_handles(node);
2742 if (node->n.other) sp_node_update_handles(node->n.other);
2743 if (node->p.other) sp_node_update_handles(node->p.other);
2744 }
2746 /**
2747 \brief Select a node
2748 \param node The node to select
2749 \param incremental If true, add to selection, otherwise deselect others
2750 \param override If true, always select this node, otherwise toggle selected status
2751 */
2752 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2753 {
2754 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2756 if (incremental) {
2757 if (override) {
2758 if (!g_list_find(nodepath->selected, node)) {
2759 nodepath->selected = g_list_prepend(nodepath->selected, node);
2760 }
2761 sp_node_set_selected(node, TRUE);
2762 } else { // toggle
2763 if (node->selected) {
2764 g_assert(g_list_find(nodepath->selected, node));
2765 nodepath->selected = g_list_remove(nodepath->selected, node);
2766 } else {
2767 g_assert(!g_list_find(nodepath->selected, node));
2768 nodepath->selected = g_list_prepend(nodepath->selected, node);
2769 }
2770 sp_node_set_selected(node, !node->selected);
2771 }
2772 } else {
2773 sp_nodepath_deselect(nodepath);
2774 nodepath->selected = g_list_prepend(nodepath->selected, node);
2775 sp_node_set_selected(node, TRUE);
2776 }
2778 sp_nodepath_update_statusbar(nodepath);
2779 }
2782 /**
2783 \brief Deselect all nodes in the nodepath
2784 */
2785 void
2786 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2787 {
2788 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2790 while (nodepath->selected) {
2791 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2792 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2793 }
2794 sp_nodepath_update_statusbar(nodepath);
2795 }
2797 /**
2798 \brief Select or invert selection of all nodes in the nodepath
2799 */
2800 void
2801 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2802 {
2803 if (!nodepath) return;
2805 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2806 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2807 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2808 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2809 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2810 }
2811 }
2812 }
2814 /**
2815 * If nothing selected, does the same as sp_nodepath_select_all();
2816 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2817 * (i.e., similar to "select all in layer", with the "selected" subpaths
2818 * being treated as "layers" in the path).
2819 */
2820 void
2821 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2822 {
2823 if (!nodepath) return;
2825 if (g_list_length (nodepath->selected) == 0) {
2826 sp_nodepath_select_all (nodepath, invert);
2827 return;
2828 }
2830 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2831 GSList *subpaths = NULL;
2833 for (GList *l = copy; l != NULL; l = l->next) {
2834 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2835 Inkscape::NodePath::SubPath *subpath = n->subpath;
2836 if (!g_slist_find (subpaths, subpath))
2837 subpaths = g_slist_prepend (subpaths, subpath);
2838 }
2840 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2841 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2842 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2843 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2844 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2845 }
2846 }
2848 g_slist_free (subpaths);
2849 g_list_free (copy);
2850 }
2852 /**
2853 * \brief Select the node after the last selected; if none is selected,
2854 * select the first within path.
2855 */
2856 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2857 {
2858 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2860 Inkscape::NodePath::Node *last = NULL;
2861 if (nodepath->selected) {
2862 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2863 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2864 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2865 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2866 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2867 if (node->selected) {
2868 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2869 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2870 if (spl->next) { // there's a next subpath
2871 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2872 last = subpath_next->first;
2873 } else if (spl->prev) { // there's a previous subpath
2874 last = NULL; // to be set later to the first node of first subpath
2875 } else {
2876 last = node->n.other;
2877 }
2878 } else {
2879 last = node->n.other;
2880 }
2881 } else {
2882 if (node->n.other) {
2883 last = node->n.other;
2884 } else {
2885 if (spl->next) { // there's a next subpath
2886 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2887 last = subpath_next->first;
2888 } else if (spl->prev) { // there's a previous subpath
2889 last = NULL; // to be set later to the first node of first subpath
2890 } else {
2891 last = (Inkscape::NodePath::Node *) subpath->first;
2892 }
2893 }
2894 }
2895 }
2896 }
2897 }
2898 sp_nodepath_deselect(nodepath);
2899 }
2901 if (last) { // there's at least one more node after selected
2902 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2903 } else { // no more nodes, select the first one in first subpath
2904 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2905 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2906 }
2907 }
2909 /**
2910 * \brief Select the node before the first selected; if none is selected,
2911 * select the last within path
2912 */
2913 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2914 {
2915 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2917 Inkscape::NodePath::Node *last = NULL;
2918 if (nodepath->selected) {
2919 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2920 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2921 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2922 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2923 if (node->selected) {
2924 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2925 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2926 if (spl->prev) { // there's a prev subpath
2927 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2928 last = subpath_prev->last;
2929 } else if (spl->next) { // there's a next subpath
2930 last = NULL; // to be set later to the last node of last subpath
2931 } else {
2932 last = node->p.other;
2933 }
2934 } else {
2935 last = node->p.other;
2936 }
2937 } else {
2938 if (node->p.other) {
2939 last = node->p.other;
2940 } else {
2941 if (spl->prev) { // there's a prev subpath
2942 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2943 last = subpath_prev->last;
2944 } else if (spl->next) { // there's a next subpath
2945 last = NULL; // to be set later to the last node of last subpath
2946 } else {
2947 last = (Inkscape::NodePath::Node *) subpath->last;
2948 }
2949 }
2950 }
2951 }
2952 }
2953 }
2954 sp_nodepath_deselect(nodepath);
2955 }
2957 if (last) { // there's at least one more node before selected
2958 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2959 } else { // no more nodes, select the last one in last subpath
2960 GList *spl = g_list_last(nodepath->subpaths);
2961 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2962 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2963 }
2964 }
2966 /**
2967 * \brief Select all nodes that are within the rectangle.
2968 */
2969 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2970 {
2971 if (!incremental) {
2972 sp_nodepath_deselect(nodepath);
2973 }
2975 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2976 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2977 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2978 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2980 if (b.contains(node->pos)) {
2981 sp_nodepath_node_select(node, TRUE, TRUE);
2982 }
2983 }
2984 }
2985 }
2988 void
2989 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2990 {
2991 g_assert (n);
2992 g_assert (nodepath);
2993 g_assert (n->subpath->nodepath == nodepath);
2995 if (g_list_length (nodepath->selected) == 0) {
2996 if (grow > 0) {
2997 sp_nodepath_node_select(n, TRUE, TRUE);
2998 }
2999 return;
3000 }
3002 if (g_list_length (nodepath->selected) == 1) {
3003 if (grow < 0) {
3004 sp_nodepath_deselect (nodepath);
3005 return;
3006 }
3007 }
3009 double n_sel_range = 0, p_sel_range = 0;
3010 Inkscape::NodePath::Node *farthest_n_node = n;
3011 Inkscape::NodePath::Node *farthest_p_node = n;
3013 // Calculate ranges
3014 {
3015 double n_range = 0, p_range = 0;
3016 bool n_going = true, p_going = true;
3017 Inkscape::NodePath::Node *n_node = n;
3018 Inkscape::NodePath::Node *p_node = n;
3019 do {
3020 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3021 if (n_node && n_going)
3022 n_node = n_node->n.other;
3023 if (n_node == NULL) {
3024 n_going = false;
3025 } else {
3026 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3027 if (n_node->selected) {
3028 n_sel_range = n_range;
3029 farthest_n_node = n_node;
3030 }
3031 if (n_node == p_node) {
3032 n_going = false;
3033 p_going = false;
3034 }
3035 }
3036 if (p_node && p_going)
3037 p_node = p_node->p.other;
3038 if (p_node == NULL) {
3039 p_going = false;
3040 } else {
3041 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3042 if (p_node->selected) {
3043 p_sel_range = p_range;
3044 farthest_p_node = p_node;
3045 }
3046 if (p_node == n_node) {
3047 n_going = false;
3048 p_going = false;
3049 }
3050 }
3051 } while (n_going || p_going);
3052 }
3054 if (grow > 0) {
3055 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3056 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3057 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3058 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3059 }
3060 } else {
3061 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3062 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3063 } else if (farthest_p_node && farthest_p_node->selected) {
3064 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3065 }
3066 }
3067 }
3069 void
3070 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3071 {
3072 g_assert (n);
3073 g_assert (nodepath);
3074 g_assert (n->subpath->nodepath == nodepath);
3076 if (g_list_length (nodepath->selected) == 0) {
3077 if (grow > 0) {
3078 sp_nodepath_node_select(n, TRUE, TRUE);
3079 }
3080 return;
3081 }
3083 if (g_list_length (nodepath->selected) == 1) {
3084 if (grow < 0) {
3085 sp_nodepath_deselect (nodepath);
3086 return;
3087 }
3088 }
3090 Inkscape::NodePath::Node *farthest_selected = NULL;
3091 double farthest_dist = 0;
3093 Inkscape::NodePath::Node *closest_unselected = NULL;
3094 double closest_dist = NR_HUGE;
3096 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3097 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3098 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3099 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3100 if (node == n)
3101 continue;
3102 if (node->selected) {
3103 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3104 farthest_dist = NR::L2(node->pos - n->pos);
3105 farthest_selected = node;
3106 }
3107 } else {
3108 if (NR::L2(node->pos - n->pos) < closest_dist) {
3109 closest_dist = NR::L2(node->pos - n->pos);
3110 closest_unselected = node;
3111 }
3112 }
3113 }
3114 }
3116 if (grow > 0) {
3117 if (closest_unselected) {
3118 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3119 }
3120 } else {
3121 if (farthest_selected) {
3122 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3123 }
3124 }
3125 }
3128 /**
3129 \brief Saves all nodes' and handles' current positions in their origin members
3130 */
3131 void
3132 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3133 {
3134 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3135 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3136 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3137 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3138 n->origin = n->pos;
3139 n->p.origin = n->p.pos;
3140 n->n.origin = n->n.pos;
3141 }
3142 }
3143 }
3145 /**
3146 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3147 */
3148 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3149 {
3150 if (!nodepath->selected) {
3151 return NULL;
3152 }
3154 GList *r = NULL;
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 (node->selected) {
3162 r = g_list_append(r, GINT_TO_POINTER(i));
3163 }
3164 }
3165 }
3166 return r;
3167 }
3169 /**
3170 \brief Restores selection by selecting nodes whose positions are in the list
3171 */
3172 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3173 {
3174 sp_nodepath_deselect(nodepath);
3176 guint i = 0;
3177 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3178 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3179 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3180 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3181 i++;
3182 if (g_list_find(r, GINT_TO_POINTER(i))) {
3183 sp_nodepath_node_select(node, TRUE, TRUE);
3184 }
3185 }
3186 }
3187 }
3190 /**
3191 \brief Adjusts handle according to node type and line code.
3192 */
3193 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3194 {
3195 g_assert(node);
3197 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3198 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3200 // nothing to do if we are an end node
3201 if (me->other == NULL) return;
3202 if (other->other == NULL) return;
3204 // nothing to do if we are a cusp node
3205 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3207 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3208 NRPathcode mecode;
3209 if (which_adjust == 1) {
3210 mecode = (NRPathcode)me->other->code;
3211 } else {
3212 mecode = (NRPathcode)node->code;
3213 }
3214 if (mecode == NR_LINETO) return;
3216 if (sp_node_side_is_line(node, other)) {
3217 // other is a line, and we are either smooth or symm
3218 Inkscape::NodePath::Node *othernode = other->other;
3219 double len = NR::L2(me->pos - node->pos);
3220 NR::Point delta = node->pos - othernode->pos;
3221 double linelen = NR::L2(delta);
3222 if (linelen < 1e-18)
3223 return;
3224 me->pos = node->pos + (len / linelen)*delta;
3225 return;
3226 }
3228 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3229 // symmetrize
3230 me->pos = 2 * node->pos - other->pos;
3231 return;
3232 } else {
3233 // smoothify
3234 double len = NR::L2(me->pos - node->pos);
3235 NR::Point delta = other->pos - node->pos;
3236 double otherlen = NR::L2(delta);
3237 if (otherlen < 1e-18) return;
3238 me->pos = node->pos - (len / otherlen) * delta;
3239 }
3240 }
3242 /**
3243 \brief Adjusts both handles according to node type and line code
3244 */
3245 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3246 {
3247 g_assert(node);
3249 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3251 /* we are either smooth or symm */
3253 if (node->p.other == NULL) return;
3254 if (node->n.other == NULL) return;
3256 if (sp_node_side_is_line(node, &node->p)) {
3257 sp_node_adjust_handle(node, 1);
3258 return;
3259 }
3261 if (sp_node_side_is_line(node, &node->n)) {
3262 sp_node_adjust_handle(node, -1);
3263 return;
3264 }
3266 /* both are curves */
3267 NR::Point const delta( node->n.pos - node->p.pos );
3269 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3270 node->p.pos = node->pos - delta / 2;
3271 node->n.pos = node->pos + delta / 2;
3272 return;
3273 }
3275 /* We are smooth */
3276 double plen = NR::L2(node->p.pos - node->pos);
3277 if (plen < 1e-18) return;
3278 double nlen = NR::L2(node->n.pos - node->pos);
3279 if (nlen < 1e-18) return;
3280 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3281 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3282 }
3284 /**
3285 * Node event callback.
3286 */
3287 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3288 {
3289 gboolean ret = FALSE;
3290 switch (event->type) {
3291 case GDK_ENTER_NOTIFY:
3292 Inkscape::NodePath::Path::active_node = n;
3293 break;
3294 case GDK_LEAVE_NOTIFY:
3295 Inkscape::NodePath::Path::active_node = NULL;
3296 break;
3297 case GDK_SCROLL:
3298 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3299 switch (event->scroll.direction) {
3300 case GDK_SCROLL_UP:
3301 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3302 break;
3303 case GDK_SCROLL_DOWN:
3304 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3305 break;
3306 default:
3307 break;
3308 }
3309 ret = TRUE;
3310 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3311 switch (event->scroll.direction) {
3312 case GDK_SCROLL_UP:
3313 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3314 break;
3315 case GDK_SCROLL_DOWN:
3316 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3317 break;
3318 default:
3319 break;
3320 }
3321 ret = TRUE;
3322 }
3323 break;
3324 case GDK_KEY_PRESS:
3325 switch (get_group0_keyval (&event->key)) {
3326 case GDK_space:
3327 if (event->key.state & GDK_BUTTON1_MASK) {
3328 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3329 stamp_repr(nodepath);
3330 ret = TRUE;
3331 }
3332 break;
3333 case GDK_Page_Up:
3334 if (event->key.state & GDK_CONTROL_MASK) {
3335 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3336 } else {
3337 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3338 }
3339 break;
3340 case GDK_Page_Down:
3341 if (event->key.state & GDK_CONTROL_MASK) {
3342 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3343 } else {
3344 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3345 }
3346 break;
3347 default:
3348 break;
3349 }
3350 break;
3351 default:
3352 break;
3353 }
3355 return ret;
3356 }
3358 /**
3359 * Handle keypress on node; directly called.
3360 */
3361 gboolean node_key(GdkEvent *event)
3362 {
3363 Inkscape::NodePath::Path *np;
3365 // there is no way to verify nodes so set active_node to nil when deleting!!
3366 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3368 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3369 gint ret = FALSE;
3370 switch (get_group0_keyval (&event->key)) {
3371 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3372 case GDK_BackSpace:
3373 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3374 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3375 sp_nodepath_update_repr(np, _("Delete node"));
3376 Inkscape::NodePath::Path::active_node = NULL;
3377 ret = TRUE;
3378 break;
3379 case GDK_c:
3380 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3381 ret = TRUE;
3382 break;
3383 case GDK_s:
3384 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3385 ret = TRUE;
3386 break;
3387 case GDK_y:
3388 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3389 ret = TRUE;
3390 break;
3391 case GDK_b:
3392 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3393 ret = TRUE;
3394 break;
3395 }
3396 return ret;
3397 }
3398 return FALSE;
3399 }
3401 /**
3402 * Mouseclick on node callback.
3403 */
3404 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3405 {
3406 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3408 if (state & GDK_CONTROL_MASK) {
3409 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3411 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3412 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3413 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3414 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3415 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3416 } else {
3417 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3418 }
3419 sp_nodepath_update_repr(nodepath, _("Change node type"));
3420 sp_nodepath_update_statusbar(nodepath);
3422 } else { //ctrl+alt+click: delete node
3423 GList *node_to_delete = NULL;
3424 node_to_delete = g_list_append(node_to_delete, n);
3425 sp_node_delete_preserve(node_to_delete);
3426 }
3428 } else {
3429 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3430 }
3431 }
3433 /**
3434 * Mouse grabbed node callback.
3435 */
3436 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3437 {
3438 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3440 if (!n->selected) {
3441 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3442 }
3444 n->is_dragging = true;
3445 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3447 sp_nodepath_remember_origins (n->subpath->nodepath);
3448 }
3450 /**
3451 * Mouse ungrabbed node callback.
3452 */
3453 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3454 {
3455 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3457 n->dragging_out = NULL;
3458 n->is_dragging = false;
3459 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3461 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3462 }
3464 /**
3465 * The point on a line, given by its angle, closest to the given point.
3466 * \param p A point.
3467 * \param a Angle of the line; it is assumed to go through coordinate origin.
3468 * \param closest Pointer to the point struct where the result is stored.
3469 * \todo FIXME: use dot product perhaps?
3470 */
3471 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3472 {
3473 if (a == HUGE_VAL) { // vertical
3474 *closest = NR::Point(0, (*p)[NR::Y]);
3475 } else {
3476 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3477 (*closest)[NR::Y] = a * (*closest)[NR::X];
3478 }
3479 }
3481 /**
3482 * Distance from the point to a line given by its angle.
3483 * \param p A point.
3484 * \param a Angle of the line; it is assumed to go through coordinate origin.
3485 */
3486 static double point_line_distance(NR::Point *p, double a)
3487 {
3488 NR::Point c;
3489 point_line_closest(p, a, &c);
3490 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]));
3491 }
3493 /**
3494 * Callback for node "request" signal.
3495 * \todo fixme: This goes to "moved" event? (lauris)
3496 */
3497 static gboolean
3498 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3499 {
3500 double yn, xn, yp, xp;
3501 double an, ap, na, pa;
3502 double d_an, d_ap, d_na, d_pa;
3503 gboolean collinear = FALSE;
3504 NR::Point c;
3505 NR::Point pr;
3507 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3509 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3511 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3512 if ( (!n->subpath->nodepath->straight_path) &&
3513 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3514 || n->dragging_out ) )
3515 {
3516 NR::Point mouse = (*p);
3518 if (!n->dragging_out) {
3519 // This is the first drag-out event; find out which handle to drag out
3520 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3521 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3523 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3524 return FALSE;
3526 Inkscape::NodePath::NodeSide *opposite;
3527 if (appr_p > appr_n) { // closer to p
3528 n->dragging_out = &n->p;
3529 opposite = &n->n;
3530 n->code = NR_CURVETO;
3531 } else if (appr_p < appr_n) { // closer to n
3532 n->dragging_out = &n->n;
3533 opposite = &n->p;
3534 n->n.other->code = NR_CURVETO;
3535 } else { // p and n nodes are the same
3536 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3537 n->dragging_out = &n->p;
3538 opposite = &n->n;
3539 n->code = NR_CURVETO;
3540 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3541 n->dragging_out = &n->n;
3542 opposite = &n->p;
3543 n->n.other->code = NR_CURVETO;
3544 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3545 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);
3546 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);
3547 if (appr_other_p > appr_other_n) { // closer to other's p handle
3548 n->dragging_out = &n->n;
3549 opposite = &n->p;
3550 n->n.other->code = NR_CURVETO;
3551 } else { // closer to other's n handle
3552 n->dragging_out = &n->p;
3553 opposite = &n->n;
3554 n->code = NR_CURVETO;
3555 }
3556 }
3557 }
3559 // if there's another handle, make sure the one we drag out starts parallel to it
3560 if (opposite->pos != n->pos) {
3561 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3562 }
3564 // knots might not be created yet!
3565 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3566 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3567 }
3569 // pass this on to the handle-moved callback
3570 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3571 sp_node_update_handles(n);
3572 return TRUE;
3573 }
3575 if (state & GDK_CONTROL_MASK) { // constrained motion
3577 // calculate relative distances of handles
3578 // n handle:
3579 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3580 xn = n->n.pos[NR::X] - n->pos[NR::X];
3581 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3582 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3583 if (n->n.other) { // if there is the next point
3584 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3585 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3586 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3587 }
3588 }
3589 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3590 if (yn < 0) { xn = -xn; yn = -yn; }
3592 // p handle:
3593 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3594 xp = n->p.pos[NR::X] - n->pos[NR::X];
3595 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3596 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3597 if (n->p.other) {
3598 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3599 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3600 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3601 }
3602 }
3603 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3604 if (yp < 0) { xp = -xp; yp = -yp; }
3606 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3607 // sliding on handles, only if at least one of the handles is non-vertical
3608 // (otherwise it's the same as ctrl+drag anyway)
3610 // calculate angles of the handles
3611 if (xn == 0) {
3612 if (yn == 0) { // no handle, consider it the continuation of the other one
3613 an = 0;
3614 collinear = TRUE;
3615 }
3616 else an = 0; // vertical; set the angle to horizontal
3617 } else an = yn/xn;
3619 if (xp == 0) {
3620 if (yp == 0) { // no handle, consider it the continuation of the other one
3621 ap = an;
3622 }
3623 else ap = 0; // vertical; set the angle to horizontal
3624 } else ap = yp/xp;
3626 if (collinear) an = ap;
3628 // angles of the perpendiculars; HUGE_VAL means vertical
3629 if (an == 0) na = HUGE_VAL; else na = -1/an;
3630 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3632 // mouse point relative to the node's original pos
3633 pr = (*p) - n->origin;
3635 // distances to the four lines (two handles and two perpendiculars)
3636 d_an = point_line_distance(&pr, an);
3637 d_na = point_line_distance(&pr, na);
3638 d_ap = point_line_distance(&pr, ap);
3639 d_pa = point_line_distance(&pr, pa);
3641 // find out which line is the closest, save its closest point in c
3642 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3643 point_line_closest(&pr, an, &c);
3644 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3645 point_line_closest(&pr, ap, &c);
3646 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3647 point_line_closest(&pr, na, &c);
3648 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3649 point_line_closest(&pr, pa, &c);
3650 }
3652 // move the node to the closest point
3653 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3654 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3655 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3656 true);
3658 } else { // constraining to hor/vert
3660 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3661 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3662 (*p)[NR::X] - n->pos[NR::X],
3663 n->origin[NR::Y] - n->pos[NR::Y],
3664 true,
3665 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3666 } else { // snap to vert
3667 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3668 n->origin[NR::X] - n->pos[NR::X],
3669 (*p)[NR::Y] - n->pos[NR::Y],
3670 true,
3671 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3672 }
3673 }
3674 } else { // move freely
3675 if (n->is_dragging) {
3676 if (state & GDK_MOD1_MASK) { // sculpt
3677 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3678 } else {
3679 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3680 (*p)[NR::X] - n->pos[NR::X],
3681 (*p)[NR::Y] - n->pos[NR::Y],
3682 (state & GDK_SHIFT_MASK) == 0);
3683 }
3684 }
3685 }
3687 n->subpath->nodepath->desktop->scroll_to_point(p);
3689 return TRUE;
3690 }
3692 /**
3693 * Node handle clicked callback.
3694 */
3695 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3696 {
3697 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3699 if (state & GDK_CONTROL_MASK) { // "delete" handle
3700 if (n->p.knot == knot) {
3701 n->p.pos = n->pos;
3702 } else if (n->n.knot == knot) {
3703 n->n.pos = n->pos;
3704 }
3705 sp_node_update_handles(n);
3706 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3707 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3708 sp_nodepath_update_statusbar(nodepath);
3710 } else { // just select or add to selection, depending in Shift
3711 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3712 }
3713 }
3715 /**
3716 * Node handle grabbed callback.
3717 */
3718 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3719 {
3720 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3722 if (!n->selected) {
3723 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3724 }
3726 // remember the origin point of the handle
3727 if (n->p.knot == knot) {
3728 n->p.origin_radial = n->p.pos - n->pos;
3729 } else if (n->n.knot == knot) {
3730 n->n.origin_radial = n->n.pos - n->pos;
3731 } else {
3732 g_assert_not_reached();
3733 }
3735 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3736 }
3738 /**
3739 * Node handle ungrabbed callback.
3740 */
3741 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3742 {
3743 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3745 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3746 if (n->p.knot == knot) {
3747 n->p.origin_radial.a = 0;
3748 sp_knot_set_position(knot, n->p.pos, state);
3749 } else if (n->n.knot == knot) {
3750 n->n.origin_radial.a = 0;
3751 sp_knot_set_position(knot, n->n.pos, state);
3752 } else {
3753 g_assert_not_reached();
3754 }
3756 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3757 }
3759 /**
3760 * Node handle "request" signal callback.
3761 */
3762 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3763 {
3764 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3766 Inkscape::NodePath::NodeSide *me, *opposite;
3767 gint which;
3768 if (n->p.knot == knot) {
3769 me = &n->p;
3770 opposite = &n->n;
3771 which = -1;
3772 } else if (n->n.knot == knot) {
3773 me = &n->n;
3774 opposite = &n->p;
3775 which = 1;
3776 } else {
3777 me = opposite = NULL;
3778 which = 0;
3779 g_assert_not_reached();
3780 }
3782 SPDesktop *desktop = n->subpath->nodepath->desktop;
3783 SnapManager &m = desktop->namedview->snap_manager;
3784 m.setup(desktop, n->subpath->nodepath->item);
3785 Inkscape::SnappedPoint s;
3787 if ((state & GDK_SHIFT_MASK) != 0) {
3788 // We will not try to snap when the shift-key is pressed
3789 // so remove the old snap indicator and don't wait for it to time-out
3790 desktop->snapindicator->remove_snappoint();
3791 }
3793 Inkscape::NodePath::Node *othernode = opposite->other;
3794 if (othernode) {
3795 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3796 /* We are smooth node adjacent with line */
3797 NR::Point const delta = *p - n->pos;
3798 NR::Coord const len = NR::L2(delta);
3799 Inkscape::NodePath::Node *othernode = opposite->other;
3800 NR::Point const ndelta = n->pos - othernode->pos;
3801 NR::Coord const linelen = NR::L2(ndelta);
3802 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3803 NR::Coord const scal = dot(delta, ndelta) / linelen;
3804 (*p) = n->pos + (scal / linelen) * ndelta;
3805 }
3806 if ((state & GDK_SHIFT_MASK) == 0) {
3807 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3808 }
3809 } else {
3810 if ((state & GDK_SHIFT_MASK) == 0) {
3811 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3812 }
3813 }
3814 } else {
3815 if ((state & GDK_SHIFT_MASK) == 0) {
3816 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3817 }
3818 }
3820 s.getPoint(*p);
3822 sp_node_adjust_handle(n, -which);
3824 return FALSE;
3825 }
3827 /**
3828 * Node handle moved callback.
3829 */
3830 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3831 {
3832 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3834 Inkscape::NodePath::NodeSide *me;
3835 Inkscape::NodePath::NodeSide *other;
3836 if (n->p.knot == knot) {
3837 me = &n->p;
3838 other = &n->n;
3839 } else if (n->n.knot == knot) {
3840 me = &n->n;
3841 other = &n->p;
3842 } else {
3843 me = NULL;
3844 other = NULL;
3845 g_assert_not_reached();
3846 }
3848 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3849 Radial rme(me->pos - n->pos);
3850 Radial rother(other->pos - n->pos);
3851 Radial rnew(*p - n->pos);
3853 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3854 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3855 /* 0 interpreted as "no snapping". */
3857 // 1. Snap to the closest PI/snaps angle, starting from zero.
3858 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3860 // 2. Snap to the original angle, its opposite and perpendiculars
3861 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3862 /* The closest PI/2 angle, starting from original angle */
3863 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3865 // Snap to the closest.
3866 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3867 ? a_snapped
3868 : a_ortho );
3869 }
3871 // 3. Snap to the angle of the opposite line, if any
3872 Inkscape::NodePath::Node *othernode = other->other;
3873 if (othernode) {
3874 NR::Point other_to_snap(0,0);
3875 if (sp_node_side_is_line(n, other)) {
3876 other_to_snap = othernode->pos - n->pos;
3877 } else {
3878 other_to_snap = other->pos - n->pos;
3879 }
3880 if (NR::L2(other_to_snap) > 1e-3) {
3881 Radial rother_to_snap(other_to_snap);
3882 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3883 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3885 // Snap to the closest.
3886 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3887 ? a_snapped
3888 : a_oppo );
3889 }
3890 }
3892 rnew.a = a_snapped;
3893 }
3895 if (state & GDK_MOD1_MASK) {
3896 // lock handle length
3897 rnew.r = me->origin_radial.r;
3898 }
3900 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3901 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3902 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3903 rother.a += rnew.a - rme.a;
3904 other->pos = NR::Point(rother) + n->pos;
3905 if (other->knot) {
3906 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3907 sp_knot_moveto(other->knot, other->pos);
3908 }
3909 }
3911 me->pos = NR::Point(rnew) + n->pos;
3912 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3914 // move knot, but without emitting the signal:
3915 // we cannot emit a "moved" signal because we're now processing it
3916 sp_knot_moveto(me->knot, me->pos);
3918 update_object(n->subpath->nodepath);
3920 /* status text */
3921 SPDesktop *desktop = n->subpath->nodepath->desktop;
3922 if (!desktop) return;
3923 SPEventContext *ec = desktop->event_context;
3924 if (!ec) return;
3925 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3926 if (!mc) return;
3928 double degrees = 180 / M_PI * rnew.a;
3929 if (degrees > 180) degrees -= 360;
3930 if (degrees < -180) degrees += 360;
3931 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3932 degrees = angle_to_compass (degrees);
3934 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3936 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3937 _("<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);
3939 g_string_free(length, TRUE);
3940 }
3942 /**
3943 * Node handle event callback.
3944 */
3945 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3946 {
3947 gboolean ret = FALSE;
3948 switch (event->type) {
3949 case GDK_KEY_PRESS:
3950 switch (get_group0_keyval (&event->key)) {
3951 case GDK_space:
3952 if (event->key.state & GDK_BUTTON1_MASK) {
3953 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3954 stamp_repr(nodepath);
3955 ret = TRUE;
3956 }
3957 break;
3958 default:
3959 break;
3960 }
3961 break;
3962 case GDK_ENTER_NOTIFY:
3963 // we use an experimentally determined threshold that seems to work fine
3964 if (NR::L2(n->pos - knot->pos) < 0.75)
3965 Inkscape::NodePath::Path::active_node = n;
3966 break;
3967 case GDK_LEAVE_NOTIFY:
3968 // we use an experimentally determined threshold that seems to work fine
3969 if (NR::L2(n->pos - knot->pos) < 0.75)
3970 Inkscape::NodePath::Path::active_node = NULL;
3971 break;
3972 default:
3973 break;
3974 }
3976 return ret;
3977 }
3979 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3980 Radial &rme, Radial &rother, gboolean const both)
3981 {
3982 rme.a += angle;
3983 if ( both
3984 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3985 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3986 {
3987 rother.a += angle;
3988 }
3989 }
3991 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3992 Radial &rme, Radial &rother, gboolean const both)
3993 {
3994 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3996 gdouble r;
3997 if ( both
3998 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3999 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4000 {
4001 r = MAX(rme.r, rother.r);
4002 } else {
4003 r = rme.r;
4004 }
4006 gdouble const weird_angle = atan2(norm_angle, r);
4007 /* Bulia says norm_angle is just the visible distance that the
4008 * object's end must travel on the screen. Left as 'angle' for want of
4009 * a better name.*/
4011 rme.a += weird_angle;
4012 if ( both
4013 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4014 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4015 {
4016 rother.a += weird_angle;
4017 }
4018 }
4020 /**
4021 * Rotate one node.
4022 */
4023 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4024 {
4025 Inkscape::NodePath::NodeSide *me, *other;
4026 bool both = false;
4028 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4029 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4031 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4032 me = &(n->p);
4033 other = &(n->n);
4034 } else if (!n->p.other) {
4035 me = &(n->n);
4036 other = &(n->p);
4037 } else {
4038 if (which > 0) { // right handle
4039 if (xn > xp) {
4040 me = &(n->n);
4041 other = &(n->p);
4042 } else {
4043 me = &(n->p);
4044 other = &(n->n);
4045 }
4046 } else if (which < 0){ // left handle
4047 if (xn <= xp) {
4048 me = &(n->n);
4049 other = &(n->p);
4050 } else {
4051 me = &(n->p);
4052 other = &(n->n);
4053 }
4054 } else { // both handles
4055 me = &(n->n);
4056 other = &(n->p);
4057 both = true;
4058 }
4059 }
4061 Radial rme(me->pos - n->pos);
4062 Radial rother(other->pos - n->pos);
4064 if (screen) {
4065 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4066 } else {
4067 node_rotate_one_internal (*n, angle, rme, rother, both);
4068 }
4070 me->pos = n->pos + NR::Point(rme);
4072 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4073 other->pos = n->pos + NR::Point(rother);
4074 }
4076 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4077 // so here we just move all the knots without emitting move signals, for speed
4078 sp_node_update_handles(n, false);
4079 }
4081 /**
4082 * Rotate selected nodes.
4083 */
4084 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4085 {
4086 if (!nodepath || !nodepath->selected) return;
4088 if (g_list_length(nodepath->selected) == 1) {
4089 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4090 node_rotate_one (n, angle, which, screen);
4091 } else {
4092 // rotate as an object:
4094 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4095 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4096 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4097 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4098 box.expandTo (n->pos); // contain all selected nodes
4099 }
4101 gdouble rot;
4102 if (screen) {
4103 gdouble const zoom = nodepath->desktop->current_zoom();
4104 gdouble const zmove = angle / zoom;
4105 gdouble const r = NR::L2(box.max() - box.midpoint());
4106 rot = atan2(zmove, r);
4107 } else {
4108 rot = angle;
4109 }
4111 NR::Point rot_center;
4112 if (Inkscape::NodePath::Path::active_node == NULL)
4113 rot_center = box.midpoint();
4114 else
4115 rot_center = Inkscape::NodePath::Path::active_node->pos;
4117 NR::Matrix t =
4118 NR::Matrix (NR::translate(-rot_center)) *
4119 NR::Matrix (NR::rotate(rot)) *
4120 NR::Matrix (NR::translate(rot_center));
4122 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4123 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4124 n->pos *= t;
4125 n->n.pos *= t;
4126 n->p.pos *= t;
4127 sp_node_update_handles(n, false);
4128 }
4129 }
4131 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4132 }
4134 /**
4135 * Scale one node.
4136 */
4137 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4138 {
4139 bool both = false;
4140 Inkscape::NodePath::NodeSide *me, *other;
4142 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4143 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4145 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4146 me = &(n->p);
4147 other = &(n->n);
4148 n->code = NR_CURVETO;
4149 } else if (!n->p.other) {
4150 me = &(n->n);
4151 other = &(n->p);
4152 if (n->n.other)
4153 n->n.other->code = NR_CURVETO;
4154 } else {
4155 if (which > 0) { // right handle
4156 if (xn > xp) {
4157 me = &(n->n);
4158 other = &(n->p);
4159 if (n->n.other)
4160 n->n.other->code = NR_CURVETO;
4161 } else {
4162 me = &(n->p);
4163 other = &(n->n);
4164 n->code = NR_CURVETO;
4165 }
4166 } else if (which < 0){ // left handle
4167 if (xn <= xp) {
4168 me = &(n->n);
4169 other = &(n->p);
4170 if (n->n.other)
4171 n->n.other->code = NR_CURVETO;
4172 } else {
4173 me = &(n->p);
4174 other = &(n->n);
4175 n->code = NR_CURVETO;
4176 }
4177 } else { // both handles
4178 me = &(n->n);
4179 other = &(n->p);
4180 both = true;
4181 n->code = NR_CURVETO;
4182 if (n->n.other)
4183 n->n.other->code = NR_CURVETO;
4184 }
4185 }
4187 Radial rme(me->pos - n->pos);
4188 Radial rother(other->pos - n->pos);
4190 rme.r += grow;
4191 if (rme.r < 0) rme.r = 0;
4192 if (rme.a == HUGE_VAL) {
4193 if (me->other) { // if direction is unknown, initialize it towards the next node
4194 Radial rme_next(me->other->pos - n->pos);
4195 rme.a = rme_next.a;
4196 } else { // if there's no next, initialize to 0
4197 rme.a = 0;
4198 }
4199 }
4200 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4201 rother.r += grow;
4202 if (rother.r < 0) rother.r = 0;
4203 if (rother.a == HUGE_VAL) {
4204 rother.a = rme.a + M_PI;
4205 }
4206 }
4208 me->pos = n->pos + NR::Point(rme);
4210 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4211 other->pos = n->pos + NR::Point(rother);
4212 }
4214 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4215 // so here we just move all the knots without emitting move signals, for speed
4216 sp_node_update_handles(n, false);
4217 }
4219 /**
4220 * Scale selected nodes.
4221 */
4222 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4223 {
4224 if (!nodepath || !nodepath->selected) return;
4226 if (g_list_length(nodepath->selected) == 1) {
4227 // scale handles of the single selected node
4228 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4229 node_scale_one (n, grow, which);
4230 } else {
4231 // scale nodes as an "object":
4233 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4234 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4235 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4236 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4237 box.expandTo (n->pos); // contain all selected nodes
4238 }
4240 double scale = (box.maxExtent() + grow)/box.maxExtent();
4242 NR::Point scale_center;
4243 if (Inkscape::NodePath::Path::active_node == NULL)
4244 scale_center = box.midpoint();
4245 else
4246 scale_center = Inkscape::NodePath::Path::active_node->pos;
4248 NR::Matrix t =
4249 NR::Matrix (NR::translate(-scale_center)) *
4250 NR::Matrix (NR::scale(scale, scale)) *
4251 NR::Matrix (NR::translate(scale_center));
4253 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4254 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4255 n->pos *= t;
4256 n->n.pos *= t;
4257 n->p.pos *= t;
4258 sp_node_update_handles(n, false);
4259 }
4260 }
4262 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4263 }
4265 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4266 {
4267 if (!nodepath) return;
4268 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4269 }
4271 /**
4272 * Flip selected nodes horizontally/vertically.
4273 */
4274 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, boost::optional<NR::Point> center)
4275 {
4276 if (!nodepath || !nodepath->selected) return;
4278 if (g_list_length(nodepath->selected) == 1 && !center) {
4279 // flip handles of the single selected node
4280 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4281 double temp = n->p.pos[axis];
4282 n->p.pos[axis] = n->n.pos[axis];
4283 n->n.pos[axis] = temp;
4284 sp_node_update_handles(n, false);
4285 } else {
4286 // scale nodes as an "object":
4288 Geom::Rect box = sp_node_selected_bbox (nodepath);
4289 if (!center) {
4290 center = from_2geom(box.midpoint());
4291 }
4292 NR::Matrix t =
4293 NR::Matrix (NR::translate(- *center)) *
4294 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4295 NR::Matrix (NR::translate(*center));
4297 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4298 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4299 n->pos *= t;
4300 n->n.pos *= t;
4301 n->p.pos *= t;
4302 sp_node_update_handles(n, false);
4303 }
4304 }
4306 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4307 }
4309 Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4310 {
4311 g_assert (nodepath->selected);
4313 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4314 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4315 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4316 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4317 box.expandTo (n->pos); // contain all selected nodes
4318 }
4319 return box;
4320 }
4322 //-----------------------------------------------
4323 /**
4324 * Return new subpath under given nodepath.
4325 */
4326 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4327 {
4328 g_assert(nodepath);
4329 g_assert(nodepath->desktop);
4331 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4333 s->nodepath = nodepath;
4334 s->closed = FALSE;
4335 s->nodes = NULL;
4336 s->first = NULL;
4337 s->last = NULL;
4339 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4340 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4341 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4343 return s;
4344 }
4346 /**
4347 * Destroy nodes in subpath, then subpath itself.
4348 */
4349 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4350 {
4351 g_assert(subpath);
4352 g_assert(subpath->nodepath);
4353 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4355 while (subpath->nodes) {
4356 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4357 }
4359 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4361 g_free(subpath);
4362 }
4364 /**
4365 * Link head to tail in subpath.
4366 */
4367 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4368 {
4369 g_assert(!sp->closed);
4370 g_assert(sp->last != sp->first);
4371 g_assert(sp->first->code == NR_MOVETO);
4373 sp->closed = TRUE;
4375 //Link the head to the tail
4376 sp->first->p.other = sp->last;
4377 sp->last->n.other = sp->first;
4378 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4379 sp->first = sp->last;
4381 //Remove the extra end node
4382 sp_nodepath_node_destroy(sp->last->n.other);
4383 }
4385 /**
4386 * Open closed (loopy) subpath at node.
4387 */
4388 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4389 {
4390 g_assert(sp->closed);
4391 g_assert(n->subpath == sp);
4392 g_assert(sp->first == sp->last);
4394 /* We create new startpoint, current node will become last one */
4396 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4397 &n->pos, &n->pos, &n->n.pos);
4400 sp->closed = FALSE;
4402 //Unlink to make a head and tail
4403 sp->first = new_path;
4404 sp->last = n;
4405 n->n.other = NULL;
4406 new_path->p.other = NULL;
4407 }
4409 /**
4410 * Return new node in subpath with given properties.
4411 * \param pos Position of node.
4412 * \param ppos Handle position in previous direction
4413 * \param npos Handle position in previous direction
4414 */
4415 Inkscape::NodePath::Node *
4416 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)
4417 {
4418 g_assert(sp);
4419 g_assert(sp->nodepath);
4420 g_assert(sp->nodepath->desktop);
4422 if (nodechunk == NULL)
4423 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4425 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4427 n->subpath = sp;
4429 if (type != Inkscape::NodePath::NODE_NONE) {
4430 // use the type from sodipodi:nodetypes
4431 n->type = type;
4432 } else {
4433 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4434 // points are (almost) collinear
4435 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4436 // endnode, or a node with a retracted handle
4437 n->type = Inkscape::NodePath::NODE_CUSP;
4438 } else {
4439 n->type = Inkscape::NodePath::NODE_SMOOTH;
4440 }
4441 } else {
4442 n->type = Inkscape::NodePath::NODE_CUSP;
4443 }
4444 }
4446 n->code = code;
4447 n->selected = FALSE;
4448 n->pos = *pos;
4449 n->p.pos = *ppos;
4450 n->n.pos = *npos;
4452 n->dragging_out = NULL;
4454 Inkscape::NodePath::Node *prev;
4455 if (next) {
4456 //g_assert(g_list_find(sp->nodes, next));
4457 prev = next->p.other;
4458 } else {
4459 prev = sp->last;
4460 }
4462 if (prev)
4463 prev->n.other = n;
4464 else
4465 sp->first = n;
4467 if (next)
4468 next->p.other = n;
4469 else
4470 sp->last = n;
4472 n->p.other = prev;
4473 n->n.other = next;
4475 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"));
4476 sp_knot_set_position(n->knot, *pos, 0);
4478 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4479 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4480 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4481 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4482 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4483 sp_knot_update_ctrl(n->knot);
4485 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4486 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4487 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4488 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4489 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4490 sp_knot_show(n->knot);
4492 // We only create handle knots and lines on demand
4493 n->p.knot = NULL;
4494 n->p.line = NULL;
4495 n->n.knot = NULL;
4496 n->n.line = NULL;
4498 sp->nodes = g_list_prepend(sp->nodes, n);
4500 return n;
4501 }
4503 /**
4504 * Destroy node and its knots, link neighbors in subpath.
4505 */
4506 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4507 {
4508 g_assert(node);
4509 g_assert(node->subpath);
4510 g_assert(SP_IS_KNOT(node->knot));
4512 Inkscape::NodePath::SubPath *sp = node->subpath;
4514 if (node->selected) { // first, deselect
4515 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4516 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4517 }
4519 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4521 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4522 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4523 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4524 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4525 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4526 g_object_unref(G_OBJECT(node->knot));
4528 if (node->p.knot) {
4529 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4530 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4531 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4532 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4533 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4534 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4535 g_object_unref(G_OBJECT(node->p.knot));
4536 node->p.knot = NULL;
4537 }
4539 if (node->n.knot) {
4540 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4541 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4542 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4543 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4544 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4545 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4546 g_object_unref(G_OBJECT(node->n.knot));
4547 node->n.knot = NULL;
4548 }
4550 if (node->p.line)
4551 gtk_object_destroy(GTK_OBJECT(node->p.line));
4552 if (node->n.line)
4553 gtk_object_destroy(GTK_OBJECT(node->n.line));
4555 if (sp->nodes) { // there are others nodes on the subpath
4556 if (sp->closed) {
4557 if (sp->first == node) {
4558 g_assert(sp->last == node);
4559 sp->first = node->n.other;
4560 sp->last = sp->first;
4561 }
4562 node->p.other->n.other = node->n.other;
4563 node->n.other->p.other = node->p.other;
4564 } else {
4565 if (sp->first == node) {
4566 sp->first = node->n.other;
4567 sp->first->code = NR_MOVETO;
4568 }
4569 if (sp->last == node) sp->last = node->p.other;
4570 if (node->p.other) node->p.other->n.other = node->n.other;
4571 if (node->n.other) node->n.other->p.other = node->p.other;
4572 }
4573 } else { // this was the last node on subpath
4574 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4575 }
4577 g_mem_chunk_free(nodechunk, node);
4578 }
4580 /**
4581 * Returns one of the node's two sides.
4582 * \param which Indicates which side.
4583 * \return Pointer to previous node side if which==-1, next if which==1.
4584 */
4585 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4586 {
4587 g_assert(node);
4589 switch (which) {
4590 case -1:
4591 return &node->p;
4592 case 1:
4593 return &node->n;
4594 default:
4595 break;
4596 }
4598 g_assert_not_reached();
4600 return NULL;
4601 }
4603 /**
4604 * Return the other side of the node, given one of its sides.
4605 */
4606 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4607 {
4608 g_assert(node);
4610 if (me == &node->p) return &node->n;
4611 if (me == &node->n) return &node->p;
4613 g_assert_not_reached();
4615 return NULL;
4616 }
4618 /**
4619 * Return NRPathcode on the given side of the node.
4620 */
4621 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4622 {
4623 g_assert(node);
4625 if (me == &node->p) {
4626 if (node->p.other) return (NRPathcode)node->code;
4627 return NR_MOVETO;
4628 }
4630 if (me == &node->n) {
4631 if (node->n.other) return (NRPathcode)node->n.other->code;
4632 return NR_MOVETO;
4633 }
4635 g_assert_not_reached();
4637 return NR_END;
4638 }
4640 /**
4641 * Return node with the given index
4642 */
4643 Inkscape::NodePath::Node *
4644 sp_nodepath_get_node_by_index(int index)
4645 {
4646 Inkscape::NodePath::Node *e = NULL;
4648 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4649 if (!nodepath) {
4650 return e;
4651 }
4653 //find segment
4654 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4656 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4657 int n = g_list_length(sp->nodes);
4658 if (sp->closed) {
4659 n++;
4660 }
4662 //if the piece belongs to this subpath grab it
4663 //otherwise move onto the next subpath
4664 if (index < n) {
4665 e = sp->first;
4666 for (int i = 0; i < index; ++i) {
4667 e = e->n.other;
4668 }
4669 break;
4670 } else {
4671 if (sp->closed) {
4672 index -= (n+1);
4673 } else {
4674 index -= n;
4675 }
4676 }
4677 }
4679 return e;
4680 }
4682 /**
4683 * Returns plain text meaning of node type.
4684 */
4685 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4686 {
4687 unsigned retracted = 0;
4688 bool endnode = false;
4690 for (int which = -1; which <= 1; which += 2) {
4691 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4692 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4693 retracted ++;
4694 if (!side->other)
4695 endnode = true;
4696 }
4698 if (retracted == 0) {
4699 if (endnode) {
4700 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4701 return _("end node");
4702 } else {
4703 switch (node->type) {
4704 case Inkscape::NodePath::NODE_CUSP:
4705 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4706 return _("cusp");
4707 case Inkscape::NodePath::NODE_SMOOTH:
4708 // TRANSLATORS: "smooth" is an adjective here
4709 return _("smooth");
4710 case Inkscape::NodePath::NODE_SYMM:
4711 return _("symmetric");
4712 }
4713 }
4714 } else if (retracted == 1) {
4715 if (endnode) {
4716 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4717 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4718 } else {
4719 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4720 }
4721 } else {
4722 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4723 }
4725 return NULL;
4726 }
4728 /**
4729 * Handles content of statusbar as long as node tool is active.
4730 */
4731 void
4732 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4733 {
4734 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");
4735 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4737 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4738 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4739 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4740 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4742 SPDesktop *desktop = NULL;
4743 if (nodepath) {
4744 desktop = nodepath->desktop;
4745 } else {
4746 desktop = SP_ACTIVE_DESKTOP;
4747 }
4749 SPEventContext *ec = desktop->event_context;
4750 if (!ec) return;
4751 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4752 if (!mc) return;
4754 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4756 if (selected_nodes == 0) {
4757 Inkscape::Selection *sel = desktop->selection;
4758 if (!sel || sel->isEmpty()) {
4759 mc->setF(Inkscape::NORMAL_MESSAGE,
4760 _("Select a single object to edit its nodes or handles."));
4761 } else {
4762 if (nodepath) {
4763 mc->setF(Inkscape::NORMAL_MESSAGE,
4764 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.",
4765 "<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.",
4766 total_nodes),
4767 total_nodes);
4768 } else {
4769 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4770 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4771 } else {
4772 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4773 }
4774 }
4775 }
4776 } else if (nodepath && selected_nodes == 1) {
4777 mc->setF(Inkscape::NORMAL_MESSAGE,
4778 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4779 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4780 total_nodes),
4781 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4782 } else {
4783 if (selected_subpaths > 1) {
4784 mc->setF(Inkscape::NORMAL_MESSAGE,
4785 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4786 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4787 total_nodes),
4788 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4789 } else {
4790 mc->setF(Inkscape::NORMAL_MESSAGE,
4791 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4792 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4793 total_nodes),
4794 selected_nodes, total_nodes, when_selected);
4795 }
4796 }
4797 }
4799 /*
4800 * returns a *copy* of the curve of that object.
4801 */
4802 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4803 if (!object)
4804 return NULL;
4806 SPCurve *curve = NULL;
4807 if (SP_IS_PATH(object)) {
4808 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4809 curve = curve_new->copy();
4810 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4811 const gchar *svgd = object->repr->attribute(key);
4812 if (svgd) {
4813 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4814 SPCurve *curve_new = new SPCurve(pv);
4815 if (curve_new) {
4816 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4817 }
4818 }
4819 }
4821 return curve;
4822 }
4824 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4825 if (!np || !np->object || !curve)
4826 return;
4828 if (SP_IS_PATH(np->object)) {
4829 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4830 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4831 } else {
4832 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4833 }
4834 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4835 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( LIVEPATHEFFECT(np->object)->lpe->getParameter(np->repr_key) );
4836 if (pathparam) {
4837 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
4838 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4839 }
4840 }
4841 }
4843 /**
4844 SPCanvasItem *
4845 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
4846 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
4847 }
4848 **/
4850 /**
4851 SPCanvasItem *
4852 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4853 SPCurve *flash_curve = curve->copy();
4854 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4855 flash_curve->transform(i2d);
4856 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4857 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4858 // unless we also flash the nodes...
4859 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4860 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4861 sp_canvas_item_show(canvasitem);
4862 flash_curve->unref();
4863 return canvasitem;
4864 }
4866 SPCanvasItem *
4867 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4868 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4869 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4870 }
4871 **/
4873 SPCanvasItem *
4874 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
4875 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
4876 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
4877 flash_curve->transform(i2d);
4878 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4879 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4880 // unless we also flash the nodes...
4881 guint32 color = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
4882 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4883 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4884 sp_canvas_item_show(canvasitem);
4885 flash_curve->unref();
4886 return canvasitem;
4887 }
4889 // TODO: Merge this with sp_nodepath_make_helper_item()!
4890 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4891 np->show_helperpath = show;
4893 if (show) {
4894 SPCurve *helper_curve = np->curve->copy();
4895 helper_curve->transform(to_2geom(np->i2d));
4896 if (!np->helper_path) {
4897 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
4899 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4900 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);
4901 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4902 sp_canvas_item_move_to_z(np->helper_path, 0);
4903 sp_canvas_item_show(np->helper_path);
4904 } else {
4905 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4906 }
4907 helper_curve->unref();
4908 } else {
4909 if (np->helper_path) {
4910 GtkObject *temp = np->helper_path;
4911 np->helper_path = NULL;
4912 gtk_object_destroy(temp);
4913 }
4914 }
4915 }
4917 /* sp_nodepath_make_straight_path:
4918 * Prevents user from curving the path by dragging a segment or activating handles etc.
4919 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4920 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4921 */
4922 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4923 np->straight_path = true;
4924 np->show_handles = false;
4925 g_message("add code to make the path straight.");
4926 // do sp_nodepath_convert_node_type on all nodes?
4927 // coding tip: search for this text : "Make selected segments lines"
4928 }
4930 /*
4931 Local Variables:
4932 mode:c++
4933 c-file-style:"stroustrup"
4934 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4935 indent-tabs-mode:nil
4936 fill-column:99
4937 End:
4938 */
4939 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :