7e46b2f10f9045b426c150110160cdf51b3971b3
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;
270 if (curve->get_segment_count() < 1) {
271 curve->unref();
272 return NULL; // prevent crash for one-node paths
273 }
275 //Create new nodepath
276 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
277 if (!np) {
278 curve->unref();
279 return NULL;
280 }
282 // Set defaults
283 np->desktop = desktop;
284 np->object = object;
285 np->subpaths = NULL;
286 np->selected = NULL;
287 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
288 np->local_change = 0;
289 np->show_handles = show_handles;
290 np->helper_path = NULL;
291 np->helper_path_vec = new HelperPathList;
292 np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
293 np->helperpath_width = 1.0;
294 np->curve = curve->copy();
295 np->show_helperpath = (prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1);
296 if (SP_IS_LPE_ITEM(object)) {
297 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
298 if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
299 np->show_helperpath = true;
300 }
301 }
302 np->straight_path = false;
303 if (IS_LIVEPATHEFFECT(object) && item) {
304 np->item = item;
305 } else {
306 np->item = SP_ITEM(object);
307 }
309 // we need to update item's transform from the repr here,
310 // because they may be out of sync when we respond
311 // to a change in repr by regenerating nodepath --bb
312 sp_object_read_attr(SP_OBJECT(np->item), "transform");
314 np->i2d = sp_item_i2d_affine(np->item);
315 np->d2i = np->i2d.inverse();
317 np->repr = repr;
318 if (repr_key_in) { // apparantly the object is an LPEObject
319 np->repr_key = g_strdup(repr_key_in);
320 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
321 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
322 if (lpeparam) {
323 lpeparam->param_setup_nodepath(np);
324 }
325 } else {
326 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
327 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
328 np->repr_key = g_strdup("inkscape:original-d");
330 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
331 if (lpe) {
332 lpe->setup_nodepath(np);
333 }
334 } else {
335 np->repr_key = g_strdup("d");
336 }
337 }
339 /* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice.
340 * So for example a closed rectangle has a nodetypestring of length 5.
341 * To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */
342 Geom::PathVector pathv_sanitized = pathv_to_linear_and_cubic_beziers(np->curve->get_pathvector());
343 np->curve->set_pathvector(pathv_sanitized);
344 guint length = np->curve->get_segment_count();
345 for (Geom::PathVector::const_iterator pit = pathv_sanitized.begin(); pit != pathv_sanitized.end(); ++pit) {
346 length += pit->empty() ? 0 : 1;
347 }
349 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
350 Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
352 // create the subpath(s) from the bpath
353 subpaths_from_pathvector(np, pathv_sanitized, typestr);
355 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
356 np->subpaths = g_list_reverse(np->subpaths);
358 delete[] typestr;
359 curve->unref();
361 // Draw helper curve
362 if (np->show_helperpath) {
363 np->helper_path = sp_nodepath_make_helper_item(np, /*desktop, */np->curve, true);
364 }
366 sp_nodepath_create_helperpaths(np);
368 return np;
369 }
371 /**
372 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
373 */
374 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
376 if (!np) //soft fail, like delete
377 return;
379 while (np->subpaths) {
380 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
381 }
383 //Inform the ShapeEditor that made me, if any, that I am gone.
384 if (np->shape_editor)
385 np->shape_editor->nodepath_destroyed();
387 g_assert(!np->selected);
389 if (np->helper_path) {
390 GtkObject *temp = np->helper_path;
391 np->helper_path = NULL;
392 gtk_object_destroy(temp);
393 }
394 if (np->curve) {
395 np->curve->unref();
396 np->curve = NULL;
397 }
399 if (np->repr_key) {
400 g_free(np->repr_key);
401 np->repr_key = NULL;
402 }
403 if (np->repr_nodetypes_key) {
404 g_free(np->repr_nodetypes_key);
405 np->repr_nodetypes_key = NULL;
406 }
408 sp_nodepath_destroy_helperpaths(np);
409 delete np->helper_path_vec;
410 np->helper_path_vec = NULL;
412 np->desktop = NULL;
414 g_free(np);
415 }
417 /**
418 * Return the node count of a given NodeSubPath.
419 */
420 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
421 {
422 if (!subpath)
423 return 0;
424 gint nodeCount = g_list_length(subpath->nodes);
425 return nodeCount;
426 }
428 /**
429 * Return the node count of a given NodePath.
430 */
431 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
432 {
433 if (!np)
434 return 0;
435 gint nodeCount = 0;
436 for (GList *item = np->subpaths ; item ; item=item->next) {
437 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
438 nodeCount += g_list_length(subpath->nodes);
439 }
440 return nodeCount;
441 }
443 /**
444 * Return the subpath count of a given NodePath.
445 */
446 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
447 {
448 if (!np)
449 return 0;
450 return g_list_length (np->subpaths);
451 }
453 /**
454 * Return the selected node count of a given NodePath.
455 */
456 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
457 {
458 if (!np)
459 return 0;
460 return g_list_length (np->selected);
461 }
463 /**
464 * Return the number of subpaths where nodes are selected in a given NodePath.
465 */
466 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
467 {
468 if (!np)
469 return 0;
470 if (!np->selected)
471 return 0;
472 if (!np->selected->next)
473 return 1;
474 gint count = 0;
475 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
476 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
477 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
478 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
479 if (node->selected) {
480 count ++;
481 break;
482 }
483 }
484 }
485 return count;
486 }
488 /**
489 * Clean up a nodepath after editing.
490 *
491 * Currently we are deleting trivial subpaths.
492 */
493 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
494 {
495 GList *badSubPaths = NULL;
497 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
498 for (GList *l = nodepath->subpaths; l ; l=l->next) {
499 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
500 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
501 badSubPaths = g_list_append(badSubPaths, sp);
502 }
504 //Delete them. This second step is because sp_nodepath_subpath_destroy()
505 //also removes the subpath from nodepath->subpaths
506 for (GList *l = badSubPaths; l ; l=l->next) {
507 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
508 sp_nodepath_subpath_destroy(sp);
509 }
511 g_list_free(badSubPaths);
512 }
514 /**
515 * Create new nodepaths from pathvector, make it subpaths of np.
516 * \param t The node type array.
517 */
518 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
519 {
520 guint i = 0; // index into node type array
521 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
522 if (pit->empty())
523 continue; // don't add single knot paths
525 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
527 NR::Point ppos = pit->initialPoint() * (Geom::Matrix)np->i2d;
528 NRPathcode pcode = NR_MOVETO;
530 /* 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)*/
531 for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
532 if( dynamic_cast<Geom::LineSegment const*>(&*cit) ||
533 dynamic_cast<Geom::HLineSegment const*>(&*cit) ||
534 dynamic_cast<Geom::VLineSegment const*>(&*cit) )
535 {
536 NR::Point pos = cit->initialPoint() * (Geom::Matrix)np->i2d;
537 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
539 ppos = cit->finalPoint() * (Geom::Matrix)np->i2d;
540 pcode = NR_LINETO;
541 }
542 else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&*cit)) {
543 std::vector<Geom::Point> points = cubic_bezier->points();
544 NR::Point pos = points[0] * (Geom::Matrix)np->i2d;
545 NR::Point npos = points[1] * (Geom::Matrix)np->i2d;
546 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
548 ppos = points[2] * (Geom::Matrix)np->i2d;
549 pcode = NR_CURVETO;
550 }
551 }
553 if (pit->closed()) {
554 // Add last knot (because sp_nodepath_subpath_close kills the last knot)
555 /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
556 * If the length is zero, don't add it to the nodepath. */
557 Geom::Curve const &closing_seg = pit->back_closed();
558 if ( ! closing_seg.isDegenerate() ) {
559 NR::Point pos = closing_seg.finalPoint() * (Geom::Matrix)np->i2d;
560 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
561 }
563 sp_nodepath_subpath_close(sp);
564 }
565 }
566 }
568 /**
569 * Convert from sodipodi:nodetypes to new style type array.
570 */
571 static
572 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
573 {
574 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
576 guint pos = 0;
578 if (types) {
579 for (guint i = 0; types[i] && ( i < length ); i++) {
580 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
581 if (types[i] != '\0') {
582 switch (types[i]) {
583 case 's':
584 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
585 break;
586 case 'z':
587 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
588 break;
589 case 'c':
590 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
591 break;
592 default:
593 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
594 break;
595 }
596 }
597 }
598 }
600 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
602 return typestr;
603 }
605 /**
606 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
607 * updated but repr is not (for speed). Used during curve and node drag.
608 */
609 static void update_object(Inkscape::NodePath::Path *np)
610 {
611 g_assert(np);
613 np->curve->unref();
614 np->curve = create_curve(np);
616 sp_nodepath_set_curve(np, np->curve);
618 if (np->show_helperpath) {
619 SPCurve * helper_curve = np->curve->copy();
620 helper_curve->transform(np->i2d);
621 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
622 helper_curve->unref();
623 }
625 // updating helperpaths of LPEItems is now done in sp_lpe_item_update();
626 //sp_nodepath_update_helperpaths(np);
628 // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
629 // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
630 np->shape_editor->update_knotholder();
631 }
633 /**
634 * Update XML path node with data from path object.
635 */
636 static void update_repr_internal(Inkscape::NodePath::Path *np)
637 {
638 g_assert(np);
640 Inkscape::XML::Node *repr = np->object->repr;
642 np->curve->unref();
643 np->curve = create_curve(np);
645 gchar *typestr = create_typestr(np);
646 gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
648 // determine if path has an effect applied and write to correct "d" attribute.
649 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
650 np->local_change++;
651 repr->setAttribute(np->repr_key, svgpath);
652 }
654 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
655 np->local_change++;
656 repr->setAttribute(np->repr_nodetypes_key, typestr);
657 }
659 g_free(svgpath);
660 g_free(typestr);
662 if (np->show_helperpath) {
663 SPCurve * helper_curve = np->curve->copy();
664 helper_curve->transform(np->i2d);
665 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
666 helper_curve->unref();
667 }
669 // TODO: do we need this call here? after all, update_object() should have been called just before
670 //sp_nodepath_update_helperpaths(np);
671 }
673 /**
674 * Update XML path node with data from path object, commit changes forever.
675 */
676 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
677 {
678 //fixme: np can be NULL, so check before proceeding
679 g_return_if_fail(np != NULL);
681 update_repr_internal(np);
682 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
684 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
685 annotation);
686 }
688 /**
689 * Update XML path node with data from path object, commit changes with undo.
690 */
691 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
692 {
693 update_repr_internal(np);
694 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
695 annotation);
696 }
698 /**
699 * Make duplicate of path, replace corresponding XML node in tree, commit.
700 */
701 static void stamp_repr(Inkscape::NodePath::Path *np)
702 {
703 g_assert(np);
705 Inkscape::XML::Node *old_repr = np->object->repr;
706 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
708 // remember the position of the item
709 gint pos = old_repr->position();
710 // remember parent
711 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
713 SPCurve *curve = create_curve(np);
714 gchar *typestr = create_typestr(np);
716 gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
718 new_repr->setAttribute(np->repr_key, svgpath);
719 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
721 // add the new repr to the parent
722 parent->appendChild(new_repr);
723 // move to the saved position
724 new_repr->setPosition(pos > 0 ? pos : 0);
726 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
727 _("Stamp"));
729 Inkscape::GC::release(new_repr);
730 g_free(svgpath);
731 g_free(typestr);
732 curve->unref();
733 }
735 /**
736 * Create curve from path.
737 */
738 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
739 {
740 SPCurve *curve = new SPCurve();
742 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
743 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
744 curve->moveto(sp->first->pos * np->d2i);
745 Inkscape::NodePath::Node *n = sp->first->n.other;
746 while (n) {
747 NR::Point const end_pt = n->pos * np->d2i;
748 switch (n->code) {
749 case NR_LINETO:
750 curve->lineto(end_pt);
751 break;
752 case NR_CURVETO:
753 curve->curveto(n->p.other->n.pos * np->d2i,
754 n->p.pos * np->d2i,
755 end_pt);
756 break;
757 default:
758 g_assert_not_reached();
759 break;
760 }
761 if (n != sp->last) {
762 n = n->n.other;
763 } else {
764 n = NULL;
765 }
766 }
767 if (sp->closed) {
768 curve->closepath();
769 }
770 }
772 return curve;
773 }
775 /**
776 * Convert path type string to sodipodi:nodetypes style.
777 */
778 static gchar *create_typestr(Inkscape::NodePath::Path *np)
779 {
780 gchar *typestr = g_new(gchar, 32);
781 gint len = 32;
782 gint pos = 0;
784 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
785 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
787 if (pos >= len) {
788 typestr = g_renew(gchar, typestr, len + 32);
789 len += 32;
790 }
792 typestr[pos++] = 'c';
794 Inkscape::NodePath::Node *n;
795 n = sp->first->n.other;
796 while (n) {
797 gchar code;
799 switch (n->type) {
800 case Inkscape::NodePath::NODE_CUSP:
801 code = 'c';
802 break;
803 case Inkscape::NodePath::NODE_SMOOTH:
804 code = 's';
805 break;
806 case Inkscape::NodePath::NODE_SYMM:
807 code = 'z';
808 break;
809 default:
810 g_assert_not_reached();
811 code = '\0';
812 break;
813 }
815 if (pos >= len) {
816 typestr = g_renew(gchar, typestr, len + 32);
817 len += 32;
818 }
820 typestr[pos++] = code;
822 if (n != sp->last) {
823 n = n->n.other;
824 } else {
825 n = NULL;
826 }
827 }
828 }
830 if (pos >= len) {
831 typestr = g_renew(gchar, typestr, len + 1);
832 len += 1;
833 }
835 typestr[pos++] = '\0';
837 return typestr;
838 }
840 // Returns different message contexts depending on the current context. This function should only
841 // be called when ec is either a SPNodeContext or SPLPEToolContext, thus we return NULL in all
842 // other cases.
843 static Inkscape::MessageContext *
844 get_message_context(SPEventContext *ec)
845 {
846 Inkscape::MessageContext *mc;
847 if (SP_IS_NODE_CONTEXT(ec)) {
848 mc = SP_NODE_CONTEXT(ec)->_node_message_context;
849 } else if (SP_IS_LPETOOL_CONTEXT(ec)) {
850 mc = SP_LPETOOL_CONTEXT(ec)->_lpetool_message_context;
851 } else {
852 g_warning ("Nodepath should only be present in Node tool or Geometric tool.");
853 return NULL;
854 }
855 }
857 /**
858 \brief Fills node and handle positions for three nodes, splitting line
859 marked by end at distance t.
860 */
861 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
862 {
863 g_assert(new_path != NULL);
864 g_assert(end != NULL);
866 g_assert(end->p.other == new_path);
867 Inkscape::NodePath::Node *start = new_path->p.other;
868 g_assert(start);
870 if (end->code == NR_LINETO) {
871 new_path->type =Inkscape::NodePath::NODE_CUSP;
872 new_path->code = NR_LINETO;
873 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
874 } else {
875 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
876 new_path->code = NR_CURVETO;
877 gdouble s = 1 - t;
878 for (int dim = 0; dim < 2; dim++) {
879 NR::Coord const f000 = start->pos[dim];
880 NR::Coord const f001 = start->n.pos[dim];
881 NR::Coord const f011 = end->p.pos[dim];
882 NR::Coord const f111 = end->pos[dim];
883 NR::Coord const f00t = s * f000 + t * f001;
884 NR::Coord const f01t = s * f001 + t * f011;
885 NR::Coord const f11t = s * f011 + t * f111;
886 NR::Coord const f0tt = s * f00t + t * f01t;
887 NR::Coord const f1tt = s * f01t + t * f11t;
888 NR::Coord const fttt = s * f0tt + t * f1tt;
889 start->n.pos[dim] = f00t;
890 new_path->p.pos[dim] = f0tt;
891 new_path->pos[dim] = fttt;
892 new_path->n.pos[dim] = f1tt;
893 end->p.pos[dim] = f11t;
894 }
895 }
896 }
898 /**
899 * Adds new node on direct line between two nodes, activates handles of all
900 * three nodes.
901 */
902 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
903 {
904 g_assert(end);
905 g_assert(end->subpath);
906 g_assert(g_list_find(end->subpath->nodes, end));
908 Inkscape::NodePath::Node *start = end->p.other;
909 g_assert( start->n.other == end );
910 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
911 end,
912 (NRPathcode)end->code == NR_LINETO?
913 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
914 (NRPathcode)end->code,
915 &start->pos, &start->pos, &start->n.pos);
916 sp_nodepath_line_midpoint(newnode, end, t);
918 sp_node_adjust_handles(start);
919 sp_node_update_handles(start);
920 sp_node_update_handles(newnode);
921 sp_node_adjust_handles(end);
922 sp_node_update_handles(end);
924 return newnode;
925 }
927 /**
928 \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
929 */
930 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
931 {
932 g_assert(node);
933 g_assert(node->subpath);
934 g_assert(g_list_find(node->subpath->nodes, node));
936 Inkscape::NodePath::SubPath *sp = node->subpath;
937 Inkscape::NodePath::Path *np = sp->nodepath;
939 if (sp->closed) {
940 sp_nodepath_subpath_open(sp, node);
941 return sp->first;
942 } else {
943 // no break for end nodes
944 if (node == sp->first) return NULL;
945 if (node == sp->last ) return NULL;
947 // create a new subpath
948 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
950 // duplicate the break node as start of the new subpath
951 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
953 // attach rest of curve to new node
954 g_assert(node->n.other);
955 newnode->n.other = node->n.other; node->n.other = NULL;
956 newnode->n.other->p.other = newnode;
957 newsubpath->last = sp->last;
958 sp->last = node;
959 node = newnode;
960 while (node->n.other) {
961 node = node->n.other;
962 node->subpath = newsubpath;
963 sp->nodes = g_list_remove(sp->nodes, node);
964 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
965 }
968 return newnode;
969 }
970 }
972 /**
973 * Duplicate node and connect to neighbours.
974 */
975 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
976 {
977 g_assert(node);
978 g_assert(node->subpath);
979 g_assert(g_list_find(node->subpath->nodes, node));
981 Inkscape::NodePath::SubPath *sp = node->subpath;
983 NRPathcode code = (NRPathcode) node->code;
984 if (code == NR_MOVETO) { // if node is the endnode,
985 node->code = NR_LINETO; // new one is inserted before it, so change that to line
986 }
988 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
990 if (!node->n.other || !node->p.other) // if node is an endnode, select it
991 return node;
992 else
993 return newnode; // otherwise select the newly created node
994 }
996 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
997 {
998 node->p.pos = (node->pos + (node->pos - node->n.pos));
999 }
1001 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1002 {
1003 node->n.pos = (node->pos + (node->pos - node->p.pos));
1004 }
1006 /**
1007 * Change line type at node, with side effects on neighbours.
1008 */
1009 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1010 {
1011 g_assert(end);
1012 g_assert(end->subpath);
1013 g_assert(end->p.other);
1015 if (end->code == static_cast< guint > ( code ) )
1016 return;
1018 Inkscape::NodePath::Node *start = end->p.other;
1020 end->code = code;
1022 if (code == NR_LINETO) {
1023 if (start->code == NR_LINETO) {
1024 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
1025 }
1026 if (end->n.other) {
1027 if (end->n.other->code == NR_LINETO) {
1028 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
1029 }
1030 }
1031 } else {
1032 NR::Point delta = end->pos - start->pos;
1033 start->n.pos = start->pos + delta / 3;
1034 end->p.pos = end->pos - delta / 3;
1035 sp_node_adjust_handle(start, 1);
1036 sp_node_adjust_handle(end, -1);
1037 }
1039 sp_node_update_handles(start);
1040 sp_node_update_handles(end);
1041 }
1043 /**
1044 * Change node type, and its handles accordingly.
1045 */
1046 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1047 {
1048 g_assert(node);
1049 g_assert(node->subpath);
1051 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1052 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1053 type =Inkscape::NodePath::NODE_CUSP;
1054 }
1055 }
1057 node->type = type;
1059 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1060 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1061 node->knot->setSize (node->selected? 11 : 9);
1062 sp_knot_update_ctrl(node->knot);
1063 } else {
1064 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1065 node->knot->setSize (node->selected? 9 : 7);
1066 sp_knot_update_ctrl(node->knot);
1067 }
1069 // if one of handles is mouseovered, preserve its position
1070 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1071 sp_node_adjust_handle(node, 1);
1072 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1073 sp_node_adjust_handle(node, -1);
1074 } else {
1075 sp_node_adjust_handles(node);
1076 }
1078 sp_node_update_handles(node);
1080 sp_nodepath_update_statusbar(node->subpath->nodepath);
1082 return node;
1083 }
1085 bool
1086 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1087 {
1088 Inkscape::NodePath::Node *othernode = side->other;
1089 if (!othernode)
1090 return false;
1091 NRPathcode const code = sp_node_path_code_from_side(node, side);
1092 if (code == NR_LINETO)
1093 return true;
1094 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1095 if (&node->p == side) {
1096 other_to_me = &othernode->n;
1097 } else if (&node->n == side) {
1098 other_to_me = &othernode->p;
1099 }
1100 if (!other_to_me)
1101 return false;
1102 bool is_line =
1103 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1104 NR::L2(node->pos - side->pos) < 1e-6);
1105 return is_line;
1106 }
1108 /**
1109 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1110 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1111 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1112 * If already cusp and set to cusp, retracts handles.
1113 */
1114 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1115 {
1116 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1118 /*
1119 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1121 if (two_handles) {
1122 // do nothing, adjust_handles called via set_node_type will line them up
1123 } else if (one_handle) {
1124 if (opposite_to_handle_is_line) {
1125 if (lined_up) {
1126 // already half-smooth; pull opposite handle too making it fully smooth
1127 } else {
1128 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1129 }
1130 } else {
1131 // pull opposite handle in line with the existing one
1132 }
1133 } else if (no_handles) {
1134 if (both_segments_are_lines OR both_segments_are_curves) {
1135 //pull both handles
1136 } else {
1137 // pull the handle opposite to line segment, making node half-smooth
1138 }
1139 }
1140 */
1141 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1142 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1143 bool p_is_line = sp_node_side_is_line(node, &node->p);
1144 bool n_is_line = sp_node_side_is_line(node, &node->n);
1146 if (p_has_handle && n_has_handle) {
1147 // do nothing, adjust_handles will line them up
1148 } else if (p_has_handle || n_has_handle) {
1149 if (p_has_handle && n_is_line) {
1150 Radial line (node->n.other->pos - node->pos);
1151 Radial handle (node->pos - node->p.pos);
1152 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1153 // already half-smooth; pull opposite handle too making it fully smooth
1154 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1155 } else {
1156 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1157 }
1158 } else if (n_has_handle && p_is_line) {
1159 Radial line (node->p.other->pos - node->pos);
1160 Radial handle (node->pos - node->n.pos);
1161 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1162 // already half-smooth; pull opposite handle too making it fully smooth
1163 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1164 } else {
1165 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1166 }
1167 } else if (p_has_handle && node->n.other) {
1168 // pull n handle
1169 node->n.other->code = NR_CURVETO;
1170 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1171 NR::L2(node->p.pos - node->pos) :
1172 NR::L2(node->n.other->pos - node->pos) / 3;
1173 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1174 } else if (n_has_handle && node->p.other) {
1175 // pull p handle
1176 node->code = NR_CURVETO;
1177 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1178 NR::L2(node->n.pos - node->pos) :
1179 NR::L2(node->p.other->pos - node->pos) / 3;
1180 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1181 }
1182 } else if (!p_has_handle && !n_has_handle) {
1183 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1184 // no handles, but both segments are either lnes or curves:
1185 //pull both handles
1187 // convert both to curves:
1188 node->code = NR_CURVETO;
1189 node->n.other->code = NR_CURVETO;
1191 NR::Point leg_prev = node->pos - node->p.other->pos;
1192 NR::Point leg_next = node->pos - node->n.other->pos;
1194 double norm_leg_prev = L2(leg_prev);
1195 double norm_leg_next = L2(leg_next);
1197 NR::Point delta;
1198 if (norm_leg_next > 0.0) {
1199 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1200 (&delta)->normalize();
1201 }
1203 if (type == Inkscape::NodePath::NODE_SYMM) {
1204 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1205 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1206 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1207 } else {
1208 // length of handle is proportional to distance to adjacent node
1209 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1210 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1211 }
1213 } else {
1214 // pull the handle opposite to line segment, making it half-smooth
1215 if (p_is_line && node->n.other) {
1216 if (type != Inkscape::NodePath::NODE_SYMM) {
1217 // pull n handle
1218 node->n.other->code = NR_CURVETO;
1219 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1220 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1221 }
1222 } else if (n_is_line && node->p.other) {
1223 if (type != Inkscape::NodePath::NODE_SYMM) {
1224 // pull p handle
1225 node->code = NR_CURVETO;
1226 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1227 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1228 }
1229 }
1230 }
1231 }
1232 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1233 // cusping a cusp: retract nodes
1234 node->p.pos = node->pos;
1235 node->n.pos = node->pos;
1236 }
1238 sp_nodepath_set_node_type (node, type);
1239 }
1241 /**
1242 * Move node to point, and adjust its and neighbouring handles.
1243 */
1244 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1245 {
1246 NR::Point delta = p - node->pos;
1247 node->pos = p;
1249 node->p.pos += delta;
1250 node->n.pos += delta;
1252 Inkscape::NodePath::Node *node_p = NULL;
1253 Inkscape::NodePath::Node *node_n = NULL;
1255 if (node->p.other) {
1256 if (node->code == NR_LINETO) {
1257 sp_node_adjust_handle(node, 1);
1258 sp_node_adjust_handle(node->p.other, -1);
1259 node_p = node->p.other;
1260 }
1261 }
1262 if (node->n.other) {
1263 if (node->n.other->code == NR_LINETO) {
1264 sp_node_adjust_handle(node, -1);
1265 sp_node_adjust_handle(node->n.other, 1);
1266 node_n = node->n.other;
1267 }
1268 }
1270 // this function is only called from batch movers that will update display at the end
1271 // themselves, so here we just move all the knots without emitting move signals, for speed
1272 sp_node_update_handles(node, false);
1273 if (node_n) {
1274 sp_node_update_handles(node_n, false);
1275 }
1276 if (node_p) {
1277 sp_node_update_handles(node_p, false);
1278 }
1279 }
1281 /**
1282 * Call sp_node_moveto() for node selection and handle possible snapping.
1283 */
1284 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1285 bool const snap, bool constrained = false,
1286 Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point())
1287 {
1288 NR::Coord best = NR_HUGE;
1289 NR::Point delta(dx, dy);
1290 NR::Point best_pt = delta;
1291 Inkscape::SnappedPoint best_abs;
1293 if (snap) {
1294 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1295 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1296 * must provide that information. */
1298 // Build a list of the unselected nodes to which the snapper should snap
1299 std::vector<Geom::Point> unselected_nodes;
1300 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1301 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1302 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1303 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1304 if (!node->selected) {
1305 unselected_nodes.push_back(to_2geom(node->pos));
1306 }
1307 }
1308 }
1310 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1312 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1313 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1314 m.setup(nodepath->desktop, false, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1315 Inkscape::SnappedPoint s;
1316 if (constrained) {
1317 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1318 dedicated_constraint.setPoint(n->pos);
1319 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(n->pos + delta), dedicated_constraint);
1320 } else {
1321 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(n->pos + delta));
1322 }
1323 if (s.getSnapped() && (s.getDistance() < best)) {
1324 best = s.getDistance();
1325 best_abs = s;
1326 best_pt = from_2geom(s.getPoint()) - n->pos;
1327 }
1328 }
1330 if (best_abs.getSnapped()) {
1331 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1332 } else {
1333 nodepath->desktop->snapindicator->remove_snappoint();
1334 }
1335 }
1337 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1338 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1339 sp_node_moveto(n, n->pos + best_pt);
1340 }
1342 // do not update repr here so that node dragging is acceptably fast
1343 update_object(nodepath);
1344 }
1346 /**
1347 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1348 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1349 near x = 0.
1350 */
1351 double
1352 sculpt_profile (double x, double alpha, guint profile)
1353 {
1354 if (x >= 1)
1355 return 0;
1356 if (x <= 0)
1357 return 1;
1359 switch (profile) {
1360 case SCULPT_PROFILE_LINEAR:
1361 return 1 - x;
1362 case SCULPT_PROFILE_BELL:
1363 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1364 case SCULPT_PROFILE_ELLIPTIC:
1365 return sqrt(1 - x*x);
1366 }
1368 return 1;
1369 }
1371 double
1372 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1373 {
1374 // extremely primitive for now, don't have time to look for the real one
1375 double lower = NR::L2(b - a);
1376 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1377 return (lower + upper)/2;
1378 }
1380 void
1381 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1382 {
1383 n->pos = n->origin + delta;
1384 n->n.pos = n->n.origin + delta_n;
1385 n->p.pos = n->p.origin + delta_p;
1386 sp_node_adjust_handles(n);
1387 sp_node_update_handles(n, false);
1388 }
1390 /**
1391 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1392 * on how far they are from the dragged node n.
1393 */
1394 static void
1395 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1396 {
1397 g_assert (n);
1398 g_assert (nodepath);
1399 g_assert (n->subpath->nodepath == nodepath);
1401 double pressure = n->knot->pressure;
1402 if (pressure == 0)
1403 pressure = 0.5; // default
1404 pressure = CLAMP (pressure, 0.2, 0.8);
1406 // map pressure to alpha = 1/5 ... 5
1407 double alpha = 1 - 2 * fabs(pressure - 0.5);
1408 if (pressure > 0.5)
1409 alpha = 1/alpha;
1411 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1413 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1414 // Only one subpath has selected nodes:
1415 // use linear mode, where the distance from n to node being dragged is calculated along the path
1417 double n_sel_range = 0, p_sel_range = 0;
1418 guint n_nodes = 0, p_nodes = 0;
1419 guint n_sel_nodes = 0, p_sel_nodes = 0;
1421 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1422 {
1423 double n_range = 0, p_range = 0;
1424 bool n_going = true, p_going = true;
1425 Inkscape::NodePath::Node *n_node = n;
1426 Inkscape::NodePath::Node *p_node = n;
1427 do {
1428 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1429 if (n_node && n_going)
1430 n_node = n_node->n.other;
1431 if (n_node == NULL) {
1432 n_going = false;
1433 } else {
1434 n_nodes ++;
1435 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1436 if (n_node->selected) {
1437 n_sel_nodes ++;
1438 n_sel_range = n_range;
1439 }
1440 if (n_node == p_node) {
1441 n_going = false;
1442 p_going = false;
1443 }
1444 }
1445 if (p_node && p_going)
1446 p_node = p_node->p.other;
1447 if (p_node == NULL) {
1448 p_going = false;
1449 } else {
1450 p_nodes ++;
1451 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1452 if (p_node->selected) {
1453 p_sel_nodes ++;
1454 p_sel_range = p_range;
1455 }
1456 if (p_node == n_node) {
1457 n_going = false;
1458 p_going = false;
1459 }
1460 }
1461 } while (n_going || p_going);
1462 }
1464 // Second pass: actually move nodes in this subpath
1465 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1466 {
1467 double n_range = 0, p_range = 0;
1468 bool n_going = true, p_going = true;
1469 Inkscape::NodePath::Node *n_node = n;
1470 Inkscape::NodePath::Node *p_node = n;
1471 do {
1472 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1473 if (n_node && n_going)
1474 n_node = n_node->n.other;
1475 if (n_node == NULL) {
1476 n_going = false;
1477 } else {
1478 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1479 if (n_node->selected) {
1480 sp_nodepath_move_node_and_handles (n_node,
1481 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1482 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1483 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1484 }
1485 if (n_node == p_node) {
1486 n_going = false;
1487 p_going = false;
1488 }
1489 }
1490 if (p_node && p_going)
1491 p_node = p_node->p.other;
1492 if (p_node == NULL) {
1493 p_going = false;
1494 } else {
1495 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1496 if (p_node->selected) {
1497 sp_nodepath_move_node_and_handles (p_node,
1498 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1499 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1500 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1501 }
1502 if (p_node == n_node) {
1503 n_going = false;
1504 p_going = false;
1505 }
1506 }
1507 } while (n_going || p_going);
1508 }
1510 } else {
1511 // Multiple subpaths have selected nodes:
1512 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1513 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1514 // fix the pear-like shape when sculpting e.g. a ring
1516 // First pass: calculate range
1517 gdouble direct_range = 0;
1518 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1519 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1520 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1521 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1522 if (node->selected) {
1523 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1524 }
1525 }
1526 }
1528 // Second pass: actually move nodes
1529 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1530 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1531 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1532 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1533 if (node->selected) {
1534 if (direct_range > 1e-6) {
1535 sp_nodepath_move_node_and_handles (node,
1536 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1537 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1538 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1539 } else {
1540 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1541 }
1543 }
1544 }
1545 }
1546 }
1548 // do not update repr here so that node dragging is acceptably fast
1549 update_object(nodepath);
1550 }
1553 /**
1554 * Move node selection to point, adjust its and neighbouring handles,
1555 * handle possible snapping, and commit the change with possible undo.
1556 */
1557 void
1558 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1559 {
1560 if (!nodepath) return;
1562 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1564 if (dx == 0) {
1565 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1566 } else if (dy == 0) {
1567 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1568 } else {
1569 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1570 }
1571 }
1573 /**
1574 * Move node selection off screen and commit the change.
1575 */
1576 void
1577 sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1578 {
1579 // borrowed from sp_selection_move_screen in selection-chemistry.c
1580 // we find out the current zoom factor and divide deltas by it
1582 gdouble zoom = desktop->current_zoom();
1583 gdouble zdx = dx / zoom;
1584 gdouble zdy = dy / zoom;
1586 if (!nodepath) return;
1588 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1590 if (dx == 0) {
1591 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1592 } else if (dy == 0) {
1593 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1594 } else {
1595 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1596 }
1597 }
1599 /**
1600 * Move selected nodes to the absolute position given
1601 */
1602 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
1603 {
1604 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1605 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1606 Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
1607 sp_node_moveto(n, npos);
1608 }
1610 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1611 }
1613 /**
1614 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1615 */
1616 boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1617 {
1618 boost::optional<Geom::Coord> no_coord;
1619 g_return_val_if_fail(nodepath->selected, no_coord);
1621 // determine coordinate of first selected node
1622 GList *nsel = nodepath->selected;
1623 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1624 NR::Coord coord = n->pos[axis];
1625 bool coincide = true;
1627 // compare it to the coordinates of all the other selected nodes
1628 for (GList *l = nsel->next; l != NULL; l = l->next) {
1629 n = (Inkscape::NodePath::Node *) l->data;
1630 if (n->pos[axis] != coord) {
1631 coincide = false;
1632 }
1633 }
1634 if (coincide) {
1635 return coord;
1636 } else {
1637 Geom::Rect bbox = sp_node_selected_bbox(nodepath);
1638 // currently we return the coordinate of the bounding box midpoint because I don't know how
1639 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1640 return bbox.midpoint()[axis];
1641 }
1642 }
1644 /** If they don't yet exist, creates knot and line for the given side of the node */
1645 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1646 {
1647 if (!side->knot) {
1648 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"));
1650 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1651 side->knot->setSize (7);
1652 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1653 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1654 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1655 sp_knot_update_ctrl(side->knot);
1657 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1658 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1659 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1660 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1661 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1662 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1663 }
1665 if (!side->line) {
1666 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1667 SP_TYPE_CTRLLINE, NULL);
1668 }
1669 }
1671 /**
1672 * Ensure the given handle of the node is visible/invisible, update its screen position
1673 */
1674 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1675 {
1676 g_assert(node != NULL);
1678 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1679 NRPathcode code = sp_node_path_code_from_side(node, side);
1681 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1683 if (show_handle) {
1684 if (!side->knot) { // No handle knot at all
1685 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1686 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1687 side->knot->pos = side->pos;
1688 if (side->knot->item)
1689 SP_CTRL(side->knot->item)->moveto(side->pos);
1690 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1691 sp_knot_show(side->knot);
1692 } else {
1693 if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1694 if (fire_move_signals) {
1695 sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1696 } else {
1697 sp_knot_moveto(side->knot, side->pos);
1698 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1699 }
1700 }
1701 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1702 sp_knot_show(side->knot);
1703 }
1704 }
1705 sp_canvas_item_show(side->line);
1706 } else {
1707 if (side->knot) {
1708 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1709 sp_knot_hide(side->knot);
1710 }
1711 }
1712 if (side->line) {
1713 sp_canvas_item_hide(side->line);
1714 }
1715 }
1716 }
1718 /**
1719 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1720 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1721 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1722 * updated; otherwise, just move the knots silently (used in batch moves).
1723 */
1724 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1725 {
1726 g_assert(node != NULL);
1728 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1729 sp_knot_show(node->knot);
1730 }
1732 if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1733 if (fire_move_signals)
1734 sp_knot_set_position(node->knot, node->pos, 0);
1735 else
1736 sp_knot_moveto(node->knot, node->pos);
1737 }
1739 gboolean show_handles = node->selected;
1740 if (node->p.other != NULL) {
1741 if (node->p.other->selected) show_handles = TRUE;
1742 }
1743 if (node->n.other != NULL) {
1744 if (node->n.other->selected) show_handles = TRUE;
1745 }
1747 if (node->subpath->nodepath->show_handles == false)
1748 show_handles = FALSE;
1750 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1751 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1752 }
1754 /**
1755 * Call sp_node_update_handles() for all nodes on subpath.
1756 */
1757 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1758 {
1759 g_assert(subpath != NULL);
1761 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1762 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1763 }
1764 }
1766 /**
1767 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1768 */
1769 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1770 {
1771 g_assert(nodepath != NULL);
1773 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1774 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1775 }
1776 }
1778 void
1779 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1780 {
1781 if (nodepath == NULL) return;
1783 nodepath->show_handles = show;
1784 sp_nodepath_update_handles(nodepath);
1785 }
1787 /**
1788 * Adds all selected nodes in nodepath to list.
1789 */
1790 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1791 {
1792 StlConv<Node *>::list(l, selected);
1793 /// \todo this adds a copying, rework when the selection becomes a stl list
1794 }
1796 /**
1797 * Align selected nodes on the specified axis.
1798 */
1799 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1800 {
1801 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1802 return;
1803 }
1805 if ( !nodepath->selected->next ) { // only one node selected
1806 return;
1807 }
1808 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1809 NR::Point dest(pNode->pos);
1810 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1811 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1812 if (pNode) {
1813 dest[axis] = pNode->pos[axis];
1814 sp_node_moveto(pNode, dest);
1815 }
1816 }
1818 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1819 }
1821 /// Helper struct.
1822 struct NodeSort
1823 {
1824 Inkscape::NodePath::Node *_node;
1825 NR::Coord _coord;
1826 /// \todo use vectorof pointers instead of calling copy ctor
1827 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1828 _node(node), _coord(node->pos[axis])
1829 {}
1831 };
1833 static bool operator<(NodeSort const &a, NodeSort const &b)
1834 {
1835 return (a._coord < b._coord);
1836 }
1838 /**
1839 * Distribute selected nodes on the specified axis.
1840 */
1841 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1842 {
1843 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1844 return;
1845 }
1847 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1848 return;
1849 }
1851 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1852 std::vector<NodeSort> sorted;
1853 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1854 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1855 if (pNode) {
1856 NodeSort n(pNode, axis);
1857 sorted.push_back(n);
1858 //dest[axis] = pNode->pos[axis];
1859 //sp_node_moveto(pNode, dest);
1860 }
1861 }
1862 std::sort(sorted.begin(), sorted.end());
1863 unsigned int len = sorted.size();
1864 //overall bboxes span
1865 float dist = (sorted.back()._coord -
1866 sorted.front()._coord);
1867 //new distance between each bbox
1868 float step = (dist) / (len - 1);
1869 float pos = sorted.front()._coord;
1870 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1871 it < sorted.end();
1872 it ++ )
1873 {
1874 NR::Point dest((*it)._node->pos);
1875 dest[axis] = pos;
1876 sp_node_moveto((*it)._node, dest);
1877 pos += step;
1878 }
1880 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1881 }
1884 /**
1885 * Call sp_nodepath_line_add_node() for all selected segments.
1886 */
1887 void
1888 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1889 {
1890 if (!nodepath) {
1891 return;
1892 }
1894 GList *nl = NULL;
1896 int n_added = 0;
1898 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1899 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1900 g_assert(t->selected);
1901 if (t->p.other && t->p.other->selected) {
1902 nl = g_list_prepend(nl, t);
1903 }
1904 }
1906 while (nl) {
1907 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1908 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1909 sp_nodepath_node_select(n, TRUE, FALSE);
1910 n_added ++;
1911 nl = g_list_remove(nl, t);
1912 }
1914 /** \todo fixme: adjust ? */
1915 sp_nodepath_update_handles(nodepath);
1917 if (n_added > 1) {
1918 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1919 } else if (n_added > 0) {
1920 sp_nodepath_update_repr(nodepath, _("Add node"));
1921 }
1923 sp_nodepath_update_statusbar(nodepath);
1924 }
1926 /**
1927 * Select segment nearest to point
1928 */
1929 void
1930 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1931 {
1932 if (!nodepath) {
1933 return;
1934 }
1936 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1937 Geom::PathVector const &pathv = curve->get_pathvector();
1938 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1940 // calculate index for nodepath's representation.
1941 unsigned int segment_index = floor(pvpos.t) + 1;
1942 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1943 segment_index += pathv[i].size() + 1;
1944 if (pathv[i].closed()) {
1945 segment_index += 1;
1946 }
1947 }
1949 curve->unref();
1951 //find segment to segment
1952 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
1954 //fixme: this can return NULL, so check before proceeding.
1955 g_return_if_fail(e != NULL);
1957 gboolean force = FALSE;
1958 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1959 force = TRUE;
1960 }
1961 sp_nodepath_node_select(e, (gboolean) toggle, force);
1962 if (e->p.other)
1963 sp_nodepath_node_select(e->p.other, TRUE, force);
1965 sp_nodepath_update_handles(nodepath);
1967 sp_nodepath_update_statusbar(nodepath);
1968 }
1970 /**
1971 * Add a node nearest to point
1972 */
1973 void
1974 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1975 {
1976 if (!nodepath) {
1977 return;
1978 }
1980 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1981 Geom::PathVector const &pathv = curve->get_pathvector();
1982 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1984 // calculate index for nodepath's representation.
1985 double int_part;
1986 double t = std::modf(pvpos.t, &int_part);
1987 unsigned int segment_index = (unsigned int)int_part + 1;
1988 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1989 segment_index += pathv[i].size() + 1;
1990 if (pathv[i].closed()) {
1991 segment_index += 1;
1992 }
1993 }
1995 curve->unref();
1997 //find segment to split
1998 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2000 //don't know why but t seems to flip for lines
2001 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2002 t = 1.0 - t;
2003 }
2005 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2006 sp_nodepath_node_select(n, FALSE, TRUE);
2008 /* fixme: adjust ? */
2009 sp_nodepath_update_handles(nodepath);
2011 sp_nodepath_update_repr(nodepath, _("Add node"));
2013 sp_nodepath_update_statusbar(nodepath);
2014 }
2016 /*
2017 * Adjusts a segment so that t moves by a certain delta for dragging
2018 * converts lines to curves
2019 *
2020 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2021 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2022 */
2023 void
2024 sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, NR::Point delta)
2025 {
2026 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node);
2028 //fixme: e and e->p can be NULL, so check for those before proceeding
2029 g_return_if_fail(e != NULL);
2030 g_return_if_fail(&e->p != NULL);
2032 /* feel good is an arbitrary parameter that distributes the delta between handles
2033 * if t of the drag point is less than 1/6 distance form the endpoint only
2034 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2035 */
2036 double feel_good;
2037 if (t <= 1.0 / 6.0)
2038 feel_good = 0;
2039 else if (t <= 0.5)
2040 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2041 else if (t <= 5.0 / 6.0)
2042 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2043 else
2044 feel_good = 1;
2046 //if we're dragging a line convert it to a curve
2047 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2048 sp_nodepath_set_line_type(e, NR_CURVETO);
2049 }
2051 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2052 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2053 e->p.other->n.pos += offsetcoord0;
2054 e->p.pos += offsetcoord1;
2056 // adjust handles of adjacent nodes where necessary
2057 sp_node_adjust_handle(e,1);
2058 sp_node_adjust_handle(e->p.other,-1);
2060 sp_nodepath_update_handles(e->subpath->nodepath);
2062 update_object(e->subpath->nodepath);
2064 sp_nodepath_update_statusbar(e->subpath->nodepath);
2065 }
2068 /**
2069 * Call sp_nodepath_break() for all selected segments.
2070 */
2071 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2072 {
2073 if (!nodepath) return;
2075 GList *tempin = g_list_copy(nodepath->selected);
2076 GList *temp = NULL;
2077 for (GList *l = tempin; l != NULL; l = l->next) {
2078 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2079 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2080 if (nn == NULL) continue; // no break, no new node
2081 temp = g_list_prepend(temp, nn);
2082 }
2083 g_list_free(tempin);
2085 if (temp) {
2086 sp_nodepath_deselect(nodepath);
2087 }
2088 for (GList *l = temp; l != NULL; l = l->next) {
2089 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2090 }
2092 sp_nodepath_update_handles(nodepath);
2094 sp_nodepath_update_repr(nodepath, _("Break path"));
2095 }
2097 /**
2098 * Duplicate the selected node(s).
2099 */
2100 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2101 {
2102 if (!nodepath) {
2103 return;
2104 }
2106 GList *temp = NULL;
2107 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2108 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2109 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2110 if (nn == NULL) continue; // could not duplicate
2111 temp = g_list_prepend(temp, nn);
2112 }
2114 if (temp) {
2115 sp_nodepath_deselect(nodepath);
2116 }
2117 for (GList *l = temp; l != NULL; l = l->next) {
2118 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2119 }
2121 sp_nodepath_update_handles(nodepath);
2123 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2124 }
2126 /**
2127 * Internal function to join two nodes by merging them into one.
2128 */
2129 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2130 {
2131 /* a and b are endpoints */
2133 // if one of the two nodes is mouseovered, fix its position
2134 NR::Point c;
2135 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2136 c = a->pos;
2137 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2138 c = b->pos;
2139 } else {
2140 // otherwise, move joined node to the midpoint
2141 c = (a->pos + b->pos) / 2;
2142 }
2144 if (a->subpath == b->subpath) {
2145 Inkscape::NodePath::SubPath *sp = a->subpath;
2146 sp_nodepath_subpath_close(sp);
2147 sp_node_moveto (sp->first, c);
2149 sp_nodepath_update_handles(sp->nodepath);
2150 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2151 return;
2152 }
2154 /* a and b are separate subpaths */
2155 Inkscape::NodePath::SubPath *sa = a->subpath;
2156 Inkscape::NodePath::SubPath *sb = b->subpath;
2157 NR::Point p;
2158 Inkscape::NodePath::Node *n;
2159 NRPathcode code;
2160 if (a == sa->first) {
2161 // we will now reverse sa, so that a is its last node, not first, and drop that node
2162 p = sa->first->n.pos;
2163 code = (NRPathcode)sa->first->n.other->code;
2164 // create new subpath
2165 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2166 // create a first moveto node on it
2167 n = sa->last;
2168 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2169 n = n->p.other;
2170 if (n == sa->first) n = NULL;
2171 while (n) {
2172 // copy the rest of the nodes from sa to t, going backwards
2173 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2174 n = n->p.other;
2175 if (n == sa->first) n = NULL;
2176 }
2177 // replace sa with t
2178 sp_nodepath_subpath_destroy(sa);
2179 sa = t;
2180 } else if (a == sa->last) {
2181 // a is already last, just drop it
2182 p = sa->last->p.pos;
2183 code = (NRPathcode)sa->last->code;
2184 sp_nodepath_node_destroy(sa->last);
2185 } else {
2186 code = NR_END;
2187 g_assert_not_reached();
2188 }
2190 if (b == sb->first) {
2191 // copy all nodes from b to a, forward
2192 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2193 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2194 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2195 }
2196 } else if (b == sb->last) {
2197 // copy all nodes from b to a, backward
2198 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2199 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2200 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2201 }
2202 } else {
2203 g_assert_not_reached();
2204 }
2205 /* and now destroy sb */
2207 sp_nodepath_subpath_destroy(sb);
2209 sp_nodepath_update_handles(sa->nodepath);
2211 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2213 sp_nodepath_update_statusbar(nodepath);
2214 }
2216 /**
2217 * Internal function to join two nodes by adding a segment between them.
2218 */
2219 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2220 {
2221 if (a->subpath == b->subpath) {
2222 Inkscape::NodePath::SubPath *sp = a->subpath;
2224 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2225 sp->closed = TRUE;
2227 sp->first->p.other = sp->last;
2228 sp->last->n.other = sp->first;
2230 sp_node_handle_mirror_p_to_n(sp->last);
2231 sp_node_handle_mirror_n_to_p(sp->first);
2233 sp->first->code = sp->last->code;
2234 sp->first = sp->last;
2236 sp_nodepath_update_handles(sp->nodepath);
2238 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2240 return;
2241 }
2243 /* a and b are separate subpaths */
2244 Inkscape::NodePath::SubPath *sa = a->subpath;
2245 Inkscape::NodePath::SubPath *sb = b->subpath;
2247 Inkscape::NodePath::Node *n;
2248 NR::Point p;
2249 NRPathcode code;
2250 if (a == sa->first) {
2251 code = (NRPathcode) sa->first->n.other->code;
2252 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2253 n = sa->last;
2254 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2255 for (n = n->p.other; n != NULL; n = n->p.other) {
2256 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2257 }
2258 sp_nodepath_subpath_destroy(sa);
2259 sa = t;
2260 } else if (a == sa->last) {
2261 code = (NRPathcode)sa->last->code;
2262 } else {
2263 code = NR_END;
2264 g_assert_not_reached();
2265 }
2267 if (b == sb->first) {
2268 n = sb->first;
2269 sp_node_handle_mirror_p_to_n(sa->last);
2270 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2271 sp_node_handle_mirror_n_to_p(sa->last);
2272 for (n = n->n.other; n != NULL; n = n->n.other) {
2273 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2274 }
2275 } else if (b == sb->last) {
2276 n = sb->last;
2277 sp_node_handle_mirror_p_to_n(sa->last);
2278 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2279 sp_node_handle_mirror_n_to_p(sa->last);
2280 for (n = n->p.other; n != NULL; n = n->p.other) {
2281 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2282 }
2283 } else {
2284 g_assert_not_reached();
2285 }
2286 /* and now destroy sb */
2288 sp_nodepath_subpath_destroy(sb);
2290 sp_nodepath_update_handles(sa->nodepath);
2292 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2293 }
2295 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2297 /**
2298 * Internal function to handle joining two nodes.
2299 */
2300 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2301 {
2302 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2304 if (g_list_length(nodepath->selected) != 2) {
2305 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2306 return;
2307 }
2309 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2310 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2312 g_assert(a != b);
2313 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2314 // someone tried to join an orphan node (i.e. a single-node subpath).
2315 // this is not worth an error message, just fail silently.
2316 return;
2317 }
2319 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2320 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2321 return;
2322 }
2324 switch(mode) {
2325 case NODE_JOIN_ENDPOINTS:
2326 do_node_selected_join(nodepath, a, b);
2327 break;
2328 case NODE_JOIN_SEGMENT:
2329 do_node_selected_join_segment(nodepath, a, b);
2330 break;
2331 }
2332 }
2334 /**
2335 * Join two nodes by merging them into one.
2336 */
2337 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2338 {
2339 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2340 }
2342 /**
2343 * Join two nodes by adding a segment between them.
2344 */
2345 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2346 {
2347 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2348 }
2350 /**
2351 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2352 */
2353 void sp_node_delete_preserve(GList *nodes_to_delete)
2354 {
2355 GSList *nodepaths = NULL;
2357 while (nodes_to_delete) {
2358 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2359 Inkscape::NodePath::SubPath *sp = node->subpath;
2360 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2361 Inkscape::NodePath::Node *sample_cursor = NULL;
2362 Inkscape::NodePath::Node *sample_end = NULL;
2363 Inkscape::NodePath::Node *delete_cursor = node;
2364 bool just_delete = false;
2366 //find the start of this contiguous selection
2367 //move left to the first node that is not selected
2368 //or the start of the non-closed path
2369 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2370 delete_cursor = curr;
2371 }
2373 //just delete at the beginning of an open path
2374 if (!delete_cursor->p.other) {
2375 sample_cursor = delete_cursor;
2376 just_delete = true;
2377 } else {
2378 sample_cursor = delete_cursor->p.other;
2379 }
2381 //calculate points for each segment
2382 int rate = 5;
2383 float period = 1.0 / rate;
2384 std::vector<NR::Point> data;
2385 if (!just_delete) {
2386 data.push_back(sample_cursor->pos);
2387 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2388 //just delete at the end of an open path
2389 if (!sp->closed && curr == sp->last) {
2390 just_delete = true;
2391 break;
2392 }
2394 //sample points on the contiguous selected segment
2395 NR::Point *bez;
2396 bez = new NR::Point [4];
2397 bez[0] = curr->pos;
2398 bez[1] = curr->n.pos;
2399 bez[2] = curr->n.other->p.pos;
2400 bez[3] = curr->n.other->pos;
2401 for (int i=1; i<rate; i++) {
2402 gdouble t = i * period;
2403 NR::Point p = bezier_pt(3, bez, t);
2404 data.push_back(p);
2405 }
2406 data.push_back(curr->n.other->pos);
2408 sample_end = curr->n.other;
2409 //break if we've come full circle or hit the end of the selection
2410 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2411 break;
2412 }
2413 }
2414 }
2416 if (!just_delete) {
2417 //calculate the best fitting single segment and adjust the endpoints
2418 NR::Point *adata;
2419 adata = new NR::Point [data.size()];
2420 copy(data.begin(), data.end(), adata);
2422 NR::Point *bez;
2423 bez = new NR::Point [4];
2424 //would decreasing error create a better fitting approximation?
2425 gdouble error = 1.0;
2426 gint ret;
2427 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2429 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2430 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2431 //the resulting nodes behave as expected.
2432 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2433 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2434 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2435 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2437 //adjust endpoints
2438 sample_cursor->n.pos = bez[1];
2439 sample_end->p.pos = bez[2];
2440 }
2442 //destroy this contiguous selection
2443 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2444 Inkscape::NodePath::Node *temp = delete_cursor;
2445 if (delete_cursor->n.other == delete_cursor) {
2446 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2447 delete_cursor = NULL;
2448 } else {
2449 delete_cursor = delete_cursor->n.other;
2450 }
2451 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2452 sp_nodepath_node_destroy(temp);
2453 }
2455 sp_nodepath_update_handles(nodepath);
2457 if (!g_slist_find(nodepaths, nodepath))
2458 nodepaths = g_slist_prepend (nodepaths, nodepath);
2459 }
2461 for (GSList *i = nodepaths; i; i = i->next) {
2462 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2463 // different nodepaths will give us one undo event per nodepath
2464 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2466 // if the entire nodepath is removed, delete the selected object.
2467 if (nodepath->subpaths == NULL ||
2468 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2469 //at least 2
2470 sp_nodepath_get_node_count(nodepath) < 2) {
2471 SPDocument *document = sp_desktop_document (nodepath->desktop);
2472 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2473 //delete this nodepath's object, not the entire selection! (though at this time, this
2474 //does not matter)
2475 sp_selection_delete(nodepath->desktop);
2476 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2477 _("Delete nodes"));
2478 } else {
2479 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2480 sp_nodepath_update_statusbar(nodepath);
2481 }
2482 }
2484 g_slist_free (nodepaths);
2485 }
2487 /**
2488 * Delete one or more selected nodes.
2489 */
2490 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2491 {
2492 if (!nodepath) return;
2493 if (!nodepath->selected) return;
2495 /** \todo fixme: do it the right way */
2496 while (nodepath->selected) {
2497 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2498 sp_nodepath_node_destroy(node);
2499 }
2502 //clean up the nodepath (such as for trivial subpaths)
2503 sp_nodepath_cleanup(nodepath);
2505 sp_nodepath_update_handles(nodepath);
2507 // if the entire nodepath is removed, delete the selected object.
2508 if (nodepath->subpaths == NULL ||
2509 sp_nodepath_get_node_count(nodepath) < 2) {
2510 SPDocument *document = sp_desktop_document (nodepath->desktop);
2511 sp_selection_delete(nodepath->desktop);
2512 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2513 _("Delete nodes"));
2514 return;
2515 }
2517 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2519 sp_nodepath_update_statusbar(nodepath);
2520 }
2522 /**
2523 * Delete one or more segments between two selected nodes.
2524 * This is the code for 'split'.
2525 */
2526 void
2527 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2528 {
2529 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2530 Inkscape::NodePath::Node *curr, *next; //Iterators
2532 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2534 if (g_list_length(nodepath->selected) != 2) {
2535 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2536 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2537 return;
2538 }
2540 //Selected nodes, not inclusive
2541 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2542 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2544 if ( ( a==b) || //same node
2545 (a->subpath != b->subpath ) || //not the same path
2546 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2547 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2548 {
2549 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2550 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2551 return;
2552 }
2554 //###########################################
2555 //# BEGIN EDITS
2556 //###########################################
2557 //##################################
2558 //# CLOSED PATH
2559 //##################################
2560 if (a->subpath->closed) {
2563 gboolean reversed = FALSE;
2565 //Since we can go in a circle, we need to find the shorter distance.
2566 // a->b or b->a
2567 start = end = NULL;
2568 int distance = 0;
2569 int minDistance = 0;
2570 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2571 if (curr==b) {
2572 //printf("a to b:%d\n", distance);
2573 start = a;//go from a to b
2574 end = b;
2575 minDistance = distance;
2576 //printf("A to B :\n");
2577 break;
2578 }
2579 distance++;
2580 }
2582 //try again, the other direction
2583 distance = 0;
2584 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2585 if (curr==a) {
2586 //printf("b to a:%d\n", distance);
2587 if (distance < minDistance) {
2588 start = b; //we go from b to a
2589 end = a;
2590 reversed = TRUE;
2591 //printf("B to A\n");
2592 }
2593 break;
2594 }
2595 distance++;
2596 }
2599 //Copy everything from 'end' to 'start' to a new subpath
2600 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2601 for (curr=end ; curr ; curr=curr->n.other) {
2602 NRPathcode code = (NRPathcode) curr->code;
2603 if (curr == end)
2604 code = NR_MOVETO;
2605 sp_nodepath_node_new(t, NULL,
2606 (Inkscape::NodePath::NodeType)curr->type, code,
2607 &curr->p.pos, &curr->pos, &curr->n.pos);
2608 if (curr == start)
2609 break;
2610 }
2611 sp_nodepath_subpath_destroy(a->subpath);
2614 }
2618 //##################################
2619 //# OPEN PATH
2620 //##################################
2621 else {
2623 //We need to get the direction of the list between A and B
2624 //Can we walk from a to b?
2625 start = end = NULL;
2626 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2627 if (curr==b) {
2628 start = a; //did it! we go from a to b
2629 end = b;
2630 //printf("A to B\n");
2631 break;
2632 }
2633 }
2634 if (!start) {//didn't work? let's try the other direction
2635 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2636 if (curr==a) {
2637 start = b; //did it! we go from b to a
2638 end = a;
2639 //printf("B to A\n");
2640 break;
2641 }
2642 }
2643 }
2644 if (!start) {
2645 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2646 _("Cannot find path between nodes."));
2647 return;
2648 }
2652 //Copy everything after 'end' to a new subpath
2653 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2654 for (curr=end ; curr ; curr=curr->n.other) {
2655 NRPathcode code = (NRPathcode) curr->code;
2656 if (curr == end)
2657 code = NR_MOVETO;
2658 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2659 &curr->p.pos, &curr->pos, &curr->n.pos);
2660 }
2662 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2663 for (curr = start->n.other ; curr ; curr=next) {
2664 next = curr->n.other;
2665 sp_nodepath_node_destroy(curr);
2666 }
2668 }
2669 //###########################################
2670 //# END EDITS
2671 //###########################################
2673 //clean up the nodepath (such as for trivial subpaths)
2674 sp_nodepath_cleanup(nodepath);
2676 sp_nodepath_update_handles(nodepath);
2678 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2680 sp_nodepath_update_statusbar(nodepath);
2681 }
2683 /**
2684 * Call sp_nodepath_set_line() for all selected segments.
2685 */
2686 void
2687 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2688 {
2689 if (nodepath == NULL) return;
2691 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2692 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2693 g_assert(n->selected);
2694 if (n->p.other && n->p.other->selected) {
2695 sp_nodepath_set_line_type(n, code);
2696 }
2697 }
2699 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2700 }
2702 /**
2703 * Call sp_nodepath_convert_node_type() for all selected nodes.
2704 */
2705 void
2706 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2707 {
2708 if (nodepath == NULL) return;
2710 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2712 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2713 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2714 }
2716 sp_nodepath_update_repr(nodepath, _("Change node type"));
2717 }
2719 /**
2720 * Change select status of node, update its own and neighbour handles.
2721 */
2722 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2723 {
2724 node->selected = selected;
2726 if (selected) {
2727 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2728 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2729 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2730 sp_knot_update_ctrl(node->knot);
2731 } else {
2732 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2733 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2734 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2735 sp_knot_update_ctrl(node->knot);
2736 }
2738 sp_node_update_handles(node);
2739 if (node->n.other) sp_node_update_handles(node->n.other);
2740 if (node->p.other) sp_node_update_handles(node->p.other);
2741 }
2743 /**
2744 \brief Select a node
2745 \param node The node to select
2746 \param incremental If true, add to selection, otherwise deselect others
2747 \param override If true, always select this node, otherwise toggle selected status
2748 */
2749 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2750 {
2751 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2753 if (incremental) {
2754 if (override) {
2755 if (!g_list_find(nodepath->selected, node)) {
2756 nodepath->selected = g_list_prepend(nodepath->selected, node);
2757 }
2758 sp_node_set_selected(node, TRUE);
2759 } else { // toggle
2760 if (node->selected) {
2761 g_assert(g_list_find(nodepath->selected, node));
2762 nodepath->selected = g_list_remove(nodepath->selected, node);
2763 } else {
2764 g_assert(!g_list_find(nodepath->selected, node));
2765 nodepath->selected = g_list_prepend(nodepath->selected, node);
2766 }
2767 sp_node_set_selected(node, !node->selected);
2768 }
2769 } else {
2770 sp_nodepath_deselect(nodepath);
2771 nodepath->selected = g_list_prepend(nodepath->selected, node);
2772 sp_node_set_selected(node, TRUE);
2773 }
2775 sp_nodepath_update_statusbar(nodepath);
2776 }
2779 /**
2780 \brief Deselect all nodes in the nodepath
2781 */
2782 void
2783 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2784 {
2785 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2787 while (nodepath->selected) {
2788 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2789 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2790 }
2791 sp_nodepath_update_statusbar(nodepath);
2792 }
2794 /**
2795 \brief Select or invert selection of all nodes in the nodepath
2796 */
2797 void
2798 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2799 {
2800 if (!nodepath) return;
2802 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2803 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2804 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2805 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2806 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2807 }
2808 }
2809 }
2811 /**
2812 * If nothing selected, does the same as sp_nodepath_select_all();
2813 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2814 * (i.e., similar to "select all in layer", with the "selected" subpaths
2815 * being treated as "layers" in the path).
2816 */
2817 void
2818 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2819 {
2820 if (!nodepath) return;
2822 if (g_list_length (nodepath->selected) == 0) {
2823 sp_nodepath_select_all (nodepath, invert);
2824 return;
2825 }
2827 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2828 GSList *subpaths = NULL;
2830 for (GList *l = copy; l != NULL; l = l->next) {
2831 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2832 Inkscape::NodePath::SubPath *subpath = n->subpath;
2833 if (!g_slist_find (subpaths, subpath))
2834 subpaths = g_slist_prepend (subpaths, subpath);
2835 }
2837 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2838 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2839 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2840 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2841 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2842 }
2843 }
2845 g_slist_free (subpaths);
2846 g_list_free (copy);
2847 }
2849 /**
2850 * \brief Select the node after the last selected; if none is selected,
2851 * select the first within path.
2852 */
2853 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2854 {
2855 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2857 Inkscape::NodePath::Node *last = NULL;
2858 if (nodepath->selected) {
2859 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2860 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2861 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2862 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2863 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2864 if (node->selected) {
2865 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2866 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2867 if (spl->next) { // there's a next subpath
2868 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2869 last = subpath_next->first;
2870 } else if (spl->prev) { // there's a previous subpath
2871 last = NULL; // to be set later to the first node of first subpath
2872 } else {
2873 last = node->n.other;
2874 }
2875 } else {
2876 last = node->n.other;
2877 }
2878 } else {
2879 if (node->n.other) {
2880 last = node->n.other;
2881 } else {
2882 if (spl->next) { // there's a next subpath
2883 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2884 last = subpath_next->first;
2885 } else if (spl->prev) { // there's a previous subpath
2886 last = NULL; // to be set later to the first node of first subpath
2887 } else {
2888 last = (Inkscape::NodePath::Node *) subpath->first;
2889 }
2890 }
2891 }
2892 }
2893 }
2894 }
2895 sp_nodepath_deselect(nodepath);
2896 }
2898 if (last) { // there's at least one more node after selected
2899 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2900 } else { // no more nodes, select the first one in first subpath
2901 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2902 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2903 }
2904 }
2906 /**
2907 * \brief Select the node before the first selected; if none is selected,
2908 * select the last within path
2909 */
2910 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2911 {
2912 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2914 Inkscape::NodePath::Node *last = NULL;
2915 if (nodepath->selected) {
2916 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2917 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2918 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2919 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2920 if (node->selected) {
2921 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2922 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2923 if (spl->prev) { // there's a prev subpath
2924 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2925 last = subpath_prev->last;
2926 } else if (spl->next) { // there's a next subpath
2927 last = NULL; // to be set later to the last node of last subpath
2928 } else {
2929 last = node->p.other;
2930 }
2931 } else {
2932 last = node->p.other;
2933 }
2934 } else {
2935 if (node->p.other) {
2936 last = node->p.other;
2937 } else {
2938 if (spl->prev) { // there's a prev subpath
2939 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2940 last = subpath_prev->last;
2941 } else if (spl->next) { // there's a next subpath
2942 last = NULL; // to be set later to the last node of last subpath
2943 } else {
2944 last = (Inkscape::NodePath::Node *) subpath->last;
2945 }
2946 }
2947 }
2948 }
2949 }
2950 }
2951 sp_nodepath_deselect(nodepath);
2952 }
2954 if (last) { // there's at least one more node before selected
2955 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2956 } else { // no more nodes, select the last one in last subpath
2957 GList *spl = g_list_last(nodepath->subpaths);
2958 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2959 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2960 }
2961 }
2963 /**
2964 * \brief Select all nodes that are within the rectangle.
2965 */
2966 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2967 {
2968 if (!incremental) {
2969 sp_nodepath_deselect(nodepath);
2970 }
2972 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2973 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2974 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2975 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2977 if (b.contains(node->pos)) {
2978 sp_nodepath_node_select(node, TRUE, TRUE);
2979 }
2980 }
2981 }
2982 }
2985 void
2986 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2987 {
2988 g_assert (n);
2989 g_assert (nodepath);
2990 g_assert (n->subpath->nodepath == nodepath);
2992 if (g_list_length (nodepath->selected) == 0) {
2993 if (grow > 0) {
2994 sp_nodepath_node_select(n, TRUE, TRUE);
2995 }
2996 return;
2997 }
2999 if (g_list_length (nodepath->selected) == 1) {
3000 if (grow < 0) {
3001 sp_nodepath_deselect (nodepath);
3002 return;
3003 }
3004 }
3006 double n_sel_range = 0, p_sel_range = 0;
3007 Inkscape::NodePath::Node *farthest_n_node = n;
3008 Inkscape::NodePath::Node *farthest_p_node = n;
3010 // Calculate ranges
3011 {
3012 double n_range = 0, p_range = 0;
3013 bool n_going = true, p_going = true;
3014 Inkscape::NodePath::Node *n_node = n;
3015 Inkscape::NodePath::Node *p_node = n;
3016 do {
3017 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3018 if (n_node && n_going)
3019 n_node = n_node->n.other;
3020 if (n_node == NULL) {
3021 n_going = false;
3022 } else {
3023 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3024 if (n_node->selected) {
3025 n_sel_range = n_range;
3026 farthest_n_node = n_node;
3027 }
3028 if (n_node == p_node) {
3029 n_going = false;
3030 p_going = false;
3031 }
3032 }
3033 if (p_node && p_going)
3034 p_node = p_node->p.other;
3035 if (p_node == NULL) {
3036 p_going = false;
3037 } else {
3038 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3039 if (p_node->selected) {
3040 p_sel_range = p_range;
3041 farthest_p_node = p_node;
3042 }
3043 if (p_node == n_node) {
3044 n_going = false;
3045 p_going = false;
3046 }
3047 }
3048 } while (n_going || p_going);
3049 }
3051 if (grow > 0) {
3052 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3053 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3054 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3055 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3056 }
3057 } else {
3058 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3059 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3060 } else if (farthest_p_node && farthest_p_node->selected) {
3061 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3062 }
3063 }
3064 }
3066 void
3067 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3068 {
3069 g_assert (n);
3070 g_assert (nodepath);
3071 g_assert (n->subpath->nodepath == nodepath);
3073 if (g_list_length (nodepath->selected) == 0) {
3074 if (grow > 0) {
3075 sp_nodepath_node_select(n, TRUE, TRUE);
3076 }
3077 return;
3078 }
3080 if (g_list_length (nodepath->selected) == 1) {
3081 if (grow < 0) {
3082 sp_nodepath_deselect (nodepath);
3083 return;
3084 }
3085 }
3087 Inkscape::NodePath::Node *farthest_selected = NULL;
3088 double farthest_dist = 0;
3090 Inkscape::NodePath::Node *closest_unselected = NULL;
3091 double closest_dist = NR_HUGE;
3093 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3094 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3095 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3096 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3097 if (node == n)
3098 continue;
3099 if (node->selected) {
3100 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3101 farthest_dist = NR::L2(node->pos - n->pos);
3102 farthest_selected = node;
3103 }
3104 } else {
3105 if (NR::L2(node->pos - n->pos) < closest_dist) {
3106 closest_dist = NR::L2(node->pos - n->pos);
3107 closest_unselected = node;
3108 }
3109 }
3110 }
3111 }
3113 if (grow > 0) {
3114 if (closest_unselected) {
3115 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3116 }
3117 } else {
3118 if (farthest_selected) {
3119 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3120 }
3121 }
3122 }
3125 /**
3126 \brief Saves all nodes' and handles' current positions in their origin members
3127 */
3128 void
3129 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3130 {
3131 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3132 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3133 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3134 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3135 n->origin = n->pos;
3136 n->p.origin = n->p.pos;
3137 n->n.origin = n->n.pos;
3138 }
3139 }
3140 }
3142 /**
3143 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3144 */
3145 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3146 {
3147 if (!nodepath->selected) {
3148 return NULL;
3149 }
3151 GList *r = NULL;
3152 guint i = 0;
3153 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3154 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3155 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3156 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3157 i++;
3158 if (node->selected) {
3159 r = g_list_append(r, GINT_TO_POINTER(i));
3160 }
3161 }
3162 }
3163 return r;
3164 }
3166 /**
3167 \brief Restores selection by selecting nodes whose positions are in the list
3168 */
3169 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3170 {
3171 sp_nodepath_deselect(nodepath);
3173 guint i = 0;
3174 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3175 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3176 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3177 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3178 i++;
3179 if (g_list_find(r, GINT_TO_POINTER(i))) {
3180 sp_nodepath_node_select(node, TRUE, TRUE);
3181 }
3182 }
3183 }
3184 }
3187 /**
3188 \brief Adjusts handle according to node type and line code.
3189 */
3190 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3191 {
3192 g_assert(node);
3194 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3195 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3197 // nothing to do if we are an end node
3198 if (me->other == NULL) return;
3199 if (other->other == NULL) return;
3201 // nothing to do if we are a cusp node
3202 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3204 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3205 NRPathcode mecode;
3206 if (which_adjust == 1) {
3207 mecode = (NRPathcode)me->other->code;
3208 } else {
3209 mecode = (NRPathcode)node->code;
3210 }
3211 if (mecode == NR_LINETO) return;
3213 if (sp_node_side_is_line(node, other)) {
3214 // other is a line, and we are either smooth or symm
3215 Inkscape::NodePath::Node *othernode = other->other;
3216 double len = NR::L2(me->pos - node->pos);
3217 NR::Point delta = node->pos - othernode->pos;
3218 double linelen = NR::L2(delta);
3219 if (linelen < 1e-18)
3220 return;
3221 me->pos = node->pos + (len / linelen)*delta;
3222 return;
3223 }
3225 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3226 // symmetrize
3227 me->pos = 2 * node->pos - other->pos;
3228 return;
3229 } else {
3230 // smoothify
3231 double len = NR::L2(me->pos - node->pos);
3232 NR::Point delta = other->pos - node->pos;
3233 double otherlen = NR::L2(delta);
3234 if (otherlen < 1e-18) return;
3235 me->pos = node->pos - (len / otherlen) * delta;
3236 }
3237 }
3239 /**
3240 \brief Adjusts both handles according to node type and line code
3241 */
3242 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3243 {
3244 g_assert(node);
3246 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3248 /* we are either smooth or symm */
3250 if (node->p.other == NULL) return;
3251 if (node->n.other == NULL) return;
3253 if (sp_node_side_is_line(node, &node->p)) {
3254 sp_node_adjust_handle(node, 1);
3255 return;
3256 }
3258 if (sp_node_side_is_line(node, &node->n)) {
3259 sp_node_adjust_handle(node, -1);
3260 return;
3261 }
3263 /* both are curves */
3264 NR::Point const delta( node->n.pos - node->p.pos );
3266 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3267 node->p.pos = node->pos - delta / 2;
3268 node->n.pos = node->pos + delta / 2;
3269 return;
3270 }
3272 /* We are smooth */
3273 double plen = NR::L2(node->p.pos - node->pos);
3274 if (plen < 1e-18) return;
3275 double nlen = NR::L2(node->n.pos - node->pos);
3276 if (nlen < 1e-18) return;
3277 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3278 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3279 }
3281 /**
3282 * Node event callback.
3283 */
3284 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3285 {
3286 gboolean ret = FALSE;
3287 switch (event->type) {
3288 case GDK_ENTER_NOTIFY:
3289 Inkscape::NodePath::Path::active_node = n;
3290 break;
3291 case GDK_LEAVE_NOTIFY:
3292 Inkscape::NodePath::Path::active_node = NULL;
3293 break;
3294 case GDK_SCROLL:
3295 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3296 switch (event->scroll.direction) {
3297 case GDK_SCROLL_UP:
3298 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3299 break;
3300 case GDK_SCROLL_DOWN:
3301 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3302 break;
3303 default:
3304 break;
3305 }
3306 ret = TRUE;
3307 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3308 switch (event->scroll.direction) {
3309 case GDK_SCROLL_UP:
3310 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3311 break;
3312 case GDK_SCROLL_DOWN:
3313 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3314 break;
3315 default:
3316 break;
3317 }
3318 ret = TRUE;
3319 }
3320 break;
3321 case GDK_KEY_PRESS:
3322 switch (get_group0_keyval (&event->key)) {
3323 case GDK_space:
3324 if (event->key.state & GDK_BUTTON1_MASK) {
3325 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3326 stamp_repr(nodepath);
3327 ret = TRUE;
3328 }
3329 break;
3330 case GDK_Page_Up:
3331 if (event->key.state & GDK_CONTROL_MASK) {
3332 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3333 } else {
3334 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3335 }
3336 break;
3337 case GDK_Page_Down:
3338 if (event->key.state & GDK_CONTROL_MASK) {
3339 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3340 } else {
3341 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3342 }
3343 break;
3344 default:
3345 break;
3346 }
3347 break;
3348 default:
3349 break;
3350 }
3352 return ret;
3353 }
3355 /**
3356 * Handle keypress on node; directly called.
3357 */
3358 gboolean node_key(GdkEvent *event)
3359 {
3360 Inkscape::NodePath::Path *np;
3362 // there is no way to verify nodes so set active_node to nil when deleting!!
3363 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3365 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3366 gint ret = FALSE;
3367 switch (get_group0_keyval (&event->key)) {
3368 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3369 case GDK_BackSpace:
3370 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3371 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3372 sp_nodepath_update_repr(np, _("Delete node"));
3373 Inkscape::NodePath::Path::active_node = NULL;
3374 ret = TRUE;
3375 break;
3376 case GDK_c:
3377 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3378 ret = TRUE;
3379 break;
3380 case GDK_s:
3381 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3382 ret = TRUE;
3383 break;
3384 case GDK_y:
3385 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3386 ret = TRUE;
3387 break;
3388 case GDK_b:
3389 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3390 ret = TRUE;
3391 break;
3392 }
3393 return ret;
3394 }
3395 return FALSE;
3396 }
3398 /**
3399 * Mouseclick on node callback.
3400 */
3401 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3402 {
3403 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3405 if (state & GDK_CONTROL_MASK) {
3406 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3408 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3409 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3410 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3411 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3412 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3413 } else {
3414 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3415 }
3416 sp_nodepath_update_repr(nodepath, _("Change node type"));
3417 sp_nodepath_update_statusbar(nodepath);
3419 } else { //ctrl+alt+click: delete node
3420 GList *node_to_delete = NULL;
3421 node_to_delete = g_list_append(node_to_delete, n);
3422 sp_node_delete_preserve(node_to_delete);
3423 }
3425 } else {
3426 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3427 }
3428 }
3430 /**
3431 * Mouse grabbed node callback.
3432 */
3433 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3434 {
3435 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3437 if (!n->selected) {
3438 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3439 }
3441 n->is_dragging = true;
3442 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3444 sp_nodepath_remember_origins (n->subpath->nodepath);
3445 }
3447 /**
3448 * Mouse ungrabbed node callback.
3449 */
3450 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3451 {
3452 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3454 n->dragging_out = NULL;
3455 n->is_dragging = false;
3456 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3458 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3459 }
3461 /**
3462 * The point on a line, given by its angle, closest to the given point.
3463 * \param p A point.
3464 * \param a Angle of the line; it is assumed to go through coordinate origin.
3465 * \param closest Pointer to the point struct where the result is stored.
3466 * \todo FIXME: use dot product perhaps?
3467 */
3468 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3469 {
3470 if (a == HUGE_VAL) { // vertical
3471 *closest = NR::Point(0, (*p)[NR::Y]);
3472 } else {
3473 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3474 (*closest)[NR::Y] = a * (*closest)[NR::X];
3475 }
3476 }
3478 /**
3479 * Distance from the point to a line given by its angle.
3480 * \param p A point.
3481 * \param a Angle of the line; it is assumed to go through coordinate origin.
3482 */
3483 static double point_line_distance(NR::Point *p, double a)
3484 {
3485 NR::Point c;
3486 point_line_closest(p, a, &c);
3487 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]));
3488 }
3490 /**
3491 * Callback for node "request" signal.
3492 * \todo fixme: This goes to "moved" event? (lauris)
3493 */
3494 static gboolean
3495 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3496 {
3497 double yn, xn, yp, xp;
3498 double an, ap, na, pa;
3499 double d_an, d_ap, d_na, d_pa;
3500 gboolean collinear = FALSE;
3501 NR::Point c;
3502 NR::Point pr;
3504 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3506 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3508 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3509 if ( (!n->subpath->nodepath->straight_path) &&
3510 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3511 || n->dragging_out ) )
3512 {
3513 NR::Point mouse = (*p);
3515 if (!n->dragging_out) {
3516 // This is the first drag-out event; find out which handle to drag out
3517 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3518 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3520 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3521 return FALSE;
3523 Inkscape::NodePath::NodeSide *opposite;
3524 if (appr_p > appr_n) { // closer to p
3525 n->dragging_out = &n->p;
3526 opposite = &n->n;
3527 n->code = NR_CURVETO;
3528 } else if (appr_p < appr_n) { // closer to n
3529 n->dragging_out = &n->n;
3530 opposite = &n->p;
3531 n->n.other->code = NR_CURVETO;
3532 } else { // p and n nodes are the same
3533 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3534 n->dragging_out = &n->p;
3535 opposite = &n->n;
3536 n->code = NR_CURVETO;
3537 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3538 n->dragging_out = &n->n;
3539 opposite = &n->p;
3540 n->n.other->code = NR_CURVETO;
3541 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3542 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);
3543 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);
3544 if (appr_other_p > appr_other_n) { // closer to other's p handle
3545 n->dragging_out = &n->n;
3546 opposite = &n->p;
3547 n->n.other->code = NR_CURVETO;
3548 } else { // closer to other's n handle
3549 n->dragging_out = &n->p;
3550 opposite = &n->n;
3551 n->code = NR_CURVETO;
3552 }
3553 }
3554 }
3556 // if there's another handle, make sure the one we drag out starts parallel to it
3557 if (opposite->pos != n->pos) {
3558 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3559 }
3561 // knots might not be created yet!
3562 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3563 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3564 }
3566 // pass this on to the handle-moved callback
3567 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3568 sp_node_update_handles(n);
3569 return TRUE;
3570 }
3572 if (state & GDK_CONTROL_MASK) { // constrained motion
3574 // calculate relative distances of handles
3575 // n handle:
3576 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3577 xn = n->n.pos[NR::X] - n->pos[NR::X];
3578 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3579 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3580 if (n->n.other) { // if there is the next point
3581 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3582 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3583 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3584 }
3585 }
3586 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3587 if (yn < 0) { xn = -xn; yn = -yn; }
3589 // p handle:
3590 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3591 xp = n->p.pos[NR::X] - n->pos[NR::X];
3592 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3593 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3594 if (n->p.other) {
3595 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3596 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3597 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3598 }
3599 }
3600 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3601 if (yp < 0) { xp = -xp; yp = -yp; }
3603 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3604 // sliding on handles, only if at least one of the handles is non-vertical
3605 // (otherwise it's the same as ctrl+drag anyway)
3607 // calculate angles of the handles
3608 if (xn == 0) {
3609 if (yn == 0) { // no handle, consider it the continuation of the other one
3610 an = 0;
3611 collinear = TRUE;
3612 }
3613 else an = 0; // vertical; set the angle to horizontal
3614 } else an = yn/xn;
3616 if (xp == 0) {
3617 if (yp == 0) { // no handle, consider it the continuation of the other one
3618 ap = an;
3619 }
3620 else ap = 0; // vertical; set the angle to horizontal
3621 } else ap = yp/xp;
3623 if (collinear) an = ap;
3625 // angles of the perpendiculars; HUGE_VAL means vertical
3626 if (an == 0) na = HUGE_VAL; else na = -1/an;
3627 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3629 // mouse point relative to the node's original pos
3630 pr = (*p) - n->origin;
3632 // distances to the four lines (two handles and two perpendiculars)
3633 d_an = point_line_distance(&pr, an);
3634 d_na = point_line_distance(&pr, na);
3635 d_ap = point_line_distance(&pr, ap);
3636 d_pa = point_line_distance(&pr, pa);
3638 // find out which line is the closest, save its closest point in c
3639 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3640 point_line_closest(&pr, an, &c);
3641 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3642 point_line_closest(&pr, ap, &c);
3643 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3644 point_line_closest(&pr, na, &c);
3645 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3646 point_line_closest(&pr, pa, &c);
3647 }
3649 // move the node to the closest point
3650 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3651 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3652 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3653 true);
3655 } else { // constraining to hor/vert
3657 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3658 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3659 (*p)[NR::X] - n->pos[NR::X],
3660 n->origin[NR::Y] - n->pos[NR::Y],
3661 true,
3662 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3663 } else { // snap to vert
3664 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3665 n->origin[NR::X] - n->pos[NR::X],
3666 (*p)[NR::Y] - n->pos[NR::Y],
3667 true,
3668 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3669 }
3670 }
3671 } else { // move freely
3672 if (n->is_dragging) {
3673 if (state & GDK_MOD1_MASK) { // sculpt
3674 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3675 } else {
3676 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3677 (*p)[NR::X] - n->pos[NR::X],
3678 (*p)[NR::Y] - n->pos[NR::Y],
3679 (state & GDK_SHIFT_MASK) == 0);
3680 }
3681 }
3682 }
3684 n->subpath->nodepath->desktop->scroll_to_point(p);
3686 return TRUE;
3687 }
3689 /**
3690 * Node handle clicked callback.
3691 */
3692 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3693 {
3694 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3696 if (state & GDK_CONTROL_MASK) { // "delete" handle
3697 if (n->p.knot == knot) {
3698 n->p.pos = n->pos;
3699 } else if (n->n.knot == knot) {
3700 n->n.pos = n->pos;
3701 }
3702 sp_node_update_handles(n);
3703 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3704 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3705 sp_nodepath_update_statusbar(nodepath);
3707 } else { // just select or add to selection, depending in Shift
3708 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3709 }
3710 }
3712 /**
3713 * Node handle grabbed callback.
3714 */
3715 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3716 {
3717 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3719 if (!n->selected) {
3720 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3721 }
3723 // remember the origin point of the handle
3724 if (n->p.knot == knot) {
3725 n->p.origin_radial = n->p.pos - n->pos;
3726 } else if (n->n.knot == knot) {
3727 n->n.origin_radial = n->n.pos - n->pos;
3728 } else {
3729 g_assert_not_reached();
3730 }
3732 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3733 }
3735 /**
3736 * Node handle ungrabbed callback.
3737 */
3738 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3739 {
3740 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3742 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3743 if (n->p.knot == knot) {
3744 n->p.origin_radial.a = 0;
3745 sp_knot_set_position(knot, n->p.pos, state);
3746 } else if (n->n.knot == knot) {
3747 n->n.origin_radial.a = 0;
3748 sp_knot_set_position(knot, n->n.pos, state);
3749 } else {
3750 g_assert_not_reached();
3751 }
3753 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3754 }
3756 /**
3757 * Node handle "request" signal callback.
3758 */
3759 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3760 {
3761 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3763 Inkscape::NodePath::NodeSide *me, *opposite;
3764 gint which;
3765 if (n->p.knot == knot) {
3766 me = &n->p;
3767 opposite = &n->n;
3768 which = -1;
3769 } else if (n->n.knot == knot) {
3770 me = &n->n;
3771 opposite = &n->p;
3772 which = 1;
3773 } else {
3774 me = opposite = NULL;
3775 which = 0;
3776 g_assert_not_reached();
3777 }
3779 SPDesktop *desktop = n->subpath->nodepath->desktop;
3780 SnapManager &m = desktop->namedview->snap_manager;
3781 m.setup(desktop, true, n->subpath->nodepath->item);
3782 Inkscape::SnappedPoint s;
3784 if ((state & GDK_SHIFT_MASK) != 0) {
3785 // We will not try to snap when the shift-key is pressed
3786 // so remove the old snap indicator and don't wait for it to time-out
3787 desktop->snapindicator->remove_snappoint();
3788 }
3790 Inkscape::NodePath::Node *othernode = opposite->other;
3791 if (othernode) {
3792 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3793 /* We are smooth node adjacent with line */
3794 NR::Point const delta = *p - n->pos;
3795 NR::Coord const len = NR::L2(delta);
3796 Inkscape::NodePath::Node *othernode = opposite->other;
3797 NR::Point const ndelta = n->pos - othernode->pos;
3798 NR::Coord const linelen = NR::L2(ndelta);
3799 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3800 NR::Coord const scal = dot(delta, ndelta) / linelen;
3801 (*p) = n->pos + (scal / linelen) * ndelta;
3802 }
3803 if ((state & GDK_SHIFT_MASK) == 0) {
3804 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(*p), Inkscape::Snapper::ConstraintLine(*p, ndelta));
3805 }
3806 } else {
3807 if ((state & GDK_SHIFT_MASK) == 0) {
3808 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(*p));
3809 }
3810 }
3811 } else {
3812 if ((state & GDK_SHIFT_MASK) == 0) {
3813 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(*p));
3814 }
3815 }
3817 Geom::Point pt2g = *p;
3818 s.getPoint(pt2g);
3819 *p = pt2g;
3821 sp_node_adjust_handle(n, -which);
3823 return FALSE;
3824 }
3826 /**
3827 * Node handle moved callback.
3828 */
3829 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3830 {
3831 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3833 Inkscape::NodePath::NodeSide *me;
3834 Inkscape::NodePath::NodeSide *other;
3835 if (n->p.knot == knot) {
3836 me = &n->p;
3837 other = &n->n;
3838 } else if (n->n.knot == knot) {
3839 me = &n->n;
3840 other = &n->p;
3841 } else {
3842 me = NULL;
3843 other = NULL;
3844 g_assert_not_reached();
3845 }
3847 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3848 Radial rme(me->pos - n->pos);
3849 Radial rother(other->pos - n->pos);
3850 Radial rnew(*p - n->pos);
3852 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3853 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3854 /* 0 interpreted as "no snapping". */
3856 // 1. Snap to the closest PI/snaps angle, starting from zero.
3857 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3859 // 2. Snap to the original angle, its opposite and perpendiculars
3860 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3861 /* The closest PI/2 angle, starting from original angle */
3862 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3864 // Snap to the closest.
3865 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3866 ? a_snapped
3867 : a_ortho );
3868 }
3870 // 3. Snap to the angle of the opposite line, if any
3871 Inkscape::NodePath::Node *othernode = other->other;
3872 if (othernode) {
3873 NR::Point other_to_snap(0,0);
3874 if (sp_node_side_is_line(n, other)) {
3875 other_to_snap = othernode->pos - n->pos;
3876 } else {
3877 other_to_snap = other->pos - n->pos;
3878 }
3879 if (NR::L2(other_to_snap) > 1e-3) {
3880 Radial rother_to_snap(other_to_snap);
3881 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3882 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3884 // Snap to the closest.
3885 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3886 ? a_snapped
3887 : a_oppo );
3888 }
3889 }
3891 rnew.a = a_snapped;
3892 }
3894 if (state & GDK_MOD1_MASK) {
3895 // lock handle length
3896 rnew.r = me->origin_radial.r;
3897 }
3899 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3900 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3901 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3902 rother.a += rnew.a - rme.a;
3903 other->pos = NR::Point(rother) + n->pos;
3904 if (other->knot) {
3905 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3906 sp_knot_moveto(other->knot, other->pos);
3907 }
3908 }
3910 me->pos = NR::Point(rnew) + n->pos;
3911 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3913 // move knot, but without emitting the signal:
3914 // we cannot emit a "moved" signal because we're now processing it
3915 sp_knot_moveto(me->knot, me->pos);
3917 update_object(n->subpath->nodepath);
3919 /* status text */
3920 SPDesktop *desktop = n->subpath->nodepath->desktop;
3921 if (!desktop) return;
3922 SPEventContext *ec = desktop->event_context;
3923 if (!ec) return;
3925 // FIXME: this is an ad-hoc crash fix but we need to find a better way (which also works in LPEToolContext)
3926 //Inkscape::MessageContext *mc = get_message_context(ec);
3927 if (!SP_IS_NODE_CONTEXT(ec)) {
3928 return;
3929 }
3930 Inkscape::MessageContext *mc = SP_NODE_CONTEXT(ec)->_node_message_context;
3932 if (!mc) return;
3934 double degrees = 180 / M_PI * rnew.a;
3935 if (degrees > 180) degrees -= 360;
3936 if (degrees < -180) degrees += 360;
3937 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3938 degrees = angle_to_compass (degrees);
3940 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3942 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3943 _("<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);
3945 g_string_free(length, TRUE);
3946 }
3948 /**
3949 * Node handle event callback.
3950 */
3951 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3952 {
3953 gboolean ret = FALSE;
3954 switch (event->type) {
3955 case GDK_KEY_PRESS:
3956 switch (get_group0_keyval (&event->key)) {
3957 case GDK_space:
3958 if (event->key.state & GDK_BUTTON1_MASK) {
3959 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3960 stamp_repr(nodepath);
3961 ret = TRUE;
3962 }
3963 break;
3964 default:
3965 break;
3966 }
3967 break;
3968 case GDK_ENTER_NOTIFY:
3969 // we use an experimentally determined threshold that seems to work fine
3970 if (NR::L2(n->pos - knot->pos) < 0.75)
3971 Inkscape::NodePath::Path::active_node = n;
3972 break;
3973 case GDK_LEAVE_NOTIFY:
3974 // we use an experimentally determined threshold that seems to work fine
3975 if (NR::L2(n->pos - knot->pos) < 0.75)
3976 Inkscape::NodePath::Path::active_node = NULL;
3977 break;
3978 default:
3979 break;
3980 }
3982 return ret;
3983 }
3985 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3986 Radial &rme, Radial &rother, gboolean const both)
3987 {
3988 rme.a += angle;
3989 if ( both
3990 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3991 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3992 {
3993 rother.a += angle;
3994 }
3995 }
3997 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3998 Radial &rme, Radial &rother, gboolean const both)
3999 {
4000 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4002 gdouble r;
4003 if ( both
4004 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4005 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4006 {
4007 r = MAX(rme.r, rother.r);
4008 } else {
4009 r = rme.r;
4010 }
4012 gdouble const weird_angle = atan2(norm_angle, r);
4013 /* Bulia says norm_angle is just the visible distance that the
4014 * object's end must travel on the screen. Left as 'angle' for want of
4015 * a better name.*/
4017 rme.a += weird_angle;
4018 if ( both
4019 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4020 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4021 {
4022 rother.a += weird_angle;
4023 }
4024 }
4026 /**
4027 * Rotate one node.
4028 */
4029 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4030 {
4031 Inkscape::NodePath::NodeSide *me, *other;
4032 bool both = false;
4034 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4035 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4037 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4038 me = &(n->p);
4039 other = &(n->n);
4040 } else if (!n->p.other) {
4041 me = &(n->n);
4042 other = &(n->p);
4043 } else {
4044 if (which > 0) { // right handle
4045 if (xn > xp) {
4046 me = &(n->n);
4047 other = &(n->p);
4048 } else {
4049 me = &(n->p);
4050 other = &(n->n);
4051 }
4052 } else if (which < 0){ // left handle
4053 if (xn <= xp) {
4054 me = &(n->n);
4055 other = &(n->p);
4056 } else {
4057 me = &(n->p);
4058 other = &(n->n);
4059 }
4060 } else { // both handles
4061 me = &(n->n);
4062 other = &(n->p);
4063 both = true;
4064 }
4065 }
4067 Radial rme(me->pos - n->pos);
4068 Radial rother(other->pos - n->pos);
4070 if (screen) {
4071 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4072 } else {
4073 node_rotate_one_internal (*n, angle, rme, rother, both);
4074 }
4076 me->pos = n->pos + NR::Point(rme);
4078 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4079 other->pos = n->pos + NR::Point(rother);
4080 }
4082 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4083 // so here we just move all the knots without emitting move signals, for speed
4084 sp_node_update_handles(n, false);
4085 }
4087 /**
4088 * Rotate selected nodes.
4089 */
4090 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4091 {
4092 if (!nodepath || !nodepath->selected) return;
4094 if (g_list_length(nodepath->selected) == 1) {
4095 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4096 node_rotate_one (n, angle, which, screen);
4097 } else {
4098 // rotate as an object:
4100 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4101 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4102 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4103 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4104 box.expandTo (n->pos); // contain all selected nodes
4105 }
4107 gdouble rot;
4108 if (screen) {
4109 gdouble const zoom = nodepath->desktop->current_zoom();
4110 gdouble const zmove = angle / zoom;
4111 gdouble const r = NR::L2(box.max() - box.midpoint());
4112 rot = atan2(zmove, r);
4113 } else {
4114 rot = angle;
4115 }
4117 NR::Point rot_center;
4118 if (Inkscape::NodePath::Path::active_node == NULL)
4119 rot_center = box.midpoint();
4120 else
4121 rot_center = Inkscape::NodePath::Path::active_node->pos;
4123 NR::Matrix t =
4124 NR::Matrix (NR::translate(-rot_center)) *
4125 NR::Matrix (NR::rotate(rot)) *
4126 NR::Matrix (NR::translate(rot_center));
4128 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4129 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4130 n->pos *= t;
4131 n->n.pos *= t;
4132 n->p.pos *= t;
4133 sp_node_update_handles(n, false);
4134 }
4135 }
4137 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4138 }
4140 /**
4141 * Scale one node.
4142 */
4143 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4144 {
4145 bool both = false;
4146 Inkscape::NodePath::NodeSide *me, *other;
4148 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4149 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4151 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4152 me = &(n->p);
4153 other = &(n->n);
4154 n->code = NR_CURVETO;
4155 } else if (!n->p.other) {
4156 me = &(n->n);
4157 other = &(n->p);
4158 if (n->n.other)
4159 n->n.other->code = NR_CURVETO;
4160 } else {
4161 if (which > 0) { // right handle
4162 if (xn > xp) {
4163 me = &(n->n);
4164 other = &(n->p);
4165 if (n->n.other)
4166 n->n.other->code = NR_CURVETO;
4167 } else {
4168 me = &(n->p);
4169 other = &(n->n);
4170 n->code = NR_CURVETO;
4171 }
4172 } else if (which < 0){ // left handle
4173 if (xn <= xp) {
4174 me = &(n->n);
4175 other = &(n->p);
4176 if (n->n.other)
4177 n->n.other->code = NR_CURVETO;
4178 } else {
4179 me = &(n->p);
4180 other = &(n->n);
4181 n->code = NR_CURVETO;
4182 }
4183 } else { // both handles
4184 me = &(n->n);
4185 other = &(n->p);
4186 both = true;
4187 n->code = NR_CURVETO;
4188 if (n->n.other)
4189 n->n.other->code = NR_CURVETO;
4190 }
4191 }
4193 Radial rme(me->pos - n->pos);
4194 Radial rother(other->pos - n->pos);
4196 rme.r += grow;
4197 if (rme.r < 0) rme.r = 0;
4198 if (rme.a == HUGE_VAL) {
4199 if (me->other) { // if direction is unknown, initialize it towards the next node
4200 Radial rme_next(me->other->pos - n->pos);
4201 rme.a = rme_next.a;
4202 } else { // if there's no next, initialize to 0
4203 rme.a = 0;
4204 }
4205 }
4206 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4207 rother.r += grow;
4208 if (rother.r < 0) rother.r = 0;
4209 if (rother.a == HUGE_VAL) {
4210 rother.a = rme.a + M_PI;
4211 }
4212 }
4214 me->pos = n->pos + NR::Point(rme);
4216 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4217 other->pos = n->pos + NR::Point(rother);
4218 }
4220 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4221 // so here we just move all the knots without emitting move signals, for speed
4222 sp_node_update_handles(n, false);
4223 }
4225 /**
4226 * Scale selected nodes.
4227 */
4228 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4229 {
4230 if (!nodepath || !nodepath->selected) return;
4232 if (g_list_length(nodepath->selected) == 1) {
4233 // scale handles of the single selected node
4234 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4235 node_scale_one (n, grow, which);
4236 } else {
4237 // scale nodes as an "object":
4239 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4240 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4241 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4242 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4243 box.expandTo (n->pos); // contain all selected nodes
4244 }
4246 double scale = (box.maxExtent() + grow)/box.maxExtent();
4248 NR::Point scale_center;
4249 if (Inkscape::NodePath::Path::active_node == NULL)
4250 scale_center = box.midpoint();
4251 else
4252 scale_center = Inkscape::NodePath::Path::active_node->pos;
4254 NR::Matrix t =
4255 NR::Matrix (NR::translate(-scale_center)) *
4256 NR::Matrix (NR::scale(scale, scale)) *
4257 NR::Matrix (NR::translate(scale_center));
4259 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4260 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4261 n->pos *= t;
4262 n->n.pos *= t;
4263 n->p.pos *= t;
4264 sp_node_update_handles(n, false);
4265 }
4266 }
4268 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4269 }
4271 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4272 {
4273 if (!nodepath) return;
4274 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4275 }
4277 /**
4278 * Flip selected nodes horizontally/vertically.
4279 */
4280 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, boost::optional<NR::Point> center)
4281 {
4282 if (!nodepath || !nodepath->selected) return;
4284 if (g_list_length(nodepath->selected) == 1 && !center) {
4285 // flip handles of the single selected node
4286 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4287 double temp = n->p.pos[axis];
4288 n->p.pos[axis] = n->n.pos[axis];
4289 n->n.pos[axis] = temp;
4290 sp_node_update_handles(n, false);
4291 } else {
4292 // scale nodes as an "object":
4294 Geom::Rect box = sp_node_selected_bbox (nodepath);
4295 if (!center) {
4296 center = box.midpoint();
4297 }
4298 NR::Matrix t =
4299 NR::Matrix (NR::translate(- *center)) *
4300 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4301 NR::Matrix (NR::translate(*center));
4303 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4304 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4305 n->pos *= t;
4306 n->n.pos *= t;
4307 n->p.pos *= t;
4308 sp_node_update_handles(n, false);
4309 }
4310 }
4312 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4313 }
4315 Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4316 {
4317 g_assert (nodepath->selected);
4319 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4320 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4321 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4322 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4323 box.expandTo (n->pos); // contain all selected nodes
4324 }
4325 return box;
4326 }
4328 //-----------------------------------------------
4329 /**
4330 * Return new subpath under given nodepath.
4331 */
4332 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4333 {
4334 g_assert(nodepath);
4335 g_assert(nodepath->desktop);
4337 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4339 s->nodepath = nodepath;
4340 s->closed = FALSE;
4341 s->nodes = NULL;
4342 s->first = NULL;
4343 s->last = NULL;
4345 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4346 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4347 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4349 return s;
4350 }
4352 /**
4353 * Destroy nodes in subpath, then subpath itself.
4354 */
4355 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4356 {
4357 g_assert(subpath);
4358 g_assert(subpath->nodepath);
4359 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4361 while (subpath->nodes) {
4362 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4363 }
4365 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4367 g_free(subpath);
4368 }
4370 /**
4371 * Link head to tail in subpath.
4372 */
4373 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4374 {
4375 g_assert(!sp->closed);
4376 g_assert(sp->last != sp->first);
4377 g_assert(sp->first->code == NR_MOVETO);
4379 sp->closed = TRUE;
4381 //Link the head to the tail
4382 sp->first->p.other = sp->last;
4383 sp->last->n.other = sp->first;
4384 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4385 sp->first = sp->last;
4387 //Remove the extra end node
4388 sp_nodepath_node_destroy(sp->last->n.other);
4389 }
4391 /**
4392 * Open closed (loopy) subpath at node.
4393 */
4394 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4395 {
4396 g_assert(sp->closed);
4397 g_assert(n->subpath == sp);
4398 g_assert(sp->first == sp->last);
4400 /* We create new startpoint, current node will become last one */
4402 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4403 &n->pos, &n->pos, &n->n.pos);
4406 sp->closed = FALSE;
4408 //Unlink to make a head and tail
4409 sp->first = new_path;
4410 sp->last = n;
4411 n->n.other = NULL;
4412 new_path->p.other = NULL;
4413 }
4415 /**
4416 * Return new node in subpath with given properties.
4417 * \param pos Position of node.
4418 * \param ppos Handle position in previous direction
4419 * \param npos Handle position in previous direction
4420 */
4421 Inkscape::NodePath::Node *
4422 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)
4423 {
4424 g_assert(sp);
4425 g_assert(sp->nodepath);
4426 g_assert(sp->nodepath->desktop);
4428 if (nodechunk == NULL)
4429 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4431 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4433 n->subpath = sp;
4435 if (type != Inkscape::NodePath::NODE_NONE) {
4436 // use the type from sodipodi:nodetypes
4437 n->type = type;
4438 } else {
4439 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4440 // points are (almost) collinear
4441 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4442 // endnode, or a node with a retracted handle
4443 n->type = Inkscape::NodePath::NODE_CUSP;
4444 } else {
4445 n->type = Inkscape::NodePath::NODE_SMOOTH;
4446 }
4447 } else {
4448 n->type = Inkscape::NodePath::NODE_CUSP;
4449 }
4450 }
4452 n->code = code;
4453 n->selected = FALSE;
4454 n->pos = *pos;
4455 n->p.pos = *ppos;
4456 n->n.pos = *npos;
4458 n->dragging_out = NULL;
4460 Inkscape::NodePath::Node *prev;
4461 if (next) {
4462 //g_assert(g_list_find(sp->nodes, next));
4463 prev = next->p.other;
4464 } else {
4465 prev = sp->last;
4466 }
4468 if (prev)
4469 prev->n.other = n;
4470 else
4471 sp->first = n;
4473 if (next)
4474 next->p.other = n;
4475 else
4476 sp->last = n;
4478 n->p.other = prev;
4479 n->n.other = next;
4481 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"));
4482 sp_knot_set_position(n->knot, *pos, 0);
4484 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4485 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4486 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4487 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4488 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4489 sp_knot_update_ctrl(n->knot);
4491 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4492 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4493 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4494 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4495 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4496 sp_knot_show(n->knot);
4498 // We only create handle knots and lines on demand
4499 n->p.knot = NULL;
4500 n->p.line = NULL;
4501 n->n.knot = NULL;
4502 n->n.line = NULL;
4504 sp->nodes = g_list_prepend(sp->nodes, n);
4506 return n;
4507 }
4509 /**
4510 * Destroy node and its knots, link neighbors in subpath.
4511 */
4512 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4513 {
4514 g_assert(node);
4515 g_assert(node->subpath);
4516 g_assert(SP_IS_KNOT(node->knot));
4518 Inkscape::NodePath::SubPath *sp = node->subpath;
4520 if (node->selected) { // first, deselect
4521 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4522 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4523 }
4525 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4527 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4528 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4529 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4530 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4531 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4532 g_object_unref(G_OBJECT(node->knot));
4534 if (node->p.knot) {
4535 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4536 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4537 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4538 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4539 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4540 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4541 g_object_unref(G_OBJECT(node->p.knot));
4542 node->p.knot = NULL;
4543 }
4545 if (node->n.knot) {
4546 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4547 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4548 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4549 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4550 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4551 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4552 g_object_unref(G_OBJECT(node->n.knot));
4553 node->n.knot = NULL;
4554 }
4556 if (node->p.line)
4557 gtk_object_destroy(GTK_OBJECT(node->p.line));
4558 if (node->n.line)
4559 gtk_object_destroy(GTK_OBJECT(node->n.line));
4561 if (sp->nodes) { // there are others nodes on the subpath
4562 if (sp->closed) {
4563 if (sp->first == node) {
4564 g_assert(sp->last == node);
4565 sp->first = node->n.other;
4566 sp->last = sp->first;
4567 }
4568 node->p.other->n.other = node->n.other;
4569 node->n.other->p.other = node->p.other;
4570 } else {
4571 if (sp->first == node) {
4572 sp->first = node->n.other;
4573 sp->first->code = NR_MOVETO;
4574 }
4575 if (sp->last == node) sp->last = node->p.other;
4576 if (node->p.other) node->p.other->n.other = node->n.other;
4577 if (node->n.other) node->n.other->p.other = node->p.other;
4578 }
4579 } else { // this was the last node on subpath
4580 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4581 }
4583 g_mem_chunk_free(nodechunk, node);
4584 }
4586 /**
4587 * Returns one of the node's two sides.
4588 * \param which Indicates which side.
4589 * \return Pointer to previous node side if which==-1, next if which==1.
4590 */
4591 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4592 {
4593 g_assert(node);
4595 switch (which) {
4596 case -1:
4597 return &node->p;
4598 case 1:
4599 return &node->n;
4600 default:
4601 break;
4602 }
4604 g_assert_not_reached();
4606 return NULL;
4607 }
4609 /**
4610 * Return the other side of the node, given one of its sides.
4611 */
4612 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4613 {
4614 g_assert(node);
4616 if (me == &node->p) return &node->n;
4617 if (me == &node->n) return &node->p;
4619 g_assert_not_reached();
4621 return NULL;
4622 }
4624 /**
4625 * Return NRPathcode on the given side of the node.
4626 */
4627 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4628 {
4629 g_assert(node);
4631 if (me == &node->p) {
4632 if (node->p.other) return (NRPathcode)node->code;
4633 return NR_MOVETO;
4634 }
4636 if (me == &node->n) {
4637 if (node->n.other) return (NRPathcode)node->n.other->code;
4638 return NR_MOVETO;
4639 }
4641 g_assert_not_reached();
4643 return NR_END;
4644 }
4646 /**
4647 * Return node with the given index
4648 */
4649 Inkscape::NodePath::Node *
4650 sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index)
4651 {
4652 Inkscape::NodePath::Node *e = NULL;
4654 if (!nodepath) {
4655 return e;
4656 }
4658 //find segment
4659 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4661 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4662 int n = g_list_length(sp->nodes);
4663 if (sp->closed) {
4664 n++;
4665 }
4667 //if the piece belongs to this subpath grab it
4668 //otherwise move onto the next subpath
4669 if (index < n) {
4670 e = sp->first;
4671 for (int i = 0; i < index; ++i) {
4672 e = e->n.other;
4673 }
4674 break;
4675 } else {
4676 if (sp->closed) {
4677 index -= (n+1);
4678 } else {
4679 index -= n;
4680 }
4681 }
4682 }
4684 return e;
4685 }
4687 /**
4688 * Returns plain text meaning of node type.
4689 */
4690 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4691 {
4692 unsigned retracted = 0;
4693 bool endnode = false;
4695 for (int which = -1; which <= 1; which += 2) {
4696 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4697 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4698 retracted ++;
4699 if (!side->other)
4700 endnode = true;
4701 }
4703 if (retracted == 0) {
4704 if (endnode) {
4705 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4706 return _("end node");
4707 } else {
4708 switch (node->type) {
4709 case Inkscape::NodePath::NODE_CUSP:
4710 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4711 return _("cusp");
4712 case Inkscape::NodePath::NODE_SMOOTH:
4713 // TRANSLATORS: "smooth" is an adjective here
4714 return _("smooth");
4715 case Inkscape::NodePath::NODE_SYMM:
4716 return _("symmetric");
4717 }
4718 }
4719 } else if (retracted == 1) {
4720 if (endnode) {
4721 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4722 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4723 } else {
4724 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4725 }
4726 } else {
4727 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4728 }
4730 return NULL;
4731 }
4733 /**
4734 * Handles content of statusbar as long as node tool is active.
4735 */
4736 void
4737 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4738 {
4739 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");
4740 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4742 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4743 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4744 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4745 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4747 SPDesktop *desktop = NULL;
4748 if (nodepath) {
4749 desktop = nodepath->desktop;
4750 } else {
4751 desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above
4752 }
4754 SPEventContext *ec = desktop->event_context;
4755 if (!ec) return;
4757 // FIXME: this is an ad-hoc crash fix but we need to find a better way (which also works in LPEToolContext)
4758 //Inkscape::MessageContext *mc = get_message_context(ec);
4759 if (!SP_IS_NODE_CONTEXT(ec)) {
4760 return;
4761 }
4763 Inkscape::MessageContext *mc = SP_NODE_CONTEXT(ec)->_node_message_context;
4764 if (!mc) return;
4766 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4768 if (selected_nodes == 0) {
4769 Inkscape::Selection *sel = desktop->selection;
4770 if (!sel || sel->isEmpty()) {
4771 mc->setF(Inkscape::NORMAL_MESSAGE,
4772 _("Select a single object to edit its nodes or handles."));
4773 } else {
4774 if (nodepath) {
4775 mc->setF(Inkscape::NORMAL_MESSAGE,
4776 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.",
4777 "<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.",
4778 total_nodes),
4779 total_nodes);
4780 } else {
4781 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4782 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4783 } else {
4784 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4785 }
4786 }
4787 }
4788 } else if (nodepath && selected_nodes == 1) {
4789 mc->setF(Inkscape::NORMAL_MESSAGE,
4790 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4791 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4792 total_nodes),
4793 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4794 } else {
4795 if (selected_subpaths > 1) {
4796 mc->setF(Inkscape::NORMAL_MESSAGE,
4797 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4798 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4799 total_nodes),
4800 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4801 } else {
4802 mc->setF(Inkscape::NORMAL_MESSAGE,
4803 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4804 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4805 total_nodes),
4806 selected_nodes, total_nodes, when_selected);
4807 }
4808 }
4809 }
4811 /*
4812 * returns a *copy* of the curve of that object.
4813 */
4814 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4815 if (!object)
4816 return NULL;
4818 SPCurve *curve = NULL;
4819 if (SP_IS_PATH(object)) {
4820 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4821 curve = curve_new->copy();
4822 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4823 const gchar *svgd = object->repr->attribute(key);
4824 if (svgd) {
4825 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4826 SPCurve *curve_new = new SPCurve(pv);
4827 if (curve_new) {
4828 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4829 }
4830 }
4831 }
4833 return curve;
4834 }
4836 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4837 if (!np || !np->object || !curve)
4838 return;
4840 if (SP_IS_PATH(np->object)) {
4841 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4842 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4843 } else {
4844 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4845 }
4846 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4847 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( LIVEPATHEFFECT(np->object)->lpe->getParameter(np->repr_key) );
4848 if (pathparam) {
4849 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
4850 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4851 }
4852 }
4853 }
4855 /**
4856 SPCanvasItem *
4857 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
4858 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
4859 }
4860 **/
4862 /**
4863 SPCanvasItem *
4864 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4865 SPCurve *flash_curve = curve->copy();
4866 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4867 flash_curve->transform(i2d);
4868 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4869 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4870 // unless we also flash the nodes...
4871 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4872 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4873 sp_canvas_item_show(canvasitem);
4874 flash_curve->unref();
4875 return canvasitem;
4876 }
4878 SPCanvasItem *
4879 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4880 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4881 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4882 }
4883 **/
4885 SPCanvasItem *
4886 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
4887 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
4888 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
4889 flash_curve->transform(i2d);
4890 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4891 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4892 // unless we also flash the nodes...
4893 guint32 color = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
4894 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4895 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4896 sp_canvas_item_show(canvasitem);
4897 flash_curve->unref();
4898 return canvasitem;
4899 }
4901 // TODO: Merge this with sp_nodepath_make_helper_item()!
4902 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4903 np->show_helperpath = show;
4905 if (show) {
4906 SPCurve *helper_curve = np->curve->copy();
4907 helper_curve->transform(np->i2d);
4908 if (!np->helper_path) {
4909 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
4911 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4912 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);
4913 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4914 sp_canvas_item_move_to_z(np->helper_path, 0);
4915 sp_canvas_item_show(np->helper_path);
4916 } else {
4917 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4918 }
4919 helper_curve->unref();
4920 } else {
4921 if (np->helper_path) {
4922 GtkObject *temp = np->helper_path;
4923 np->helper_path = NULL;
4924 gtk_object_destroy(temp);
4925 }
4926 }
4927 }
4929 /* sp_nodepath_make_straight_path:
4930 * Prevents user from curving the path by dragging a segment or activating handles etc.
4931 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4932 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4933 */
4934 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4935 np->straight_path = true;
4936 np->show_handles = false;
4937 g_message("add code to make the path straight.");
4938 // do sp_nodepath_convert_node_type on all nodes?
4939 // coding tip: search for this text : "Make selected segments lines"
4940 }
4942 /*
4943 Local Variables:
4944 mode:c++
4945 c-file-style:"stroustrup"
4946 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4947 indent-tabs-mode:nil
4948 fill-column:99
4949 End:
4950 */
4951 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :