1 #define __SP_NODEPATH_C__
3 /** \file
4 * Path handler in node edit mode
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * Portions of this code are in public domain; node sculpting functions written by bulia byak are under GNU GPL
11 */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "display/canvas-bpath.h"
19 #include "display/curve.h"
20 #include "display/sp-ctrlline.h"
21 #include "display/sodipodi-ctrl.h"
22 #include "display/sp-canvas-util.h"
23 #include <glibmm/i18n.h>
24 #include <2geom/pathvector.h>
25 #include <2geom/sbasis-to-bezier.h>
26 #include <2geom/bezier-curve.h>
27 #include <2geom/hvlinesegment.h>
28 #include "helper/units.h"
29 #include "helper/geom.h"
30 #include "knot.h"
31 #include "inkscape.h"
32 #include "document.h"
33 #include "sp-namedview.h"
34 #include "desktop.h"
35 #include "desktop-handles.h"
36 #include "snap.h"
37 #include "message-stack.h"
38 #include "message-context.h"
39 #include "node-context.h"
40 #include "lpe-tool-context.h"
41 #include "shape-editor.h"
42 #include "selection-chemistry.h"
43 #include "selection.h"
44 #include "xml/repr.h"
45 #include "prefs-utils.h"
46 #include "sp-metrics.h"
47 #include "sp-path.h"
48 #include "libnr/nr-matrix-ops.h"
49 #include "svg/svg.h"
50 #include "verbs.h"
51 #include "display/bezier-utils.h"
52 #include <vector>
53 #include <algorithm>
54 #include <cstring>
55 #include <cmath>
56 #include <string>
57 #include "live_effects/lpeobject.h"
58 #include "live_effects/lpeobject-reference.h"
59 #include "live_effects/effect.h"
60 #include "live_effects/parameter/parameter.h"
61 #include "live_effects/parameter/path.h"
62 #include "util/mathfns.h"
63 #include "display/snap-indicator.h"
64 #include "snapped-point.h"
66 class NR::Matrix;
68 /// \todo
69 /// evil evil evil. FIXME: conflict of two different Path classes!
70 /// There is a conflict in the namespace between two classes named Path.
71 /// #include "sp-flowtext.h"
72 /// #include "sp-flowregion.h"
74 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
75 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
76 GType sp_flowregion_get_type (void);
77 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
78 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
79 GType sp_flowtext_get_type (void);
80 // end evil workaround
82 #include "helper/stlport.h"
85 /// \todo fixme: Implement these via preferences */
87 #define NODE_FILL 0xbfbfbf00
88 #define NODE_STROKE 0x000000ff
89 #define NODE_FILL_HI 0xff000000
90 #define NODE_STROKE_HI 0x000000ff
91 #define NODE_FILL_SEL 0x0000ffff
92 #define NODE_STROKE_SEL 0x000000ff
93 #define NODE_FILL_SEL_HI 0xff000000
94 #define NODE_STROKE_SEL_HI 0x000000ff
95 #define KNOT_FILL 0xffffffff
96 #define KNOT_STROKE 0x000000ff
97 #define KNOT_FILL_HI 0xff000000
98 #define KNOT_STROKE_HI 0x000000ff
100 static GMemChunk *nodechunk = NULL;
102 /* Creation from object */
104 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t);
105 static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length);
107 /* Object updating */
109 static void stamp_repr(Inkscape::NodePath::Path *np);
110 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
111 static gchar *create_typestr(Inkscape::NodePath::Path *np);
113 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
115 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
117 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
119 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
121 /* Adjust handle placement, if the node or the other handle is moved */
122 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
123 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
125 /* Node event callbacks */
126 static void node_clicked(SPKnot *knot, guint state, gpointer data);
127 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
128 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
129 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
131 /* Handle event callbacks */
132 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
133 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
134 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
135 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
136 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
137 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
139 /* Constructors and destructors */
141 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
142 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
143 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
144 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
145 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
146 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
147 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
149 /* Helpers */
151 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
152 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
153 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
155 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
156 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
158 // active_node indicates mouseover node
159 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
161 static SPCanvasItem *
162 sp_nodepath_make_helper_item(Inkscape::NodePath::Path *np, /*SPDesktop *desktop, */const SPCurve *curve, bool show = false) {
163 SPCurve *helper_curve = curve->copy();
164 helper_curve->transform(np->i2d);
165 SPCanvasItem *helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
166 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
167 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(helper_path), 0, SP_WIND_RULE_NONZERO);
168 sp_canvas_item_move_to_z(helper_path, 0);
169 if (show) {
170 sp_canvas_item_show(helper_path);
171 }
172 helper_curve->unref();
173 return helper_path;
174 }
176 static SPCanvasItem *
177 canvasitem_from_pathvec(Inkscape::NodePath::Path *np, Geom::PathVector const &pathv, bool show) {
178 SPCurve *helper_curve = new SPCurve(pathv);
179 return sp_nodepath_make_helper_item(np, helper_curve, show);
180 }
182 static void
183 sp_nodepath_create_helperpaths(Inkscape::NodePath::Path *np) {
184 //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> >* helper_path_vec;
185 if (!SP_IS_LPE_ITEM(np->item)) {
186 g_print ("Only LPEItems can have helperpaths!\n");
187 return;
188 }
190 SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
191 PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
192 for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
193 Inkscape::LivePathEffect::LPEObjectReference *lperef = (*i);
194 Inkscape::LivePathEffect::Effect *lpe = lperef->lpeobject->lpe;
195 // create new canvas items from the effect's helper paths
196 std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
197 for (std::vector<Geom::PathVector>::iterator j = hpaths.begin(); j != hpaths.end(); ++j) {
198 (*np->helper_path_vec)[lpe].push_back(canvasitem_from_pathvec(np, *j, true));
199 }
200 }
201 }
203 void
204 sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np) {
205 //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> >* helper_path_vec;
206 if (!SP_IS_LPE_ITEM(np->item)) {
207 g_print ("Only LPEItems can have helperpaths!\n");
208 return;
209 }
211 SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
212 PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
213 for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
214 Inkscape::LivePathEffect::Effect *lpe = (*i)->lpeobject->lpe;
215 /* update canvas items from the effect's helper paths; note that this code relies on the
216 * fact that getHelperPaths() will always return the same number of helperpaths in the same
217 * order as during their creation in sp_nodepath_create_helperpaths
218 */
219 std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
220 for (unsigned int j = 0; j < hpaths.size(); ++j) {
221 SPCurve *curve = new SPCurve(hpaths[j]);
222 curve->transform(np->i2d);
223 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(((*np->helper_path_vec)[lpe])[j]), curve);
224 curve = curve->unref();
225 }
226 }
227 }
229 static void
230 sp_nodepath_destroy_helperpaths(Inkscape::NodePath::Path *np) {
231 for (HelperPathList::iterator i = np->helper_path_vec->begin(); i != np->helper_path_vec->end(); ++i) {
232 for (std::vector<SPCanvasItem *>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j) {
233 GtkObject *temp = *j;
234 *j = NULL;
235 gtk_object_destroy(temp);
236 }
237 }
238 }
241 /**
242 * \brief Creates new nodepath from item
243 */
244 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
245 {
246 Inkscape::XML::Node *repr = object->repr;
248 /** \todo
249 * FIXME: remove this. We don't want to edit paths inside flowtext.
250 * Instead we will build our flowtext with cloned paths, so that the
251 * real paths are outside the flowtext and thus editable as usual.
252 */
253 if (SP_IS_FLOWTEXT(object)) {
254 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
255 if SP_IS_FLOWREGION(child) {
256 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
257 if (grandchild && SP_IS_PATH(grandchild)) {
258 object = SP_ITEM(grandchild);
259 break;
260 }
261 }
262 }
263 }
265 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
267 if (curve == NULL) {
268 return NULL;
269 }
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 = 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 = pathv_to_linear_and_cubic_beziers(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;
379 }
381 while (np->subpaths) {
382 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
383 }
385 //Inform the ShapeEditor that made me, if any, that I am gone.
386 if (np->shape_editor)
387 np->shape_editor->nodepath_destroyed();
389 g_assert(!np->selected);
391 if (np->helper_path) {
392 GtkObject *temp = np->helper_path;
393 np->helper_path = NULL;
394 gtk_object_destroy(temp);
395 }
396 if (np->curve) {
397 np->curve->unref();
398 np->curve = NULL;
399 }
401 if (np->repr_key) {
402 g_free(np->repr_key);
403 np->repr_key = NULL;
404 }
405 if (np->repr_nodetypes_key) {
406 g_free(np->repr_nodetypes_key);
407 np->repr_nodetypes_key = NULL;
408 }
410 sp_nodepath_destroy_helperpaths(np);
411 delete np->helper_path_vec;
412 np->helper_path_vec = NULL;
414 np->desktop = NULL;
416 g_free(np);
417 }
419 /**
420 * Return the node count of a given NodeSubPath.
421 */
422 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
423 {
424 int nodeCount = 0;
426 if (subpath) {
427 nodeCount = g_list_length(subpath->nodes);
428 }
430 return nodeCount;
431 }
433 /**
434 * Return the node count of a given NodePath.
435 */
436 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
437 {
438 gint nodeCount = 0;
439 if (np) {
440 for (GList *item = np->subpaths ; item ; item=item->next) {
441 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
442 nodeCount += g_list_length(subpath->nodes);
443 }
444 }
445 return nodeCount;
446 }
448 /**
449 * Return the subpath count of a given NodePath.
450 */
451 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
452 {
453 gint nodeCount = 0;
454 if (np) {
455 nodeCount = g_list_length(np->subpaths);
456 }
457 return nodeCount;
458 }
460 /**
461 * Return the selected node count of a given NodePath.
462 */
463 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
464 {
465 gint nodeCount = 0;
466 if (np) {
467 nodeCount = g_list_length(np->selected);
468 }
469 return nodeCount;
470 }
472 /**
473 * Return the number of subpaths where nodes are selected in a given NodePath.
474 */
475 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
476 {
477 gint nodeCount = 0;
478 if (np && np->selected) {
479 if (!np->selected->next) {
480 nodeCount = 1;
481 } else {
482 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
483 Inkscape::NodePath::SubPath *subpath = static_cast<Inkscape::NodePath::SubPath *>(spl->data);
484 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
485 Inkscape::NodePath::Node *node = static_cast<Inkscape::NodePath::Node *>(nl->data);
486 if (node->selected) {
487 nodeCount++;
488 break;
489 }
490 }
491 }
492 }
493 }
494 return nodeCount;
495 }
497 /**
498 * Clean up a nodepath after editing.
499 *
500 * Currently we are deleting trivial subpaths.
501 */
502 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
503 {
504 GList *badSubPaths = NULL;
506 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
507 for (GList *l = nodepath->subpaths; l ; l=l->next) {
508 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
509 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
510 badSubPaths = g_list_append(badSubPaths, sp);
511 }
513 //Delete them. This second step is because sp_nodepath_subpath_destroy()
514 //also removes the subpath from nodepath->subpaths
515 for (GList *l = badSubPaths; l ; l=l->next) {
516 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
517 sp_nodepath_subpath_destroy(sp);
518 }
520 g_list_free(badSubPaths);
521 }
523 /**
524 * Create new nodepaths from pathvector, make it subpaths of np.
525 * \param t The node type array.
526 */
527 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
528 {
529 guint i = 0; // index into node type array
530 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
531 if (pit->empty())
532 continue; // don't add single knot paths
534 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
536 NR::Point ppos = pit->initialPoint() * (Geom::Matrix)np->i2d;
537 NRPathcode pcode = NR_MOVETO;
539 /* Johan: Note that this is pretty arcane code. I am pretty sure it is working correctly, be very certain to change it! (better to just rewrite this whole method)*/
540 for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
541 if( dynamic_cast<Geom::LineSegment const*>(&*cit) ||
542 dynamic_cast<Geom::HLineSegment const*>(&*cit) ||
543 dynamic_cast<Geom::VLineSegment const*>(&*cit) )
544 {
545 NR::Point pos = cit->initialPoint() * (Geom::Matrix)np->i2d;
546 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
548 ppos = cit->finalPoint() * (Geom::Matrix)np->i2d;
549 pcode = NR_LINETO;
550 }
551 else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&*cit)) {
552 std::vector<Geom::Point> points = cubic_bezier->points();
553 NR::Point pos = points[0] * (Geom::Matrix)np->i2d;
554 NR::Point npos = points[1] * (Geom::Matrix)np->i2d;
555 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
557 ppos = points[2] * (Geom::Matrix)np->i2d;
558 pcode = NR_CURVETO;
559 }
560 }
562 if (pit->closed()) {
563 // Add last knot (because sp_nodepath_subpath_close kills the last knot)
564 /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
565 * If the length is zero, don't add it to the nodepath. */
566 Geom::Curve const &closing_seg = pit->back_closed();
567 if ( ! closing_seg.isDegenerate() ) {
568 NR::Point pos = closing_seg.finalPoint() * (Geom::Matrix)np->i2d;
569 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
570 }
572 sp_nodepath_subpath_close(sp);
573 }
574 }
575 }
577 /**
578 * Convert from sodipodi:nodetypes to new style type array.
579 */
580 static
581 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
582 {
583 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
585 guint pos = 0;
587 if (types) {
588 for (guint i = 0; types[i] && ( i < length ); i++) {
589 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
590 if (types[i] != '\0') {
591 switch (types[i]) {
592 case 's':
593 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
594 break;
595 case 'z':
596 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
597 break;
598 case 'c':
599 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
600 break;
601 default:
602 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
603 break;
604 }
605 }
606 }
607 }
609 while (pos < length) {
610 typestr[pos++] = Inkscape::NodePath::NODE_NONE;
611 }
613 return typestr;
614 }
616 /**
617 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
618 * updated but repr is not (for speed). Used during curve and node drag.
619 */
620 static void update_object(Inkscape::NodePath::Path *np)
621 {
622 g_assert(np);
624 np->curve->unref();
625 np->curve = create_curve(np);
627 sp_nodepath_set_curve(np, np->curve);
629 if (np->show_helperpath) {
630 SPCurve * helper_curve = np->curve->copy();
631 helper_curve->transform(np->i2d);
632 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
633 helper_curve->unref();
634 }
636 // updating helperpaths of LPEItems is now done in sp_lpe_item_update();
637 //sp_nodepath_update_helperpaths(np);
639 // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
640 // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
641 np->shape_editor->update_knotholder();
642 }
644 /**
645 * Update XML path node with data from path object.
646 */
647 static void update_repr_internal(Inkscape::NodePath::Path *np)
648 {
649 g_assert(np);
651 Inkscape::XML::Node *repr = np->object->repr;
653 np->curve->unref();
654 np->curve = create_curve(np);
656 gchar *typestr = create_typestr(np);
657 gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
659 // determine if path has an effect applied and write to correct "d" attribute.
660 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
661 np->local_change++;
662 repr->setAttribute(np->repr_key, svgpath);
663 }
665 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
666 np->local_change++;
667 repr->setAttribute(np->repr_nodetypes_key, typestr);
668 }
670 g_free(svgpath);
671 g_free(typestr);
673 if (np->show_helperpath) {
674 SPCurve * helper_curve = np->curve->copy();
675 helper_curve->transform(np->i2d);
676 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
677 helper_curve->unref();
678 }
680 // TODO: do we need this call here? after all, update_object() should have been called just before
681 //sp_nodepath_update_helperpaths(np);
682 }
684 /**
685 * Update XML path node with data from path object, commit changes forever.
686 */
687 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
688 {
689 //fixme: np can be NULL, so check before proceeding
690 g_return_if_fail(np != NULL);
692 update_repr_internal(np);
693 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
695 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
696 annotation);
697 }
699 /**
700 * Update XML path node with data from path object, commit changes with undo.
701 */
702 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
703 {
704 update_repr_internal(np);
705 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
706 annotation);
707 }
709 /**
710 * Make duplicate of path, replace corresponding XML node in tree, commit.
711 */
712 static void stamp_repr(Inkscape::NodePath::Path *np)
713 {
714 g_assert(np);
716 Inkscape::XML::Node *old_repr = np->object->repr;
717 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
719 // remember the position of the item
720 gint pos = old_repr->position();
721 // remember parent
722 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
724 SPCurve *curve = create_curve(np);
725 gchar *typestr = create_typestr(np);
727 gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
729 new_repr->setAttribute(np->repr_key, svgpath);
730 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
732 // add the new repr to the parent
733 parent->appendChild(new_repr);
734 // move to the saved position
735 new_repr->setPosition(pos > 0 ? pos : 0);
737 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
738 _("Stamp"));
740 Inkscape::GC::release(new_repr);
741 g_free(svgpath);
742 g_free(typestr);
743 curve->unref();
744 }
746 /**
747 * Create curve from path.
748 */
749 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
750 {
751 SPCurve *curve = new SPCurve();
753 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
754 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
755 curve->moveto(sp->first->pos * np->d2i);
756 Inkscape::NodePath::Node *n = sp->first->n.other;
757 while (n) {
758 NR::Point const end_pt = n->pos * np->d2i;
759 switch (n->code) {
760 case NR_LINETO:
761 curve->lineto(end_pt);
762 break;
763 case NR_CURVETO:
764 curve->curveto(n->p.other->n.pos * np->d2i,
765 n->p.pos * np->d2i,
766 end_pt);
767 break;
768 default:
769 g_assert_not_reached();
770 break;
771 }
772 if (n != sp->last) {
773 n = n->n.other;
774 } else {
775 n = NULL;
776 }
777 }
778 if (sp->closed) {
779 curve->closepath();
780 }
781 }
783 return curve;
784 }
786 /**
787 * Convert path type string to sodipodi:nodetypes style.
788 */
789 static gchar *create_typestr(Inkscape::NodePath::Path *np)
790 {
791 gchar *typestr = g_new(gchar, 32);
792 gint len = 32;
793 gint pos = 0;
795 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
796 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
798 if (pos >= len) {
799 typestr = g_renew(gchar, typestr, len + 32);
800 len += 32;
801 }
803 typestr[pos++] = 'c';
805 Inkscape::NodePath::Node *n;
806 n = sp->first->n.other;
807 while (n) {
808 gchar code;
810 switch (n->type) {
811 case Inkscape::NodePath::NODE_CUSP:
812 code = 'c';
813 break;
814 case Inkscape::NodePath::NODE_SMOOTH:
815 code = 's';
816 break;
817 case Inkscape::NodePath::NODE_SYMM:
818 code = 'z';
819 break;
820 default:
821 g_assert_not_reached();
822 code = '\0';
823 break;
824 }
826 if (pos >= len) {
827 typestr = g_renew(gchar, typestr, len + 32);
828 len += 32;
829 }
831 typestr[pos++] = code;
833 if (n != sp->last) {
834 n = n->n.other;
835 } else {
836 n = NULL;
837 }
838 }
839 }
841 if (pos >= len) {
842 typestr = g_renew(gchar, typestr, len + 1);
843 len += 1;
844 }
846 typestr[pos++] = '\0';
848 return typestr;
849 }
851 // Returns different message contexts depending on the current context. This function should only
852 // be called when ec is either a SPNodeContext or SPLPEToolContext, thus we return NULL in all
853 // other cases.
854 static Inkscape::MessageContext *
855 get_message_context(SPEventContext *ec)
856 {
857 Inkscape::MessageContext *mc = 0;
859 if (SP_IS_NODE_CONTEXT(ec)) {
860 mc = SP_NODE_CONTEXT(ec)->_node_message_context;
861 } else if (SP_IS_LPETOOL_CONTEXT(ec)) {
862 mc = SP_LPETOOL_CONTEXT(ec)->_lpetool_message_context;
863 } else {
864 g_warning ("Nodepath should only be present in Node tool or Geometric tool.");
865 }
867 return mc;
868 }
870 /**
871 \brief Fills node and handle positions for three nodes, splitting line
872 marked by end at distance t.
873 */
874 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
875 {
876 g_assert(new_path != NULL);
877 g_assert(end != NULL);
879 g_assert(end->p.other == new_path);
880 Inkscape::NodePath::Node *start = new_path->p.other;
881 g_assert(start);
883 if (end->code == NR_LINETO) {
884 new_path->type =Inkscape::NodePath::NODE_CUSP;
885 new_path->code = NR_LINETO;
886 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
887 } else {
888 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
889 new_path->code = NR_CURVETO;
890 gdouble s = 1 - t;
891 for (int dim = 0; dim < 2; dim++) {
892 NR::Coord const f000 = start->pos[dim];
893 NR::Coord const f001 = start->n.pos[dim];
894 NR::Coord const f011 = end->p.pos[dim];
895 NR::Coord const f111 = end->pos[dim];
896 NR::Coord const f00t = s * f000 + t * f001;
897 NR::Coord const f01t = s * f001 + t * f011;
898 NR::Coord const f11t = s * f011 + t * f111;
899 NR::Coord const f0tt = s * f00t + t * f01t;
900 NR::Coord const f1tt = s * f01t + t * f11t;
901 NR::Coord const fttt = s * f0tt + t * f1tt;
902 start->n.pos[dim] = f00t;
903 new_path->p.pos[dim] = f0tt;
904 new_path->pos[dim] = fttt;
905 new_path->n.pos[dim] = f1tt;
906 end->p.pos[dim] = f11t;
907 }
908 }
909 }
911 /**
912 * Adds new node on direct line between two nodes, activates handles of all
913 * three nodes.
914 */
915 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
916 {
917 g_assert(end);
918 g_assert(end->subpath);
919 g_assert(g_list_find(end->subpath->nodes, end));
921 Inkscape::NodePath::Node *start = end->p.other;
922 g_assert( start->n.other == end );
923 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
924 end,
925 (NRPathcode)end->code == NR_LINETO?
926 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
927 (NRPathcode)end->code,
928 &start->pos, &start->pos, &start->n.pos);
929 sp_nodepath_line_midpoint(newnode, end, t);
931 sp_node_adjust_handles(start);
932 sp_node_update_handles(start);
933 sp_node_update_handles(newnode);
934 sp_node_adjust_handles(end);
935 sp_node_update_handles(end);
937 return newnode;
938 }
940 /**
941 \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
942 */
943 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
944 {
945 g_assert(node);
946 g_assert(node->subpath);
947 g_assert(g_list_find(node->subpath->nodes, node));
949 Inkscape::NodePath::Node* result = 0;
950 Inkscape::NodePath::SubPath *sp = node->subpath;
951 Inkscape::NodePath::Path *np = sp->nodepath;
953 if (sp->closed) {
954 sp_nodepath_subpath_open(sp, node);
955 result = sp->first;
956 } else if ( (node == sp->first) || (node == sp->last ) ){
957 // no break for end nodes
958 result = 0;
959 } else {
960 // create a new subpath
961 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
963 // duplicate the break node as start of the new subpath
964 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL,
965 static_cast<Inkscape::NodePath::NodeType>(node->type),
966 NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
968 // attach rest of curve to new node
969 g_assert(node->n.other);
970 newnode->n.other = node->n.other; node->n.other = NULL;
971 newnode->n.other->p.other = newnode;
972 newsubpath->last = sp->last;
973 sp->last = node;
974 node = newnode;
975 while (node->n.other) {
976 node = node->n.other;
977 node->subpath = newsubpath;
978 sp->nodes = g_list_remove(sp->nodes, node);
979 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
980 }
983 result = newnode;
984 }
985 return result;
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 }
1011 }
1013 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
1014 {
1015 node->p.pos = (node->pos + (node->pos - node->n.pos));
1016 }
1018 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1019 {
1020 node->n.pos = (node->pos + (node->pos - node->p.pos));
1021 }
1023 /**
1024 * Change line type at node, with side effects on neighbours.
1025 */
1026 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1027 {
1028 g_assert(end);
1029 g_assert(end->subpath);
1030 g_assert(end->p.other);
1032 if (end->code != static_cast<guint>(code) ) {
1033 Inkscape::NodePath::Node *start = end->p.other;
1035 end->code = code;
1037 if (code == NR_LINETO) {
1038 if (start->code == NR_LINETO) {
1039 sp_nodepath_set_node_type(start, Inkscape::NodePath::NODE_CUSP);
1040 }
1041 if (end->n.other) {
1042 if (end->n.other->code == NR_LINETO) {
1043 sp_nodepath_set_node_type(end, Inkscape::NodePath::NODE_CUSP);
1044 }
1045 }
1046 } else {
1047 NR::Point delta = end->pos - start->pos;
1048 start->n.pos = start->pos + delta / 3;
1049 end->p.pos = end->pos - delta / 3;
1050 sp_node_adjust_handle(start, 1);
1051 sp_node_adjust_handle(end, -1);
1052 }
1054 sp_node_update_handles(start);
1055 sp_node_update_handles(end);
1056 }
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 // TODO clean up multiple returns
1105 Inkscape::NodePath::Node *othernode = side->other;
1106 if (!othernode)
1107 return false;
1108 NRPathcode const code = sp_node_path_code_from_side(node, side);
1109 if (code == NR_LINETO)
1110 return true;
1111 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1112 if (&node->p == side) {
1113 other_to_me = &othernode->n;
1114 } else if (&node->n == side) {
1115 other_to_me = &othernode->p;
1116 }
1117 if (!other_to_me)
1118 return false;
1119 bool is_line =
1120 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1121 NR::L2(node->pos - side->pos) < 1e-6);
1122 return is_line;
1123 }
1125 /**
1126 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1127 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1128 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1129 * If already cusp and set to cusp, retracts handles.
1130 */
1131 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1132 {
1133 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1135 /*
1136 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1138 if (two_handles) {
1139 // do nothing, adjust_handles called via set_node_type will line them up
1140 } else if (one_handle) {
1141 if (opposite_to_handle_is_line) {
1142 if (lined_up) {
1143 // already half-smooth; pull opposite handle too making it fully smooth
1144 } else {
1145 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1146 }
1147 } else {
1148 // pull opposite handle in line with the existing one
1149 }
1150 } else if (no_handles) {
1151 if (both_segments_are_lines OR both_segments_are_curves) {
1152 //pull both handles
1153 } else {
1154 // pull the handle opposite to line segment, making node half-smooth
1155 }
1156 }
1157 */
1158 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1159 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1160 bool p_is_line = sp_node_side_is_line(node, &node->p);
1161 bool n_is_line = sp_node_side_is_line(node, &node->n);
1163 if (p_has_handle && n_has_handle) {
1164 // do nothing, adjust_handles will line them up
1165 } else if (p_has_handle || n_has_handle) {
1166 if (p_has_handle && n_is_line) {
1167 Radial line (node->n.other->pos - node->pos);
1168 Radial handle (node->pos - node->p.pos);
1169 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1170 // already half-smooth; pull opposite handle too making it fully smooth
1171 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1172 } else {
1173 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1174 }
1175 } else if (n_has_handle && p_is_line) {
1176 Radial line (node->p.other->pos - node->pos);
1177 Radial handle (node->pos - node->n.pos);
1178 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1179 // already half-smooth; pull opposite handle too making it fully smooth
1180 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1181 } else {
1182 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1183 }
1184 } else if (p_has_handle && node->n.other) {
1185 // pull n handle
1186 node->n.other->code = NR_CURVETO;
1187 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1188 NR::L2(node->p.pos - node->pos) :
1189 NR::L2(node->n.other->pos - node->pos) / 3;
1190 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1191 } else if (n_has_handle && node->p.other) {
1192 // pull p handle
1193 node->code = NR_CURVETO;
1194 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1195 NR::L2(node->n.pos - node->pos) :
1196 NR::L2(node->p.other->pos - node->pos) / 3;
1197 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1198 }
1199 } else if (!p_has_handle && !n_has_handle) {
1200 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1201 // no handles, but both segments are either lnes or curves:
1202 //pull both handles
1204 // convert both to curves:
1205 node->code = NR_CURVETO;
1206 node->n.other->code = NR_CURVETO;
1208 NR::Point leg_prev = node->pos - node->p.other->pos;
1209 NR::Point leg_next = node->pos - node->n.other->pos;
1211 double norm_leg_prev = L2(leg_prev);
1212 double norm_leg_next = L2(leg_next);
1214 NR::Point delta;
1215 if (norm_leg_next > 0.0) {
1216 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1217 (&delta)->normalize();
1218 }
1220 if (type == Inkscape::NodePath::NODE_SYMM) {
1221 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1222 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1223 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1224 } else {
1225 // length of handle is proportional to distance to adjacent node
1226 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1227 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1228 }
1230 } else {
1231 // pull the handle opposite to line segment, making it half-smooth
1232 if (p_is_line && node->n.other) {
1233 if (type != Inkscape::NodePath::NODE_SYMM) {
1234 // pull n handle
1235 node->n.other->code = NR_CURVETO;
1236 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1237 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1238 }
1239 } else if (n_is_line && node->p.other) {
1240 if (type != Inkscape::NodePath::NODE_SYMM) {
1241 // pull p handle
1242 node->code = NR_CURVETO;
1243 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1244 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1245 }
1246 }
1247 }
1248 }
1249 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1250 // cusping a cusp: retract nodes
1251 node->p.pos = node->pos;
1252 node->n.pos = node->pos;
1253 }
1255 sp_nodepath_set_node_type (node, type);
1256 }
1258 /**
1259 * Move node to point, and adjust its and neighbouring handles.
1260 */
1261 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1262 {
1263 NR::Point delta = p - node->pos;
1264 node->pos = p;
1266 node->p.pos += delta;
1267 node->n.pos += delta;
1269 Inkscape::NodePath::Node *node_p = NULL;
1270 Inkscape::NodePath::Node *node_n = NULL;
1272 if (node->p.other) {
1273 if (node->code == NR_LINETO) {
1274 sp_node_adjust_handle(node, 1);
1275 sp_node_adjust_handle(node->p.other, -1);
1276 node_p = node->p.other;
1277 }
1278 }
1279 if (node->n.other) {
1280 if (node->n.other->code == NR_LINETO) {
1281 sp_node_adjust_handle(node, -1);
1282 sp_node_adjust_handle(node->n.other, 1);
1283 node_n = node->n.other;
1284 }
1285 }
1287 // this function is only called from batch movers that will update display at the end
1288 // themselves, so here we just move all the knots without emitting move signals, for speed
1289 sp_node_update_handles(node, false);
1290 if (node_n) {
1291 sp_node_update_handles(node_n, false);
1292 }
1293 if (node_p) {
1294 sp_node_update_handles(node_p, false);
1295 }
1296 }
1298 /**
1299 * Call sp_node_moveto() for node selection and handle possible snapping.
1300 */
1301 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1302 bool const snap, bool constrained = false,
1303 Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point())
1304 {
1305 NR::Coord best = NR_HUGE;
1306 NR::Point delta(dx, dy);
1307 NR::Point best_pt = delta;
1308 Inkscape::SnappedPoint best_abs;
1310 if (snap) {
1311 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1312 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1313 * must provide that information. */
1315 // Build a list of the unselected nodes to which the snapper should snap
1316 std::vector<Geom::Point> unselected_nodes;
1317 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1318 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1319 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1320 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1321 if (!node->selected) {
1322 unselected_nodes.push_back(to_2geom(node->pos));
1323 }
1324 }
1325 }
1327 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1329 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1330 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1331 m.setup(nodepath->desktop, false, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1332 Inkscape::SnappedPoint s;
1333 if (constrained) {
1334 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1335 dedicated_constraint.setPoint(n->pos);
1336 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(n->pos + delta), dedicated_constraint);
1337 } else {
1338 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(n->pos + delta));
1339 }
1340 if (s.getSnapped() && (s.getDistance() < best)) {
1341 best = s.getDistance();
1342 best_abs = s;
1343 best_pt = from_2geom(s.getPoint()) - n->pos;
1344 }
1345 }
1347 if (best_abs.getSnapped()) {
1348 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1349 } else {
1350 nodepath->desktop->snapindicator->remove_snappoint();
1351 }
1352 }
1354 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1355 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1356 sp_node_moveto(n, n->pos + best_pt);
1357 }
1359 // do not update repr here so that node dragging is acceptably fast
1360 update_object(nodepath);
1361 }
1363 /**
1364 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1365 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1366 near x = 0.
1367 */
1368 double
1369 sculpt_profile (double x, double alpha, guint profile)
1370 {
1371 double result = 1;
1373 if (x >= 1) {
1374 result = 0;
1375 } else if (x <= 0) {
1376 result = 1;
1377 } else {
1378 switch (profile) {
1379 case SCULPT_PROFILE_LINEAR:
1380 result = 1 - x;
1381 break;
1382 case SCULPT_PROFILE_BELL:
1383 result = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1384 break;
1385 case SCULPT_PROFILE_ELLIPTIC:
1386 result = sqrt(1 - x*x);
1387 break;
1388 default:
1389 g_assert_not_reached();
1390 }
1391 }
1393 return result;
1394 }
1396 double
1397 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1398 {
1399 // extremely primitive for now, don't have time to look for the real one
1400 double lower = NR::L2(b - a);
1401 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1402 return (lower + upper)/2;
1403 }
1405 void
1406 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1407 {
1408 n->pos = n->origin + delta;
1409 n->n.pos = n->n.origin + delta_n;
1410 n->p.pos = n->p.origin + delta_p;
1411 sp_node_adjust_handles(n);
1412 sp_node_update_handles(n, false);
1413 }
1415 /**
1416 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1417 * on how far they are from the dragged node n.
1418 */
1419 static void
1420 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1421 {
1422 g_assert (n);
1423 g_assert (nodepath);
1424 g_assert (n->subpath->nodepath == nodepath);
1426 double pressure = n->knot->pressure;
1427 if (pressure == 0)
1428 pressure = 0.5; // default
1429 pressure = CLAMP (pressure, 0.2, 0.8);
1431 // map pressure to alpha = 1/5 ... 5
1432 double alpha = 1 - 2 * fabs(pressure - 0.5);
1433 if (pressure > 0.5)
1434 alpha = 1/alpha;
1436 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1438 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1439 // Only one subpath has selected nodes:
1440 // use linear mode, where the distance from n to node being dragged is calculated along the path
1442 double n_sel_range = 0, p_sel_range = 0;
1443 guint n_nodes = 0, p_nodes = 0;
1444 guint n_sel_nodes = 0, p_sel_nodes = 0;
1446 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1447 {
1448 double n_range = 0, p_range = 0;
1449 bool n_going = true, p_going = true;
1450 Inkscape::NodePath::Node *n_node = n;
1451 Inkscape::NodePath::Node *p_node = n;
1452 do {
1453 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1454 if (n_node && n_going)
1455 n_node = n_node->n.other;
1456 if (n_node == NULL) {
1457 n_going = false;
1458 } else {
1459 n_nodes ++;
1460 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1461 if (n_node->selected) {
1462 n_sel_nodes ++;
1463 n_sel_range = n_range;
1464 }
1465 if (n_node == p_node) {
1466 n_going = false;
1467 p_going = false;
1468 }
1469 }
1470 if (p_node && p_going)
1471 p_node = p_node->p.other;
1472 if (p_node == NULL) {
1473 p_going = false;
1474 } else {
1475 p_nodes ++;
1476 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1477 if (p_node->selected) {
1478 p_sel_nodes ++;
1479 p_sel_range = p_range;
1480 }
1481 if (p_node == n_node) {
1482 n_going = false;
1483 p_going = false;
1484 }
1485 }
1486 } while (n_going || p_going);
1487 }
1489 // Second pass: actually move nodes in this subpath
1490 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1491 {
1492 double n_range = 0, p_range = 0;
1493 bool n_going = true, p_going = true;
1494 Inkscape::NodePath::Node *n_node = n;
1495 Inkscape::NodePath::Node *p_node = n;
1496 do {
1497 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1498 if (n_node && n_going)
1499 n_node = n_node->n.other;
1500 if (n_node == NULL) {
1501 n_going = false;
1502 } else {
1503 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1504 if (n_node->selected) {
1505 sp_nodepath_move_node_and_handles (n_node,
1506 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1507 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1508 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1509 }
1510 if (n_node == p_node) {
1511 n_going = false;
1512 p_going = false;
1513 }
1514 }
1515 if (p_node && p_going)
1516 p_node = p_node->p.other;
1517 if (p_node == NULL) {
1518 p_going = false;
1519 } else {
1520 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1521 if (p_node->selected) {
1522 sp_nodepath_move_node_and_handles (p_node,
1523 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1524 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1525 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1526 }
1527 if (p_node == n_node) {
1528 n_going = false;
1529 p_going = false;
1530 }
1531 }
1532 } while (n_going || p_going);
1533 }
1535 } else {
1536 // Multiple subpaths have selected nodes:
1537 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1538 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1539 // fix the pear-like shape when sculpting e.g. a ring
1541 // First pass: calculate range
1542 gdouble direct_range = 0;
1543 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1544 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1545 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1546 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1547 if (node->selected) {
1548 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1549 }
1550 }
1551 }
1553 // Second pass: actually move nodes
1554 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1555 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1556 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1557 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1558 if (node->selected) {
1559 if (direct_range > 1e-6) {
1560 sp_nodepath_move_node_and_handles (node,
1561 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1562 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1563 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1564 } else {
1565 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1566 }
1568 }
1569 }
1570 }
1571 }
1573 // do not update repr here so that node dragging is acceptably fast
1574 update_object(nodepath);
1575 }
1578 /**
1579 * Move node selection to point, adjust its and neighbouring handles,
1580 * handle possible snapping, and commit the change with possible undo.
1581 */
1582 void
1583 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1584 {
1585 if (!nodepath) return;
1587 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1589 if (dx == 0) {
1590 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1591 } else if (dy == 0) {
1592 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1593 } else {
1594 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1595 }
1596 }
1598 /**
1599 * Move node selection off screen and commit the change.
1600 */
1601 void
1602 sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1603 {
1604 // borrowed from sp_selection_move_screen in selection-chemistry.c
1605 // we find out the current zoom factor and divide deltas by it
1607 gdouble zoom = desktop->current_zoom();
1608 gdouble zdx = dx / zoom;
1609 gdouble zdy = dy / zoom;
1611 if (!nodepath) return;
1613 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1615 if (dx == 0) {
1616 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1617 } else if (dy == 0) {
1618 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1619 } else {
1620 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1621 }
1622 }
1624 /**
1625 * Move selected nodes to the absolute position given
1626 */
1627 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
1628 {
1629 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1630 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1631 Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
1632 sp_node_moveto(n, npos);
1633 }
1635 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1636 }
1638 /**
1639 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1640 */
1641 boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1642 {
1643 boost::optional<Geom::Coord> no_coord;
1644 g_return_val_if_fail(nodepath->selected, no_coord);
1646 // determine coordinate of first selected node
1647 GList *nsel = nodepath->selected;
1648 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1649 NR::Coord coord = n->pos[axis];
1650 bool coincide = true;
1652 // compare it to the coordinates of all the other selected nodes
1653 for (GList *l = nsel->next; l != NULL; l = l->next) {
1654 n = (Inkscape::NodePath::Node *) l->data;
1655 if (n->pos[axis] != coord) {
1656 coincide = false;
1657 }
1658 }
1659 if (coincide) {
1660 return coord;
1661 } else {
1662 Geom::Rect bbox = sp_node_selected_bbox(nodepath);
1663 // currently we return the coordinate of the bounding box midpoint because I don't know how
1664 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1665 return bbox.midpoint()[axis];
1666 }
1667 }
1669 /** If they don't yet exist, creates knot and line for the given side of the node */
1670 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1671 {
1672 if (!side->knot) {
1673 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"));
1675 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1676 side->knot->setSize (7);
1677 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1678 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1679 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1680 sp_knot_update_ctrl(side->knot);
1682 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1683 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1684 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1685 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1686 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1687 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1688 }
1690 if (!side->line) {
1691 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1692 SP_TYPE_CTRLLINE, NULL);
1693 }
1694 }
1696 /**
1697 * Ensure the given handle of the node is visible/invisible, update its screen position
1698 */
1699 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1700 {
1701 g_assert(node != NULL);
1703 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1704 NRPathcode code = sp_node_path_code_from_side(node, side);
1706 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1708 if (show_handle) {
1709 if (!side->knot) { // No handle knot at all
1710 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1711 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1712 side->knot->pos = side->pos;
1713 if (side->knot->item)
1714 SP_CTRL(side->knot->item)->moveto(side->pos);
1715 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1716 sp_knot_show(side->knot);
1717 } else {
1718 if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1719 if (fire_move_signals) {
1720 sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1721 } else {
1722 sp_knot_moveto(side->knot, side->pos);
1723 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1724 }
1725 }
1726 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1727 sp_knot_show(side->knot);
1728 }
1729 }
1730 sp_canvas_item_show(side->line);
1731 } else {
1732 if (side->knot) {
1733 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1734 sp_knot_hide(side->knot);
1735 }
1736 }
1737 if (side->line) {
1738 sp_canvas_item_hide(side->line);
1739 }
1740 }
1741 }
1743 /**
1744 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1745 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1746 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1747 * updated; otherwise, just move the knots silently (used in batch moves).
1748 */
1749 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1750 {
1751 g_assert(node != NULL);
1753 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1754 sp_knot_show(node->knot);
1755 }
1757 if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1758 if (fire_move_signals)
1759 sp_knot_set_position(node->knot, node->pos, 0);
1760 else
1761 sp_knot_moveto(node->knot, node->pos);
1762 }
1764 gboolean show_handles = node->selected;
1765 if (node->p.other != NULL) {
1766 if (node->p.other->selected) show_handles = TRUE;
1767 }
1768 if (node->n.other != NULL) {
1769 if (node->n.other->selected) show_handles = TRUE;
1770 }
1772 if (node->subpath->nodepath->show_handles == false)
1773 show_handles = FALSE;
1775 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1776 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1777 }
1779 /**
1780 * Call sp_node_update_handles() for all nodes on subpath.
1781 */
1782 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1783 {
1784 g_assert(subpath != NULL);
1786 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1787 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1788 }
1789 }
1791 /**
1792 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1793 */
1794 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1795 {
1796 g_assert(nodepath != NULL);
1798 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1799 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1800 }
1801 }
1803 void
1804 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1805 {
1806 if (nodepath) {
1807 nodepath->show_handles = show;
1808 sp_nodepath_update_handles(nodepath);
1809 }
1810 }
1812 /**
1813 * Adds all selected nodes in nodepath to list.
1814 */
1815 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1816 {
1817 StlConv<Node *>::list(l, selected);
1818 /// \todo this adds a copying, rework when the selection becomes a stl list
1819 }
1821 /**
1822 * Align selected nodes on the specified axis.
1823 */
1824 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1825 {
1826 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1827 return;
1828 }
1830 if ( !nodepath->selected->next ) { // only one node selected
1831 return;
1832 }
1833 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1834 NR::Point dest(pNode->pos);
1835 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1836 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1837 if (pNode) {
1838 dest[axis] = pNode->pos[axis];
1839 sp_node_moveto(pNode, dest);
1840 }
1841 }
1843 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1844 }
1846 /// Helper struct.
1847 struct NodeSort
1848 {
1849 Inkscape::NodePath::Node *_node;
1850 NR::Coord _coord;
1851 /// \todo use vectorof pointers instead of calling copy ctor
1852 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1853 _node(node), _coord(node->pos[axis])
1854 {}
1856 };
1858 static bool operator<(NodeSort const &a, NodeSort const &b)
1859 {
1860 return (a._coord < b._coord);
1861 }
1863 /**
1864 * Distribute selected nodes on the specified axis.
1865 */
1866 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1867 {
1868 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1869 return;
1870 }
1872 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1873 return;
1874 }
1876 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1877 std::vector<NodeSort> sorted;
1878 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1879 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1880 if (pNode) {
1881 NodeSort n(pNode, axis);
1882 sorted.push_back(n);
1883 //dest[axis] = pNode->pos[axis];
1884 //sp_node_moveto(pNode, dest);
1885 }
1886 }
1887 std::sort(sorted.begin(), sorted.end());
1888 unsigned int len = sorted.size();
1889 //overall bboxes span
1890 float dist = (sorted.back()._coord -
1891 sorted.front()._coord);
1892 //new distance between each bbox
1893 float step = (dist) / (len - 1);
1894 float pos = sorted.front()._coord;
1895 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1896 it < sorted.end();
1897 it ++ )
1898 {
1899 NR::Point dest((*it)._node->pos);
1900 dest[axis] = pos;
1901 sp_node_moveto((*it)._node, dest);
1902 pos += step;
1903 }
1905 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1906 }
1909 /**
1910 * Call sp_nodepath_line_add_node() for all selected segments.
1911 */
1912 void
1913 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1914 {
1915 if (!nodepath) {
1916 return;
1917 }
1919 GList *nl = NULL;
1921 int n_added = 0;
1923 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1924 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1925 g_assert(t->selected);
1926 if (t->p.other && t->p.other->selected) {
1927 nl = g_list_prepend(nl, t);
1928 }
1929 }
1931 while (nl) {
1932 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1933 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1934 sp_nodepath_node_select(n, TRUE, FALSE);
1935 n_added ++;
1936 nl = g_list_remove(nl, t);
1937 }
1939 /** \todo fixme: adjust ? */
1940 sp_nodepath_update_handles(nodepath);
1942 if (n_added > 1) {
1943 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1944 } else if (n_added > 0) {
1945 sp_nodepath_update_repr(nodepath, _("Add node"));
1946 }
1948 sp_nodepath_update_statusbar(nodepath);
1949 }
1951 /**
1952 * Select segment nearest to point
1953 */
1954 void
1955 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1956 {
1957 if (!nodepath) {
1958 return;
1959 }
1961 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1962 Geom::PathVector const &pathv = curve->get_pathvector();
1963 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1965 // calculate index for nodepath's representation.
1966 unsigned int segment_index = floor(pvpos.t) + 1;
1967 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1968 segment_index += pathv[i].size() + 1;
1969 if (pathv[i].closed()) {
1970 segment_index += 1;
1971 }
1972 }
1974 curve->unref();
1976 //find segment to segment
1977 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
1979 //fixme: this can return NULL, so check before proceeding.
1980 g_return_if_fail(e != NULL);
1982 gboolean force = FALSE;
1983 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1984 force = TRUE;
1985 }
1986 sp_nodepath_node_select(e, (gboolean) toggle, force);
1987 if (e->p.other)
1988 sp_nodepath_node_select(e->p.other, TRUE, force);
1990 sp_nodepath_update_handles(nodepath);
1992 sp_nodepath_update_statusbar(nodepath);
1993 }
1995 /**
1996 * Add a node nearest to point
1997 */
1998 void
1999 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
2000 {
2001 if (!nodepath) {
2002 return;
2003 }
2005 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2006 Geom::PathVector const &pathv = curve->get_pathvector();
2007 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
2009 // calculate index for nodepath's representation.
2010 double int_part;
2011 double t = std::modf(pvpos.t, &int_part);
2012 unsigned int segment_index = (unsigned int)int_part + 1;
2013 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
2014 segment_index += pathv[i].size() + 1;
2015 if (pathv[i].closed()) {
2016 segment_index += 1;
2017 }
2018 }
2020 curve->unref();
2022 //find segment to split
2023 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2025 //don't know why but t seems to flip for lines
2026 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2027 t = 1.0 - t;
2028 }
2030 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2031 sp_nodepath_node_select(n, FALSE, TRUE);
2033 /* fixme: adjust ? */
2034 sp_nodepath_update_handles(nodepath);
2036 sp_nodepath_update_repr(nodepath, _("Add node"));
2038 sp_nodepath_update_statusbar(nodepath);
2039 }
2041 /*
2042 * Adjusts a segment so that t moves by a certain delta for dragging
2043 * converts lines to curves
2044 *
2045 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2046 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2047 */
2048 void
2049 sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, NR::Point delta)
2050 {
2051 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node);
2053 //fixme: e and e->p can be NULL, so check for those before proceeding
2054 g_return_if_fail(e != NULL);
2055 g_return_if_fail(&e->p != NULL);
2057 /* feel good is an arbitrary parameter that distributes the delta between handles
2058 * if t of the drag point is less than 1/6 distance form the endpoint only
2059 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2060 */
2061 double feel_good;
2062 if (t <= 1.0 / 6.0)
2063 feel_good = 0;
2064 else if (t <= 0.5)
2065 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2066 else if (t <= 5.0 / 6.0)
2067 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2068 else
2069 feel_good = 1;
2071 //if we're dragging a line convert it to a curve
2072 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2073 sp_nodepath_set_line_type(e, NR_CURVETO);
2074 }
2076 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2077 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2078 e->p.other->n.pos += offsetcoord0;
2079 e->p.pos += offsetcoord1;
2081 // adjust handles of adjacent nodes where necessary
2082 sp_node_adjust_handle(e,1);
2083 sp_node_adjust_handle(e->p.other,-1);
2085 sp_nodepath_update_handles(e->subpath->nodepath);
2087 update_object(e->subpath->nodepath);
2089 sp_nodepath_update_statusbar(e->subpath->nodepath);
2090 }
2093 /**
2094 * Call sp_nodepath_break() for all selected segments.
2095 */
2096 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2097 {
2098 if (!nodepath) return;
2100 GList *tempin = g_list_copy(nodepath->selected);
2101 GList *temp = NULL;
2102 for (GList *l = tempin; l != NULL; l = l->next) {
2103 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2104 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2105 if (nn == NULL) continue; // no break, no new node
2106 temp = g_list_prepend(temp, nn);
2107 }
2108 g_list_free(tempin);
2110 if (temp) {
2111 sp_nodepath_deselect(nodepath);
2112 }
2113 for (GList *l = temp; l != NULL; l = l->next) {
2114 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2115 }
2117 sp_nodepath_update_handles(nodepath);
2119 sp_nodepath_update_repr(nodepath, _("Break path"));
2120 }
2122 /**
2123 * Duplicate the selected node(s).
2124 */
2125 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2126 {
2127 if (!nodepath) {
2128 return;
2129 }
2131 GList *temp = NULL;
2132 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2133 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2134 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2135 if (nn == NULL) continue; // could not duplicate
2136 temp = g_list_prepend(temp, nn);
2137 }
2139 if (temp) {
2140 sp_nodepath_deselect(nodepath);
2141 }
2142 for (GList *l = temp; l != NULL; l = l->next) {
2143 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2144 }
2146 sp_nodepath_update_handles(nodepath);
2148 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2149 }
2151 /**
2152 * Internal function to join two nodes by merging them into one.
2153 */
2154 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2155 {
2156 /* a and b are endpoints */
2158 // if one of the two nodes is mouseovered, fix its position
2159 NR::Point c;
2160 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2161 c = a->pos;
2162 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2163 c = b->pos;
2164 } else {
2165 // otherwise, move joined node to the midpoint
2166 c = (a->pos + b->pos) / 2;
2167 }
2169 if (a->subpath == b->subpath) {
2170 Inkscape::NodePath::SubPath *sp = a->subpath;
2171 sp_nodepath_subpath_close(sp);
2172 sp_node_moveto (sp->first, c);
2174 sp_nodepath_update_handles(sp->nodepath);
2175 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2176 return;
2177 }
2179 /* a and b are separate subpaths */
2180 Inkscape::NodePath::SubPath *sa = a->subpath;
2181 Inkscape::NodePath::SubPath *sb = b->subpath;
2182 NR::Point p;
2183 Inkscape::NodePath::Node *n;
2184 NRPathcode code;
2185 if (a == sa->first) {
2186 // we will now reverse sa, so that a is its last node, not first, and drop that node
2187 p = sa->first->n.pos;
2188 code = (NRPathcode)sa->first->n.other->code;
2189 // create new subpath
2190 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2191 // create a first moveto node on it
2192 n = sa->last;
2193 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2194 n = n->p.other;
2195 if (n == sa->first) n = NULL;
2196 while (n) {
2197 // copy the rest of the nodes from sa to t, going backwards
2198 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2199 n = n->p.other;
2200 if (n == sa->first) n = NULL;
2201 }
2202 // replace sa with t
2203 sp_nodepath_subpath_destroy(sa);
2204 sa = t;
2205 } else if (a == sa->last) {
2206 // a is already last, just drop it
2207 p = sa->last->p.pos;
2208 code = (NRPathcode)sa->last->code;
2209 sp_nodepath_node_destroy(sa->last);
2210 } else {
2211 code = NR_END;
2212 g_assert_not_reached();
2213 }
2215 if (b == sb->first) {
2216 // copy all nodes from b to a, forward
2217 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2218 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2219 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2220 }
2221 } else if (b == sb->last) {
2222 // copy all nodes from b to a, backward
2223 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2224 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2225 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2226 }
2227 } else {
2228 g_assert_not_reached();
2229 }
2230 /* and now destroy sb */
2232 sp_nodepath_subpath_destroy(sb);
2234 sp_nodepath_update_handles(sa->nodepath);
2236 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2238 sp_nodepath_update_statusbar(nodepath);
2239 }
2241 /**
2242 * Internal function to join two nodes by adding a segment between them.
2243 */
2244 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2245 {
2246 if (a->subpath == b->subpath) {
2247 Inkscape::NodePath::SubPath *sp = a->subpath;
2249 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2250 sp->closed = TRUE;
2252 sp->first->p.other = sp->last;
2253 sp->last->n.other = sp->first;
2255 sp_node_handle_mirror_p_to_n(sp->last);
2256 sp_node_handle_mirror_n_to_p(sp->first);
2258 sp->first->code = sp->last->code;
2259 sp->first = sp->last;
2261 sp_nodepath_update_handles(sp->nodepath);
2263 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2265 return;
2266 }
2268 /* a and b are separate subpaths */
2269 Inkscape::NodePath::SubPath *sa = a->subpath;
2270 Inkscape::NodePath::SubPath *sb = b->subpath;
2272 Inkscape::NodePath::Node *n;
2273 NR::Point p;
2274 NRPathcode code;
2275 if (a == sa->first) {
2276 code = (NRPathcode) sa->first->n.other->code;
2277 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2278 n = sa->last;
2279 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2280 for (n = n->p.other; n != NULL; n = n->p.other) {
2281 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2282 }
2283 sp_nodepath_subpath_destroy(sa);
2284 sa = t;
2285 } else if (a == sa->last) {
2286 code = (NRPathcode)sa->last->code;
2287 } else {
2288 code = NR_END;
2289 g_assert_not_reached();
2290 }
2292 if (b == sb->first) {
2293 n = sb->first;
2294 sp_node_handle_mirror_p_to_n(sa->last);
2295 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2296 sp_node_handle_mirror_n_to_p(sa->last);
2297 for (n = n->n.other; n != NULL; n = n->n.other) {
2298 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2299 }
2300 } else if (b == sb->last) {
2301 n = sb->last;
2302 sp_node_handle_mirror_p_to_n(sa->last);
2303 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2304 sp_node_handle_mirror_n_to_p(sa->last);
2305 for (n = n->p.other; n != NULL; n = n->p.other) {
2306 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2307 }
2308 } else {
2309 g_assert_not_reached();
2310 }
2311 /* and now destroy sb */
2313 sp_nodepath_subpath_destroy(sb);
2315 sp_nodepath_update_handles(sa->nodepath);
2317 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2318 }
2320 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2322 /**
2323 * Internal function to handle joining two nodes.
2324 */
2325 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2326 {
2327 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2329 if (g_list_length(nodepath->selected) != 2) {
2330 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2331 return;
2332 }
2334 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2335 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2337 g_assert(a != b);
2338 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2339 // someone tried to join an orphan node (i.e. a single-node subpath).
2340 // this is not worth an error message, just fail silently.
2341 return;
2342 }
2344 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2345 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2346 return;
2347 }
2349 switch(mode) {
2350 case NODE_JOIN_ENDPOINTS:
2351 do_node_selected_join(nodepath, a, b);
2352 break;
2353 case NODE_JOIN_SEGMENT:
2354 do_node_selected_join_segment(nodepath, a, b);
2355 break;
2356 }
2357 }
2359 /**
2360 * Join two nodes by merging them into one.
2361 */
2362 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2363 {
2364 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2365 }
2367 /**
2368 * Join two nodes by adding a segment between them.
2369 */
2370 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2371 {
2372 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2373 }
2375 /**
2376 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2377 */
2378 void sp_node_delete_preserve(GList *nodes_to_delete)
2379 {
2380 GSList *nodepaths = NULL;
2382 while (nodes_to_delete) {
2383 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2384 Inkscape::NodePath::SubPath *sp = node->subpath;
2385 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2386 Inkscape::NodePath::Node *sample_cursor = NULL;
2387 Inkscape::NodePath::Node *sample_end = NULL;
2388 Inkscape::NodePath::Node *delete_cursor = node;
2389 bool just_delete = false;
2391 //find the start of this contiguous selection
2392 //move left to the first node that is not selected
2393 //or the start of the non-closed path
2394 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2395 delete_cursor = curr;
2396 }
2398 //just delete at the beginning of an open path
2399 if (!delete_cursor->p.other) {
2400 sample_cursor = delete_cursor;
2401 just_delete = true;
2402 } else {
2403 sample_cursor = delete_cursor->p.other;
2404 }
2406 //calculate points for each segment
2407 int rate = 5;
2408 float period = 1.0 / rate;
2409 std::vector<NR::Point> data;
2410 if (!just_delete) {
2411 data.push_back(sample_cursor->pos);
2412 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2413 //just delete at the end of an open path
2414 if (!sp->closed && curr == sp->last) {
2415 just_delete = true;
2416 break;
2417 }
2419 //sample points on the contiguous selected segment
2420 NR::Point *bez;
2421 bez = new NR::Point [4];
2422 bez[0] = curr->pos;
2423 bez[1] = curr->n.pos;
2424 bez[2] = curr->n.other->p.pos;
2425 bez[3] = curr->n.other->pos;
2426 for (int i=1; i<rate; i++) {
2427 gdouble t = i * period;
2428 NR::Point p = bezier_pt(3, bez, t);
2429 data.push_back(p);
2430 }
2431 data.push_back(curr->n.other->pos);
2433 sample_end = curr->n.other;
2434 //break if we've come full circle or hit the end of the selection
2435 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2436 break;
2437 }
2438 }
2439 }
2441 if (!just_delete) {
2442 //calculate the best fitting single segment and adjust the endpoints
2443 NR::Point *adata;
2444 adata = new NR::Point [data.size()];
2445 copy(data.begin(), data.end(), adata);
2447 NR::Point *bez;
2448 bez = new NR::Point [4];
2449 //would decreasing error create a better fitting approximation?
2450 gdouble error = 1.0;
2451 gint ret;
2452 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2454 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2455 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2456 //the resulting nodes behave as expected.
2457 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2458 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2459 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2460 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2462 //adjust endpoints
2463 sample_cursor->n.pos = bez[1];
2464 sample_end->p.pos = bez[2];
2465 }
2467 //destroy this contiguous selection
2468 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2469 Inkscape::NodePath::Node *temp = delete_cursor;
2470 if (delete_cursor->n.other == delete_cursor) {
2471 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2472 delete_cursor = NULL;
2473 } else {
2474 delete_cursor = delete_cursor->n.other;
2475 }
2476 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2477 sp_nodepath_node_destroy(temp);
2478 }
2480 sp_nodepath_update_handles(nodepath);
2482 if (!g_slist_find(nodepaths, nodepath))
2483 nodepaths = g_slist_prepend (nodepaths, nodepath);
2484 }
2486 for (GSList *i = nodepaths; i; i = i->next) {
2487 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2488 // different nodepaths will give us one undo event per nodepath
2489 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2491 // if the entire nodepath is removed, delete the selected object.
2492 if (nodepath->subpaths == NULL ||
2493 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2494 //at least 2
2495 sp_nodepath_get_node_count(nodepath) < 2) {
2496 SPDocument *document = sp_desktop_document (nodepath->desktop);
2497 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2498 //delete this nodepath's object, not the entire selection! (though at this time, this
2499 //does not matter)
2500 sp_selection_delete(nodepath->desktop);
2501 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2502 _("Delete nodes"));
2503 } else {
2504 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2505 sp_nodepath_update_statusbar(nodepath);
2506 }
2507 }
2509 g_slist_free (nodepaths);
2510 }
2512 /**
2513 * Delete one or more selected nodes.
2514 */
2515 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2516 {
2517 if (!nodepath) return;
2518 if (!nodepath->selected) return;
2520 /** \todo fixme: do it the right way */
2521 while (nodepath->selected) {
2522 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2523 sp_nodepath_node_destroy(node);
2524 }
2527 //clean up the nodepath (such as for trivial subpaths)
2528 sp_nodepath_cleanup(nodepath);
2530 sp_nodepath_update_handles(nodepath);
2532 // if the entire nodepath is removed, delete the selected object.
2533 if (nodepath->subpaths == NULL ||
2534 sp_nodepath_get_node_count(nodepath) < 2) {
2535 SPDocument *document = sp_desktop_document (nodepath->desktop);
2536 sp_selection_delete(nodepath->desktop);
2537 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2538 _("Delete nodes"));
2539 return;
2540 }
2542 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2544 sp_nodepath_update_statusbar(nodepath);
2545 }
2547 /**
2548 * Delete one or more segments between two selected nodes.
2549 * This is the code for 'split'.
2550 */
2551 void
2552 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2553 {
2554 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2555 Inkscape::NodePath::Node *curr, *next; //Iterators
2557 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2559 if (g_list_length(nodepath->selected) != 2) {
2560 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2561 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2562 return;
2563 }
2565 //Selected nodes, not inclusive
2566 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2567 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2569 if ( ( a==b) || //same node
2570 (a->subpath != b->subpath ) || //not the same path
2571 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2572 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2573 {
2574 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2575 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2576 return;
2577 }
2579 //###########################################
2580 //# BEGIN EDITS
2581 //###########################################
2582 //##################################
2583 //# CLOSED PATH
2584 //##################################
2585 if (a->subpath->closed) {
2588 gboolean reversed = FALSE;
2590 //Since we can go in a circle, we need to find the shorter distance.
2591 // a->b or b->a
2592 start = end = NULL;
2593 int distance = 0;
2594 int minDistance = 0;
2595 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2596 if (curr==b) {
2597 //printf("a to b:%d\n", distance);
2598 start = a;//go from a to b
2599 end = b;
2600 minDistance = distance;
2601 //printf("A to B :\n");
2602 break;
2603 }
2604 distance++;
2605 }
2607 //try again, the other direction
2608 distance = 0;
2609 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2610 if (curr==a) {
2611 //printf("b to a:%d\n", distance);
2612 if (distance < minDistance) {
2613 start = b; //we go from b to a
2614 end = a;
2615 reversed = TRUE;
2616 //printf("B to A\n");
2617 }
2618 break;
2619 }
2620 distance++;
2621 }
2624 //Copy everything from 'end' to 'start' to a new subpath
2625 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2626 for (curr=end ; curr ; curr=curr->n.other) {
2627 NRPathcode code = (NRPathcode) curr->code;
2628 if (curr == end)
2629 code = NR_MOVETO;
2630 sp_nodepath_node_new(t, NULL,
2631 (Inkscape::NodePath::NodeType)curr->type, code,
2632 &curr->p.pos, &curr->pos, &curr->n.pos);
2633 if (curr == start)
2634 break;
2635 }
2636 sp_nodepath_subpath_destroy(a->subpath);
2639 }
2643 //##################################
2644 //# OPEN PATH
2645 //##################################
2646 else {
2648 //We need to get the direction of the list between A and B
2649 //Can we walk from a to b?
2650 start = end = NULL;
2651 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2652 if (curr==b) {
2653 start = a; //did it! we go from a to b
2654 end = b;
2655 //printf("A to B\n");
2656 break;
2657 }
2658 }
2659 if (!start) {//didn't work? let's try the other direction
2660 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2661 if (curr==a) {
2662 start = b; //did it! we go from b to a
2663 end = a;
2664 //printf("B to A\n");
2665 break;
2666 }
2667 }
2668 }
2669 if (!start) {
2670 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2671 _("Cannot find path between nodes."));
2672 return;
2673 }
2677 //Copy everything after 'end' to a new subpath
2678 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2679 for (curr=end ; curr ; curr=curr->n.other) {
2680 NRPathcode code = (NRPathcode) curr->code;
2681 if (curr == end)
2682 code = NR_MOVETO;
2683 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2684 &curr->p.pos, &curr->pos, &curr->n.pos);
2685 }
2687 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2688 for (curr = start->n.other ; curr ; curr=next) {
2689 next = curr->n.other;
2690 sp_nodepath_node_destroy(curr);
2691 }
2693 }
2694 //###########################################
2695 //# END EDITS
2696 //###########################################
2698 //clean up the nodepath (such as for trivial subpaths)
2699 sp_nodepath_cleanup(nodepath);
2701 sp_nodepath_update_handles(nodepath);
2703 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2705 sp_nodepath_update_statusbar(nodepath);
2706 }
2708 /**
2709 * Call sp_nodepath_set_line() for all selected segments.
2710 */
2711 void
2712 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2713 {
2714 if (nodepath == NULL) return;
2716 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2717 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2718 g_assert(n->selected);
2719 if (n->p.other && n->p.other->selected) {
2720 sp_nodepath_set_line_type(n, code);
2721 }
2722 }
2724 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2725 }
2727 /**
2728 * Call sp_nodepath_convert_node_type() for all selected nodes.
2729 */
2730 void
2731 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2732 {
2733 if (nodepath == NULL) return;
2735 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2737 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2738 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2739 }
2741 sp_nodepath_update_repr(nodepath, _("Change node type"));
2742 }
2744 /**
2745 * Change select status of node, update its own and neighbour handles.
2746 */
2747 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2748 {
2749 node->selected = selected;
2751 if (selected) {
2752 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2753 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2754 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2755 sp_knot_update_ctrl(node->knot);
2756 } else {
2757 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2758 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2759 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2760 sp_knot_update_ctrl(node->knot);
2761 }
2763 sp_node_update_handles(node);
2764 if (node->n.other) sp_node_update_handles(node->n.other);
2765 if (node->p.other) sp_node_update_handles(node->p.other);
2766 }
2768 /**
2769 \brief Select a node
2770 \param node The node to select
2771 \param incremental If true, add to selection, otherwise deselect others
2772 \param override If true, always select this node, otherwise toggle selected status
2773 */
2774 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2775 {
2776 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2778 if (incremental) {
2779 if (override) {
2780 if (!g_list_find(nodepath->selected, node)) {
2781 nodepath->selected = g_list_prepend(nodepath->selected, node);
2782 }
2783 sp_node_set_selected(node, TRUE);
2784 } else { // toggle
2785 if (node->selected) {
2786 g_assert(g_list_find(nodepath->selected, node));
2787 nodepath->selected = g_list_remove(nodepath->selected, node);
2788 } else {
2789 g_assert(!g_list_find(nodepath->selected, node));
2790 nodepath->selected = g_list_prepend(nodepath->selected, node);
2791 }
2792 sp_node_set_selected(node, !node->selected);
2793 }
2794 } else {
2795 sp_nodepath_deselect(nodepath);
2796 nodepath->selected = g_list_prepend(nodepath->selected, node);
2797 sp_node_set_selected(node, TRUE);
2798 }
2800 sp_nodepath_update_statusbar(nodepath);
2801 }
2804 /**
2805 \brief Deselect all nodes in the nodepath
2806 */
2807 void
2808 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2809 {
2810 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2812 while (nodepath->selected) {
2813 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2814 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2815 }
2816 sp_nodepath_update_statusbar(nodepath);
2817 }
2819 /**
2820 \brief Select or invert selection of all nodes in the nodepath
2821 */
2822 void
2823 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2824 {
2825 if (!nodepath) return;
2827 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2828 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2829 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2830 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2831 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2832 }
2833 }
2834 }
2836 /**
2837 * If nothing selected, does the same as sp_nodepath_select_all();
2838 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2839 * (i.e., similar to "select all in layer", with the "selected" subpaths
2840 * being treated as "layers" in the path).
2841 */
2842 void
2843 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2844 {
2845 if (!nodepath) return;
2847 if (g_list_length (nodepath->selected) == 0) {
2848 sp_nodepath_select_all (nodepath, invert);
2849 return;
2850 }
2852 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2853 GSList *subpaths = NULL;
2855 for (GList *l = copy; l != NULL; l = l->next) {
2856 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2857 Inkscape::NodePath::SubPath *subpath = n->subpath;
2858 if (!g_slist_find (subpaths, subpath))
2859 subpaths = g_slist_prepend (subpaths, subpath);
2860 }
2862 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2863 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2864 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2865 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2866 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2867 }
2868 }
2870 g_slist_free (subpaths);
2871 g_list_free (copy);
2872 }
2874 /**
2875 * \brief Select the node after the last selected; if none is selected,
2876 * select the first within path.
2877 */
2878 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2879 {
2880 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2882 Inkscape::NodePath::Node *last = NULL;
2883 if (nodepath->selected) {
2884 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2885 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2886 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2887 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2888 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2889 if (node->selected) {
2890 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2891 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2892 if (spl->next) { // there's a next subpath
2893 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2894 last = subpath_next->first;
2895 } else if (spl->prev) { // there's a previous subpath
2896 last = NULL; // to be set later to the first node of first subpath
2897 } else {
2898 last = node->n.other;
2899 }
2900 } else {
2901 last = node->n.other;
2902 }
2903 } else {
2904 if (node->n.other) {
2905 last = node->n.other;
2906 } else {
2907 if (spl->next) { // there's a next subpath
2908 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2909 last = subpath_next->first;
2910 } else if (spl->prev) { // there's a previous subpath
2911 last = NULL; // to be set later to the first node of first subpath
2912 } else {
2913 last = (Inkscape::NodePath::Node *) subpath->first;
2914 }
2915 }
2916 }
2917 }
2918 }
2919 }
2920 sp_nodepath_deselect(nodepath);
2921 }
2923 if (last) { // there's at least one more node after selected
2924 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2925 } else { // no more nodes, select the first one in first subpath
2926 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2927 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2928 }
2929 }
2931 /**
2932 * \brief Select the node before the first selected; if none is selected,
2933 * select the last within path
2934 */
2935 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2936 {
2937 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2939 Inkscape::NodePath::Node *last = NULL;
2940 if (nodepath->selected) {
2941 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2942 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2943 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2944 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2945 if (node->selected) {
2946 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2947 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2948 if (spl->prev) { // there's a prev subpath
2949 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2950 last = subpath_prev->last;
2951 } else if (spl->next) { // there's a next subpath
2952 last = NULL; // to be set later to the last node of last subpath
2953 } else {
2954 last = node->p.other;
2955 }
2956 } else {
2957 last = node->p.other;
2958 }
2959 } else {
2960 if (node->p.other) {
2961 last = node->p.other;
2962 } else {
2963 if (spl->prev) { // there's a prev subpath
2964 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2965 last = subpath_prev->last;
2966 } else if (spl->next) { // there's a next subpath
2967 last = NULL; // to be set later to the last node of last subpath
2968 } else {
2969 last = (Inkscape::NodePath::Node *) subpath->last;
2970 }
2971 }
2972 }
2973 }
2974 }
2975 }
2976 sp_nodepath_deselect(nodepath);
2977 }
2979 if (last) { // there's at least one more node before selected
2980 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2981 } else { // no more nodes, select the last one in last subpath
2982 GList *spl = g_list_last(nodepath->subpaths);
2983 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2984 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2985 }
2986 }
2988 /**
2989 * \brief Select all nodes that are within the rectangle.
2990 */
2991 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2992 {
2993 if (!incremental) {
2994 sp_nodepath_deselect(nodepath);
2995 }
2997 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2998 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2999 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3000 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3002 if (b.contains(node->pos)) {
3003 sp_nodepath_node_select(node, TRUE, TRUE);
3004 }
3005 }
3006 }
3007 }
3010 void
3011 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3012 {
3013 g_assert (n);
3014 g_assert (nodepath);
3015 g_assert (n->subpath->nodepath == nodepath);
3017 if (g_list_length (nodepath->selected) == 0) {
3018 if (grow > 0) {
3019 sp_nodepath_node_select(n, TRUE, TRUE);
3020 }
3021 return;
3022 }
3024 if (g_list_length (nodepath->selected) == 1) {
3025 if (grow < 0) {
3026 sp_nodepath_deselect (nodepath);
3027 return;
3028 }
3029 }
3031 double n_sel_range = 0, p_sel_range = 0;
3032 Inkscape::NodePath::Node *farthest_n_node = n;
3033 Inkscape::NodePath::Node *farthest_p_node = n;
3035 // Calculate ranges
3036 {
3037 double n_range = 0, p_range = 0;
3038 bool n_going = true, p_going = true;
3039 Inkscape::NodePath::Node *n_node = n;
3040 Inkscape::NodePath::Node *p_node = n;
3041 do {
3042 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3043 if (n_node && n_going)
3044 n_node = n_node->n.other;
3045 if (n_node == NULL) {
3046 n_going = false;
3047 } else {
3048 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3049 if (n_node->selected) {
3050 n_sel_range = n_range;
3051 farthest_n_node = n_node;
3052 }
3053 if (n_node == p_node) {
3054 n_going = false;
3055 p_going = false;
3056 }
3057 }
3058 if (p_node && p_going)
3059 p_node = p_node->p.other;
3060 if (p_node == NULL) {
3061 p_going = false;
3062 } else {
3063 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3064 if (p_node->selected) {
3065 p_sel_range = p_range;
3066 farthest_p_node = p_node;
3067 }
3068 if (p_node == n_node) {
3069 n_going = false;
3070 p_going = false;
3071 }
3072 }
3073 } while (n_going || p_going);
3074 }
3076 if (grow > 0) {
3077 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3078 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3079 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3080 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3081 }
3082 } else {
3083 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3084 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3085 } else if (farthest_p_node && farthest_p_node->selected) {
3086 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3087 }
3088 }
3089 }
3091 void
3092 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3093 {
3094 g_assert (n);
3095 g_assert (nodepath);
3096 g_assert (n->subpath->nodepath == nodepath);
3098 if (g_list_length (nodepath->selected) == 0) {
3099 if (grow > 0) {
3100 sp_nodepath_node_select(n, TRUE, TRUE);
3101 }
3102 return;
3103 }
3105 if (g_list_length (nodepath->selected) == 1) {
3106 if (grow < 0) {
3107 sp_nodepath_deselect (nodepath);
3108 return;
3109 }
3110 }
3112 Inkscape::NodePath::Node *farthest_selected = NULL;
3113 double farthest_dist = 0;
3115 Inkscape::NodePath::Node *closest_unselected = NULL;
3116 double closest_dist = NR_HUGE;
3118 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3119 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3120 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3121 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3122 if (node == n)
3123 continue;
3124 if (node->selected) {
3125 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3126 farthest_dist = NR::L2(node->pos - n->pos);
3127 farthest_selected = node;
3128 }
3129 } else {
3130 if (NR::L2(node->pos - n->pos) < closest_dist) {
3131 closest_dist = NR::L2(node->pos - n->pos);
3132 closest_unselected = node;
3133 }
3134 }
3135 }
3136 }
3138 if (grow > 0) {
3139 if (closest_unselected) {
3140 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3141 }
3142 } else {
3143 if (farthest_selected) {
3144 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3145 }
3146 }
3147 }
3150 /**
3151 \brief Saves all nodes' and handles' current positions in their origin members
3152 */
3153 void
3154 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3155 {
3156 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3157 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3158 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3159 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3160 n->origin = n->pos;
3161 n->p.origin = n->p.pos;
3162 n->n.origin = n->n.pos;
3163 }
3164 }
3165 }
3167 /**
3168 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3169 */
3170 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3171 {
3172 GList *r = NULL;
3173 if (nodepath->selected) {
3174 guint i = 0;
3175 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3176 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3177 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3178 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3179 i++;
3180 if (node->selected) {
3181 r = g_list_append(r, GINT_TO_POINTER(i));
3182 }
3183 }
3184 }
3185 }
3186 return r;
3187 }
3189 /**
3190 \brief Restores selection by selecting nodes whose positions are in the list
3191 */
3192 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3193 {
3194 sp_nodepath_deselect(nodepath);
3196 guint i = 0;
3197 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3198 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3199 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3200 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3201 i++;
3202 if (g_list_find(r, GINT_TO_POINTER(i))) {
3203 sp_nodepath_node_select(node, TRUE, TRUE);
3204 }
3205 }
3206 }
3207 }
3210 /**
3211 \brief Adjusts handle according to node type and line code.
3212 */
3213 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3214 {
3215 g_assert(node);
3217 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3218 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3220 // nothing to do if we are an end node
3221 if (me->other == NULL) return;
3222 if (other->other == NULL) return;
3224 // nothing to do if we are a cusp node
3225 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3227 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3228 NRPathcode mecode;
3229 if (which_adjust == 1) {
3230 mecode = (NRPathcode)me->other->code;
3231 } else {
3232 mecode = (NRPathcode)node->code;
3233 }
3234 if (mecode == NR_LINETO) return;
3236 if (sp_node_side_is_line(node, other)) {
3237 // other is a line, and we are either smooth or symm
3238 Inkscape::NodePath::Node *othernode = other->other;
3239 double len = NR::L2(me->pos - node->pos);
3240 NR::Point delta = node->pos - othernode->pos;
3241 double linelen = NR::L2(delta);
3242 if (linelen < 1e-18)
3243 return;
3244 me->pos = node->pos + (len / linelen)*delta;
3245 return;
3246 }
3248 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3249 // symmetrize
3250 me->pos = 2 * node->pos - other->pos;
3251 return;
3252 } else {
3253 // smoothify
3254 double len = NR::L2(me->pos - node->pos);
3255 NR::Point delta = other->pos - node->pos;
3256 double otherlen = NR::L2(delta);
3257 if (otherlen < 1e-18) return;
3258 me->pos = node->pos - (len / otherlen) * delta;
3259 }
3260 }
3262 /**
3263 \brief Adjusts both handles according to node type and line code
3264 */
3265 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3266 {
3267 g_assert(node);
3269 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3271 /* we are either smooth or symm */
3273 if (node->p.other == NULL) return;
3274 if (node->n.other == NULL) return;
3276 if (sp_node_side_is_line(node, &node->p)) {
3277 sp_node_adjust_handle(node, 1);
3278 return;
3279 }
3281 if (sp_node_side_is_line(node, &node->n)) {
3282 sp_node_adjust_handle(node, -1);
3283 return;
3284 }
3286 /* both are curves */
3287 NR::Point const delta( node->n.pos - node->p.pos );
3289 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3290 node->p.pos = node->pos - delta / 2;
3291 node->n.pos = node->pos + delta / 2;
3292 return;
3293 }
3295 /* We are smooth */
3296 double plen = NR::L2(node->p.pos - node->pos);
3297 if (plen < 1e-18) return;
3298 double nlen = NR::L2(node->n.pos - node->pos);
3299 if (nlen < 1e-18) return;
3300 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3301 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3302 }
3304 /**
3305 * Node event callback.
3306 */
3307 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3308 {
3309 gboolean ret = FALSE;
3310 switch (event->type) {
3311 case GDK_ENTER_NOTIFY:
3312 Inkscape::NodePath::Path::active_node = n;
3313 break;
3314 case GDK_LEAVE_NOTIFY:
3315 Inkscape::NodePath::Path::active_node = NULL;
3316 break;
3317 case GDK_SCROLL:
3318 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3319 switch (event->scroll.direction) {
3320 case GDK_SCROLL_UP:
3321 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3322 break;
3323 case GDK_SCROLL_DOWN:
3324 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3325 break;
3326 default:
3327 break;
3328 }
3329 ret = TRUE;
3330 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3331 switch (event->scroll.direction) {
3332 case GDK_SCROLL_UP:
3333 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3334 break;
3335 case GDK_SCROLL_DOWN:
3336 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3337 break;
3338 default:
3339 break;
3340 }
3341 ret = TRUE;
3342 }
3343 break;
3344 case GDK_KEY_PRESS:
3345 switch (get_group0_keyval (&event->key)) {
3346 case GDK_space:
3347 if (event->key.state & GDK_BUTTON1_MASK) {
3348 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3349 stamp_repr(nodepath);
3350 ret = TRUE;
3351 }
3352 break;
3353 case GDK_Page_Up:
3354 if (event->key.state & GDK_CONTROL_MASK) {
3355 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3356 } else {
3357 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3358 }
3359 break;
3360 case GDK_Page_Down:
3361 if (event->key.state & GDK_CONTROL_MASK) {
3362 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3363 } else {
3364 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3365 }
3366 break;
3367 default:
3368 break;
3369 }
3370 break;
3371 default:
3372 break;
3373 }
3375 return ret;
3376 }
3378 /**
3379 * Handle keypress on node; directly called.
3380 */
3381 gboolean node_key(GdkEvent *event)
3382 {
3383 Inkscape::NodePath::Path *np;
3385 // there is no way to verify nodes so set active_node to nil when deleting!!
3386 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3388 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3389 gint ret = FALSE;
3390 switch (get_group0_keyval (&event->key)) {
3391 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3392 case GDK_BackSpace:
3393 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3394 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3395 sp_nodepath_update_repr(np, _("Delete node"));
3396 Inkscape::NodePath::Path::active_node = NULL;
3397 ret = TRUE;
3398 break;
3399 case GDK_c:
3400 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3401 ret = TRUE;
3402 break;
3403 case GDK_s:
3404 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3405 ret = TRUE;
3406 break;
3407 case GDK_y:
3408 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3409 ret = TRUE;
3410 break;
3411 case GDK_b:
3412 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3413 ret = TRUE;
3414 break;
3415 }
3416 return ret;
3417 }
3418 return FALSE;
3419 }
3421 /**
3422 * Mouseclick on node callback.
3423 */
3424 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3425 {
3426 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3428 if (state & GDK_CONTROL_MASK) {
3429 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3431 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3432 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3433 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3434 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3435 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3436 } else {
3437 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3438 }
3439 sp_nodepath_update_repr(nodepath, _("Change node type"));
3440 sp_nodepath_update_statusbar(nodepath);
3442 } else { //ctrl+alt+click: delete node
3443 GList *node_to_delete = NULL;
3444 node_to_delete = g_list_append(node_to_delete, n);
3445 sp_node_delete_preserve(node_to_delete);
3446 }
3448 } else {
3449 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3450 }
3451 }
3453 /**
3454 * Mouse grabbed node callback.
3455 */
3456 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3457 {
3458 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3460 if (!n->selected) {
3461 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3462 }
3464 n->is_dragging = true;
3465 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3467 sp_nodepath_remember_origins (n->subpath->nodepath);
3468 }
3470 /**
3471 * Mouse ungrabbed node callback.
3472 */
3473 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3474 {
3475 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3477 n->dragging_out = NULL;
3478 n->is_dragging = false;
3479 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3481 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3482 }
3484 /**
3485 * The point on a line, given by its angle, closest to the given point.
3486 * \param p A point.
3487 * \param a Angle of the line; it is assumed to go through coordinate origin.
3488 * \param closest Pointer to the point struct where the result is stored.
3489 * \todo FIXME: use dot product perhaps?
3490 */
3491 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3492 {
3493 if (a == HUGE_VAL) { // vertical
3494 *closest = NR::Point(0, (*p)[NR::Y]);
3495 } else {
3496 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3497 (*closest)[NR::Y] = a * (*closest)[NR::X];
3498 }
3499 }
3501 /**
3502 * Distance from the point to a line given by its angle.
3503 * \param p A point.
3504 * \param a Angle of the line; it is assumed to go through coordinate origin.
3505 */
3506 static double point_line_distance(NR::Point *p, double a)
3507 {
3508 NR::Point c;
3509 point_line_closest(p, a, &c);
3510 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]));
3511 }
3513 /**
3514 * Callback for node "request" signal.
3515 * \todo fixme: This goes to "moved" event? (lauris)
3516 */
3517 static gboolean
3518 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3519 {
3520 double yn, xn, yp, xp;
3521 double an, ap, na, pa;
3522 double d_an, d_ap, d_na, d_pa;
3523 gboolean collinear = FALSE;
3524 NR::Point c;
3525 NR::Point pr;
3527 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3529 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3531 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3532 if ( (!n->subpath->nodepath->straight_path) &&
3533 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3534 || n->dragging_out ) )
3535 {
3536 NR::Point mouse = (*p);
3538 if (!n->dragging_out) {
3539 // This is the first drag-out event; find out which handle to drag out
3540 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3541 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3543 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3544 return FALSE;
3546 Inkscape::NodePath::NodeSide *opposite;
3547 if (appr_p > appr_n) { // closer to p
3548 n->dragging_out = &n->p;
3549 opposite = &n->n;
3550 n->code = NR_CURVETO;
3551 } else if (appr_p < appr_n) { // closer to n
3552 n->dragging_out = &n->n;
3553 opposite = &n->p;
3554 n->n.other->code = NR_CURVETO;
3555 } else { // p and n nodes are the same
3556 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3557 n->dragging_out = &n->p;
3558 opposite = &n->n;
3559 n->code = NR_CURVETO;
3560 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3561 n->dragging_out = &n->n;
3562 opposite = &n->p;
3563 n->n.other->code = NR_CURVETO;
3564 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3565 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);
3566 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);
3567 if (appr_other_p > appr_other_n) { // closer to other's p handle
3568 n->dragging_out = &n->n;
3569 opposite = &n->p;
3570 n->n.other->code = NR_CURVETO;
3571 } else { // closer to other's n handle
3572 n->dragging_out = &n->p;
3573 opposite = &n->n;
3574 n->code = NR_CURVETO;
3575 }
3576 }
3577 }
3579 // if there's another handle, make sure the one we drag out starts parallel to it
3580 if (opposite->pos != n->pos) {
3581 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3582 }
3584 // knots might not be created yet!
3585 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3586 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3587 }
3589 // pass this on to the handle-moved callback
3590 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3591 sp_node_update_handles(n);
3592 return TRUE;
3593 }
3595 if (state & GDK_CONTROL_MASK) { // constrained motion
3597 // calculate relative distances of handles
3598 // n handle:
3599 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3600 xn = n->n.pos[NR::X] - n->pos[NR::X];
3601 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3602 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3603 if (n->n.other) { // if there is the next point
3604 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3605 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3606 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3607 }
3608 }
3609 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3610 if (yn < 0) { xn = -xn; yn = -yn; }
3612 // p handle:
3613 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3614 xp = n->p.pos[NR::X] - n->pos[NR::X];
3615 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3616 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3617 if (n->p.other) {
3618 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3619 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3620 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3621 }
3622 }
3623 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3624 if (yp < 0) { xp = -xp; yp = -yp; }
3626 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3627 // sliding on handles, only if at least one of the handles is non-vertical
3628 // (otherwise it's the same as ctrl+drag anyway)
3630 // calculate angles of the handles
3631 if (xn == 0) {
3632 if (yn == 0) { // no handle, consider it the continuation of the other one
3633 an = 0;
3634 collinear = TRUE;
3635 }
3636 else an = 0; // vertical; set the angle to horizontal
3637 } else an = yn/xn;
3639 if (xp == 0) {
3640 if (yp == 0) { // no handle, consider it the continuation of the other one
3641 ap = an;
3642 }
3643 else ap = 0; // vertical; set the angle to horizontal
3644 } else ap = yp/xp;
3646 if (collinear) an = ap;
3648 // angles of the perpendiculars; HUGE_VAL means vertical
3649 if (an == 0) na = HUGE_VAL; else na = -1/an;
3650 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3652 // mouse point relative to the node's original pos
3653 pr = (*p) - n->origin;
3655 // distances to the four lines (two handles and two perpendiculars)
3656 d_an = point_line_distance(&pr, an);
3657 d_na = point_line_distance(&pr, na);
3658 d_ap = point_line_distance(&pr, ap);
3659 d_pa = point_line_distance(&pr, pa);
3661 // find out which line is the closest, save its closest point in c
3662 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3663 point_line_closest(&pr, an, &c);
3664 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3665 point_line_closest(&pr, ap, &c);
3666 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3667 point_line_closest(&pr, na, &c);
3668 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3669 point_line_closest(&pr, pa, &c);
3670 }
3672 // move the node to the closest point
3673 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3674 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3675 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3676 true);
3678 } else { // constraining to hor/vert
3680 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3681 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3682 (*p)[NR::X] - n->pos[NR::X],
3683 n->origin[NR::Y] - n->pos[NR::Y],
3684 true,
3685 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3686 } else { // snap to vert
3687 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3688 n->origin[NR::X] - n->pos[NR::X],
3689 (*p)[NR::Y] - n->pos[NR::Y],
3690 true,
3691 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3692 }
3693 }
3694 } else { // move freely
3695 if (n->is_dragging) {
3696 if (state & GDK_MOD1_MASK) { // sculpt
3697 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3698 } else {
3699 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3700 (*p)[NR::X] - n->pos[NR::X],
3701 (*p)[NR::Y] - n->pos[NR::Y],
3702 (state & GDK_SHIFT_MASK) == 0);
3703 }
3704 }
3705 }
3707 n->subpath->nodepath->desktop->scroll_to_point(p);
3709 return TRUE;
3710 }
3712 /**
3713 * Node handle clicked callback.
3714 */
3715 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3716 {
3717 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3719 if (state & GDK_CONTROL_MASK) { // "delete" handle
3720 if (n->p.knot == knot) {
3721 n->p.pos = n->pos;
3722 } else if (n->n.knot == knot) {
3723 n->n.pos = n->pos;
3724 }
3725 sp_node_update_handles(n);
3726 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3727 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3728 sp_nodepath_update_statusbar(nodepath);
3730 } else { // just select or add to selection, depending in Shift
3731 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3732 }
3733 }
3735 /**
3736 * Node handle grabbed callback.
3737 */
3738 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3739 {
3740 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3742 if (!n->selected) {
3743 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3744 }
3746 // remember the origin point of the handle
3747 if (n->p.knot == knot) {
3748 n->p.origin_radial = n->p.pos - n->pos;
3749 } else if (n->n.knot == knot) {
3750 n->n.origin_radial = n->n.pos - n->pos;
3751 } else {
3752 g_assert_not_reached();
3753 }
3755 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3756 }
3758 /**
3759 * Node handle ungrabbed callback.
3760 */
3761 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3762 {
3763 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3765 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3766 if (n->p.knot == knot) {
3767 n->p.origin_radial.a = 0;
3768 sp_knot_set_position(knot, n->p.pos, state);
3769 } else if (n->n.knot == knot) {
3770 n->n.origin_radial.a = 0;
3771 sp_knot_set_position(knot, n->n.pos, state);
3772 } else {
3773 g_assert_not_reached();
3774 }
3776 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3777 }
3779 /**
3780 * Node handle "request" signal callback.
3781 */
3782 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3783 {
3784 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3786 Inkscape::NodePath::NodeSide *me, *opposite;
3787 gint which;
3788 if (n->p.knot == knot) {
3789 me = &n->p;
3790 opposite = &n->n;
3791 which = -1;
3792 } else if (n->n.knot == knot) {
3793 me = &n->n;
3794 opposite = &n->p;
3795 which = 1;
3796 } else {
3797 me = opposite = NULL;
3798 which = 0;
3799 g_assert_not_reached();
3800 }
3802 SPDesktop *desktop = n->subpath->nodepath->desktop;
3803 SnapManager &m = desktop->namedview->snap_manager;
3804 m.setup(desktop, true, n->subpath->nodepath->item);
3805 Inkscape::SnappedPoint s;
3807 if ((state & GDK_SHIFT_MASK) != 0) {
3808 // We will not try to snap when the shift-key is pressed
3809 // so remove the old snap indicator and don't wait for it to time-out
3810 desktop->snapindicator->remove_snappoint();
3811 }
3813 Inkscape::NodePath::Node *othernode = opposite->other;
3814 if (othernode) {
3815 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3816 /* We are smooth node adjacent with line */
3817 NR::Point const delta = *p - n->pos;
3818 NR::Coord const len = NR::L2(delta);
3819 Inkscape::NodePath::Node *othernode = opposite->other;
3820 NR::Point const ndelta = n->pos - othernode->pos;
3821 NR::Coord const linelen = NR::L2(ndelta);
3822 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3823 NR::Coord const scal = dot(delta, ndelta) / linelen;
3824 (*p) = n->pos + (scal / linelen) * ndelta;
3825 }
3826 if ((state & GDK_SHIFT_MASK) == 0) {
3827 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(*p), Inkscape::Snapper::ConstraintLine(*p, ndelta));
3828 }
3829 } else {
3830 if ((state & GDK_SHIFT_MASK) == 0) {
3831 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(*p));
3832 }
3833 }
3834 } else {
3835 if ((state & GDK_SHIFT_MASK) == 0) {
3836 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(*p));
3837 }
3838 }
3840 Geom::Point pt2g = *p;
3841 s.getPoint(pt2g);
3842 *p = pt2g;
3844 sp_node_adjust_handle(n, -which);
3846 return FALSE;
3847 }
3849 /**
3850 * Node handle moved callback.
3851 */
3852 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3853 {
3854 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3856 Inkscape::NodePath::NodeSide *me;
3857 Inkscape::NodePath::NodeSide *other;
3858 if (n->p.knot == knot) {
3859 me = &n->p;
3860 other = &n->n;
3861 } else if (n->n.knot == knot) {
3862 me = &n->n;
3863 other = &n->p;
3864 } else {
3865 me = NULL;
3866 other = NULL;
3867 g_assert_not_reached();
3868 }
3870 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3871 Radial rme(me->pos - n->pos);
3872 Radial rother(other->pos - n->pos);
3873 Radial rnew(*p - n->pos);
3875 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3876 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3877 /* 0 interpreted as "no snapping". */
3879 // 1. Snap to the closest PI/snaps angle, starting from zero.
3880 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3882 // 2. Snap to the original angle, its opposite and perpendiculars
3883 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3884 /* The closest PI/2 angle, starting from original angle */
3885 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3887 // Snap to the closest.
3888 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3889 ? a_snapped
3890 : a_ortho );
3891 }
3893 // 3. Snap to the angle of the opposite line, if any
3894 Inkscape::NodePath::Node *othernode = other->other;
3895 if (othernode) {
3896 NR::Point other_to_snap(0,0);
3897 if (sp_node_side_is_line(n, other)) {
3898 other_to_snap = othernode->pos - n->pos;
3899 } else {
3900 other_to_snap = other->pos - n->pos;
3901 }
3902 if (NR::L2(other_to_snap) > 1e-3) {
3903 Radial rother_to_snap(other_to_snap);
3904 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3905 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3907 // Snap to the closest.
3908 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3909 ? a_snapped
3910 : a_oppo );
3911 }
3912 }
3914 rnew.a = a_snapped;
3915 }
3917 if (state & GDK_MOD1_MASK) {
3918 // lock handle length
3919 rnew.r = me->origin_radial.r;
3920 }
3922 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3923 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3924 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3925 rother.a += rnew.a - rme.a;
3926 other->pos = NR::Point(rother) + n->pos;
3927 if (other->knot) {
3928 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3929 sp_knot_moveto(other->knot, other->pos);
3930 }
3931 }
3933 me->pos = NR::Point(rnew) + n->pos;
3934 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3936 // move knot, but without emitting the signal:
3937 // we cannot emit a "moved" signal because we're now processing it
3938 sp_knot_moveto(me->knot, me->pos);
3940 update_object(n->subpath->nodepath);
3942 /* status text */
3943 SPDesktop *desktop = n->subpath->nodepath->desktop;
3944 if (!desktop) return;
3945 SPEventContext *ec = desktop->event_context;
3946 if (!ec) return;
3948 Inkscape::MessageContext *mc = get_message_context(ec);
3950 if (!mc) return;
3952 double degrees = 180 / M_PI * rnew.a;
3953 if (degrees > 180) degrees -= 360;
3954 if (degrees < -180) degrees += 360;
3955 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3956 degrees = angle_to_compass (degrees);
3958 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3960 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3961 _("<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);
3963 g_string_free(length, TRUE);
3964 }
3966 /**
3967 * Node handle event callback.
3968 */
3969 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3970 {
3971 gboolean ret = FALSE;
3972 switch (event->type) {
3973 case GDK_KEY_PRESS:
3974 switch (get_group0_keyval (&event->key)) {
3975 case GDK_space:
3976 if (event->key.state & GDK_BUTTON1_MASK) {
3977 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3978 stamp_repr(nodepath);
3979 ret = TRUE;
3980 }
3981 break;
3982 default:
3983 break;
3984 }
3985 break;
3986 case GDK_ENTER_NOTIFY:
3987 // we use an experimentally determined threshold that seems to work fine
3988 if (NR::L2(n->pos - knot->pos) < 0.75)
3989 Inkscape::NodePath::Path::active_node = n;
3990 break;
3991 case GDK_LEAVE_NOTIFY:
3992 // we use an experimentally determined threshold that seems to work fine
3993 if (NR::L2(n->pos - knot->pos) < 0.75)
3994 Inkscape::NodePath::Path::active_node = NULL;
3995 break;
3996 default:
3997 break;
3998 }
4000 return ret;
4001 }
4003 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
4004 Radial &rme, Radial &rother, gboolean const both)
4005 {
4006 rme.a += angle;
4007 if ( both
4008 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4009 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4010 {
4011 rother.a += angle;
4012 }
4013 }
4015 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
4016 Radial &rme, Radial &rother, gboolean const both)
4017 {
4018 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4020 gdouble r;
4021 if ( both
4022 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4023 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4024 {
4025 r = MAX(rme.r, rother.r);
4026 } else {
4027 r = rme.r;
4028 }
4030 gdouble const weird_angle = atan2(norm_angle, r);
4031 /* Bulia says norm_angle is just the visible distance that the
4032 * object's end must travel on the screen. Left as 'angle' for want of
4033 * a better name.*/
4035 rme.a += weird_angle;
4036 if ( both
4037 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4038 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4039 {
4040 rother.a += weird_angle;
4041 }
4042 }
4044 /**
4045 * Rotate one node.
4046 */
4047 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4048 {
4049 Inkscape::NodePath::NodeSide *me, *other;
4050 bool both = false;
4052 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4053 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4055 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4056 me = &(n->p);
4057 other = &(n->n);
4058 } else if (!n->p.other) {
4059 me = &(n->n);
4060 other = &(n->p);
4061 } else {
4062 if (which > 0) { // right handle
4063 if (xn > xp) {
4064 me = &(n->n);
4065 other = &(n->p);
4066 } else {
4067 me = &(n->p);
4068 other = &(n->n);
4069 }
4070 } else if (which < 0){ // left handle
4071 if (xn <= xp) {
4072 me = &(n->n);
4073 other = &(n->p);
4074 } else {
4075 me = &(n->p);
4076 other = &(n->n);
4077 }
4078 } else { // both handles
4079 me = &(n->n);
4080 other = &(n->p);
4081 both = true;
4082 }
4083 }
4085 Radial rme(me->pos - n->pos);
4086 Radial rother(other->pos - n->pos);
4088 if (screen) {
4089 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4090 } else {
4091 node_rotate_one_internal (*n, angle, rme, rother, both);
4092 }
4094 me->pos = n->pos + NR::Point(rme);
4096 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4097 other->pos = n->pos + NR::Point(rother);
4098 }
4100 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4101 // so here we just move all the knots without emitting move signals, for speed
4102 sp_node_update_handles(n, false);
4103 }
4105 /**
4106 * Rotate selected nodes.
4107 */
4108 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4109 {
4110 if (!nodepath || !nodepath->selected) return;
4112 if (g_list_length(nodepath->selected) == 1) {
4113 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4114 node_rotate_one (n, angle, which, screen);
4115 } else {
4116 // rotate as an object:
4118 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4119 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4120 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4121 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4122 box.expandTo (n->pos); // contain all selected nodes
4123 }
4125 gdouble rot;
4126 if (screen) {
4127 gdouble const zoom = nodepath->desktop->current_zoom();
4128 gdouble const zmove = angle / zoom;
4129 gdouble const r = NR::L2(box.max() - box.midpoint());
4130 rot = atan2(zmove, r);
4131 } else {
4132 rot = angle;
4133 }
4135 NR::Point rot_center;
4136 if (Inkscape::NodePath::Path::active_node == NULL)
4137 rot_center = box.midpoint();
4138 else
4139 rot_center = Inkscape::NodePath::Path::active_node->pos;
4141 NR::Matrix t =
4142 NR::Matrix (NR::translate(-rot_center)) *
4143 NR::Matrix (NR::rotate(rot)) *
4144 NR::Matrix (NR::translate(rot_center));
4146 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4147 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4148 n->pos *= t;
4149 n->n.pos *= t;
4150 n->p.pos *= t;
4151 sp_node_update_handles(n, false);
4152 }
4153 }
4155 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4156 }
4158 /**
4159 * Scale one node.
4160 */
4161 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4162 {
4163 bool both = false;
4164 Inkscape::NodePath::NodeSide *me, *other;
4166 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4167 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4169 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4170 me = &(n->p);
4171 other = &(n->n);
4172 n->code = NR_CURVETO;
4173 } else if (!n->p.other) {
4174 me = &(n->n);
4175 other = &(n->p);
4176 if (n->n.other)
4177 n->n.other->code = NR_CURVETO;
4178 } else {
4179 if (which > 0) { // right handle
4180 if (xn > xp) {
4181 me = &(n->n);
4182 other = &(n->p);
4183 if (n->n.other)
4184 n->n.other->code = NR_CURVETO;
4185 } else {
4186 me = &(n->p);
4187 other = &(n->n);
4188 n->code = NR_CURVETO;
4189 }
4190 } else if (which < 0){ // left handle
4191 if (xn <= xp) {
4192 me = &(n->n);
4193 other = &(n->p);
4194 if (n->n.other)
4195 n->n.other->code = NR_CURVETO;
4196 } else {
4197 me = &(n->p);
4198 other = &(n->n);
4199 n->code = NR_CURVETO;
4200 }
4201 } else { // both handles
4202 me = &(n->n);
4203 other = &(n->p);
4204 both = true;
4205 n->code = NR_CURVETO;
4206 if (n->n.other)
4207 n->n.other->code = NR_CURVETO;
4208 }
4209 }
4211 Radial rme(me->pos - n->pos);
4212 Radial rother(other->pos - n->pos);
4214 rme.r += grow;
4215 if (rme.r < 0) rme.r = 0;
4216 if (rme.a == HUGE_VAL) {
4217 if (me->other) { // if direction is unknown, initialize it towards the next node
4218 Radial rme_next(me->other->pos - n->pos);
4219 rme.a = rme_next.a;
4220 } else { // if there's no next, initialize to 0
4221 rme.a = 0;
4222 }
4223 }
4224 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4225 rother.r += grow;
4226 if (rother.r < 0) rother.r = 0;
4227 if (rother.a == HUGE_VAL) {
4228 rother.a = rme.a + M_PI;
4229 }
4230 }
4232 me->pos = n->pos + NR::Point(rme);
4234 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4235 other->pos = n->pos + NR::Point(rother);
4236 }
4238 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4239 // so here we just move all the knots without emitting move signals, for speed
4240 sp_node_update_handles(n, false);
4241 }
4243 /**
4244 * Scale selected nodes.
4245 */
4246 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4247 {
4248 if (!nodepath || !nodepath->selected) return;
4250 if (g_list_length(nodepath->selected) == 1) {
4251 // scale handles of the single selected node
4252 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4253 node_scale_one (n, grow, which);
4254 } else {
4255 // scale nodes as an "object":
4257 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4258 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4259 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4260 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4261 box.expandTo (n->pos); // contain all selected nodes
4262 }
4264 double scale = (box.maxExtent() + grow)/box.maxExtent();
4266 NR::Point scale_center;
4267 if (Inkscape::NodePath::Path::active_node == NULL)
4268 scale_center = box.midpoint();
4269 else
4270 scale_center = Inkscape::NodePath::Path::active_node->pos;
4272 NR::Matrix t =
4273 NR::Matrix (NR::translate(-scale_center)) *
4274 NR::Matrix (NR::scale(scale, scale)) *
4275 NR::Matrix (NR::translate(scale_center));
4277 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4278 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4279 n->pos *= t;
4280 n->n.pos *= t;
4281 n->p.pos *= t;
4282 sp_node_update_handles(n, false);
4283 }
4284 }
4286 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4287 }
4289 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4290 {
4291 if (!nodepath) return;
4292 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4293 }
4295 /**
4296 * Flip selected nodes horizontally/vertically.
4297 */
4298 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, boost::optional<NR::Point> center)
4299 {
4300 if (!nodepath || !nodepath->selected) return;
4302 if (g_list_length(nodepath->selected) == 1 && !center) {
4303 // flip handles of the single selected node
4304 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4305 double temp = n->p.pos[axis];
4306 n->p.pos[axis] = n->n.pos[axis];
4307 n->n.pos[axis] = temp;
4308 sp_node_update_handles(n, false);
4309 } else {
4310 // scale nodes as an "object":
4312 Geom::Rect box = sp_node_selected_bbox (nodepath);
4313 if (!center) {
4314 center = box.midpoint();
4315 }
4316 NR::Matrix t =
4317 NR::Matrix (NR::translate(- *center)) *
4318 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4319 NR::Matrix (NR::translate(*center));
4321 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4322 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4323 n->pos *= t;
4324 n->n.pos *= t;
4325 n->p.pos *= t;
4326 sp_node_update_handles(n, false);
4327 }
4328 }
4330 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4331 }
4333 Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4334 {
4335 g_assert (nodepath->selected);
4337 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4338 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4339 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4340 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4341 box.expandTo (n->pos); // contain all selected nodes
4342 }
4343 return box;
4344 }
4346 //-----------------------------------------------
4347 /**
4348 * Return new subpath under given nodepath.
4349 */
4350 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4351 {
4352 g_assert(nodepath);
4353 g_assert(nodepath->desktop);
4355 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4357 s->nodepath = nodepath;
4358 s->closed = FALSE;
4359 s->nodes = NULL;
4360 s->first = NULL;
4361 s->last = NULL;
4363 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4364 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4365 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4367 return s;
4368 }
4370 /**
4371 * Destroy nodes in subpath, then subpath itself.
4372 */
4373 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4374 {
4375 g_assert(subpath);
4376 g_assert(subpath->nodepath);
4377 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4379 while (subpath->nodes) {
4380 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4381 }
4383 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4385 g_free(subpath);
4386 }
4388 /**
4389 * Link head to tail in subpath.
4390 */
4391 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4392 {
4393 g_assert(!sp->closed);
4394 g_assert(sp->last != sp->first);
4395 g_assert(sp->first->code == NR_MOVETO);
4397 sp->closed = TRUE;
4399 //Link the head to the tail
4400 sp->first->p.other = sp->last;
4401 sp->last->n.other = sp->first;
4402 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4403 sp->first = sp->last;
4405 //Remove the extra end node
4406 sp_nodepath_node_destroy(sp->last->n.other);
4407 }
4409 /**
4410 * Open closed (loopy) subpath at node.
4411 */
4412 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4413 {
4414 g_assert(sp->closed);
4415 g_assert(n->subpath == sp);
4416 g_assert(sp->first == sp->last);
4418 /* We create new startpoint, current node will become last one */
4420 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4421 &n->pos, &n->pos, &n->n.pos);
4424 sp->closed = FALSE;
4426 //Unlink to make a head and tail
4427 sp->first = new_path;
4428 sp->last = n;
4429 n->n.other = NULL;
4430 new_path->p.other = NULL;
4431 }
4433 /**
4434 * Return new node in subpath with given properties.
4435 * \param pos Position of node.
4436 * \param ppos Handle position in previous direction
4437 * \param npos Handle position in previous direction
4438 */
4439 Inkscape::NodePath::Node *
4440 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)
4441 {
4442 g_assert(sp);
4443 g_assert(sp->nodepath);
4444 g_assert(sp->nodepath->desktop);
4446 if (nodechunk == NULL)
4447 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4449 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4451 n->subpath = sp;
4453 if (type != Inkscape::NodePath::NODE_NONE) {
4454 // use the type from sodipodi:nodetypes
4455 n->type = type;
4456 } else {
4457 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4458 // points are (almost) collinear
4459 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4460 // endnode, or a node with a retracted handle
4461 n->type = Inkscape::NodePath::NODE_CUSP;
4462 } else {
4463 n->type = Inkscape::NodePath::NODE_SMOOTH;
4464 }
4465 } else {
4466 n->type = Inkscape::NodePath::NODE_CUSP;
4467 }
4468 }
4470 n->code = code;
4471 n->selected = FALSE;
4472 n->pos = *pos;
4473 n->p.pos = *ppos;
4474 n->n.pos = *npos;
4476 n->dragging_out = NULL;
4478 Inkscape::NodePath::Node *prev;
4479 if (next) {
4480 //g_assert(g_list_find(sp->nodes, next));
4481 prev = next->p.other;
4482 } else {
4483 prev = sp->last;
4484 }
4486 if (prev)
4487 prev->n.other = n;
4488 else
4489 sp->first = n;
4491 if (next)
4492 next->p.other = n;
4493 else
4494 sp->last = n;
4496 n->p.other = prev;
4497 n->n.other = next;
4499 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"));
4500 sp_knot_set_position(n->knot, *pos, 0);
4502 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4503 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4504 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4505 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4506 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4507 sp_knot_update_ctrl(n->knot);
4509 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4510 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4511 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4512 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4513 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4514 sp_knot_show(n->knot);
4516 // We only create handle knots and lines on demand
4517 n->p.knot = NULL;
4518 n->p.line = NULL;
4519 n->n.knot = NULL;
4520 n->n.line = NULL;
4522 sp->nodes = g_list_prepend(sp->nodes, n);
4524 return n;
4525 }
4527 /**
4528 * Destroy node and its knots, link neighbors in subpath.
4529 */
4530 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4531 {
4532 g_assert(node);
4533 g_assert(node->subpath);
4534 g_assert(SP_IS_KNOT(node->knot));
4536 Inkscape::NodePath::SubPath *sp = node->subpath;
4538 if (node->selected) { // first, deselect
4539 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4540 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4541 }
4543 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4545 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4546 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4547 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4548 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4549 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4550 g_object_unref(G_OBJECT(node->knot));
4552 if (node->p.knot) {
4553 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4554 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4555 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4556 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4557 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4558 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4559 g_object_unref(G_OBJECT(node->p.knot));
4560 node->p.knot = NULL;
4561 }
4563 if (node->n.knot) {
4564 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4565 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4566 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4567 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4568 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4569 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4570 g_object_unref(G_OBJECT(node->n.knot));
4571 node->n.knot = NULL;
4572 }
4574 if (node->p.line)
4575 gtk_object_destroy(GTK_OBJECT(node->p.line));
4576 if (node->n.line)
4577 gtk_object_destroy(GTK_OBJECT(node->n.line));
4579 if (sp->nodes) { // there are others nodes on the subpath
4580 if (sp->closed) {
4581 if (sp->first == node) {
4582 g_assert(sp->last == node);
4583 sp->first = node->n.other;
4584 sp->last = sp->first;
4585 }
4586 node->p.other->n.other = node->n.other;
4587 node->n.other->p.other = node->p.other;
4588 } else {
4589 if (sp->first == node) {
4590 sp->first = node->n.other;
4591 sp->first->code = NR_MOVETO;
4592 }
4593 if (sp->last == node) sp->last = node->p.other;
4594 if (node->p.other) node->p.other->n.other = node->n.other;
4595 if (node->n.other) node->n.other->p.other = node->p.other;
4596 }
4597 } else { // this was the last node on subpath
4598 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4599 }
4601 g_mem_chunk_free(nodechunk, node);
4602 }
4604 /**
4605 * Returns one of the node's two sides.
4606 * \param which Indicates which side.
4607 * \return Pointer to previous node side if which==-1, next if which==1.
4608 */
4609 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4610 {
4611 g_assert(node);
4612 Inkscape::NodePath::NodeSide * result = 0;
4613 switch (which) {
4614 case -1:
4615 result = &node->p;
4616 break;
4617 case 1:
4618 result = &node->n;
4619 break;
4620 default:
4621 g_assert_not_reached();
4622 }
4624 return result;
4625 }
4627 /**
4628 * Return the other side of the node, given one of its sides.
4629 */
4630 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4631 {
4632 g_assert(node);
4633 Inkscape::NodePath::NodeSide *result = 0;
4635 if (me == &node->p) {
4636 result = &node->n;
4637 } else if (me == &node->n) {
4638 result = &node->p;
4639 } else {
4640 g_assert_not_reached();
4641 }
4643 return result;
4644 }
4646 /**
4647 * Return NRPathcode on the given side of the node.
4648 */
4649 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4650 {
4651 g_assert(node);
4653 NRPathcode result = NR_END;
4654 if (me == &node->p) {
4655 if (node->p.other) {
4656 result = (NRPathcode)node->code;
4657 } else {
4658 result = NR_MOVETO;
4659 }
4660 } else if (me == &node->n) {
4661 if (node->n.other) {
4662 result = (NRPathcode)node->n.other->code;
4663 } else {
4664 result = NR_MOVETO;
4665 }
4666 } else {
4667 g_assert_not_reached();
4668 }
4670 return result;
4671 }
4673 /**
4674 * Return node with the given index
4675 */
4676 Inkscape::NodePath::Node *
4677 sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index)
4678 {
4679 Inkscape::NodePath::Node *e = NULL;
4681 if (!nodepath) {
4682 return e;
4683 }
4685 //find segment
4686 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4688 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4689 int n = g_list_length(sp->nodes);
4690 if (sp->closed) {
4691 n++;
4692 }
4694 //if the piece belongs to this subpath grab it
4695 //otherwise move onto the next subpath
4696 if (index < n) {
4697 e = sp->first;
4698 for (int i = 0; i < index; ++i) {
4699 e = e->n.other;
4700 }
4701 break;
4702 } else {
4703 if (sp->closed) {
4704 index -= (n+1);
4705 } else {
4706 index -= n;
4707 }
4708 }
4709 }
4711 return e;
4712 }
4714 /**
4715 * Returns plain text meaning of node type.
4716 */
4717 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4718 {
4719 unsigned retracted = 0;
4720 bool endnode = false;
4722 for (int which = -1; which <= 1; which += 2) {
4723 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4724 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4725 retracted ++;
4726 if (!side->other)
4727 endnode = true;
4728 }
4730 if (retracted == 0) {
4731 if (endnode) {
4732 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4733 return _("end node");
4734 } else {
4735 switch (node->type) {
4736 case Inkscape::NodePath::NODE_CUSP:
4737 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4738 return _("cusp");
4739 case Inkscape::NodePath::NODE_SMOOTH:
4740 // TRANSLATORS: "smooth" is an adjective here
4741 return _("smooth");
4742 case Inkscape::NodePath::NODE_SYMM:
4743 return _("symmetric");
4744 }
4745 }
4746 } else if (retracted == 1) {
4747 if (endnode) {
4748 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4749 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4750 } else {
4751 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4752 }
4753 } else {
4754 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4755 }
4757 return NULL;
4758 }
4760 /**
4761 * Handles content of statusbar as long as node tool is active.
4762 */
4763 void
4764 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4765 {
4766 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");
4767 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4769 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4770 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4771 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4772 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4774 SPDesktop *desktop = NULL;
4775 if (nodepath) {
4776 desktop = nodepath->desktop;
4777 } else {
4778 desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above
4779 }
4781 SPEventContext *ec = desktop->event_context;
4782 if (!ec) return;
4784 Inkscape::MessageContext *mc = get_message_context(ec);
4785 if (!mc) return;
4787 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4789 if (selected_nodes == 0) {
4790 Inkscape::Selection *sel = desktop->selection;
4791 if (!sel || sel->isEmpty()) {
4792 mc->setF(Inkscape::NORMAL_MESSAGE,
4793 _("Select a single object to edit its nodes or handles."));
4794 } else {
4795 if (nodepath) {
4796 mc->setF(Inkscape::NORMAL_MESSAGE,
4797 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.",
4798 "<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.",
4799 total_nodes),
4800 total_nodes);
4801 } else {
4802 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4803 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4804 } else {
4805 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4806 }
4807 }
4808 }
4809 } else if (nodepath && selected_nodes == 1) {
4810 mc->setF(Inkscape::NORMAL_MESSAGE,
4811 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4812 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4813 total_nodes),
4814 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4815 } else {
4816 if (selected_subpaths > 1) {
4817 mc->setF(Inkscape::NORMAL_MESSAGE,
4818 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4819 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4820 total_nodes),
4821 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4822 } else {
4823 mc->setF(Inkscape::NORMAL_MESSAGE,
4824 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4825 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4826 total_nodes),
4827 selected_nodes, total_nodes, when_selected);
4828 }
4829 }
4830 }
4832 /*
4833 * returns a *copy* of the curve of that object.
4834 */
4835 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4836 if (!object)
4837 return NULL;
4839 SPCurve *curve = NULL;
4840 if (SP_IS_PATH(object)) {
4841 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4842 curve = curve_new->copy();
4843 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4844 const gchar *svgd = object->repr->attribute(key);
4845 if (svgd) {
4846 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4847 SPCurve *curve_new = new SPCurve(pv);
4848 if (curve_new) {
4849 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4850 }
4851 }
4852 }
4854 return curve;
4855 }
4857 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4858 if (!np || !np->object || !curve)
4859 return;
4861 if (SP_IS_PATH(np->object)) {
4862 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4863 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4864 } else {
4865 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4866 }
4867 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4868 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( LIVEPATHEFFECT(np->object)->lpe->getParameter(np->repr_key) );
4869 if (pathparam) {
4870 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
4871 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4872 }
4873 }
4874 }
4876 /**
4877 SPCanvasItem *
4878 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
4879 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
4880 }
4881 **/
4883 /**
4884 SPCanvasItem *
4885 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4886 SPCurve *flash_curve = curve->copy();
4887 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4888 flash_curve->transform(i2d);
4889 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4890 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4891 // unless we also flash the nodes...
4892 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4893 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4894 sp_canvas_item_show(canvasitem);
4895 flash_curve->unref();
4896 return canvasitem;
4897 }
4899 SPCanvasItem *
4900 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4901 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4902 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4903 }
4904 **/
4906 SPCanvasItem *
4907 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
4908 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
4909 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
4910 flash_curve->transform(i2d);
4911 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4912 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4913 // unless we also flash the nodes...
4914 guint32 color = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
4915 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4916 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4917 sp_canvas_item_show(canvasitem);
4918 flash_curve->unref();
4919 return canvasitem;
4920 }
4922 // TODO: Merge this with sp_nodepath_make_helper_item()!
4923 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4924 np->show_helperpath = show;
4926 if (show) {
4927 SPCurve *helper_curve = np->curve->copy();
4928 helper_curve->transform(np->i2d);
4929 if (!np->helper_path) {
4930 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
4932 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4933 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);
4934 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4935 sp_canvas_item_move_to_z(np->helper_path, 0);
4936 sp_canvas_item_show(np->helper_path);
4937 } else {
4938 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4939 }
4940 helper_curve->unref();
4941 } else {
4942 if (np->helper_path) {
4943 GtkObject *temp = np->helper_path;
4944 np->helper_path = NULL;
4945 gtk_object_destroy(temp);
4946 }
4947 }
4948 }
4950 /* sp_nodepath_make_straight_path:
4951 * Prevents user from curving the path by dragging a segment or activating handles etc.
4952 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4953 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4954 */
4955 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4956 np->straight_path = true;
4957 np->show_handles = false;
4958 g_message("add code to make the path straight.");
4959 // do sp_nodepath_convert_node_type on all nodes?
4960 // coding tip: search for this text : "Make selected segments lines"
4961 }
4963 /*
4964 Local Variables:
4965 mode:c++
4966 c-file-style:"stroustrup"
4967 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4968 indent-tabs-mode:nil
4969 fill-column:99
4970 End:
4971 */
4972 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :