3481f6ed2cc0ac21afb3705954112d5e0ee09125
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 "preferences.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 "live_effects/lpeobject.h"
57 #include "live_effects/lpeobject-reference.h"
58 #include "live_effects/effect.h"
59 #include "live_effects/parameter/parameter.h"
60 #include "live_effects/parameter/path.h"
61 #include "util/mathfns.h"
62 #include "display/snap-indicator.h"
63 #include "snapped-point.h"
65 namespace Geom { class Matrix; }
67 /// \todo
68 /// evil evil evil. FIXME: conflict of two different Path classes!
69 /// There is a conflict in the namespace between two classes named Path.
70 /// #include "sp-flowtext.h"
71 /// #include "sp-flowregion.h"
73 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
74 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
75 GType sp_flowregion_get_type (void);
76 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
77 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
78 GType sp_flowtext_get_type (void);
79 // end evil workaround
81 #include "helper/stlport.h"
84 /// \todo fixme: Implement these via preferences */
86 #define NODE_FILL 0xbfbfbf00
87 #define NODE_STROKE 0x000000ff
88 #define NODE_FILL_HI 0xff000000
89 #define NODE_STROKE_HI 0x000000ff
90 #define NODE_FILL_SEL 0x0000ffff
91 #define NODE_STROKE_SEL 0x000000ff
92 #define NODE_FILL_SEL_HI 0xff000000
93 #define NODE_STROKE_SEL_HI 0x000000ff
94 #define KNOT_FILL 0xffffffff
95 #define KNOT_STROKE 0x000000ff
96 #define KNOT_FILL_HI 0xff000000
97 #define KNOT_STROKE_HI 0x000000ff
99 static GMemChunk *nodechunk = NULL;
101 /* Creation from object */
103 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t);
104 static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length);
106 /* Object updating */
108 static void stamp_repr(Inkscape::NodePath::Path *np);
109 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
110 static gchar *create_typestr(Inkscape::NodePath::Path *np);
112 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
114 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
116 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
118 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
120 /* Adjust handle placement, if the node or the other handle is moved */
121 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
122 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
123 static void sp_node_adjust_handles_auto(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, Geom::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, Geom::Point *p, guint state, gpointer data);
136 static void node_handle_moved(SPKnot *knot, Geom::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 Geom::Point *ppos, Geom::Point *pos, Geom::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->get_lpe();
195 if (lpe) {
196 // create new canvas items from the effect's helper paths
197 std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
198 for (std::vector<Geom::PathVector>::iterator j = hpaths.begin(); j != hpaths.end(); ++j) {
199 (*np->helper_path_vec)[lpe].push_back(canvasitem_from_pathvec(np, *j, true));
200 }
201 }
202 }
203 }
205 void
206 sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np) {
207 //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> >* helper_path_vec;
208 if (!SP_IS_LPE_ITEM(np->item)) {
209 g_print ("Only LPEItems can have helperpaths!\n");
210 return;
211 }
213 SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
214 PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
215 for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
216 Inkscape::LivePathEffect::Effect *lpe = (*i)->lpeobject->get_lpe();
217 if (lpe) {
218 /* update canvas items from the effect's helper paths; note that this code relies on the
219 * fact that getHelperPaths() will always return the same number of helperpaths in the same
220 * order as during their creation in sp_nodepath_create_helperpaths
221 */
222 std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
223 for (unsigned int j = 0; j < hpaths.size(); ++j) {
224 SPCurve *curve = new SPCurve(hpaths[j]);
225 curve->transform(np->i2d);
226 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(((*np->helper_path_vec)[lpe])[j]), curve);
227 curve = curve->unref();
228 }
229 }
230 }
231 }
233 static void
234 sp_nodepath_destroy_helperpaths(Inkscape::NodePath::Path *np) {
235 for (HelperPathList::iterator i = np->helper_path_vec->begin(); i != np->helper_path_vec->end(); ++i) {
236 for (std::vector<SPCanvasItem *>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j) {
237 GtkObject *temp = *j;
238 *j = NULL;
239 gtk_object_destroy(temp);
240 }
241 }
242 }
245 /**
246 * \brief Creates new nodepath from item
247 */
248 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
249 {
250 Inkscape::XML::Node *repr = object->repr;
252 /** \todo
253 * FIXME: remove this. We don't want to edit paths inside flowtext.
254 * Instead we will build our flowtext with cloned paths, so that the
255 * real paths are outside the flowtext and thus editable as usual.
256 */
257 if (SP_IS_FLOWTEXT(object)) {
258 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
259 if SP_IS_FLOWREGION(child) {
260 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
261 if (grandchild && SP_IS_PATH(grandchild)) {
262 object = SP_ITEM(grandchild);
263 break;
264 }
265 }
266 }
267 }
269 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
271 if (curve == NULL) {
272 return NULL;
273 }
275 if (curve->get_segment_count() < 1) {
276 curve->unref();
277 return NULL; // prevent crash for one-node paths
278 }
280 //Create new nodepath
281 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
282 if (!np) {
283 curve->unref();
284 return NULL;
285 }
287 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
289 // Set defaults
290 np->desktop = desktop;
291 np->object = object;
292 np->subpaths = NULL;
293 np->selected = NULL;
294 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
295 np->local_change = 0;
296 np->show_handles = show_handles;
297 np->helper_path = NULL;
298 np->helper_path_vec = new HelperPathList;
299 np->helperpath_rgba = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
300 np->helperpath_width = 1.0;
301 np->curve = curve->copy();
302 np->show_helperpath = prefs->getBool("/tools/nodes/show_helperpath");
303 if (SP_IS_LPE_ITEM(object)) {
304 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
305 if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
306 np->show_helperpath = true;
307 }
308 }
309 np->straight_path = false;
310 if (IS_LIVEPATHEFFECT(object) && item) {
311 np->item = item;
312 } else {
313 np->item = SP_ITEM(object);
314 }
316 // we need to update item's transform from the repr here,
317 // because they may be out of sync when we respond
318 // to a change in repr by regenerating nodepath --bb
319 sp_object_read_attr(SP_OBJECT(np->item), "transform");
321 np->i2d = sp_item_i2d_affine(np->item);
322 np->d2i = np->i2d.inverse();
324 np->repr = repr;
325 if (repr_key_in) { // apparently the object is an LPEObject (this is a dirty check, hopefully nobody tries feeding non-lpeobjects into this method with non-null repr_key_in)
326 np->repr_key = g_strdup(repr_key_in);
327 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
328 Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(object)->get_lpe();
329 if (!lpe) {
330 g_error("sp_nodepath_new: lpeobject without real lpe passed as argument!");
331 sp_nodepath_destroy(np);
332 }
333 Inkscape::LivePathEffect::Parameter *lpeparam = lpe->getParameter(repr_key_in);
334 if (lpeparam) {
335 lpeparam->param_setup_nodepath(np);
336 }
337 } else {
338 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
339 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
340 np->repr_key = g_strdup("inkscape:original-d");
342 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
343 if (lpe) {
344 lpe->setup_nodepath(np);
345 }
346 } else {
347 np->repr_key = g_strdup("d");
348 }
349 }
351 /* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice.
352 * So for example a closed rectangle has a nodetypestring of length 5.
353 * To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */
354 Geom::PathVector pathv_sanitized = pathv_to_linear_and_cubic_beziers(np->curve->get_pathvector());
355 np->curve->set_pathvector(pathv_sanitized);
356 guint length = np->curve->get_segment_count();
357 for (Geom::PathVector::const_iterator pit = pathv_sanitized.begin(); pit != pathv_sanitized.end(); ++pit) {
358 length += pit->empty() ? 0 : 1;
359 }
361 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
362 Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
364 // create the subpath(s) from the bpath
365 subpaths_from_pathvector(np, pathv_sanitized, typestr);
367 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
368 np->subpaths = g_list_reverse(np->subpaths);
370 delete[] typestr;
371 curve->unref();
373 // Draw helper curve
374 if (np->show_helperpath) {
375 np->helper_path = sp_nodepath_make_helper_item(np, /*desktop, */np->curve, true);
376 }
378 sp_nodepath_create_helperpaths(np);
380 return np;
381 }
383 /**
384 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
385 */
386 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
388 if (!np) { //soft fail, like delete
389 return;
390 }
392 while (np->subpaths) {
393 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
394 }
396 //Inform the ShapeEditor that made me, if any, that I am gone.
397 if (np->shape_editor)
398 np->shape_editor->nodepath_destroyed();
400 g_assert(!np->selected);
402 if (np->helper_path) {
403 GtkObject *temp = np->helper_path;
404 np->helper_path = NULL;
405 gtk_object_destroy(temp);
406 }
407 if (np->curve) {
408 np->curve->unref();
409 np->curve = NULL;
410 }
412 if (np->repr_key) {
413 g_free(np->repr_key);
414 np->repr_key = NULL;
415 }
416 if (np->repr_nodetypes_key) {
417 g_free(np->repr_nodetypes_key);
418 np->repr_nodetypes_key = NULL;
419 }
421 sp_nodepath_destroy_helperpaths(np);
422 delete np->helper_path_vec;
423 np->helper_path_vec = NULL;
425 np->desktop = NULL;
427 g_free(np);
428 }
430 /**
431 * Return the node count of a given NodeSubPath.
432 */
433 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
434 {
435 int nodeCount = 0;
437 if (subpath) {
438 nodeCount = g_list_length(subpath->nodes);
439 }
441 return nodeCount;
442 }
444 /**
445 * Return the node count of a given NodePath.
446 */
447 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
448 {
449 gint nodeCount = 0;
450 if (np) {
451 for (GList *item = np->subpaths ; item ; item=item->next) {
452 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
453 nodeCount += g_list_length(subpath->nodes);
454 }
455 }
456 return nodeCount;
457 }
459 /**
460 * Return the subpath count of a given NodePath.
461 */
462 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
463 {
464 gint nodeCount = 0;
465 if (np) {
466 nodeCount = g_list_length(np->subpaths);
467 }
468 return nodeCount;
469 }
471 /**
472 * Return the selected node count of a given NodePath.
473 */
474 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
475 {
476 gint nodeCount = 0;
477 if (np) {
478 nodeCount = g_list_length(np->selected);
479 }
480 return nodeCount;
481 }
483 /**
484 * Return the number of subpaths where nodes are selected in a given NodePath.
485 */
486 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
487 {
488 gint nodeCount = 0;
489 if (np && np->selected) {
490 if (!np->selected->next) {
491 nodeCount = 1;
492 } else {
493 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
494 Inkscape::NodePath::SubPath *subpath = static_cast<Inkscape::NodePath::SubPath *>(spl->data);
495 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
496 Inkscape::NodePath::Node *node = static_cast<Inkscape::NodePath::Node *>(nl->data);
497 if (node->selected) {
498 nodeCount++;
499 break;
500 }
501 }
502 }
503 }
504 }
505 return nodeCount;
506 }
508 /**
509 * Clean up a nodepath after editing.
510 *
511 * Currently we are deleting trivial subpaths.
512 */
513 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
514 {
515 GList *badSubPaths = NULL;
517 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
518 for (GList *l = nodepath->subpaths; l ; l=l->next) {
519 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
520 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
521 badSubPaths = g_list_append(badSubPaths, sp);
522 }
524 //Delete them. This second step is because sp_nodepath_subpath_destroy()
525 //also removes the subpath from nodepath->subpaths
526 for (GList *l = badSubPaths; l ; l=l->next) {
527 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
528 sp_nodepath_subpath_destroy(sp);
529 }
531 g_list_free(badSubPaths);
532 }
534 /**
535 * Create new nodepaths from pathvector, make it subpaths of np.
536 * \param t The node type array.
537 */
538 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
539 {
540 guint i = 0; // index into node type array
541 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
542 if (pit->empty())
543 continue; // don't add single knot paths
545 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
547 Geom::Point ppos = pit->initialPoint() * np->i2d;
548 NRPathcode pcode = NR_MOVETO;
550 /* 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)*/
551 for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
552 if( dynamic_cast<Geom::LineSegment const*>(&*cit) ||
553 dynamic_cast<Geom::HLineSegment const*>(&*cit) ||
554 dynamic_cast<Geom::VLineSegment const*>(&*cit) )
555 {
556 Geom::Point pos = cit->initialPoint() * (Geom::Matrix)np->i2d;
557 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
559 ppos = cit->finalPoint() * (Geom::Matrix)np->i2d;
560 pcode = NR_LINETO;
561 }
562 else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&*cit)) {
563 std::vector<Geom::Point> points = cubic_bezier->points();
564 Geom::Point pos = points[0] * (Geom::Matrix)np->i2d;
565 Geom::Point npos = points[1] * (Geom::Matrix)np->i2d;
566 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
568 ppos = points[2] * (Geom::Matrix)np->i2d;
569 pcode = NR_CURVETO;
570 }
571 }
573 if (pit->closed()) {
574 // Add last knot (because sp_nodepath_subpath_close kills the last knot)
575 /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
576 * If the length is zero, don't add it to the nodepath. */
577 Geom::Curve const &closing_seg = pit->back_closed();
578 // Don't use !closing_seg.isDegenerate() as it is too precise, and does not account for floating point rounding probs (LP bug #257289)
579 if ( ! are_near(closing_seg.initialPoint(), closing_seg.finalPoint()) ) {
580 Geom::Point pos = closing_seg.finalPoint() * (Geom::Matrix)np->i2d;
581 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
582 }
584 sp_nodepath_subpath_close(sp);
585 }
586 }
587 }
589 /**
590 * Convert from sodipodi:nodetypes to new style type array.
591 */
592 static
593 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
594 {
595 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
597 guint pos = 0;
599 if (types) {
600 for (guint i = 0; types[i] && ( i < length ); i++) {
601 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
602 if (types[i] != '\0') {
603 switch (types[i]) {
604 case 's':
605 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
606 break;
607 case 'a':
608 typestr[pos++] =Inkscape::NodePath::NODE_AUTO;
609 break;
610 case 'z':
611 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
612 break;
613 case 'c':
614 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
615 break;
616 default:
617 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
618 break;
619 }
620 }
621 }
622 }
624 while (pos < length) {
625 typestr[pos++] = Inkscape::NodePath::NODE_NONE;
626 }
628 return typestr;
629 }
631 /**
632 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
633 * updated but repr is not (for speed). Used during curve and node drag.
634 */
635 static void update_object(Inkscape::NodePath::Path *np)
636 {
637 g_assert(np);
639 np->curve->unref();
640 np->curve = create_curve(np);
642 sp_nodepath_set_curve(np, np->curve);
644 if (np->show_helperpath) {
645 SPCurve * helper_curve = np->curve->copy();
646 helper_curve->transform(np->i2d);
647 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
648 helper_curve->unref();
649 }
651 // updating helperpaths of LPEItems is now done in sp_lpe_item_update();
652 //sp_nodepath_update_helperpaths(np);
654 // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
655 // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
656 np->shape_editor->update_knotholder();
657 }
659 /**
660 * Update XML path node with data from path object.
661 */
662 static void update_repr_internal(Inkscape::NodePath::Path *np)
663 {
664 g_assert(np);
666 Inkscape::XML::Node *repr = np->object->repr;
668 np->curve->unref();
669 np->curve = create_curve(np);
671 gchar *typestr = create_typestr(np);
672 gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
674 // determine if path has an effect applied and write to correct "d" attribute.
675 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
676 np->local_change++;
677 repr->setAttribute(np->repr_key, svgpath);
678 }
680 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
681 np->local_change++;
682 repr->setAttribute(np->repr_nodetypes_key, typestr);
683 }
685 g_free(svgpath);
686 g_free(typestr);
688 if (np->show_helperpath) {
689 SPCurve * helper_curve = np->curve->copy();
690 helper_curve->transform(np->i2d);
691 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
692 helper_curve->unref();
693 }
695 // TODO: do we need this call here? after all, update_object() should have been called just before
696 //sp_nodepath_update_helperpaths(np);
697 }
699 /**
700 * Update XML path node with data from path object, commit changes forever.
701 */
702 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
703 {
704 //fixme: np can be NULL, so check before proceeding
705 g_return_if_fail(np != NULL);
707 update_repr_internal(np);
708 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
710 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
711 annotation);
712 }
714 /**
715 * Update XML path node with data from path object, commit changes with undo.
716 */
717 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
718 {
719 update_repr_internal(np);
720 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
721 annotation);
722 }
724 /**
725 * Make duplicate of path, replace corresponding XML node in tree, commit.
726 */
727 static void stamp_repr(Inkscape::NodePath::Path *np)
728 {
729 g_assert(np);
731 Inkscape::XML::Node *old_repr = np->object->repr;
732 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
734 // remember the position of the item
735 gint pos = old_repr->position();
736 // remember parent
737 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
739 SPCurve *curve = create_curve(np);
740 gchar *typestr = create_typestr(np);
742 gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
744 new_repr->setAttribute(np->repr_key, svgpath);
745 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
747 // add the new repr to the parent
748 parent->appendChild(new_repr);
749 // move to the saved position
750 new_repr->setPosition(pos > 0 ? pos : 0);
752 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
753 _("Stamp"));
755 Inkscape::GC::release(new_repr);
756 g_free(svgpath);
757 g_free(typestr);
758 curve->unref();
759 }
761 /**
762 * Create curve from path.
763 */
764 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
765 {
766 SPCurve *curve = new SPCurve();
768 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
769 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
770 curve->moveto(sp->first->pos * np->d2i);
771 Inkscape::NodePath::Node *n = sp->first->n.other;
772 while (n) {
773 Geom::Point const end_pt = n->pos * np->d2i;
774 switch (n->code) {
775 case NR_LINETO:
776 curve->lineto(end_pt);
777 break;
778 case NR_CURVETO:
779 curve->curveto(n->p.other->n.pos * np->d2i,
780 n->p.pos * np->d2i,
781 end_pt);
782 break;
783 default:
784 g_assert_not_reached();
785 break;
786 }
787 if (n != sp->last) {
788 n = n->n.other;
789 } else {
790 n = NULL;
791 }
792 }
793 if (sp->closed) {
794 curve->closepath();
795 }
796 }
798 return curve;
799 }
801 /**
802 * Convert path type string to sodipodi:nodetypes style.
803 */
804 static gchar *create_typestr(Inkscape::NodePath::Path *np)
805 {
806 gchar *typestr = g_new(gchar, 32);
807 gint len = 32;
808 gint pos = 0;
810 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
811 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
813 if (pos >= len) {
814 typestr = g_renew(gchar, typestr, len + 32);
815 len += 32;
816 }
818 typestr[pos++] = 'c';
820 Inkscape::NodePath::Node *n;
821 n = sp->first->n.other;
822 while (n) {
823 gchar code;
825 switch (n->type) {
826 case Inkscape::NodePath::NODE_CUSP:
827 code = 'c';
828 break;
829 case Inkscape::NodePath::NODE_SMOOTH:
830 code = 's';
831 break;
832 case Inkscape::NodePath::NODE_AUTO:
833 code = 'a';
834 break;
835 case Inkscape::NodePath::NODE_SYMM:
836 code = 'z';
837 break;
838 default:
839 g_assert_not_reached();
840 code = '\0';
841 break;
842 }
844 if (pos >= len) {
845 typestr = g_renew(gchar, typestr, len + 32);
846 len += 32;
847 }
849 typestr[pos++] = code;
851 if (n != sp->last) {
852 n = n->n.other;
853 } else {
854 n = NULL;
855 }
856 }
857 }
859 if (pos >= len) {
860 typestr = g_renew(gchar, typestr, len + 1);
861 len += 1;
862 }
864 typestr[pos++] = '\0';
866 return typestr;
867 }
869 // Returns different message contexts depending on the current context. This function should only
870 // be called when ec is either a SPNodeContext or SPLPEToolContext, thus we return NULL in all
871 // other cases.
872 static Inkscape::MessageContext *
873 get_message_context(SPEventContext *ec)
874 {
875 Inkscape::MessageContext *mc = 0;
877 if (SP_IS_NODE_CONTEXT(ec)) {
878 mc = SP_NODE_CONTEXT(ec)->_node_message_context;
879 } else if (SP_IS_LPETOOL_CONTEXT(ec)) {
880 mc = SP_LPETOOL_CONTEXT(ec)->_lpetool_message_context;
881 } else {
882 g_warning ("Nodepath should only be present in Node tool or Geometric tool.");
883 }
885 return mc;
886 }
888 /**
889 \brief Fills node and handle positions for three nodes, splitting line
890 marked by end at distance t.
891 */
892 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
893 {
894 g_assert(new_path != NULL);
895 g_assert(end != NULL);
897 g_assert(end->p.other == new_path);
898 Inkscape::NodePath::Node *start = new_path->p.other;
899 g_assert(start);
901 if (end->code == NR_LINETO) {
902 new_path->type =Inkscape::NodePath::NODE_CUSP;
903 new_path->code = NR_LINETO;
904 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
905 } else {
906 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
907 new_path->code = NR_CURVETO;
908 gdouble s = 1 - t;
909 for (int dim = 0; dim < 2; dim++) {
910 Geom::Coord const f000 = start->pos[dim];
911 Geom::Coord const f001 = start->n.pos[dim];
912 Geom::Coord const f011 = end->p.pos[dim];
913 Geom::Coord const f111 = end->pos[dim];
914 Geom::Coord const f00t = s * f000 + t * f001;
915 Geom::Coord const f01t = s * f001 + t * f011;
916 Geom::Coord const f11t = s * f011 + t * f111;
917 Geom::Coord const f0tt = s * f00t + t * f01t;
918 Geom::Coord const f1tt = s * f01t + t * f11t;
919 Geom::Coord const fttt = s * f0tt + t * f1tt;
920 start->n.pos[dim] = f00t;
921 new_path->p.pos[dim] = f0tt;
922 new_path->pos[dim] = fttt;
923 new_path->n.pos[dim] = f1tt;
924 end->p.pos[dim] = f11t;
925 }
926 }
927 }
929 /**
930 * Adds new node on direct line between two nodes, activates handles of all
931 * three nodes.
932 */
933 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
934 {
935 g_assert(end);
936 g_assert(end->subpath);
937 g_assert(g_list_find(end->subpath->nodes, end));
939 Inkscape::NodePath::Node *start = end->p.other;
940 g_assert( start->n.other == end );
941 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
942 end,
943 (NRPathcode)end->code == NR_LINETO?
944 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
945 (NRPathcode)end->code,
946 &start->pos, &start->pos, &start->n.pos);
947 sp_nodepath_line_midpoint(newnode, end, t);
949 sp_node_adjust_handles(start);
950 sp_node_update_handles(start);
951 sp_node_update_handles(newnode);
952 sp_node_adjust_handles(end);
953 sp_node_update_handles(end);
955 return newnode;
956 }
958 /**
959 \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
960 */
961 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
962 {
963 g_assert(node);
964 g_assert(node->subpath);
965 g_assert(g_list_find(node->subpath->nodes, node));
967 Inkscape::NodePath::Node* result = 0;
968 Inkscape::NodePath::SubPath *sp = node->subpath;
969 Inkscape::NodePath::Path *np = sp->nodepath;
971 if (sp->closed) {
972 sp_nodepath_subpath_open(sp, node);
973 result = sp->first;
974 } else if ( (node == sp->first) || (node == sp->last ) ){
975 // no break for end nodes
976 result = 0;
977 } else {
978 // create a new subpath
979 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
981 // duplicate the break node as start of the new subpath
982 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL,
983 static_cast<Inkscape::NodePath::NodeType>(node->type),
984 NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
986 // attach rest of curve to new node
987 g_assert(node->n.other);
988 newnode->n.other = node->n.other; node->n.other = NULL;
989 newnode->n.other->p.other = newnode;
990 newsubpath->last = sp->last;
991 sp->last = node;
992 node = newnode;
993 while (node->n.other) {
994 node = node->n.other;
995 node->subpath = newsubpath;
996 sp->nodes = g_list_remove(sp->nodes, node);
997 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
998 }
1001 result = newnode;
1002 }
1003 return result;
1004 }
1006 /**
1007 * Duplicate node and connect to neighbours.
1008 */
1009 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
1010 {
1011 g_assert(node);
1012 g_assert(node->subpath);
1013 g_assert(g_list_find(node->subpath->nodes, node));
1015 Inkscape::NodePath::SubPath *sp = node->subpath;
1017 NRPathcode code = (NRPathcode) node->code;
1018 if (code == NR_MOVETO) { // if node is the endnode,
1019 node->code = NR_LINETO; // new one is inserted before it, so change that to line
1020 }
1022 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
1024 if (!node->n.other || !node->p.other) { // if node is an endnode, select it
1025 return node;
1026 } else {
1027 return newnode; // otherwise select the newly created node
1028 }
1029 }
1031 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
1032 {
1033 node->p.pos = (node->pos + (node->pos - node->n.pos));
1034 }
1036 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1037 {
1038 node->n.pos = (node->pos + (node->pos - node->p.pos));
1039 }
1041 /**
1042 * Change line type at node, with side effects on neighbours.
1043 */
1044 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1045 {
1046 g_assert(end);
1047 g_assert(end->subpath);
1048 g_assert(end->p.other);
1050 if (end->code != static_cast<guint>(code) ) {
1051 Inkscape::NodePath::Node *start = end->p.other;
1053 end->code = code;
1055 if (code == NR_LINETO) {
1056 if (start->code == NR_LINETO) {
1057 sp_nodepath_set_node_type(start, Inkscape::NodePath::NODE_CUSP);
1058 }
1059 if (end->n.other) {
1060 if (end->n.other->code == NR_LINETO) {
1061 sp_nodepath_set_node_type(end, Inkscape::NodePath::NODE_CUSP);
1062 }
1063 }
1065 if (start->type == Inkscape::NodePath::NODE_AUTO)
1066 start->type = Inkscape::NodePath::NODE_SMOOTH;
1067 if (end->type == Inkscape::NodePath::NODE_AUTO)
1068 end->type = Inkscape::NodePath::NODE_SMOOTH;
1070 sp_node_adjust_handle(start, -1);
1071 sp_node_adjust_handle(end, 1);
1073 } else {
1074 Geom::Point delta = end->pos - start->pos;
1075 start->n.pos = start->pos + delta / 3;
1076 end->p.pos = end->pos - delta / 3;
1077 sp_node_adjust_handle(start, 1);
1078 sp_node_adjust_handle(end, -1);
1079 }
1081 sp_node_update_handles(start);
1082 sp_node_update_handles(end);
1083 }
1084 }
1086 static void
1087 sp_nodepath_update_node_knot(Inkscape::NodePath::Node *node)
1088 {
1089 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1090 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1091 node->knot->setSize (node->selected? 11 : 9);
1092 sp_knot_update_ctrl(node->knot);
1093 } else if (node->type == Inkscape::NodePath::NODE_AUTO) {
1094 node->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1095 node->knot->setSize (node->selected? 11 : 9);
1096 sp_knot_update_ctrl(node->knot);
1097 } else {
1098 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1099 node->knot->setSize (node->selected? 9 : 7);
1100 sp_knot_update_ctrl(node->knot);
1101 }
1102 }
1105 /**
1106 * Change node type, and its handles accordingly.
1107 */
1108 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1109 {
1110 g_assert(node);
1111 g_assert(node->subpath);
1113 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1114 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1115 type =Inkscape::NodePath::NODE_CUSP;
1116 }
1117 }
1119 node->type = type;
1121 sp_nodepath_update_node_knot(node);
1123 // if one of handles is mouseovered, preserve its position
1124 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1125 sp_node_adjust_handle(node, 1);
1126 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1127 sp_node_adjust_handle(node, -1);
1128 } else {
1129 sp_node_adjust_handles(node);
1130 }
1132 sp_node_update_handles(node);
1134 sp_nodepath_update_statusbar(node->subpath->nodepath);
1136 return node;
1137 }
1139 bool
1140 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1141 {
1142 // TODO clean up multiple returns
1143 Inkscape::NodePath::Node *othernode = side->other;
1144 if (!othernode)
1145 return false;
1146 NRPathcode const code = sp_node_path_code_from_side(node, side);
1147 if (code == NR_LINETO)
1148 return true;
1149 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1150 if (&node->p == side) {
1151 other_to_me = &othernode->n;
1152 } else if (&node->n == side) {
1153 other_to_me = &othernode->p;
1154 }
1155 if (!other_to_me)
1156 return false;
1157 bool is_line =
1158 (Geom::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1159 Geom::L2(node->pos - side->pos) < 1e-6);
1160 return is_line;
1161 }
1163 /**
1164 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1165 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1166 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1167 * If already cusp and set to cusp, retracts handles.
1168 */
1169 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1170 {
1171 if (type == Inkscape::NodePath::NODE_AUTO) {
1172 if (node->p.other != NULL)
1173 node->code = NR_CURVETO;
1174 if (node->n.other != NULL)
1175 node->n.other->code = NR_CURVETO;
1176 }
1178 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1180 /*
1181 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1183 if (two_handles) {
1184 // do nothing, adjust_handles called via set_node_type will line them up
1185 } else if (one_handle) {
1186 if (opposite_to_handle_is_line) {
1187 if (lined_up) {
1188 // already half-smooth; pull opposite handle too making it fully smooth
1189 } else {
1190 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1191 }
1192 } else {
1193 // pull opposite handle in line with the existing one
1194 }
1195 } else if (no_handles) {
1196 if (both_segments_are_lines OR both_segments_are_curves) {
1197 //pull both handles
1198 } else {
1199 // pull the handle opposite to line segment, making node half-smooth
1200 }
1201 }
1202 */
1203 bool p_has_handle = (Geom::L2(node->pos - node->p.pos) > 1e-6);
1204 bool n_has_handle = (Geom::L2(node->pos - node->n.pos) > 1e-6);
1205 bool p_is_line = sp_node_side_is_line(node, &node->p);
1206 bool n_is_line = sp_node_side_is_line(node, &node->n);
1208 if (p_has_handle && n_has_handle) {
1209 // do nothing, adjust_handles will line them up
1210 } else if (p_has_handle || n_has_handle) {
1211 if (p_has_handle && n_is_line) {
1212 Radial line (node->n.other->pos - node->pos);
1213 Radial handle (node->pos - node->p.pos);
1214 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1215 // already half-smooth; pull opposite handle too making it fully smooth
1216 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1217 } else {
1218 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1219 }
1220 } else if (n_has_handle && p_is_line) {
1221 Radial line (node->p.other->pos - node->pos);
1222 Radial handle (node->pos - node->n.pos);
1223 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1224 // already half-smooth; pull opposite handle too making it fully smooth
1225 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1226 } else {
1227 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1228 }
1229 } else if (p_has_handle && node->n.other) {
1230 // pull n handle
1231 node->n.other->code = NR_CURVETO;
1232 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1233 Geom::L2(node->p.pos - node->pos) :
1234 Geom::L2(node->n.other->pos - node->pos) / 3;
1235 node->n.pos = node->pos - (len / Geom::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1236 } else if (n_has_handle && node->p.other) {
1237 // pull p handle
1238 node->code = NR_CURVETO;
1239 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1240 Geom::L2(node->n.pos - node->pos) :
1241 Geom::L2(node->p.other->pos - node->pos) / 3;
1242 node->p.pos = node->pos - (len / Geom::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1243 }
1244 } else if (!p_has_handle && !n_has_handle) {
1245 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1246 // no handles, but both segments are either lnes or curves:
1247 //pull both handles
1249 // convert both to curves:
1250 node->code = NR_CURVETO;
1251 node->n.other->code = NR_CURVETO;
1253 sp_node_adjust_handles_auto(node);
1254 } else {
1255 // pull the handle opposite to line segment, making it half-smooth
1256 if (p_is_line && node->n.other) {
1257 if (type != Inkscape::NodePath::NODE_SYMM) {
1258 // pull n handle
1259 node->n.other->code = NR_CURVETO;
1260 double len = Geom::L2(node->n.other->pos - node->pos) / 3;
1261 node->n.pos = node->pos + (len / Geom::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1262 }
1263 } else if (n_is_line && node->p.other) {
1264 if (type != Inkscape::NodePath::NODE_SYMM) {
1265 // pull p handle
1266 node->code = NR_CURVETO;
1267 double len = Geom::L2(node->p.other->pos - node->pos) / 3;
1268 node->p.pos = node->pos + (len / Geom::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1269 }
1270 }
1271 }
1272 }
1273 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1274 // cusping a cusp: retract nodes
1275 node->p.pos = node->pos;
1276 node->n.pos = node->pos;
1277 }
1279 sp_nodepath_set_node_type (node, type);
1280 }
1282 /**
1283 * Move node to point, and adjust its and neighbouring handles.
1284 */
1285 void sp_node_moveto(Inkscape::NodePath::Node *node, Geom::Point p)
1286 {
1287 if (node->type == Inkscape::NodePath::NODE_AUTO) {
1288 node->pos = p;
1289 sp_node_adjust_handles_auto(node);
1290 } else {
1291 Geom::Point delta = p - node->pos;
1292 node->pos = p;
1294 node->p.pos += delta;
1295 node->n.pos += delta;
1296 }
1298 Inkscape::NodePath::Node *node_p = NULL;
1299 Inkscape::NodePath::Node *node_n = NULL;
1301 if (node->p.other) {
1302 if (node->code == NR_LINETO) {
1303 sp_node_adjust_handle(node, 1);
1304 sp_node_adjust_handle(node->p.other, -1);
1305 node_p = node->p.other;
1306 }
1307 if (!node->p.other->selected && node->p.other->type == Inkscape::NodePath::NODE_AUTO) {
1308 sp_node_adjust_handles_auto(node->p.other);
1309 node_p = node->p.other;
1310 }
1311 }
1312 if (node->n.other) {
1313 if (node->n.other->code == NR_LINETO) {
1314 sp_node_adjust_handle(node, -1);
1315 sp_node_adjust_handle(node->n.other, 1);
1316 node_n = node->n.other;
1317 }
1318 if (!node->n.other->selected && node->n.other->type == Inkscape::NodePath::NODE_AUTO) {
1319 sp_node_adjust_handles_auto(node->n.other);
1320 node_n = node->n.other;
1321 }
1322 }
1324 // this function is only called from batch movers that will update display at the end
1325 // themselves, so here we just move all the knots without emitting move signals, for speed
1326 sp_node_update_handles(node, false);
1327 if (node_n) {
1328 sp_node_update_handles(node_n, false);
1329 }
1330 if (node_p) {
1331 sp_node_update_handles(node_p, false);
1332 }
1333 }
1335 /**
1336 * Call sp_node_moveto() for node selection and handle possible snapping.
1337 */
1338 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, Geom::Coord dx, Geom::Coord dy,
1339 bool const snap, bool constrained = false,
1340 Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point())
1341 {
1342 Geom::Coord best = NR_HUGE;
1343 Geom::Point delta(dx, dy);
1344 Geom::Point best_pt = delta;
1345 Inkscape::SnappedPoint best_abs;
1347 if (snap) {
1348 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1349 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1350 * must provide that information. */
1352 // Build a list of the unselected nodes to which the snapper should snap
1353 std::vector<Geom::Point> unselected_nodes;
1354 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1355 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1356 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1357 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1358 if (!node->selected) {
1359 unselected_nodes.push_back(to_2geom(node->pos));
1360 }
1361 }
1362 }
1364 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1366 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1367 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1368 m.setup(nodepath->desktop, false, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1369 Inkscape::SnappedPoint s;
1370 if (constrained) {
1371 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1372 dedicated_constraint.setPoint(n->pos);
1373 s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), dedicated_constraint);
1374 } else {
1375 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta));
1376 }
1377 if (s.getSnapped() && (s.getDistance() < best)) {
1378 best = s.getDistance();
1379 best_abs = s;
1380 best_pt = from_2geom(s.getPoint()) - n->pos;
1381 }
1382 }
1384 if (best_abs.getSnapped()) {
1385 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1386 } else {
1387 nodepath->desktop->snapindicator->remove_snappoint();
1388 }
1389 }
1391 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1392 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1393 sp_node_moveto(n, n->pos + best_pt);
1394 }
1396 // do not update repr here so that node dragging is acceptably fast
1397 update_object(nodepath);
1398 }
1400 /**
1401 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1402 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1403 near x = 0.
1404 */
1405 double
1406 sculpt_profile (double x, double alpha, guint profile)
1407 {
1408 double result = 1;
1410 if (x >= 1) {
1411 result = 0;
1412 } else if (x <= 0) {
1413 result = 1;
1414 } else {
1415 switch (profile) {
1416 case SCULPT_PROFILE_LINEAR:
1417 result = 1 - x;
1418 break;
1419 case SCULPT_PROFILE_BELL:
1420 result = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1421 break;
1422 case SCULPT_PROFILE_ELLIPTIC:
1423 result = sqrt(1 - x*x);
1424 break;
1425 default:
1426 g_assert_not_reached();
1427 }
1428 }
1430 return result;
1431 }
1433 double
1434 bezier_length (Geom::Point a, Geom::Point ah, Geom::Point bh, Geom::Point b)
1435 {
1436 // extremely primitive for now, don't have time to look for the real one
1437 double lower = Geom::L2(b - a);
1438 double upper = Geom::L2(ah - a) + Geom::L2(bh - ah) + Geom::L2(bh - b);
1439 return (lower + upper)/2;
1440 }
1442 void
1443 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, Geom::Point delta, Geom::Point delta_n, Geom::Point delta_p)
1444 {
1445 n->pos = n->origin + delta;
1446 n->n.pos = n->n.origin + delta_n;
1447 n->p.pos = n->p.origin + delta_p;
1448 sp_node_adjust_handles(n);
1449 sp_node_update_handles(n, false);
1450 }
1452 /**
1453 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1454 * on how far they are from the dragged node n.
1455 */
1456 static void
1457 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, Geom::Point delta)
1458 {
1459 g_assert (n);
1460 g_assert (nodepath);
1461 g_assert (n->subpath->nodepath == nodepath);
1463 double pressure = n->knot->pressure;
1464 if (pressure == 0)
1465 pressure = 0.5; // default
1466 pressure = CLAMP (pressure, 0.2, 0.8);
1468 // map pressure to alpha = 1/5 ... 5
1469 double alpha = 1 - 2 * fabs(pressure - 0.5);
1470 if (pressure > 0.5)
1471 alpha = 1/alpha;
1473 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1474 guint profile = prefs->getInt("/tools/nodes/sculpting_profile", SCULPT_PROFILE_BELL);
1476 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1477 // Only one subpath has selected nodes:
1478 // use linear mode, where the distance from n to node being dragged is calculated along the path
1480 double n_sel_range = 0, p_sel_range = 0;
1481 guint n_nodes = 0, p_nodes = 0;
1482 guint n_sel_nodes = 0, p_sel_nodes = 0;
1484 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1485 {
1486 double n_range = 0, p_range = 0;
1487 bool n_going = true, p_going = true;
1488 Inkscape::NodePath::Node *n_node = n;
1489 Inkscape::NodePath::Node *p_node = n;
1490 do {
1491 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1492 if (n_node && n_going)
1493 n_node = n_node->n.other;
1494 if (n_node == NULL) {
1495 n_going = false;
1496 } else {
1497 n_nodes ++;
1498 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1499 if (n_node->selected) {
1500 n_sel_nodes ++;
1501 n_sel_range = n_range;
1502 }
1503 if (n_node == p_node) {
1504 n_going = false;
1505 p_going = false;
1506 }
1507 }
1508 if (p_node && p_going)
1509 p_node = p_node->p.other;
1510 if (p_node == NULL) {
1511 p_going = false;
1512 } else {
1513 p_nodes ++;
1514 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1515 if (p_node->selected) {
1516 p_sel_nodes ++;
1517 p_sel_range = p_range;
1518 }
1519 if (p_node == n_node) {
1520 n_going = false;
1521 p_going = false;
1522 }
1523 }
1524 } while (n_going || p_going);
1525 }
1527 // Second pass: actually move nodes in this subpath
1528 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1529 {
1530 double n_range = 0, p_range = 0;
1531 bool n_going = true, p_going = true;
1532 Inkscape::NodePath::Node *n_node = n;
1533 Inkscape::NodePath::Node *p_node = n;
1534 do {
1535 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1536 if (n_node && n_going)
1537 n_node = n_node->n.other;
1538 if (n_node == NULL) {
1539 n_going = false;
1540 } else {
1541 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1542 if (n_node->selected) {
1543 sp_nodepath_move_node_and_handles (n_node,
1544 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1545 sculpt_profile ((n_range + Geom::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1546 sculpt_profile ((n_range - Geom::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1547 }
1548 if (n_node == p_node) {
1549 n_going = false;
1550 p_going = false;
1551 }
1552 }
1553 if (p_node && p_going)
1554 p_node = p_node->p.other;
1555 if (p_node == NULL) {
1556 p_going = false;
1557 } else {
1558 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1559 if (p_node->selected) {
1560 sp_nodepath_move_node_and_handles (p_node,
1561 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1562 sculpt_profile ((p_range - Geom::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1563 sculpt_profile ((p_range + Geom::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1564 }
1565 if (p_node == n_node) {
1566 n_going = false;
1567 p_going = false;
1568 }
1569 }
1570 } while (n_going || p_going);
1571 }
1573 } else {
1574 // Multiple subpaths have selected nodes:
1575 // use spatial mode, where the distance from n to node being dragged is measured directly as Geom::L2.
1576 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1577 // fix the pear-like shape when sculpting e.g. a ring
1579 // First pass: calculate range
1580 gdouble direct_range = 0;
1581 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1582 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1583 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1584 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1585 if (node->selected) {
1586 direct_range = MAX(direct_range, Geom::L2(node->origin - n->origin));
1587 }
1588 }
1589 }
1591 // Second pass: actually move nodes
1592 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1593 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1594 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1595 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1596 if (node->selected) {
1597 if (direct_range > 1e-6) {
1598 sp_nodepath_move_node_and_handles (node,
1599 sculpt_profile (Geom::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1600 sculpt_profile (Geom::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1601 sculpt_profile (Geom::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1602 } else {
1603 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1604 }
1606 }
1607 }
1608 }
1609 }
1611 // do not update repr here so that node dragging is acceptably fast
1612 update_object(nodepath);
1613 }
1616 /**
1617 * Move node selection to point, adjust its and neighbouring handles,
1618 * handle possible snapping, and commit the change with possible undo.
1619 */
1620 void
1621 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1622 {
1623 if (!nodepath) return;
1625 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1627 if (dx == 0) {
1628 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1629 } else if (dy == 0) {
1630 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1631 } else {
1632 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1633 }
1634 }
1636 /**
1637 * Move node selection off screen and commit the change.
1638 */
1639 void
1640 sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1641 {
1642 // borrowed from sp_selection_move_screen in selection-chemistry.c
1643 // we find out the current zoom factor and divide deltas by it
1645 gdouble zoom = desktop->current_zoom();
1646 gdouble zdx = dx / zoom;
1647 gdouble zdy = dy / zoom;
1649 if (!nodepath) return;
1651 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1653 if (dx == 0) {
1654 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1655 } else if (dy == 0) {
1656 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1657 } else {
1658 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1659 }
1660 }
1662 /**
1663 * Move selected nodes to the absolute position given
1664 */
1665 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
1666 {
1667 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1668 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1669 Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
1670 sp_node_moveto(n, npos);
1671 }
1673 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1674 }
1676 /**
1677 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return Geom::Nothing
1678 */
1679 boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1680 {
1681 boost::optional<Geom::Coord> no_coord;
1682 g_return_val_if_fail(nodepath->selected, no_coord);
1684 // determine coordinate of first selected node
1685 GList *nsel = nodepath->selected;
1686 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1687 Geom::Coord coord = n->pos[axis];
1688 bool coincide = true;
1690 // compare it to the coordinates of all the other selected nodes
1691 for (GList *l = nsel->next; l != NULL; l = l->next) {
1692 n = (Inkscape::NodePath::Node *) l->data;
1693 if (n->pos[axis] != coord) {
1694 coincide = false;
1695 }
1696 }
1697 if (coincide) {
1698 return coord;
1699 } else {
1700 Geom::Rect bbox = sp_node_selected_bbox(nodepath);
1701 // currently we return the coordinate of the bounding box midpoint because I don't know how
1702 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1703 return bbox.midpoint()[axis];
1704 }
1705 }
1707 /** If they don't yet exist, creates knot and line for the given side of the node */
1708 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1709 {
1710 if (!side->knot) {
1711 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"));
1713 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1714 side->knot->setSize (7);
1715 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1716 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1717 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1718 sp_knot_update_ctrl(side->knot);
1720 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1721 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1722 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1723 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1724 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1725 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1726 }
1728 if (!side->line) {
1729 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1730 SP_TYPE_CTRLLINE, NULL);
1731 }
1732 }
1734 /**
1735 * Ensure the given handle of the node is visible/invisible, update its screen position
1736 */
1737 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1738 {
1739 g_assert(node != NULL);
1741 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1742 NRPathcode code = sp_node_path_code_from_side(node, side);
1744 show_handle = show_handle && (code == NR_CURVETO) && (Geom::L2(side->pos - node->pos) > 1e-6);
1746 if (show_handle) {
1747 if (!side->knot) { // No handle knot at all
1748 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1749 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1750 side->knot->pos = side->pos;
1751 if (side->knot->item)
1752 SP_CTRL(side->knot->item)->moveto(side->pos);
1753 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1754 sp_knot_show(side->knot);
1755 } else {
1756 if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1757 if (fire_move_signals) {
1758 sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1759 } else {
1760 sp_knot_moveto(side->knot, side->pos);
1761 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1762 }
1763 }
1764 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1765 sp_knot_show(side->knot);
1766 }
1767 }
1768 sp_canvas_item_show(side->line);
1769 } else {
1770 if (side->knot) {
1771 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1772 sp_knot_hide(side->knot);
1773 }
1774 }
1775 if (side->line) {
1776 sp_canvas_item_hide(side->line);
1777 }
1778 }
1779 }
1781 /**
1782 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1783 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1784 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1785 * updated; otherwise, just move the knots silently (used in batch moves).
1786 */
1787 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1788 {
1789 g_assert(node != NULL);
1791 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1792 sp_knot_show(node->knot);
1793 }
1795 if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1796 if (fire_move_signals)
1797 sp_knot_set_position(node->knot, node->pos, 0);
1798 else
1799 sp_knot_moveto(node->knot, node->pos);
1800 }
1802 gboolean show_handles = node->selected;
1803 if (node->p.other != NULL) {
1804 if (node->p.other->selected) show_handles = TRUE;
1805 }
1806 if (node->n.other != NULL) {
1807 if (node->n.other->selected) show_handles = TRUE;
1808 }
1810 if (node->subpath->nodepath->show_handles == false)
1811 show_handles = FALSE;
1813 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1814 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1815 }
1817 /**
1818 * Call sp_node_update_handles() for all nodes on subpath.
1819 */
1820 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1821 {
1822 g_assert(subpath != NULL);
1824 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1825 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1826 }
1827 }
1829 /**
1830 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1831 */
1832 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1833 {
1834 g_assert(nodepath != NULL);
1836 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1837 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1838 }
1839 }
1841 void
1842 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1843 {
1844 if (nodepath) {
1845 nodepath->show_handles = show;
1846 sp_nodepath_update_handles(nodepath);
1847 }
1848 }
1850 /**
1851 * Adds all selected nodes in nodepath to list.
1852 */
1853 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1854 {
1855 StlConv<Node *>::list(l, selected);
1856 /// \todo this adds a copying, rework when the selection becomes a stl list
1857 }
1859 /**
1860 * Align selected nodes on the specified axis.
1861 */
1862 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1863 {
1864 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1865 return;
1866 }
1868 if ( !nodepath->selected->next ) { // only one node selected
1869 return;
1870 }
1871 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1872 Geom::Point dest(pNode->pos);
1873 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1874 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1875 if (pNode) {
1876 dest[axis] = pNode->pos[axis];
1877 sp_node_moveto(pNode, dest);
1878 }
1879 }
1881 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1882 }
1884 /// Helper struct.
1885 struct NodeSort
1886 {
1887 Inkscape::NodePath::Node *_node;
1888 Geom::Coord _coord;
1889 /// \todo use vectorof pointers instead of calling copy ctor
1890 NodeSort(Inkscape::NodePath::Node *node, Geom::Dim2 axis) :
1891 _node(node), _coord(node->pos[axis])
1892 {}
1894 };
1896 static bool operator<(NodeSort const &a, NodeSort const &b)
1897 {
1898 return (a._coord < b._coord);
1899 }
1901 /**
1902 * Distribute selected nodes on the specified axis.
1903 */
1904 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1905 {
1906 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1907 return;
1908 }
1910 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1911 return;
1912 }
1914 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1915 std::vector<NodeSort> sorted;
1916 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1917 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1918 if (pNode) {
1919 NodeSort n(pNode, axis);
1920 sorted.push_back(n);
1921 //dest[axis] = pNode->pos[axis];
1922 //sp_node_moveto(pNode, dest);
1923 }
1924 }
1925 std::sort(sorted.begin(), sorted.end());
1926 unsigned int len = sorted.size();
1927 //overall bboxes span
1928 float dist = (sorted.back()._coord -
1929 sorted.front()._coord);
1930 //new distance between each bbox
1931 float step = (dist) / (len - 1);
1932 float pos = sorted.front()._coord;
1933 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1934 it < sorted.end();
1935 it ++ )
1936 {
1937 Geom::Point dest((*it)._node->pos);
1938 dest[axis] = pos;
1939 sp_node_moveto((*it)._node, dest);
1940 pos += step;
1941 }
1943 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1944 }
1947 /**
1948 * Call sp_nodepath_line_add_node() for all selected segments.
1949 */
1950 void
1951 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1952 {
1953 if (!nodepath) {
1954 return;
1955 }
1957 GList *nl = NULL;
1959 int n_added = 0;
1961 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1962 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1963 g_assert(t->selected);
1964 if (t->p.other && t->p.other->selected) {
1965 nl = g_list_prepend(nl, t);
1966 }
1967 }
1969 while (nl) {
1970 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1971 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1972 sp_nodepath_node_select(n, TRUE, FALSE);
1973 n_added ++;
1974 nl = g_list_remove(nl, t);
1975 }
1977 /** \todo fixme: adjust ? */
1978 sp_nodepath_update_handles(nodepath);
1980 if (n_added > 1) {
1981 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1982 } else if (n_added > 0) {
1983 sp_nodepath_update_repr(nodepath, _("Add node"));
1984 }
1986 sp_nodepath_update_statusbar(nodepath);
1987 }
1989 /**
1990 * Select segment nearest to point
1991 */
1992 void
1993 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p, bool toggle)
1994 {
1995 if (!nodepath) {
1996 return;
1997 }
1999 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2000 Geom::PathVector const &pathv = curve->get_pathvector();
2001 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2002 if (!pvpos) {
2003 g_print ("Possible error?\n");
2004 return;
2005 }
2007 // calculate index for nodepath's representation.
2008 unsigned int segment_index = floor(pvpos->t) + 1;
2009 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2010 segment_index += pathv[i].size() + 1;
2011 if (pathv[i].closed()) {
2012 segment_index += 1;
2013 }
2014 }
2016 curve->unref();
2018 //find segment to segment
2019 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2021 //fixme: this can return NULL, so check before proceeding.
2022 g_return_if_fail(e != NULL);
2024 gboolean force = FALSE;
2025 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
2026 force = TRUE;
2027 }
2028 sp_nodepath_node_select(e, (gboolean) toggle, force);
2029 if (e->p.other)
2030 sp_nodepath_node_select(e->p.other, TRUE, force);
2032 sp_nodepath_update_handles(nodepath);
2034 sp_nodepath_update_statusbar(nodepath);
2035 }
2037 /**
2038 * Add a node nearest to point
2039 */
2040 void
2041 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p)
2042 {
2043 if (!nodepath) {
2044 return;
2045 }
2047 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2048 Geom::PathVector const &pathv = curve->get_pathvector();
2049 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2050 if (!pvpos) {
2051 g_print ("Possible error?\n");
2052 return;
2053 }
2055 // calculate index for nodepath's representation.
2056 double int_part;
2057 double t = std::modf(pvpos->t, &int_part);
2058 unsigned int segment_index = (unsigned int)int_part + 1;
2059 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2060 segment_index += pathv[i].size() + 1;
2061 if (pathv[i].closed()) {
2062 segment_index += 1;
2063 }
2064 }
2066 curve->unref();
2068 //find segment to split
2069 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2071 //don't know why but t seems to flip for lines
2072 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2073 t = 1.0 - t;
2074 }
2076 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2077 sp_nodepath_node_select(n, FALSE, TRUE);
2079 /* fixme: adjust ? */
2080 sp_nodepath_update_handles(nodepath);
2082 sp_nodepath_update_repr(nodepath, _("Add node"));
2084 sp_nodepath_update_statusbar(nodepath);
2085 }
2087 /*
2088 * Adjusts a segment so that t moves by a certain delta for dragging
2089 * converts lines to curves
2090 *
2091 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2092 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2093 */
2094 void
2095 sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, Geom::Point delta)
2096 {
2097 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node);
2099 //fixme: e and e->p can be NULL, so check for those before proceeding
2100 g_return_if_fail(e != NULL);
2101 g_return_if_fail(&e->p != NULL);
2103 if (e->type == Inkscape::NodePath::NODE_AUTO) {
2104 e->type = Inkscape::NodePath::NODE_SMOOTH;
2105 sp_nodepath_update_node_knot (e);
2106 }
2107 if (e->p.other->type == Inkscape::NodePath::NODE_AUTO) {
2108 e->p.other->type = Inkscape::NodePath::NODE_SMOOTH;
2109 sp_nodepath_update_node_knot (e->p.other);
2110 }
2112 /* feel good is an arbitrary parameter that distributes the delta between handles
2113 * if t of the drag point is less than 1/6 distance form the endpoint only
2114 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2115 */
2116 double feel_good;
2117 if (t <= 1.0 / 6.0)
2118 feel_good = 0;
2119 else if (t <= 0.5)
2120 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2121 else if (t <= 5.0 / 6.0)
2122 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2123 else
2124 feel_good = 1;
2126 //if we're dragging a line convert it to a curve
2127 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2128 sp_nodepath_set_line_type(e, NR_CURVETO);
2129 }
2131 Geom::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2132 Geom::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2133 e->p.other->n.pos += offsetcoord0;
2134 e->p.pos += offsetcoord1;
2136 // adjust handles of adjacent nodes where necessary
2137 sp_node_adjust_handle(e,1);
2138 sp_node_adjust_handle(e->p.other,-1);
2140 sp_nodepath_update_handles(e->subpath->nodepath);
2142 update_object(e->subpath->nodepath);
2144 sp_nodepath_update_statusbar(e->subpath->nodepath);
2145 }
2148 /**
2149 * Call sp_nodepath_break() for all selected segments.
2150 */
2151 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2152 {
2153 if (!nodepath) return;
2155 GList *tempin = g_list_copy(nodepath->selected);
2156 GList *temp = NULL;
2157 for (GList *l = tempin; l != NULL; l = l->next) {
2158 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2159 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2160 if (nn == NULL) continue; // no break, no new node
2161 temp = g_list_prepend(temp, nn);
2162 }
2163 g_list_free(tempin);
2165 if (temp) {
2166 sp_nodepath_deselect(nodepath);
2167 }
2168 for (GList *l = temp; l != NULL; l = l->next) {
2169 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2170 }
2172 sp_nodepath_update_handles(nodepath);
2174 sp_nodepath_update_repr(nodepath, _("Break path"));
2175 }
2177 /**
2178 * Duplicate the selected node(s).
2179 */
2180 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2181 {
2182 if (!nodepath) {
2183 return;
2184 }
2186 GList *temp = NULL;
2187 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2188 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2189 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2190 if (nn == NULL) continue; // could not duplicate
2191 temp = g_list_prepend(temp, nn);
2192 }
2194 if (temp) {
2195 sp_nodepath_deselect(nodepath);
2196 }
2197 for (GList *l = temp; l != NULL; l = l->next) {
2198 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2199 }
2201 sp_nodepath_update_handles(nodepath);
2203 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2204 }
2206 /**
2207 * Internal function to join two nodes by merging them into one.
2208 */
2209 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2210 {
2211 /* a and b are endpoints */
2213 // if one of the two nodes is mouseovered, fix its position
2214 Geom::Point c;
2215 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2216 c = a->pos;
2217 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2218 c = b->pos;
2219 } else {
2220 // otherwise, move joined node to the midpoint
2221 c = (a->pos + b->pos) / 2;
2222 }
2224 if (a->subpath == b->subpath) {
2225 Inkscape::NodePath::SubPath *sp = a->subpath;
2226 sp_nodepath_subpath_close(sp);
2227 sp_node_moveto (sp->first, c);
2229 sp_nodepath_update_handles(sp->nodepath);
2230 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2231 return;
2232 }
2234 /* a and b are separate subpaths */
2235 Inkscape::NodePath::SubPath *sa = a->subpath;
2236 Inkscape::NodePath::SubPath *sb = b->subpath;
2237 Geom::Point p;
2238 Inkscape::NodePath::Node *n;
2239 NRPathcode code;
2240 if (a == sa->first) {
2241 // we will now reverse sa, so that a is its last node, not first, and drop that node
2242 p = sa->first->n.pos;
2243 code = (NRPathcode)sa->first->n.other->code;
2244 // create new subpath
2245 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2246 // create a first moveto node on it
2247 n = sa->last;
2248 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2249 n = n->p.other;
2250 if (n == sa->first) n = NULL;
2251 while (n) {
2252 // copy the rest of the nodes from sa to t, going backwards
2253 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2254 n = n->p.other;
2255 if (n == sa->first) n = NULL;
2256 }
2257 // replace sa with t
2258 sp_nodepath_subpath_destroy(sa);
2259 sa = t;
2260 } else if (a == sa->last) {
2261 // a is already last, just drop it
2262 p = sa->last->p.pos;
2263 code = (NRPathcode)sa->last->code;
2264 sp_nodepath_node_destroy(sa->last);
2265 } else {
2266 code = NR_END;
2267 g_assert_not_reached();
2268 }
2270 if (b == sb->first) {
2271 // copy all nodes from b to a, forward
2272 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2273 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2274 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2275 }
2276 } else if (b == sb->last) {
2277 // copy all nodes from b to a, backward
2278 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2279 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2280 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2281 }
2282 } else {
2283 g_assert_not_reached();
2284 }
2285 /* and now destroy sb */
2287 sp_nodepath_subpath_destroy(sb);
2289 sp_nodepath_update_handles(sa->nodepath);
2291 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2293 sp_nodepath_update_statusbar(nodepath);
2294 }
2296 /**
2297 * Internal function to join two nodes by adding a segment between them.
2298 */
2299 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2300 {
2301 if (a->subpath == b->subpath) {
2302 Inkscape::NodePath::SubPath *sp = a->subpath;
2304 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2305 sp->closed = TRUE;
2307 sp->first->p.other = sp->last;
2308 sp->last->n.other = sp->first;
2310 sp_node_handle_mirror_p_to_n(sp->last);
2311 sp_node_handle_mirror_n_to_p(sp->first);
2313 sp->first->code = sp->last->code;
2314 sp->first = sp->last;
2316 sp_nodepath_update_handles(sp->nodepath);
2318 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2320 return;
2321 }
2323 /* a and b are separate subpaths */
2324 Inkscape::NodePath::SubPath *sa = a->subpath;
2325 Inkscape::NodePath::SubPath *sb = b->subpath;
2327 Inkscape::NodePath::Node *n;
2328 Geom::Point p;
2329 NRPathcode code;
2330 if (a == sa->first) {
2331 code = (NRPathcode) sa->first->n.other->code;
2332 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2333 n = sa->last;
2334 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2335 for (n = n->p.other; n != NULL; n = n->p.other) {
2336 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2337 }
2338 sp_nodepath_subpath_destroy(sa);
2339 sa = t;
2340 } else if (a == sa->last) {
2341 code = (NRPathcode)sa->last->code;
2342 } else {
2343 code = NR_END;
2344 g_assert_not_reached();
2345 }
2347 if (b == sb->first) {
2348 n = sb->first;
2349 sp_node_handle_mirror_p_to_n(sa->last);
2350 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2351 sp_node_handle_mirror_n_to_p(sa->last);
2352 for (n = n->n.other; n != NULL; n = n->n.other) {
2353 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2354 }
2355 } else if (b == sb->last) {
2356 n = sb->last;
2357 sp_node_handle_mirror_p_to_n(sa->last);
2358 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2359 sp_node_handle_mirror_n_to_p(sa->last);
2360 for (n = n->p.other; n != NULL; n = n->p.other) {
2361 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2362 }
2363 } else {
2364 g_assert_not_reached();
2365 }
2366 /* and now destroy sb */
2368 sp_nodepath_subpath_destroy(sb);
2370 sp_nodepath_update_handles(sa->nodepath);
2372 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2373 }
2375 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2377 /**
2378 * Internal function to handle joining two nodes.
2379 */
2380 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2381 {
2382 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2384 if (g_list_length(nodepath->selected) != 2) {
2385 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2386 return;
2387 }
2389 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2390 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2392 g_assert(a != b);
2393 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2394 // someone tried to join an orphan node (i.e. a single-node subpath).
2395 // this is not worth an error message, just fail silently.
2396 return;
2397 }
2399 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2400 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2401 return;
2402 }
2404 switch(mode) {
2405 case NODE_JOIN_ENDPOINTS:
2406 do_node_selected_join(nodepath, a, b);
2407 break;
2408 case NODE_JOIN_SEGMENT:
2409 do_node_selected_join_segment(nodepath, a, b);
2410 break;
2411 }
2412 }
2414 /**
2415 * Join two nodes by merging them into one.
2416 */
2417 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2418 {
2419 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2420 }
2422 /**
2423 * Join two nodes by adding a segment between them.
2424 */
2425 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2426 {
2427 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2428 }
2430 /**
2431 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2432 */
2433 void sp_node_delete_preserve(GList *nodes_to_delete)
2434 {
2435 GSList *nodepaths = NULL;
2437 while (nodes_to_delete) {
2438 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2439 Inkscape::NodePath::SubPath *sp = node->subpath;
2440 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2441 Inkscape::NodePath::Node *sample_cursor = NULL;
2442 Inkscape::NodePath::Node *sample_end = NULL;
2443 Inkscape::NodePath::Node *delete_cursor = node;
2444 bool just_delete = false;
2446 //find the start of this contiguous selection
2447 //move left to the first node that is not selected
2448 //or the start of the non-closed path
2449 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2450 delete_cursor = curr;
2451 }
2453 //just delete at the beginning of an open path
2454 if (!delete_cursor->p.other) {
2455 sample_cursor = delete_cursor;
2456 just_delete = true;
2457 } else {
2458 sample_cursor = delete_cursor->p.other;
2459 }
2461 //calculate points for each segment
2462 int rate = 5;
2463 float period = 1.0 / rate;
2464 std::vector<Geom::Point> data;
2465 if (!just_delete) {
2466 data.push_back(sample_cursor->pos);
2467 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2468 //just delete at the end of an open path
2469 if (!sp->closed && curr == sp->last) {
2470 just_delete = true;
2471 break;
2472 }
2474 //sample points on the contiguous selected segment
2475 Geom::Point *bez;
2476 bez = new Geom::Point [4];
2477 bez[0] = curr->pos;
2478 bez[1] = curr->n.pos;
2479 bez[2] = curr->n.other->p.pos;
2480 bez[3] = curr->n.other->pos;
2481 for (int i=1; i<rate; i++) {
2482 gdouble t = i * period;
2483 Geom::Point p = bezier_pt(3, bez, t);
2484 data.push_back(p);
2485 }
2486 data.push_back(curr->n.other->pos);
2488 sample_end = curr->n.other;
2489 //break if we've come full circle or hit the end of the selection
2490 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2491 break;
2492 }
2493 }
2494 }
2496 if (!just_delete) {
2497 //calculate the best fitting single segment and adjust the endpoints
2498 Geom::Point *adata;
2499 adata = new Geom::Point [data.size()];
2500 copy(data.begin(), data.end(), adata);
2502 Geom::Point *bez;
2503 bez = new Geom::Point [4];
2504 //would decreasing error create a better fitting approximation?
2505 gdouble error = 1.0;
2506 gint ret;
2507 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2509 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2510 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2511 //the resulting nodes behave as expected.
2512 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2513 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2514 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2515 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2517 //adjust endpoints
2518 sample_cursor->n.pos = bez[1];
2519 sample_end->p.pos = bez[2];
2520 }
2522 //destroy this contiguous selection
2523 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2524 Inkscape::NodePath::Node *temp = delete_cursor;
2525 if (delete_cursor->n.other == delete_cursor) {
2526 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2527 delete_cursor = NULL;
2528 } else {
2529 delete_cursor = delete_cursor->n.other;
2530 }
2531 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2532 sp_nodepath_node_destroy(temp);
2533 }
2535 sp_nodepath_update_handles(nodepath);
2537 if (!g_slist_find(nodepaths, nodepath))
2538 nodepaths = g_slist_prepend (nodepaths, nodepath);
2539 }
2541 for (GSList *i = nodepaths; i; i = i->next) {
2542 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2543 // different nodepaths will give us one undo event per nodepath
2544 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2546 // if the entire nodepath is removed, delete the selected object.
2547 if (nodepath->subpaths == NULL ||
2548 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2549 //at least 2
2550 sp_nodepath_get_node_count(nodepath) < 2) {
2551 SPDocument *document = sp_desktop_document (nodepath->desktop);
2552 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2553 //delete this nodepath's object, not the entire selection! (though at this time, this
2554 //does not matter)
2555 sp_selection_delete(nodepath->desktop);
2556 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2557 _("Delete nodes"));
2558 } else {
2559 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2560 sp_nodepath_update_statusbar(nodepath);
2561 }
2562 }
2564 g_slist_free (nodepaths);
2565 }
2567 /**
2568 * Delete one or more selected nodes.
2569 */
2570 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2571 {
2572 if (!nodepath) return;
2573 if (!nodepath->selected) return;
2575 /** \todo fixme: do it the right way */
2576 while (nodepath->selected) {
2577 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2578 sp_nodepath_node_destroy(node);
2579 }
2582 //clean up the nodepath (such as for trivial subpaths)
2583 sp_nodepath_cleanup(nodepath);
2585 sp_nodepath_update_handles(nodepath);
2587 // if the entire nodepath is removed, delete the selected object.
2588 if (nodepath->subpaths == NULL ||
2589 sp_nodepath_get_node_count(nodepath) < 2) {
2590 SPDocument *document = sp_desktop_document (nodepath->desktop);
2591 sp_selection_delete(nodepath->desktop);
2592 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2593 _("Delete nodes"));
2594 return;
2595 }
2597 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2599 sp_nodepath_update_statusbar(nodepath);
2600 }
2602 /**
2603 * Delete one or more segments between two selected nodes.
2604 * This is the code for 'split'.
2605 */
2606 void
2607 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2608 {
2609 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2610 Inkscape::NodePath::Node *curr, *next; //Iterators
2612 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2614 if (g_list_length(nodepath->selected) != 2) {
2615 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2616 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2617 return;
2618 }
2620 //Selected nodes, not inclusive
2621 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2622 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2624 if ( ( a==b) || //same node
2625 (a->subpath != b->subpath ) || //not the same path
2626 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2627 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2628 {
2629 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2630 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2631 return;
2632 }
2634 //###########################################
2635 //# BEGIN EDITS
2636 //###########################################
2637 //##################################
2638 //# CLOSED PATH
2639 //##################################
2640 if (a->subpath->closed) {
2643 gboolean reversed = FALSE;
2645 //Since we can go in a circle, we need to find the shorter distance.
2646 // a->b or b->a
2647 start = end = NULL;
2648 int distance = 0;
2649 int minDistance = 0;
2650 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2651 if (curr==b) {
2652 //printf("a to b:%d\n", distance);
2653 start = a;//go from a to b
2654 end = b;
2655 minDistance = distance;
2656 //printf("A to B :\n");
2657 break;
2658 }
2659 distance++;
2660 }
2662 //try again, the other direction
2663 distance = 0;
2664 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2665 if (curr==a) {
2666 //printf("b to a:%d\n", distance);
2667 if (distance < minDistance) {
2668 start = b; //we go from b to a
2669 end = a;
2670 reversed = TRUE;
2671 //printf("B to A\n");
2672 }
2673 break;
2674 }
2675 distance++;
2676 }
2679 //Copy everything from 'end' to 'start' to a new subpath
2680 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2681 for (curr=end ; curr ; curr=curr->n.other) {
2682 NRPathcode code = (NRPathcode) curr->code;
2683 if (curr == end)
2684 code = NR_MOVETO;
2685 sp_nodepath_node_new(t, NULL,
2686 (Inkscape::NodePath::NodeType)curr->type, code,
2687 &curr->p.pos, &curr->pos, &curr->n.pos);
2688 if (curr == start)
2689 break;
2690 }
2691 sp_nodepath_subpath_destroy(a->subpath);
2694 }
2698 //##################################
2699 //# OPEN PATH
2700 //##################################
2701 else {
2703 //We need to get the direction of the list between A and B
2704 //Can we walk from a to b?
2705 start = end = NULL;
2706 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2707 if (curr==b) {
2708 start = a; //did it! we go from a to b
2709 end = b;
2710 //printf("A to B\n");
2711 break;
2712 }
2713 }
2714 if (!start) {//didn't work? let's try the other direction
2715 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2716 if (curr==a) {
2717 start = b; //did it! we go from b to a
2718 end = a;
2719 //printf("B to A\n");
2720 break;
2721 }
2722 }
2723 }
2724 if (!start) {
2725 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2726 _("Cannot find path between nodes."));
2727 return;
2728 }
2732 //Copy everything after 'end' to a new subpath
2733 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2734 for (curr=end ; curr ; curr=curr->n.other) {
2735 NRPathcode code = (NRPathcode) curr->code;
2736 if (curr == end)
2737 code = NR_MOVETO;
2738 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2739 &curr->p.pos, &curr->pos, &curr->n.pos);
2740 }
2742 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2743 for (curr = start->n.other ; curr ; curr=next) {
2744 next = curr->n.other;
2745 sp_nodepath_node_destroy(curr);
2746 }
2748 }
2749 //###########################################
2750 //# END EDITS
2751 //###########################################
2753 //clean up the nodepath (such as for trivial subpaths)
2754 sp_nodepath_cleanup(nodepath);
2756 sp_nodepath_update_handles(nodepath);
2758 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2760 sp_nodepath_update_statusbar(nodepath);
2761 }
2763 /**
2764 * Call sp_nodepath_set_line() for all selected segments.
2765 */
2766 void
2767 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2768 {
2769 if (nodepath == NULL) return;
2771 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2772 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2773 g_assert(n->selected);
2774 if (n->p.other && n->p.other->selected) {
2775 sp_nodepath_set_line_type(n, code);
2776 }
2777 }
2779 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2780 }
2782 /**
2783 * Call sp_nodepath_convert_node_type() for all selected nodes.
2784 */
2785 void
2786 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2787 {
2788 if (nodepath == NULL) return;
2790 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2792 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2793 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2794 }
2796 sp_nodepath_update_repr(nodepath, _("Change node type"));
2797 }
2799 /**
2800 * Change select status of node, update its own and neighbour handles.
2801 */
2802 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2803 {
2804 node->selected = selected;
2806 if (selected) {
2807 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 11 : 9);
2808 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2809 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2810 sp_knot_update_ctrl(node->knot);
2811 } else {
2812 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 9 : 7);
2813 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2814 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2815 sp_knot_update_ctrl(node->knot);
2816 }
2818 sp_node_update_handles(node);
2819 if (node->n.other) sp_node_update_handles(node->n.other);
2820 if (node->p.other) sp_node_update_handles(node->p.other);
2821 }
2823 /**
2824 \brief Select a node
2825 \param node The node to select
2826 \param incremental If true, add to selection, otherwise deselect others
2827 \param override If true, always select this node, otherwise toggle selected status
2828 */
2829 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2830 {
2831 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2833 if (incremental) {
2834 if (override) {
2835 if (!g_list_find(nodepath->selected, node)) {
2836 nodepath->selected = g_list_prepend(nodepath->selected, node);
2837 }
2838 sp_node_set_selected(node, TRUE);
2839 } else { // toggle
2840 if (node->selected) {
2841 g_assert(g_list_find(nodepath->selected, node));
2842 nodepath->selected = g_list_remove(nodepath->selected, node);
2843 } else {
2844 g_assert(!g_list_find(nodepath->selected, node));
2845 nodepath->selected = g_list_prepend(nodepath->selected, node);
2846 }
2847 sp_node_set_selected(node, !node->selected);
2848 }
2849 } else {
2850 sp_nodepath_deselect(nodepath);
2851 nodepath->selected = g_list_prepend(nodepath->selected, node);
2852 sp_node_set_selected(node, TRUE);
2853 }
2855 sp_nodepath_update_statusbar(nodepath);
2856 }
2859 /**
2860 \brief Deselect all nodes in the nodepath
2861 */
2862 void
2863 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2864 {
2865 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2867 while (nodepath->selected) {
2868 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2869 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2870 }
2871 sp_nodepath_update_statusbar(nodepath);
2872 }
2874 /**
2875 \brief Select or invert selection of all nodes in the nodepath
2876 */
2877 void
2878 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2879 {
2880 if (!nodepath) return;
2882 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2883 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2884 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2885 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2886 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2887 }
2888 }
2889 }
2891 /**
2892 * If nothing selected, does the same as sp_nodepath_select_all();
2893 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2894 * (i.e., similar to "select all in layer", with the "selected" subpaths
2895 * being treated as "layers" in the path).
2896 */
2897 void
2898 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2899 {
2900 if (!nodepath) return;
2902 if (g_list_length (nodepath->selected) == 0) {
2903 sp_nodepath_select_all (nodepath, invert);
2904 return;
2905 }
2907 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2908 GSList *subpaths = NULL;
2910 for (GList *l = copy; l != NULL; l = l->next) {
2911 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2912 Inkscape::NodePath::SubPath *subpath = n->subpath;
2913 if (!g_slist_find (subpaths, subpath))
2914 subpaths = g_slist_prepend (subpaths, subpath);
2915 }
2917 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2918 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2919 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2920 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2921 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2922 }
2923 }
2925 g_slist_free (subpaths);
2926 g_list_free (copy);
2927 }
2929 /**
2930 * \brief Select the node after the last selected; if none is selected,
2931 * select the first within path.
2932 */
2933 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2934 {
2935 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2937 Inkscape::NodePath::Node *last = NULL;
2938 if (nodepath->selected) {
2939 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2940 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2941 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2942 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2943 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2944 if (node->selected) {
2945 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2946 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2947 if (spl->next) { // there's a next subpath
2948 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2949 last = subpath_next->first;
2950 } else if (spl->prev) { // there's a previous subpath
2951 last = NULL; // to be set later to the first node of first subpath
2952 } else {
2953 last = node->n.other;
2954 }
2955 } else {
2956 last = node->n.other;
2957 }
2958 } else {
2959 if (node->n.other) {
2960 last = node->n.other;
2961 } else {
2962 if (spl->next) { // there's a next subpath
2963 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2964 last = subpath_next->first;
2965 } else if (spl->prev) { // there's a previous subpath
2966 last = NULL; // to be set later to the first node of first subpath
2967 } else {
2968 last = (Inkscape::NodePath::Node *) subpath->first;
2969 }
2970 }
2971 }
2972 }
2973 }
2974 }
2975 sp_nodepath_deselect(nodepath);
2976 }
2978 if (last) { // there's at least one more node after selected
2979 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2980 } else { // no more nodes, select the first one in first subpath
2981 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2982 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2983 }
2984 }
2986 /**
2987 * \brief Select the node before the first selected; if none is selected,
2988 * select the last within path
2989 */
2990 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2991 {
2992 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2994 Inkscape::NodePath::Node *last = NULL;
2995 if (nodepath->selected) {
2996 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2997 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2998 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2999 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3000 if (node->selected) {
3001 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
3002 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
3003 if (spl->prev) { // there's a prev subpath
3004 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3005 last = subpath_prev->last;
3006 } else if (spl->next) { // there's a next subpath
3007 last = NULL; // to be set later to the last node of last subpath
3008 } else {
3009 last = node->p.other;
3010 }
3011 } else {
3012 last = node->p.other;
3013 }
3014 } else {
3015 if (node->p.other) {
3016 last = node->p.other;
3017 } else {
3018 if (spl->prev) { // there's a prev subpath
3019 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3020 last = subpath_prev->last;
3021 } else if (spl->next) { // there's a next subpath
3022 last = NULL; // to be set later to the last node of last subpath
3023 } else {
3024 last = (Inkscape::NodePath::Node *) subpath->last;
3025 }
3026 }
3027 }
3028 }
3029 }
3030 }
3031 sp_nodepath_deselect(nodepath);
3032 }
3034 if (last) { // there's at least one more node before selected
3035 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
3036 } else { // no more nodes, select the last one in last subpath
3037 GList *spl = g_list_last(nodepath->subpaths);
3038 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3039 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
3040 }
3041 }
3043 /**
3044 * \brief Select all nodes that are within the rectangle.
3045 */
3046 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, Geom::Rect const &b, gboolean incremental)
3047 {
3048 if (!incremental) {
3049 sp_nodepath_deselect(nodepath);
3050 }
3052 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3053 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3054 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3055 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3057 if (b.contains(node->pos)) {
3058 sp_nodepath_node_select(node, TRUE, TRUE);
3059 }
3060 }
3061 }
3062 }
3065 void
3066 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3067 {
3068 g_assert (n);
3069 g_assert (nodepath);
3070 g_assert (n->subpath->nodepath == nodepath);
3072 if (g_list_length (nodepath->selected) == 0) {
3073 if (grow > 0) {
3074 sp_nodepath_node_select(n, TRUE, TRUE);
3075 }
3076 return;
3077 }
3079 if (g_list_length (nodepath->selected) == 1) {
3080 if (grow < 0) {
3081 sp_nodepath_deselect (nodepath);
3082 return;
3083 }
3084 }
3086 double n_sel_range = 0, p_sel_range = 0;
3087 Inkscape::NodePath::Node *farthest_n_node = n;
3088 Inkscape::NodePath::Node *farthest_p_node = n;
3090 // Calculate ranges
3091 {
3092 double n_range = 0, p_range = 0;
3093 bool n_going = true, p_going = true;
3094 Inkscape::NodePath::Node *n_node = n;
3095 Inkscape::NodePath::Node *p_node = n;
3096 do {
3097 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3098 if (n_node && n_going)
3099 n_node = n_node->n.other;
3100 if (n_node == NULL) {
3101 n_going = false;
3102 } else {
3103 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3104 if (n_node->selected) {
3105 n_sel_range = n_range;
3106 farthest_n_node = n_node;
3107 }
3108 if (n_node == p_node) {
3109 n_going = false;
3110 p_going = false;
3111 }
3112 }
3113 if (p_node && p_going)
3114 p_node = p_node->p.other;
3115 if (p_node == NULL) {
3116 p_going = false;
3117 } else {
3118 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3119 if (p_node->selected) {
3120 p_sel_range = p_range;
3121 farthest_p_node = p_node;
3122 }
3123 if (p_node == n_node) {
3124 n_going = false;
3125 p_going = false;
3126 }
3127 }
3128 } while (n_going || p_going);
3129 }
3131 if (grow > 0) {
3132 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3133 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3134 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3135 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3136 }
3137 } else {
3138 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3139 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3140 } else if (farthest_p_node && farthest_p_node->selected) {
3141 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3142 }
3143 }
3144 }
3146 void
3147 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3148 {
3149 g_assert (n);
3150 g_assert (nodepath);
3151 g_assert (n->subpath->nodepath == nodepath);
3153 if (g_list_length (nodepath->selected) == 0) {
3154 if (grow > 0) {
3155 sp_nodepath_node_select(n, TRUE, TRUE);
3156 }
3157 return;
3158 }
3160 if (g_list_length (nodepath->selected) == 1) {
3161 if (grow < 0) {
3162 sp_nodepath_deselect (nodepath);
3163 return;
3164 }
3165 }
3167 Inkscape::NodePath::Node *farthest_selected = NULL;
3168 double farthest_dist = 0;
3170 Inkscape::NodePath::Node *closest_unselected = NULL;
3171 double closest_dist = NR_HUGE;
3173 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3174 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3175 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3176 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3177 if (node == n)
3178 continue;
3179 if (node->selected) {
3180 if (Geom::L2(node->pos - n->pos) > farthest_dist) {
3181 farthest_dist = Geom::L2(node->pos - n->pos);
3182 farthest_selected = node;
3183 }
3184 } else {
3185 if (Geom::L2(node->pos - n->pos) < closest_dist) {
3186 closest_dist = Geom::L2(node->pos - n->pos);
3187 closest_unselected = node;
3188 }
3189 }
3190 }
3191 }
3193 if (grow > 0) {
3194 if (closest_unselected) {
3195 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3196 }
3197 } else {
3198 if (farthest_selected) {
3199 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3200 }
3201 }
3202 }
3205 /**
3206 \brief Saves all nodes' and handles' current positions in their origin members
3207 */
3208 void
3209 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3210 {
3211 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3212 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3213 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3214 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3215 n->origin = n->pos;
3216 n->p.origin = n->p.pos;
3217 n->n.origin = n->n.pos;
3218 }
3219 }
3220 }
3222 /**
3223 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3224 */
3225 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3226 {
3227 GList *r = NULL;
3228 if (nodepath->selected) {
3229 guint i = 0;
3230 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3231 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3232 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3233 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3234 i++;
3235 if (node->selected) {
3236 r = g_list_append(r, GINT_TO_POINTER(i));
3237 }
3238 }
3239 }
3240 }
3241 return r;
3242 }
3244 /**
3245 \brief Restores selection by selecting nodes whose positions are in the list
3246 */
3247 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3248 {
3249 sp_nodepath_deselect(nodepath);
3251 guint i = 0;
3252 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3253 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3254 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3255 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3256 i++;
3257 if (g_list_find(r, GINT_TO_POINTER(i))) {
3258 sp_nodepath_node_select(node, TRUE, TRUE);
3259 }
3260 }
3261 }
3262 }
3265 /**
3266 \brief Adjusts handle according to node type and line code.
3267 */
3268 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3269 {
3270 g_assert(node);
3272 // nothing to do for auto nodes (sp_node_adjust_handles() does the job)
3273 if (node->type == Inkscape::NodePath::NODE_AUTO)
3274 return;
3276 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3277 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3279 // nothing to do if we are an end node
3280 if (me->other == NULL) return;
3281 if (other->other == NULL) return;
3283 // nothing to do if we are a cusp node
3284 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3286 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3287 NRPathcode mecode;
3288 if (which_adjust == 1) {
3289 mecode = (NRPathcode)me->other->code;
3290 } else {
3291 mecode = (NRPathcode)node->code;
3292 }
3293 if (mecode == NR_LINETO) return;
3295 if (sp_node_side_is_line(node, other)) {
3296 // other is a line, and we are either smooth or symm
3297 Inkscape::NodePath::Node *othernode = other->other;
3298 double len = Geom::L2(me->pos - node->pos);
3299 Geom::Point delta = node->pos - othernode->pos;
3300 double linelen = Geom::L2(delta);
3301 if (linelen < 1e-18)
3302 return;
3303 me->pos = node->pos + (len / linelen)*delta;
3304 return;
3305 }
3307 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3308 // symmetrize
3309 me->pos = 2 * node->pos - other->pos;
3310 return;
3311 } else {
3312 // smoothify
3313 double len = Geom::L2(me->pos - node->pos);
3314 Geom::Point delta = other->pos - node->pos;
3315 double otherlen = Geom::L2(delta);
3316 if (otherlen < 1e-18) return;
3317 me->pos = node->pos - (len / otherlen) * delta;
3318 }
3319 }
3321 /**
3322 \brief Adjusts both handles according to node type and line code
3323 */
3324 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3325 {
3326 g_assert(node);
3328 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3330 /* we are either smooth or symm */
3332 if (node->p.other == NULL) return;
3333 if (node->n.other == NULL) return;
3335 if (node->type == Inkscape::NodePath::NODE_AUTO) {
3336 sp_node_adjust_handles_auto(node);
3337 return;
3338 }
3340 if (sp_node_side_is_line(node, &node->p)) {
3341 sp_node_adjust_handle(node, 1);
3342 return;
3343 }
3345 if (sp_node_side_is_line(node, &node->n)) {
3346 sp_node_adjust_handle(node, -1);
3347 return;
3348 }
3350 /* both are curves */
3351 Geom::Point const delta( node->n.pos - node->p.pos );
3353 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3354 node->p.pos = node->pos - delta / 2;
3355 node->n.pos = node->pos + delta / 2;
3356 return;
3357 }
3359 /* We are smooth */
3360 double plen = Geom::L2(node->p.pos - node->pos);
3361 if (plen < 1e-18) return;
3362 double nlen = Geom::L2(node->n.pos - node->pos);
3363 if (nlen < 1e-18) return;
3364 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3365 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3366 }
3368 static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node)
3369 {
3370 if (node->p.other == NULL || node->n.other == NULL) {
3371 node->p.pos = node->pos;
3372 node->n.pos = node->pos;
3373 return;
3374 }
3376 Geom::Point leg_prev = to_2geom(node->p.other->pos - node->pos);
3377 Geom::Point leg_next = to_2geom(node->n.other->pos - node->pos);
3379 double norm_leg_prev = Geom::L2(leg_prev);
3380 double norm_leg_next = Geom::L2(leg_next);
3382 Geom::Point delta;
3383 if (norm_leg_next > 0.0) {
3384 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
3385 delta.normalize();
3386 }
3388 node->p.pos = node->pos - norm_leg_prev / 3 * delta;
3389 node->n.pos = node->pos + norm_leg_next / 3 * delta;
3390 }
3392 /**
3393 * Node event callback.
3394 */
3395 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3396 {
3397 gboolean ret = FALSE;
3398 switch (event->type) {
3399 case GDK_ENTER_NOTIFY:
3400 Inkscape::NodePath::Path::active_node = n;
3401 break;
3402 case GDK_LEAVE_NOTIFY:
3403 Inkscape::NodePath::Path::active_node = NULL;
3404 break;
3405 case GDK_SCROLL:
3406 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3407 switch (event->scroll.direction) {
3408 case GDK_SCROLL_UP:
3409 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3410 break;
3411 case GDK_SCROLL_DOWN:
3412 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3413 break;
3414 default:
3415 break;
3416 }
3417 ret = TRUE;
3418 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3419 switch (event->scroll.direction) {
3420 case GDK_SCROLL_UP:
3421 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3422 break;
3423 case GDK_SCROLL_DOWN:
3424 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3425 break;
3426 default:
3427 break;
3428 }
3429 ret = TRUE;
3430 }
3431 break;
3432 case GDK_KEY_PRESS:
3433 switch (get_group0_keyval (&event->key)) {
3434 case GDK_space:
3435 if (event->key.state & GDK_BUTTON1_MASK) {
3436 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3437 stamp_repr(nodepath);
3438 ret = TRUE;
3439 }
3440 break;
3441 case GDK_Page_Up:
3442 if (event->key.state & GDK_CONTROL_MASK) {
3443 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3444 } else {
3445 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3446 }
3447 break;
3448 case GDK_Page_Down:
3449 if (event->key.state & GDK_CONTROL_MASK) {
3450 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3451 } else {
3452 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3453 }
3454 break;
3455 default:
3456 break;
3457 }
3458 break;
3459 default:
3460 break;
3461 }
3463 return ret;
3464 }
3466 /**
3467 * Handle keypress on node; directly called.
3468 */
3469 gboolean node_key(GdkEvent *event)
3470 {
3471 Inkscape::NodePath::Path *np;
3473 // there is no way to verify nodes so set active_node to nil when deleting!!
3474 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3476 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3477 gint ret = FALSE;
3478 switch (get_group0_keyval (&event->key)) {
3479 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3480 case GDK_BackSpace:
3481 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3482 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3483 sp_nodepath_update_repr(np, _("Delete node"));
3484 Inkscape::NodePath::Path::active_node = NULL;
3485 ret = TRUE;
3486 break;
3487 case GDK_c:
3488 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3489 ret = TRUE;
3490 break;
3491 case GDK_s:
3492 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3493 ret = TRUE;
3494 break;
3495 case GDK_a:
3496 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_AUTO);
3497 ret = TRUE;
3498 break;
3499 case GDK_y:
3500 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3501 ret = TRUE;
3502 break;
3503 case GDK_b:
3504 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3505 ret = TRUE;
3506 break;
3507 }
3508 return ret;
3509 }
3510 return FALSE;
3511 }
3513 /**
3514 * Mouseclick on node callback.
3515 */
3516 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3517 {
3518 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3520 if (state & GDK_CONTROL_MASK) {
3521 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3523 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3524 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3525 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3526 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3527 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3528 } else if (n->type == Inkscape::NodePath::NODE_SYMM) {
3529 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_AUTO);
3530 } else {
3531 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3532 }
3533 sp_nodepath_update_repr(nodepath, _("Change node type"));
3534 sp_nodepath_update_statusbar(nodepath);
3536 } else { //ctrl+alt+click: delete node
3537 GList *node_to_delete = NULL;
3538 node_to_delete = g_list_append(node_to_delete, n);
3539 sp_node_delete_preserve(node_to_delete);
3540 }
3542 } else {
3543 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3544 }
3545 }
3547 /**
3548 * Mouse grabbed node callback.
3549 */
3550 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3551 {
3552 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3554 if (!n->selected) {
3555 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3556 }
3558 n->is_dragging = true;
3559 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3561 sp_nodepath_remember_origins (n->subpath->nodepath);
3562 }
3564 /**
3565 * Mouse ungrabbed node callback.
3566 */
3567 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3568 {
3569 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3571 n->dragging_out = NULL;
3572 n->is_dragging = false;
3573 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3575 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3576 }
3578 /**
3579 * The point on a line, given by its angle, closest to the given point.
3580 * \param p A point.
3581 * \param a Angle of the line; it is assumed to go through coordinate origin.
3582 * \param closest Pointer to the point struct where the result is stored.
3583 * \todo FIXME: use dot product perhaps?
3584 */
3585 static void point_line_closest(Geom::Point *p, double a, Geom::Point *closest)
3586 {
3587 if (a == HUGE_VAL) { // vertical
3588 *closest = Geom::Point(0, (*p)[Geom::Y]);
3589 } else {
3590 (*closest)[Geom::X] = ( a * (*p)[Geom::Y] + (*p)[Geom::X]) / (a*a + 1);
3591 (*closest)[Geom::Y] = a * (*closest)[Geom::X];
3592 }
3593 }
3595 /**
3596 * Distance from the point to a line given by its angle.
3597 * \param p A point.
3598 * \param a Angle of the line; it is assumed to go through coordinate origin.
3599 */
3600 static double point_line_distance(Geom::Point *p, double a)
3601 {
3602 Geom::Point c;
3603 point_line_closest(p, a, &c);
3604 return sqrt(((*p)[Geom::X] - c[Geom::X])*((*p)[Geom::X] - c[Geom::X]) + ((*p)[Geom::Y] - c[Geom::Y])*((*p)[Geom::Y] - c[Geom::Y]));
3605 }
3607 /**
3608 * Callback for node "request" signal.
3609 * \todo fixme: This goes to "moved" event? (lauris)
3610 */
3611 static gboolean
3612 node_request(SPKnot */*knot*/, Geom::Point *p, guint state, gpointer data)
3613 {
3614 double yn, xn, yp, xp;
3615 double an, ap, na, pa;
3616 double d_an, d_ap, d_na, d_pa;
3617 gboolean collinear = FALSE;
3618 Geom::Point c;
3619 Geom::Point pr;
3621 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3623 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3625 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3626 if ( (!n->subpath->nodepath->straight_path) &&
3627 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3628 || n->dragging_out ) )
3629 {
3630 Geom::Point mouse = (*p);
3632 if (!n->dragging_out) {
3633 // This is the first drag-out event; find out which handle to drag out
3634 double appr_n = (n->n.other ? Geom::L2(n->n.other->pos - n->pos) - Geom::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3635 double appr_p = (n->p.other ? Geom::L2(n->p.other->pos - n->pos) - Geom::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3637 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3638 return FALSE;
3640 Inkscape::NodePath::NodeSide *opposite;
3641 if (appr_p > appr_n) { // closer to p
3642 n->dragging_out = &n->p;
3643 opposite = &n->n;
3644 n->code = NR_CURVETO;
3645 } else if (appr_p < appr_n) { // closer to n
3646 n->dragging_out = &n->n;
3647 opposite = &n->p;
3648 n->n.other->code = NR_CURVETO;
3649 } else { // p and n nodes are the same
3650 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3651 n->dragging_out = &n->p;
3652 opposite = &n->n;
3653 n->code = NR_CURVETO;
3654 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3655 n->dragging_out = &n->n;
3656 opposite = &n->p;
3657 n->n.other->code = NR_CURVETO;
3658 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3659 double appr_other_n = (n->n.other ? Geom::L2(n->n.other->n.pos - n->pos) - Geom::L2(n->n.other->n.pos - (*p)) : -HUGE_VAL);
3660 double appr_other_p = (n->n.other ? Geom::L2(n->n.other->p.pos - n->pos) - Geom::L2(n->n.other->p.pos - (*p)) : -HUGE_VAL);
3661 if (appr_other_p > appr_other_n) { // closer to other's p handle
3662 n->dragging_out = &n->n;
3663 opposite = &n->p;
3664 n->n.other->code = NR_CURVETO;
3665 } else { // closer to other's n handle
3666 n->dragging_out = &n->p;
3667 opposite = &n->n;
3668 n->code = NR_CURVETO;
3669 }
3670 }
3671 }
3673 // if there's another handle, make sure the one we drag out starts parallel to it
3674 if (opposite->pos != n->pos) {
3675 mouse = n->pos - Geom::L2(mouse - n->pos) * Geom::unit_vector(opposite->pos - n->pos);
3676 }
3678 // knots might not be created yet!
3679 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3680 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3681 }
3683 // pass this on to the handle-moved callback
3684 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3685 sp_node_update_handles(n);
3686 return TRUE;
3687 }
3689 if (state & GDK_CONTROL_MASK) { // constrained motion
3691 // calculate relative distances of handles
3692 // n handle:
3693 yn = n->n.pos[Geom::Y] - n->pos[Geom::Y];
3694 xn = n->n.pos[Geom::X] - n->pos[Geom::X];
3695 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3696 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3697 if (n->n.other) { // if there is the next point
3698 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3699 yn = n->n.other->origin[Geom::Y] - n->origin[Geom::Y]; // use origin because otherwise the direction will change as you drag
3700 xn = n->n.other->origin[Geom::X] - n->origin[Geom::X];
3701 }
3702 }
3703 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3704 if (yn < 0) { xn = -xn; yn = -yn; }
3706 // p handle:
3707 yp = n->p.pos[Geom::Y] - n->pos[Geom::Y];
3708 xp = n->p.pos[Geom::X] - n->pos[Geom::X];
3709 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3710 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3711 if (n->p.other) {
3712 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3713 yp = n->p.other->origin[Geom::Y] - n->origin[Geom::Y];
3714 xp = n->p.other->origin[Geom::X] - n->origin[Geom::X];
3715 }
3716 }
3717 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3718 if (yp < 0) { xp = -xp; yp = -yp; }
3720 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3721 // sliding on handles, only if at least one of the handles is non-vertical
3722 // (otherwise it's the same as ctrl+drag anyway)
3724 // calculate angles of the handles
3725 if (xn == 0) {
3726 if (yn == 0) { // no handle, consider it the continuation of the other one
3727 an = 0;
3728 collinear = TRUE;
3729 }
3730 else an = 0; // vertical; set the angle to horizontal
3731 } else an = yn/xn;
3733 if (xp == 0) {
3734 if (yp == 0) { // no handle, consider it the continuation of the other one
3735 ap = an;
3736 }
3737 else ap = 0; // vertical; set the angle to horizontal
3738 } else ap = yp/xp;
3740 if (collinear) an = ap;
3742 // angles of the perpendiculars; HUGE_VAL means vertical
3743 if (an == 0) na = HUGE_VAL; else na = -1/an;
3744 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3746 // mouse point relative to the node's original pos
3747 pr = (*p) - n->origin;
3749 // distances to the four lines (two handles and two perpendiculars)
3750 d_an = point_line_distance(&pr, an);
3751 d_na = point_line_distance(&pr, na);
3752 d_ap = point_line_distance(&pr, ap);
3753 d_pa = point_line_distance(&pr, pa);
3755 // find out which line is the closest, save its closest point in c
3756 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3757 point_line_closest(&pr, an, &c);
3758 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3759 point_line_closest(&pr, ap, &c);
3760 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3761 point_line_closest(&pr, na, &c);
3762 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3763 point_line_closest(&pr, pa, &c);
3764 }
3766 // move the node to the closest point
3767 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3768 n->origin[Geom::X] + c[Geom::X] - n->pos[Geom::X],
3769 n->origin[Geom::Y] + c[Geom::Y] - n->pos[Geom::Y],
3770 true);
3772 } else { // constraining to hor/vert
3774 if (fabs((*p)[Geom::X] - n->origin[Geom::X]) > fabs((*p)[Geom::Y] - n->origin[Geom::Y])) { // snap to hor
3775 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3776 (*p)[Geom::X] - n->pos[Geom::X],
3777 n->origin[Geom::Y] - n->pos[Geom::Y],
3778 true,
3779 true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::X]));
3780 } else { // snap to vert
3781 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3782 n->origin[Geom::X] - n->pos[Geom::X],
3783 (*p)[Geom::Y] - n->pos[Geom::Y],
3784 true,
3785 true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::Y]));
3786 }
3787 }
3788 } else { // move freely
3789 if (n->is_dragging) {
3790 if (state & GDK_MOD1_MASK) { // sculpt
3791 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3792 } else {
3793 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3794 (*p)[Geom::X] - n->pos[Geom::X],
3795 (*p)[Geom::Y] - n->pos[Geom::Y],
3796 (state & GDK_SHIFT_MASK) == 0);
3797 }
3798 }
3799 }
3801 n->subpath->nodepath->desktop->scroll_to_point(*p);
3803 return TRUE;
3804 }
3806 /**
3807 * Node handle clicked callback.
3808 */
3809 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3810 {
3811 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3813 if (state & GDK_CONTROL_MASK) { // "delete" handle
3814 if (n->p.knot == knot) {
3815 n->p.pos = n->pos;
3816 } else if (n->n.knot == knot) {
3817 n->n.pos = n->pos;
3818 }
3819 sp_node_update_handles(n);
3820 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3821 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3822 sp_nodepath_update_statusbar(nodepath);
3824 } else { // just select or add to selection, depending in Shift
3825 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3826 }
3827 }
3829 /**
3830 * Node handle grabbed callback.
3831 */
3832 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3833 {
3834 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3836 // convert auto -> smooth when dragging handle
3837 if (n->type == Inkscape::NodePath::NODE_AUTO) {
3838 n->type = Inkscape::NodePath::NODE_SMOOTH;
3839 sp_nodepath_update_node_knot (n);
3840 }
3842 if (!n->selected) {
3843 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3844 }
3846 // remember the origin point of the handle
3847 if (n->p.knot == knot) {
3848 n->p.origin_radial = n->p.pos - n->pos;
3849 } else if (n->n.knot == knot) {
3850 n->n.origin_radial = n->n.pos - n->pos;
3851 } else {
3852 g_assert_not_reached();
3853 }
3855 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3856 }
3858 /**
3859 * Node handle ungrabbed callback.
3860 */
3861 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3862 {
3863 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3865 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3866 if (n->p.knot == knot) {
3867 n->p.origin_radial.a = 0;
3868 sp_knot_set_position(knot, n->p.pos, state);
3869 } else if (n->n.knot == knot) {
3870 n->n.origin_radial.a = 0;
3871 sp_knot_set_position(knot, n->n.pos, state);
3872 } else {
3873 g_assert_not_reached();
3874 }
3876 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3877 }
3879 /**
3880 * Node handle "request" signal callback.
3881 */
3882 static gboolean node_handle_request(SPKnot *knot, Geom::Point *p, guint state, gpointer data)
3883 {
3884 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3886 Inkscape::NodePath::NodeSide *me, *opposite;
3887 gint which;
3888 if (n->p.knot == knot) {
3889 me = &n->p;
3890 opposite = &n->n;
3891 which = -1;
3892 } else if (n->n.knot == knot) {
3893 me = &n->n;
3894 opposite = &n->p;
3895 which = 1;
3896 } else {
3897 me = opposite = NULL;
3898 which = 0;
3899 g_assert_not_reached();
3900 }
3902 SPDesktop *desktop = n->subpath->nodepath->desktop;
3903 SnapManager &m = desktop->namedview->snap_manager;
3904 m.setup(desktop, true, n->subpath->nodepath->item);
3905 Inkscape::SnappedPoint s;
3907 if ((state & GDK_SHIFT_MASK) != 0) {
3908 // We will not try to snap when the shift-key is pressed
3909 // so remove the old snap indicator and don't wait for it to time-out
3910 desktop->snapindicator->remove_snappoint();
3911 }
3913 Inkscape::NodePath::Node *othernode = opposite->other;
3914 if (othernode) {
3915 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3916 /* We are smooth node adjacent with line */
3917 Geom::Point const delta = *p - n->pos;
3918 Geom::Coord const len = Geom::L2(delta);
3919 Inkscape::NodePath::Node *othernode = opposite->other;
3920 Geom::Point const ndelta = n->pos - othernode->pos;
3921 Geom::Coord const linelen = Geom::L2(ndelta);
3922 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3923 Geom::Coord const scal = dot(delta, ndelta) / linelen;
3924 (*p) = n->pos + (scal / linelen) * ndelta;
3925 }
3926 if ((state & GDK_SHIFT_MASK) == 0) {
3927 s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(*p), Inkscape::Snapper::ConstraintLine(*p, ndelta));
3928 }
3929 } else {
3930 if ((state & GDK_SHIFT_MASK) == 0) {
3931 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(*p));
3932 }
3933 }
3934 } else {
3935 if ((state & GDK_SHIFT_MASK) == 0) {
3936 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(*p));
3937 }
3938 }
3940 Geom::Point pt2g = *p;
3941 s.getPoint(pt2g);
3942 *p = pt2g;
3944 sp_node_adjust_handle(n, -which);
3946 return FALSE;
3947 }
3949 /**
3950 * Node handle moved callback.
3951 */
3952 static void node_handle_moved(SPKnot *knot, Geom::Point *p, guint state, gpointer data)
3953 {
3954 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3955 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
3957 Inkscape::NodePath::NodeSide *me;
3958 Inkscape::NodePath::NodeSide *other;
3959 if (n->p.knot == knot) {
3960 me = &n->p;
3961 other = &n->n;
3962 } else if (n->n.knot == knot) {
3963 me = &n->n;
3964 other = &n->p;
3965 } else {
3966 me = NULL;
3967 other = NULL;
3968 g_assert_not_reached();
3969 }
3971 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3972 Radial rme(me->pos - n->pos);
3973 Radial rother(other->pos - n->pos);
3974 Radial rnew(*p - n->pos);
3976 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3977 int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
3978 /* 0 interpreted as "no snapping". */
3980 // 1. Snap to the closest PI/snaps angle, starting from zero.
3981 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3983 // 2. Snap to the original angle, its opposite and perpendiculars
3984 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3985 /* The closest PI/2 angle, starting from original angle */
3986 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3988 // Snap to the closest.
3989 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3990 ? a_snapped
3991 : a_ortho );
3992 }
3994 // 3. Snap to the angle of the opposite line, if any
3995 Inkscape::NodePath::Node *othernode = other->other;
3996 if (othernode) {
3997 Geom::Point other_to_snap(0,0);
3998 if (sp_node_side_is_line(n, other)) {
3999 other_to_snap = othernode->pos - n->pos;
4000 } else {
4001 other_to_snap = other->pos - n->pos;
4002 }
4003 if (Geom::L2(other_to_snap) > 1e-3) {
4004 Radial rother_to_snap(other_to_snap);
4005 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
4006 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
4008 // Snap to the closest.
4009 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
4010 ? a_snapped
4011 : a_oppo );
4012 }
4013 }
4015 rnew.a = a_snapped;
4016 }
4018 if (state & GDK_MOD1_MASK) {
4019 // lock handle length
4020 rnew.r = me->origin_radial.r;
4021 }
4023 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
4024 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
4025 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
4026 rother.a += rnew.a - rme.a;
4027 other->pos = Geom::Point(rother) + n->pos;
4028 if (other->knot) {
4029 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
4030 sp_knot_moveto(other->knot, other->pos);
4031 }
4032 }
4034 me->pos = Geom::Point(rnew) + n->pos;
4035 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
4037 // move knot, but without emitting the signal:
4038 // we cannot emit a "moved" signal because we're now processing it
4039 sp_knot_moveto(me->knot, me->pos);
4041 update_object(n->subpath->nodepath);
4043 /* status text */
4044 SPDesktop *desktop = n->subpath->nodepath->desktop;
4045 if (!desktop) return;
4046 SPEventContext *ec = desktop->event_context;
4047 if (!ec) return;
4049 Inkscape::MessageContext *mc = get_message_context(ec);
4051 if (!mc) return;
4053 double degrees = 180 / M_PI * rnew.a;
4054 if (degrees > 180) degrees -= 360;
4055 if (degrees < -180) degrees += 360;
4056 if (prefs->getBool("/options/compassangledisplay/value"))
4057 degrees = angle_to_compass (degrees);
4059 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
4061 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
4062 _("<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);
4064 g_string_free(length, TRUE);
4065 }
4067 /**
4068 * Node handle event callback.
4069 */
4070 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
4071 {
4072 gboolean ret = FALSE;
4073 switch (event->type) {
4074 case GDK_KEY_PRESS:
4075 switch (get_group0_keyval (&event->key)) {
4076 case GDK_space:
4077 if (event->key.state & GDK_BUTTON1_MASK) {
4078 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
4079 stamp_repr(nodepath);
4080 ret = TRUE;
4081 }
4082 break;
4083 default:
4084 break;
4085 }
4086 break;
4087 case GDK_ENTER_NOTIFY:
4088 // we use an experimentally determined threshold that seems to work fine
4089 if (Geom::L2(n->pos - knot->pos) < 0.75)
4090 Inkscape::NodePath::Path::active_node = n;
4091 break;
4092 case GDK_LEAVE_NOTIFY:
4093 // we use an experimentally determined threshold that seems to work fine
4094 if (Geom::L2(n->pos - knot->pos) < 0.75)
4095 Inkscape::NodePath::Path::active_node = NULL;
4096 break;
4097 default:
4098 break;
4099 }
4101 return ret;
4102 }
4104 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
4105 Radial &rme, Radial &rother, gboolean const both)
4106 {
4107 rme.a += angle;
4108 if ( both
4109 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4110 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4111 {
4112 rother.a += angle;
4113 }
4114 }
4116 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
4117 Radial &rme, Radial &rother, gboolean const both)
4118 {
4119 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4121 gdouble r;
4122 if ( both
4123 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4124 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4125 {
4126 r = MAX(rme.r, rother.r);
4127 } else {
4128 r = rme.r;
4129 }
4131 gdouble const weird_angle = atan2(norm_angle, r);
4132 /* Bulia says norm_angle is just the visible distance that the
4133 * object's end must travel on the screen. Left as 'angle' for want of
4134 * a better name.*/
4136 rme.a += weird_angle;
4137 if ( both
4138 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4139 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4140 {
4141 rother.a += weird_angle;
4142 }
4143 }
4145 /**
4146 * Rotate one node.
4147 */
4148 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4149 {
4150 Inkscape::NodePath::NodeSide *me, *other;
4151 bool both = false;
4153 double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4154 double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4156 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4157 me = &(n->p);
4158 other = &(n->n);
4159 } else if (!n->p.other) {
4160 me = &(n->n);
4161 other = &(n->p);
4162 } else {
4163 if (which > 0) { // right handle
4164 if (xn > xp) {
4165 me = &(n->n);
4166 other = &(n->p);
4167 } else {
4168 me = &(n->p);
4169 other = &(n->n);
4170 }
4171 } else if (which < 0){ // left handle
4172 if (xn <= xp) {
4173 me = &(n->n);
4174 other = &(n->p);
4175 } else {
4176 me = &(n->p);
4177 other = &(n->n);
4178 }
4179 } else { // both handles
4180 me = &(n->n);
4181 other = &(n->p);
4182 both = true;
4183 }
4184 }
4186 Radial rme(me->pos - n->pos);
4187 Radial rother(other->pos - n->pos);
4189 if (screen) {
4190 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4191 } else {
4192 node_rotate_one_internal (*n, angle, rme, rother, both);
4193 }
4195 me->pos = n->pos + Geom::Point(rme);
4197 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4198 other->pos = n->pos + Geom::Point(rother);
4199 }
4201 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4202 // so here we just move all the knots without emitting move signals, for speed
4203 sp_node_update_handles(n, false);
4204 }
4206 /**
4207 * Rotate selected nodes.
4208 */
4209 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4210 {
4211 if (!nodepath || !nodepath->selected) return;
4213 if (g_list_length(nodepath->selected) == 1) {
4214 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4215 node_rotate_one (n, angle, which, screen);
4216 } else {
4217 // rotate as an object:
4219 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4220 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4221 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4222 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4223 box.expandTo (n->pos); // contain all selected nodes
4224 }
4226 gdouble rot;
4227 if (screen) {
4228 gdouble const zoom = nodepath->desktop->current_zoom();
4229 gdouble const zmove = angle / zoom;
4230 gdouble const r = Geom::L2(box.max() - box.midpoint());
4231 rot = atan2(zmove, r);
4232 } else {
4233 rot = angle;
4234 }
4236 Geom::Point rot_center;
4237 if (Inkscape::NodePath::Path::active_node == NULL)
4238 rot_center = box.midpoint();
4239 else
4240 rot_center = Inkscape::NodePath::Path::active_node->pos;
4242 Geom::Matrix t =
4243 Geom::Matrix (Geom::Translate(-rot_center)) *
4244 Geom::Matrix (Geom::Rotate(rot)) *
4245 Geom::Matrix (Geom::Translate(rot_center));
4247 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4248 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4249 n->pos *= t;
4250 n->n.pos *= t;
4251 n->p.pos *= t;
4252 sp_node_update_handles(n, false);
4253 }
4254 }
4256 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4257 }
4259 /**
4260 * Scale one node.
4261 */
4262 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4263 {
4264 bool both = false;
4265 Inkscape::NodePath::NodeSide *me, *other;
4267 double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4268 double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4270 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4271 me = &(n->p);
4272 other = &(n->n);
4273 n->code = NR_CURVETO;
4274 } else if (!n->p.other) {
4275 me = &(n->n);
4276 other = &(n->p);
4277 if (n->n.other)
4278 n->n.other->code = NR_CURVETO;
4279 } else {
4280 if (which > 0) { // right handle
4281 if (xn > xp) {
4282 me = &(n->n);
4283 other = &(n->p);
4284 if (n->n.other)
4285 n->n.other->code = NR_CURVETO;
4286 } else {
4287 me = &(n->p);
4288 other = &(n->n);
4289 n->code = NR_CURVETO;
4290 }
4291 } else if (which < 0){ // left handle
4292 if (xn <= xp) {
4293 me = &(n->n);
4294 other = &(n->p);
4295 if (n->n.other)
4296 n->n.other->code = NR_CURVETO;
4297 } else {
4298 me = &(n->p);
4299 other = &(n->n);
4300 n->code = NR_CURVETO;
4301 }
4302 } else { // both handles
4303 me = &(n->n);
4304 other = &(n->p);
4305 both = true;
4306 n->code = NR_CURVETO;
4307 if (n->n.other)
4308 n->n.other->code = NR_CURVETO;
4309 }
4310 }
4312 Radial rme(me->pos - n->pos);
4313 Radial rother(other->pos - n->pos);
4315 rme.r += grow;
4316 if (rme.r < 0) rme.r = 0;
4317 if (rme.a == HUGE_VAL) {
4318 if (me->other) { // if direction is unknown, initialize it towards the next node
4319 Radial rme_next(me->other->pos - n->pos);
4320 rme.a = rme_next.a;
4321 } else { // if there's no next, initialize to 0
4322 rme.a = 0;
4323 }
4324 }
4325 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4326 rother.r += grow;
4327 if (rother.r < 0) rother.r = 0;
4328 if (rother.a == HUGE_VAL) {
4329 rother.a = rme.a + M_PI;
4330 }
4331 }
4333 me->pos = n->pos + Geom::Point(rme);
4335 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4336 other->pos = n->pos + Geom::Point(rother);
4337 }
4339 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4340 // so here we just move all the knots without emitting move signals, for speed
4341 sp_node_update_handles(n, false);
4342 }
4344 /**
4345 * Scale selected nodes.
4346 */
4347 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4348 {
4349 if (!nodepath || !nodepath->selected) return;
4351 if (g_list_length(nodepath->selected) == 1) {
4352 // scale handles of the single selected node
4353 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4354 node_scale_one (n, grow, which);
4355 } else {
4356 // scale nodes as an "object":
4358 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4359 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4360 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4361 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4362 box.expandTo (n->pos); // contain all selected nodes
4363 }
4365 double scale = (box.maxExtent() + grow)/box.maxExtent();
4367 Geom::Point scale_center;
4368 if (Inkscape::NodePath::Path::active_node == NULL)
4369 scale_center = box.midpoint();
4370 else
4371 scale_center = Inkscape::NodePath::Path::active_node->pos;
4373 Geom::Matrix t =
4374 Geom::Matrix (Geom::Translate(-scale_center)) *
4375 Geom::Matrix (Geom::Scale(scale, scale)) *
4376 Geom::Matrix (Geom::Translate(scale_center));
4378 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4379 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4380 n->pos *= t;
4381 n->n.pos *= t;
4382 n->p.pos *= t;
4383 sp_node_update_handles(n, false);
4384 }
4385 }
4387 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4388 }
4390 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4391 {
4392 if (!nodepath) return;
4393 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4394 }
4396 /**
4397 * Flip selected nodes horizontally/vertically.
4398 */
4399 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis, boost::optional<Geom::Point> center)
4400 {
4401 if (!nodepath || !nodepath->selected) return;
4403 if (g_list_length(nodepath->selected) == 1 && !center) {
4404 // flip handles of the single selected node
4405 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4406 double temp = n->p.pos[axis];
4407 n->p.pos[axis] = n->n.pos[axis];
4408 n->n.pos[axis] = temp;
4409 sp_node_update_handles(n, false);
4410 } else {
4411 // scale nodes as an "object":
4413 Geom::Rect box = sp_node_selected_bbox (nodepath);
4414 if (!center) {
4415 center = box.midpoint();
4416 }
4417 Geom::Matrix t =
4418 Geom::Matrix (Geom::Translate(- *center)) *
4419 Geom::Matrix ((axis == Geom::X)? Geom::Scale(-1, 1) : Geom::Scale(1, -1)) *
4420 Geom::Matrix (Geom::Translate(*center));
4422 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4423 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4424 n->pos *= t;
4425 n->n.pos *= t;
4426 n->p.pos *= t;
4427 sp_node_update_handles(n, false);
4428 }
4429 }
4431 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4432 }
4434 Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4435 {
4436 g_assert (nodepath->selected);
4438 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4439 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4440 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4441 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4442 box.expandTo (n->pos); // contain all selected nodes
4443 }
4444 return box;
4445 }
4447 //-----------------------------------------------
4448 /**
4449 * Return new subpath under given nodepath.
4450 */
4451 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4452 {
4453 g_assert(nodepath);
4454 g_assert(nodepath->desktop);
4456 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4458 s->nodepath = nodepath;
4459 s->closed = FALSE;
4460 s->nodes = NULL;
4461 s->first = NULL;
4462 s->last = NULL;
4464 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4465 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4466 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4468 return s;
4469 }
4471 /**
4472 * Destroy nodes in subpath, then subpath itself.
4473 */
4474 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4475 {
4476 g_assert(subpath);
4477 g_assert(subpath->nodepath);
4478 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4480 while (subpath->nodes) {
4481 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4482 }
4484 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4486 g_free(subpath);
4487 }
4489 /**
4490 * Link head to tail in subpath.
4491 */
4492 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4493 {
4494 g_assert(!sp->closed);
4495 g_assert(sp->last != sp->first);
4496 g_assert(sp->first->code == NR_MOVETO);
4498 sp->closed = TRUE;
4500 //Link the head to the tail
4501 sp->first->p.other = sp->last;
4502 sp->last->n.other = sp->first;
4503 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4504 sp->first = sp->last;
4506 //Remove the extra end node
4507 sp_nodepath_node_destroy(sp->last->n.other);
4508 }
4510 /**
4511 * Open closed (loopy) subpath at node.
4512 */
4513 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4514 {
4515 g_assert(sp->closed);
4516 g_assert(n->subpath == sp);
4517 g_assert(sp->first == sp->last);
4519 /* We create new startpoint, current node will become last one */
4521 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4522 &n->pos, &n->pos, &n->n.pos);
4525 sp->closed = FALSE;
4527 //Unlink to make a head and tail
4528 sp->first = new_path;
4529 sp->last = n;
4530 n->n.other = NULL;
4531 new_path->p.other = NULL;
4532 }
4534 /**
4535 * Return new node in subpath with given properties.
4536 * \param pos Position of node.
4537 * \param ppos Handle position in previous direction
4538 * \param npos Handle position in previous direction
4539 */
4540 Inkscape::NodePath::Node *
4541 sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp, Inkscape::NodePath::Node *next, Inkscape::NodePath::NodeType type, NRPathcode code, Geom::Point *ppos, Geom::Point *pos, Geom::Point *npos)
4542 {
4543 g_assert(sp);
4544 g_assert(sp->nodepath);
4545 g_assert(sp->nodepath->desktop);
4547 if (nodechunk == NULL)
4548 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4550 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4552 n->subpath = sp;
4554 if (type != Inkscape::NodePath::NODE_NONE) {
4555 // use the type from sodipodi:nodetypes
4556 n->type = type;
4557 } else {
4558 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4559 // points are (almost) collinear
4560 if (Geom::L2(*pos - *ppos) < 1e-6 || Geom::L2(*pos - *npos) < 1e-6) {
4561 // endnode, or a node with a retracted handle
4562 n->type = Inkscape::NodePath::NODE_CUSP;
4563 } else {
4564 n->type = Inkscape::NodePath::NODE_SMOOTH;
4565 }
4566 } else {
4567 n->type = Inkscape::NodePath::NODE_CUSP;
4568 }
4569 }
4571 n->code = code;
4572 n->selected = FALSE;
4573 n->pos = *pos;
4574 n->p.pos = *ppos;
4575 n->n.pos = *npos;
4577 n->dragging_out = NULL;
4579 Inkscape::NodePath::Node *prev;
4580 if (next) {
4581 //g_assert(g_list_find(sp->nodes, next));
4582 prev = next->p.other;
4583 } else {
4584 prev = sp->last;
4585 }
4587 if (prev)
4588 prev->n.other = n;
4589 else
4590 sp->first = n;
4592 if (next)
4593 next->p.other = n;
4594 else
4595 sp->last = n;
4597 n->p.other = prev;
4598 n->n.other = next;
4600 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"));
4601 sp_knot_set_position(n->knot, *pos, 0);
4603 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4604 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4605 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4607 sp_nodepath_update_node_knot(n);
4609 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4610 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4611 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4612 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4613 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4614 sp_knot_show(n->knot);
4616 // We only create handle knots and lines on demand
4617 n->p.knot = NULL;
4618 n->p.line = NULL;
4619 n->n.knot = NULL;
4620 n->n.line = NULL;
4622 sp->nodes = g_list_prepend(sp->nodes, n);
4624 return n;
4625 }
4627 /**
4628 * Destroy node and its knots, link neighbors in subpath.
4629 */
4630 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4631 {
4632 g_assert(node);
4633 g_assert(node->subpath);
4634 g_assert(SP_IS_KNOT(node->knot));
4636 Inkscape::NodePath::SubPath *sp = node->subpath;
4638 if (node->selected) { // first, deselect
4639 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4640 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4641 }
4643 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4645 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4646 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4647 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4648 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4649 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4650 g_object_unref(G_OBJECT(node->knot));
4652 if (node->p.knot) {
4653 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4654 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4655 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4656 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4657 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4658 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4659 g_object_unref(G_OBJECT(node->p.knot));
4660 node->p.knot = NULL;
4661 }
4663 if (node->n.knot) {
4664 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4665 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4666 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4667 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4668 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4669 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4670 g_object_unref(G_OBJECT(node->n.knot));
4671 node->n.knot = NULL;
4672 }
4674 if (node->p.line)
4675 gtk_object_destroy(GTK_OBJECT(node->p.line));
4676 if (node->n.line)
4677 gtk_object_destroy(GTK_OBJECT(node->n.line));
4679 if (sp->nodes) { // there are others nodes on the subpath
4680 if (sp->closed) {
4681 if (sp->first == node) {
4682 g_assert(sp->last == node);
4683 sp->first = node->n.other;
4684 sp->last = sp->first;
4685 }
4686 node->p.other->n.other = node->n.other;
4687 node->n.other->p.other = node->p.other;
4688 } else {
4689 if (sp->first == node) {
4690 sp->first = node->n.other;
4691 sp->first->code = NR_MOVETO;
4692 }
4693 if (sp->last == node) sp->last = node->p.other;
4694 if (node->p.other) node->p.other->n.other = node->n.other;
4695 if (node->n.other) node->n.other->p.other = node->p.other;
4696 }
4697 } else { // this was the last node on subpath
4698 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4699 }
4701 g_mem_chunk_free(nodechunk, node);
4702 }
4704 /**
4705 * Returns one of the node's two sides.
4706 * \param which Indicates which side.
4707 * \return Pointer to previous node side if which==-1, next if which==1.
4708 */
4709 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4710 {
4711 g_assert(node);
4712 Inkscape::NodePath::NodeSide * result = 0;
4713 switch (which) {
4714 case -1:
4715 result = &node->p;
4716 break;
4717 case 1:
4718 result = &node->n;
4719 break;
4720 default:
4721 g_assert_not_reached();
4722 }
4724 return result;
4725 }
4727 /**
4728 * Return the other side of the node, given one of its sides.
4729 */
4730 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4731 {
4732 g_assert(node);
4733 Inkscape::NodePath::NodeSide *result = 0;
4735 if (me == &node->p) {
4736 result = &node->n;
4737 } else if (me == &node->n) {
4738 result = &node->p;
4739 } else {
4740 g_assert_not_reached();
4741 }
4743 return result;
4744 }
4746 /**
4747 * Return NRPathcode on the given side of the node.
4748 */
4749 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4750 {
4751 g_assert(node);
4753 NRPathcode result = NR_END;
4754 if (me == &node->p) {
4755 if (node->p.other) {
4756 result = (NRPathcode)node->code;
4757 } else {
4758 result = NR_MOVETO;
4759 }
4760 } else if (me == &node->n) {
4761 if (node->n.other) {
4762 result = (NRPathcode)node->n.other->code;
4763 } else {
4764 result = NR_MOVETO;
4765 }
4766 } else {
4767 g_assert_not_reached();
4768 }
4770 return result;
4771 }
4773 /**
4774 * Return node with the given index
4775 */
4776 Inkscape::NodePath::Node *
4777 sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index)
4778 {
4779 Inkscape::NodePath::Node *e = NULL;
4781 if (!nodepath) {
4782 return e;
4783 }
4785 //find segment
4786 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4788 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4789 int n = g_list_length(sp->nodes);
4790 if (sp->closed) {
4791 n++;
4792 }
4794 //if the piece belongs to this subpath grab it
4795 //otherwise move onto the next subpath
4796 if (index < n) {
4797 e = sp->first;
4798 for (int i = 0; i < index; ++i) {
4799 e = e->n.other;
4800 }
4801 break;
4802 } else {
4803 if (sp->closed) {
4804 index -= (n+1);
4805 } else {
4806 index -= n;
4807 }
4808 }
4809 }
4811 return e;
4812 }
4814 /**
4815 * Returns plain text meaning of node type.
4816 */
4817 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4818 {
4819 unsigned retracted = 0;
4820 bool endnode = false;
4822 for (int which = -1; which <= 1; which += 2) {
4823 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4824 if (side->other && Geom::L2(side->pos - node->pos) < 1e-6)
4825 retracted ++;
4826 if (!side->other)
4827 endnode = true;
4828 }
4830 if (retracted == 0) {
4831 if (endnode) {
4832 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4833 return _("end node");
4834 } else {
4835 switch (node->type) {
4836 case Inkscape::NodePath::NODE_CUSP:
4837 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4838 return _("cusp");
4839 case Inkscape::NodePath::NODE_SMOOTH:
4840 // TRANSLATORS: "smooth" is an adjective here
4841 return _("smooth");
4842 case Inkscape::NodePath::NODE_AUTO:
4843 return _("auto");
4844 case Inkscape::NodePath::NODE_SYMM:
4845 return _("symmetric");
4846 }
4847 }
4848 } else if (retracted == 1) {
4849 if (endnode) {
4850 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4851 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4852 } else {
4853 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4854 }
4855 } else {
4856 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4857 }
4859 return NULL;
4860 }
4862 /**
4863 * Handles content of statusbar as long as node tool is active.
4864 */
4865 void
4866 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4867 {
4868 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");
4869 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4871 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4872 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4873 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4874 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4876 SPDesktop *desktop = NULL;
4877 if (nodepath) {
4878 desktop = nodepath->desktop;
4879 } else {
4880 desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above
4881 }
4883 SPEventContext *ec = desktop->event_context;
4884 if (!ec) return;
4886 Inkscape::MessageContext *mc = get_message_context(ec);
4887 if (!mc) return;
4889 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4891 if (selected_nodes == 0) {
4892 Inkscape::Selection *sel = desktop->selection;
4893 if (!sel || sel->isEmpty()) {
4894 mc->setF(Inkscape::NORMAL_MESSAGE,
4895 _("Select a single object to edit its nodes or handles."));
4896 } else {
4897 if (nodepath) {
4898 mc->setF(Inkscape::NORMAL_MESSAGE,
4899 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.",
4900 "<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.",
4901 total_nodes),
4902 total_nodes);
4903 } else {
4904 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4905 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4906 } else {
4907 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4908 }
4909 }
4910 }
4911 } else if (nodepath && selected_nodes == 1) {
4912 mc->setF(Inkscape::NORMAL_MESSAGE,
4913 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4914 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4915 total_nodes),
4916 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4917 } else {
4918 if (selected_subpaths > 1) {
4919 mc->setF(Inkscape::NORMAL_MESSAGE,
4920 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4921 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4922 total_nodes),
4923 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4924 } else {
4925 mc->setF(Inkscape::NORMAL_MESSAGE,
4926 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4927 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4928 total_nodes),
4929 selected_nodes, total_nodes, when_selected);
4930 }
4931 }
4932 }
4934 /*
4935 * returns a *copy* of the curve of that object.
4936 */
4937 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4938 if (!object)
4939 return NULL;
4941 SPCurve *curve = NULL;
4942 if (SP_IS_PATH(object)) {
4943 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4944 curve = curve_new->copy();
4945 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4946 const gchar *svgd = object->repr->attribute(key);
4947 if (svgd) {
4948 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4949 SPCurve *curve_new = new SPCurve(pv);
4950 if (curve_new) {
4951 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4952 }
4953 }
4954 }
4956 return curve;
4957 }
4959 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4960 if (!np || !np->object || !curve)
4961 return;
4963 if (SP_IS_PATH(np->object)) {
4964 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4965 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4966 } else {
4967 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4968 }
4969 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4970 Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(np->object)->get_lpe();
4971 if (lpe) {
4972 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( lpe->getParameter(np->repr_key) );
4973 if (pathparam) {
4974 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
4975 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4976 }
4977 }
4978 }
4979 }
4981 /*
4982 SPCanvasItem *
4983 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
4984 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
4985 }
4986 */
4988 /*
4989 SPCanvasItem *
4990 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4991 SPCurve *flash_curve = curve->copy();
4992 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4993 flash_curve->transform(i2d);
4994 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4995 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4996 // unless we also flash the nodes...
4997 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4998 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4999 sp_canvas_item_show(canvasitem);
5000 flash_curve->unref();
5001 return canvasitem;
5002 }
5004 SPCanvasItem *
5005 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
5006 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
5007 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
5008 prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff));
5009 }
5010 */
5012 SPCanvasItem *
5013 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
5014 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
5015 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
5016 flash_curve->transform(i2d);
5017 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
5018 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
5019 // unless we also flash the nodes...
5020 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
5021 guint32 color = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
5022 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
5023 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
5024 sp_canvas_item_show(canvasitem);
5025 flash_curve->unref();
5026 return canvasitem;
5027 }
5029 // TODO: Merge this with sp_nodepath_make_helper_item()!
5030 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
5031 np->show_helperpath = show;
5033 if (show) {
5034 SPCurve *helper_curve = np->curve->copy();
5035 helper_curve->transform(np->i2d);
5036 if (!np->helper_path) {
5037 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
5039 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
5040 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);
5041 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
5042 sp_canvas_item_move_to_z(np->helper_path, 0);
5043 sp_canvas_item_show(np->helper_path);
5044 } else {
5045 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
5046 }
5047 helper_curve->unref();
5048 } else {
5049 if (np->helper_path) {
5050 GtkObject *temp = np->helper_path;
5051 np->helper_path = NULL;
5052 gtk_object_destroy(temp);
5053 }
5054 }
5055 }
5057 /* sp_nodepath_make_straight_path:
5058 * Prevents user from curving the path by dragging a segment or activating handles etc.
5059 * The resulting path is a linear interpolation between nodal points, with only straight segments.
5060 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
5061 */
5062 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
5063 np->straight_path = true;
5064 np->show_handles = false;
5065 g_message("add code to make the path straight.");
5066 // do sp_nodepath_convert_node_type on all nodes?
5067 // coding tip: search for this text : "Make selected segments lines"
5068 }
5070 /*
5071 Local Variables:
5072 mode:c++
5073 c-file-style:"stroustrup"
5074 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
5075 indent-tabs-mode:nil
5076 fill-column:99
5077 End:
5078 */
5079 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :