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