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 <2geom/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 const &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 const &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 np->helper_path_vec.clear();
243 }
246 /**
247 * \brief Creates new nodepath from item
248 *
249 * \todo create proper constructor for nodepath::path, this method returns null a constructor cannot so this cannot be simply converted to constructor.
250 */
251 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
252 {
253 Inkscape::XML::Node *repr = object->repr;
255 /** \todo
256 * FIXME: remove this. We don't want to edit paths inside flowtext.
257 * Instead we will build our flowtext with cloned paths, so that the
258 * real paths are outside the flowtext and thus editable as usual.
259 */
260 if (SP_IS_FLOWTEXT(object)) {
261 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
262 if SP_IS_FLOWREGION(child) {
263 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
264 if (grandchild && SP_IS_PATH(grandchild)) {
265 object = SP_ITEM(grandchild);
266 break;
267 }
268 }
269 }
270 }
272 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
274 if (curve == NULL) {
275 return NULL;
276 }
278 if (curve->get_segment_count() < 1) {
279 curve->unref();
280 return NULL; // prevent crash for one-node paths
281 }
283 //Create new nodepath
284 Inkscape::NodePath::Path *np = new Inkscape::NodePath::Path();
285 if (!np) {
286 curve->unref();
287 return NULL;
288 }
290 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
292 // Set defaults
293 np->desktop = desktop;
294 np->object = object;
295 np->subpaths = NULL;
296 np->selected = NULL;
297 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
298 np->local_change = 0;
299 np->show_handles = show_handles;
300 np->helper_path = NULL;
301 np->helperpath_rgba = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
302 np->helperpath_width = 1.0;
303 np->curve = curve->copy();
304 np->show_helperpath = prefs->getBool("/tools/nodes/show_helperpath");
305 if (SP_IS_LPE_ITEM(object)) {
306 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
307 if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
308 np->show_helperpath = true;
309 }
310 }
311 np->straight_path = false;
312 if (IS_LIVEPATHEFFECT(object) && item) {
313 np->item = item;
314 } else {
315 np->item = SP_ITEM(object);
316 }
318 np->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE);
320 // we need to update item's transform from the repr here,
321 // because they may be out of sync when we respond
322 // to a change in repr by regenerating nodepath --bb
323 sp_object_read_attr(SP_OBJECT(np->item), "transform");
325 np->i2d = sp_item_i2d_affine(np->item);
326 np->d2i = np->i2d.inverse();
328 np->repr = repr;
329 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)
330 np->repr_key = g_strdup(repr_key_in);
331 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
332 Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(object)->get_lpe();
333 if (!lpe) {
334 g_error("sp_nodepath_new: lpeobject without real lpe passed as argument!");
335 delete np;
336 }
337 Inkscape::LivePathEffect::Parameter *lpeparam = lpe->getParameter(repr_key_in);
338 if (lpeparam) {
339 lpeparam->param_setup_nodepath(np);
340 }
341 } else {
342 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
343 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
344 np->repr_key = g_strdup("inkscape:original-d");
346 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
347 if (lpe) {
348 lpe->setup_nodepath(np);
349 }
350 } else {
351 np->repr_key = g_strdup("d");
352 }
353 }
355 /* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice.
356 * So for example a closed rectangle has a nodetypestring of length 5.
357 * To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */
358 Geom::PathVector pathv_sanitized = pathv_to_linear_and_cubic_beziers(np->curve->get_pathvector());
359 np->curve->set_pathvector(pathv_sanitized);
360 guint length = np->curve->get_segment_count();
361 for (Geom::PathVector::const_iterator pit = pathv_sanitized.begin(); pit != pathv_sanitized.end(); ++pit) {
362 length += pit->empty() ? 0 : 1;
363 }
365 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
366 Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
368 // create the subpath(s) from the bpath
369 subpaths_from_pathvector(np, pathv_sanitized, typestr);
371 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
372 np->subpaths = g_list_reverse(np->subpaths);
374 delete[] typestr;
375 curve->unref();
377 // Draw helper curve
378 if (np->show_helperpath) {
379 np->helper_path = sp_nodepath_make_helper_item(np, /*desktop, */np->curve, true);
380 }
382 sp_nodepath_create_helperpaths(np);
384 return np;
385 }
387 /**
388 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
389 */
390 Inkscape::NodePath::Path::~Path() {
391 while (this->subpaths) {
392 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) this->subpaths->data);
393 }
395 //Inform the ShapeEditor that made me, if any, that I am gone.
396 if (this->shape_editor)
397 this->shape_editor->nodepath_destroyed();
399 g_assert(!this->selected);
401 if (this->helper_path) {
402 GtkObject *temp = this->helper_path;
403 this->helper_path = NULL;
404 gtk_object_destroy(temp);
405 }
406 if (this->curve) {
407 this->curve->unref();
408 this->curve = NULL;
409 }
411 if (this->repr_key) {
412 g_free(this->repr_key);
413 this->repr_key = NULL;
414 }
415 if (this->repr_nodetypes_key) {
416 g_free(this->repr_nodetypes_key);
417 this->repr_nodetypes_key = NULL;
418 }
420 sp_nodepath_destroy_helperpaths(this);
422 this->desktop = NULL;
423 }
425 /**
426 * Return the node count of a given NodeSubPath.
427 */
428 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
429 {
430 int nodeCount = 0;
432 if (subpath) {
433 nodeCount = g_list_length(subpath->nodes);
434 }
436 return nodeCount;
437 }
439 /**
440 * Return the node count of a given NodePath.
441 */
442 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
443 {
444 gint nodeCount = 0;
445 if (np) {
446 for (GList *item = np->subpaths ; item ; item=item->next) {
447 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
448 nodeCount += g_list_length(subpath->nodes);
449 }
450 }
451 return nodeCount;
452 }
454 /**
455 * Return the subpath count of a given NodePath.
456 */
457 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
458 {
459 gint nodeCount = 0;
460 if (np) {
461 nodeCount = g_list_length(np->subpaths);
462 }
463 return nodeCount;
464 }
466 /**
467 * Return the selected node count of a given NodePath.
468 */
469 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
470 {
471 gint nodeCount = 0;
472 if (np) {
473 nodeCount = g_list_length(np->selected);
474 }
475 return nodeCount;
476 }
478 /**
479 * Return the number of subpaths where nodes are selected in a given NodePath.
480 */
481 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
482 {
483 gint nodeCount = 0;
484 if (np && np->selected) {
485 if (!np->selected->next) {
486 nodeCount = 1;
487 } else {
488 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
489 Inkscape::NodePath::SubPath *subpath = static_cast<Inkscape::NodePath::SubPath *>(spl->data);
490 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
491 Inkscape::NodePath::Node *node = static_cast<Inkscape::NodePath::Node *>(nl->data);
492 if (node->selected) {
493 nodeCount++;
494 break;
495 }
496 }
497 }
498 }
499 }
500 return nodeCount;
501 }
503 /**
504 * Clean up a nodepath after editing.
505 *
506 * Currently we are deleting trivial subpaths.
507 */
508 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
509 {
510 GList *badSubPaths = NULL;
512 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
513 for (GList *l = nodepath->subpaths; l ; l=l->next) {
514 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
515 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
516 badSubPaths = g_list_append(badSubPaths, sp);
517 }
519 //Delete them. This second step is because sp_nodepath_subpath_destroy()
520 //also removes the subpath from nodepath->subpaths
521 for (GList *l = badSubPaths; l ; l=l->next) {
522 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
523 sp_nodepath_subpath_destroy(sp);
524 }
526 g_list_free(badSubPaths);
527 }
529 /**
530 * Create new nodepaths from pathvector, make it subpaths of np.
531 * \param t The node type array.
532 */
533 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
534 {
535 guint i = 0; // index into node type array
536 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
537 if (pit->empty())
538 continue; // don't add single knot paths
540 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
542 Geom::Point ppos = pit->initialPoint() * np->i2d;
543 NRPathcode pcode = NR_MOVETO;
545 /* 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)*/
546 for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
547 if( dynamic_cast<Geom::LineSegment const*>(&*cit) ||
548 dynamic_cast<Geom::HLineSegment const*>(&*cit) ||
549 dynamic_cast<Geom::VLineSegment const*>(&*cit) )
550 {
551 Geom::Point pos = cit->initialPoint() * (Geom::Matrix)np->i2d;
552 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
554 ppos = cit->finalPoint() * (Geom::Matrix)np->i2d;
555 pcode = NR_LINETO;
556 }
557 else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&*cit)) {
558 std::vector<Geom::Point> points = cubic_bezier->points();
559 Geom::Point pos = points[0] * (Geom::Matrix)np->i2d;
560 Geom::Point npos = points[1] * (Geom::Matrix)np->i2d;
561 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
563 ppos = points[2] * (Geom::Matrix)np->i2d;
564 pcode = NR_CURVETO;
565 }
566 }
568 if (pit->closed()) {
569 // Add last knot (because sp_nodepath_subpath_close kills the last knot)
570 /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
571 * If the length is zero, don't add it to the nodepath. */
572 Geom::Curve const &closing_seg = pit->back_closed();
573 // Don't use !closing_seg.isDegenerate() as it is too precise, and does not account for floating point rounding probs (LP bug #257289)
574 if ( ! are_near(closing_seg.initialPoint(), closing_seg.finalPoint()) ) {
575 Geom::Point pos = closing_seg.finalPoint() * (Geom::Matrix)np->i2d;
576 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
577 }
579 sp_nodepath_subpath_close(sp);
580 }
581 }
582 }
584 /**
585 * Convert from sodipodi:nodetypes to new style type array.
586 */
587 static
588 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
589 {
590 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
592 guint pos = 0;
594 if (types) {
595 for (guint i = 0; types[i] && ( i < length ); i++) {
596 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
597 if (types[i] != '\0') {
598 switch (types[i]) {
599 case 's':
600 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
601 break;
602 case 'a':
603 typestr[pos++] =Inkscape::NodePath::NODE_AUTO;
604 break;
605 case 'z':
606 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
607 break;
608 case 'c':
609 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
610 break;
611 default:
612 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
613 break;
614 }
615 }
616 }
617 }
619 while (pos < length) {
620 typestr[pos++] = Inkscape::NodePath::NODE_NONE;
621 }
623 return typestr;
624 }
626 /**
627 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
628 * updated but repr is not (for speed). Used during curve and node drag.
629 */
630 static void update_object(Inkscape::NodePath::Path *np)
631 {
632 g_assert(np);
634 np->curve->unref();
635 np->curve = create_curve(np);
637 sp_nodepath_set_curve(np, np->curve);
639 if (np->show_helperpath) {
640 SPCurve * helper_curve = np->curve->copy();
641 helper_curve->transform(np->i2d);
642 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
643 helper_curve->unref();
644 }
646 // updating helperpaths of LPEItems is now done in sp_lpe_item_update();
647 //sp_nodepath_update_helperpaths(np);
649 // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
650 // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
651 np->shape_editor->update_knotholder();
652 }
654 /**
655 * Update XML path node with data from path object.
656 */
657 static void update_repr_internal(Inkscape::NodePath::Path *np)
658 {
659 g_assert(np);
661 Inkscape::XML::Node *repr = np->object->repr;
663 np->curve->unref();
664 np->curve = create_curve(np);
666 gchar *typestr = create_typestr(np);
667 gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
669 // determine if path has an effect applied and write to correct "d" attribute.
670 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
671 np->local_change++;
672 repr->setAttribute(np->repr_key, svgpath);
673 }
675 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
676 np->local_change++;
677 repr->setAttribute(np->repr_nodetypes_key, typestr);
678 }
680 g_free(svgpath);
681 g_free(typestr);
683 if (np->show_helperpath) {
684 SPCurve * helper_curve = np->curve->copy();
685 helper_curve->transform(np->i2d);
686 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
687 helper_curve->unref();
688 }
690 // TODO: do we need this call here? after all, update_object() should have been called just before
691 //sp_nodepath_update_helperpaths(np);
692 }
694 /**
695 * Update XML path node with data from path object, commit changes forever.
696 */
697 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
698 {
699 //fixme: np can be NULL, so check before proceeding
700 g_return_if_fail(np != NULL);
702 update_repr_internal(np);
703 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
705 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
706 annotation);
707 }
709 /**
710 * Update XML path node with data from path object, commit changes with undo.
711 */
712 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
713 {
714 update_repr_internal(np);
715 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
716 annotation);
717 }
719 /**
720 * Make duplicate of path, replace corresponding XML node in tree, commit.
721 */
722 static void stamp_repr(Inkscape::NodePath::Path *np)
723 {
724 g_assert(np);
726 Inkscape::XML::Node *old_repr = np->object->repr;
727 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
729 // remember the position of the item
730 gint pos = old_repr->position();
731 // remember parent
732 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
734 SPCurve *curve = create_curve(np);
735 gchar *typestr = create_typestr(np);
737 gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
739 new_repr->setAttribute(np->repr_key, svgpath);
740 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
742 // add the new repr to the parent
743 parent->appendChild(new_repr);
744 // move to the saved position
745 new_repr->setPosition(pos > 0 ? pos : 0);
747 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
748 _("Stamp"));
750 Inkscape::GC::release(new_repr);
751 g_free(svgpath);
752 g_free(typestr);
753 curve->unref();
754 }
756 /**
757 * Create curve from path.
758 */
759 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
760 {
761 SPCurve *curve = new SPCurve();
763 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
764 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
765 curve->moveto(sp->first->pos * np->d2i);
766 Inkscape::NodePath::Node *n = sp->first->n.other;
767 while (n) {
768 Geom::Point const end_pt = n->pos * np->d2i;
769 if (!IS_FINITE(n->pos[0]) || !IS_FINITE(n->pos[1])){
770 g_message("niet finite");
771 }
772 switch (n->code) {
773 case NR_LINETO:
774 curve->lineto(end_pt);
775 break;
776 case NR_CURVETO:
777 curve->curveto(n->p.other->n.pos * np->d2i,
778 n->p.pos * np->d2i,
779 end_pt);
780 break;
781 default:
782 g_assert_not_reached();
783 break;
784 }
785 if (n != sp->last) {
786 n = n->n.other;
787 } else {
788 n = NULL;
789 }
790 }
791 if (sp->closed) {
792 curve->closepath();
793 }
794 }
796 return curve;
797 }
799 /**
800 * Convert path type string to sodipodi:nodetypes style.
801 */
802 static gchar *create_typestr(Inkscape::NodePath::Path *np)
803 {
804 gchar *typestr = g_new(gchar, 32);
805 gint len = 32;
806 gint pos = 0;
808 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
809 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
811 if (pos >= len) {
812 typestr = g_renew(gchar, typestr, len + 32);
813 len += 32;
814 }
816 typestr[pos++] = 'c';
818 Inkscape::NodePath::Node *n;
819 n = sp->first->n.other;
820 while (n) {
821 gchar code;
823 switch (n->type) {
824 case Inkscape::NodePath::NODE_CUSP:
825 code = 'c';
826 break;
827 case Inkscape::NodePath::NODE_SMOOTH:
828 code = 's';
829 break;
830 case Inkscape::NodePath::NODE_AUTO:
831 code = 'a';
832 break;
833 case Inkscape::NodePath::NODE_SYMM:
834 code = 'z';
835 break;
836 default:
837 g_assert_not_reached();
838 code = '\0';
839 break;
840 }
842 if (pos >= len) {
843 typestr = g_renew(gchar, typestr, len + 32);
844 len += 32;
845 }
847 typestr[pos++] = code;
849 if (n != sp->last) {
850 n = n->n.other;
851 } else {
852 n = NULL;
853 }
854 }
855 }
857 if (pos >= len) {
858 typestr = g_renew(gchar, typestr, len + 1);
859 len += 1;
860 }
862 typestr[pos++] = '\0';
864 return typestr;
865 }
867 // Returns different message contexts depending on the current context. This function should only
868 // be called when ec is either a SPNodeContext or SPLPEToolContext, thus we return NULL in all
869 // other cases.
870 static Inkscape::MessageContext *
871 get_message_context(SPEventContext *ec)
872 {
873 Inkscape::MessageContext *mc = 0;
875 if (SP_IS_NODE_CONTEXT(ec)) {
876 mc = SP_NODE_CONTEXT(ec)->_node_message_context;
877 } else if (SP_IS_LPETOOL_CONTEXT(ec)) {
878 mc = SP_LPETOOL_CONTEXT(ec)->_lpetool_message_context;
879 } else {
880 g_warning ("Nodepath should only be present in Node tool or Geometric tool.");
881 }
883 return mc;
884 }
886 /**
887 \brief Fills node and handle positions for three nodes, splitting line
888 marked by end at distance t.
889 */
890 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
891 {
892 g_assert(new_path != NULL);
893 g_assert(end != NULL);
895 g_assert(end->p.other == new_path);
896 Inkscape::NodePath::Node *start = new_path->p.other;
897 g_assert(start);
899 if (end->code == NR_LINETO) {
900 new_path->type =Inkscape::NodePath::NODE_CUSP;
901 new_path->code = NR_LINETO;
902 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
903 } else {
904 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
905 new_path->code = NR_CURVETO;
906 gdouble s = 1 - t;
907 for (int dim = 0; dim < 2; dim++) {
908 Geom::Coord const f000 = start->pos[dim];
909 Geom::Coord const f001 = start->n.pos[dim];
910 Geom::Coord const f011 = end->p.pos[dim];
911 Geom::Coord const f111 = end->pos[dim];
912 Geom::Coord const f00t = s * f000 + t * f001;
913 Geom::Coord const f01t = s * f001 + t * f011;
914 Geom::Coord const f11t = s * f011 + t * f111;
915 Geom::Coord const f0tt = s * f00t + t * f01t;
916 Geom::Coord const f1tt = s * f01t + t * f11t;
917 Geom::Coord const fttt = s * f0tt + t * f1tt;
918 start->n.pos[dim] = f00t;
919 new_path->p.pos[dim] = f0tt;
920 new_path->pos[dim] = fttt;
921 new_path->n.pos[dim] = f1tt;
922 end->p.pos[dim] = f11t;
923 }
924 }
925 }
927 /**
928 * Adds new node on direct line between two nodes, activates handles of all
929 * three nodes.
930 */
931 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
932 {
933 g_assert(end);
934 g_assert(end->subpath);
935 g_assert(g_list_find(end->subpath->nodes, end));
937 Inkscape::NodePath::Node *start = end->p.other;
938 g_assert( start->n.other == end );
939 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
940 end,
941 (NRPathcode)end->code == NR_LINETO?
942 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
943 (NRPathcode)end->code,
944 &start->pos, &start->pos, &start->n.pos);
945 sp_nodepath_line_midpoint(newnode, end, t);
947 sp_node_adjust_handles(start);
948 sp_node_update_handles(start);
949 sp_node_update_handles(newnode);
950 sp_node_adjust_handles(end);
951 sp_node_update_handles(end);
953 return newnode;
954 }
956 /**
957 \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
958 */
959 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
960 {
961 g_assert(node);
962 g_assert(node->subpath);
963 g_assert(g_list_find(node->subpath->nodes, node));
965 Inkscape::NodePath::Node* result = 0;
966 Inkscape::NodePath::SubPath *sp = node->subpath;
967 Inkscape::NodePath::Path *np = sp->nodepath;
969 if (sp->closed) {
970 sp_nodepath_subpath_open(sp, node);
971 result = sp->first;
972 } else if ( (node == sp->first) || (node == sp->last ) ){
973 // no break for end nodes
974 result = 0;
975 } else {
976 // create a new subpath
977 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
979 // duplicate the break node as start of the new subpath
980 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL,
981 static_cast<Inkscape::NodePath::NodeType>(node->type),
982 NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
984 // attach rest of curve to new node
985 g_assert(node->n.other);
986 newnode->n.other = node->n.other; node->n.other = NULL;
987 newnode->n.other->p.other = newnode;
988 newsubpath->last = sp->last;
989 sp->last = node;
990 node = newnode;
991 while (node->n.other) {
992 node = node->n.other;
993 node->subpath = newsubpath;
994 sp->nodes = g_list_remove(sp->nodes, node);
995 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
996 }
999 result = newnode;
1000 }
1001 return result;
1002 }
1004 /**
1005 * Duplicate node and connect to neighbours.
1006 */
1007 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
1008 {
1009 g_assert(node);
1010 g_assert(node->subpath);
1011 g_assert(g_list_find(node->subpath->nodes, node));
1013 Inkscape::NodePath::SubPath *sp = node->subpath;
1015 NRPathcode code = (NRPathcode) node->code;
1016 if (code == NR_MOVETO) { // if node is the endnode,
1017 node->code = NR_LINETO; // new one is inserted before it, so change that to line
1018 }
1020 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
1022 if (!node->n.other || !node->p.other) { // if node is an endnode, select it
1023 return node;
1024 } else {
1025 return newnode; // otherwise select the newly created node
1026 }
1027 }
1029 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
1030 {
1031 node->p.pos = (node->pos + (node->pos - node->n.pos));
1032 }
1034 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1035 {
1036 node->n.pos = (node->pos + (node->pos - node->p.pos));
1037 }
1039 /**
1040 * Change line type at node, with side effects on neighbours.
1041 */
1042 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1043 {
1044 g_assert(end);
1045 g_assert(end->subpath);
1046 g_assert(end->p.other);
1048 if (end->code != static_cast<guint>(code) ) {
1049 Inkscape::NodePath::Node *start = end->p.other;
1051 end->code = code;
1053 if (code == NR_LINETO) {
1054 if (start->code == NR_LINETO) {
1055 sp_nodepath_set_node_type(start, Inkscape::NodePath::NODE_CUSP);
1056 }
1057 if (end->n.other) {
1058 if (end->n.other->code == NR_LINETO) {
1059 sp_nodepath_set_node_type(end, Inkscape::NodePath::NODE_CUSP);
1060 }
1061 }
1063 if (start->type == Inkscape::NodePath::NODE_AUTO)
1064 start->type = Inkscape::NodePath::NODE_SMOOTH;
1065 if (end->type == Inkscape::NodePath::NODE_AUTO)
1066 end->type = Inkscape::NodePath::NODE_SMOOTH;
1068 start->n.pos = start->pos;
1069 end->p.pos = end->pos;
1071 sp_node_adjust_handle(start, -1);
1072 sp_node_adjust_handle(end, 1);
1074 } else {
1075 Geom::Point delta = end->pos - start->pos;
1076 start->n.pos = start->pos + delta / 3;
1077 end->p.pos = end->pos - delta / 3;
1078 sp_node_adjust_handle(start, 1);
1079 sp_node_adjust_handle(end, -1);
1080 }
1082 sp_node_update_handles(start);
1083 sp_node_update_handles(end);
1084 }
1085 }
1087 static void
1088 sp_nodepath_update_node_knot(Inkscape::NodePath::Node *node)
1089 {
1090 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1091 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1092 node->knot->setSize (node->selected? 11 : 9);
1093 sp_knot_update_ctrl(node->knot);
1094 } else if (node->type == Inkscape::NodePath::NODE_AUTO) {
1095 node->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1096 node->knot->setSize (node->selected? 11 : 9);
1097 sp_knot_update_ctrl(node->knot);
1098 } else {
1099 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1100 node->knot->setSize (node->selected? 9 : 7);
1101 sp_knot_update_ctrl(node->knot);
1102 }
1103 }
1106 /**
1107 * Change node type, and its handles accordingly.
1108 */
1109 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1110 {
1111 g_assert(node);
1112 g_assert(node->subpath);
1114 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1115 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1116 type =Inkscape::NodePath::NODE_CUSP;
1117 }
1118 }
1120 node->type = type;
1122 sp_nodepath_update_node_knot(node);
1124 // if one of handles is mouseovered, preserve its position
1125 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1126 sp_node_adjust_handle(node, 1);
1127 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1128 sp_node_adjust_handle(node, -1);
1129 } else {
1130 sp_node_adjust_handles(node);
1131 }
1133 sp_node_update_handles(node);
1135 sp_nodepath_update_statusbar(node->subpath->nodepath);
1137 return node;
1138 }
1140 bool
1141 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1142 {
1143 // TODO clean up multiple returns
1144 Inkscape::NodePath::Node *othernode = side->other;
1145 if (!othernode)
1146 return false;
1147 NRPathcode const code = sp_node_path_code_from_side(node, side);
1148 if (code == NR_LINETO)
1149 return true;
1150 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1151 if (&node->p == side) {
1152 other_to_me = &othernode->n;
1153 } else if (&node->n == side) {
1154 other_to_me = &othernode->p;
1155 }
1156 if (!other_to_me)
1157 return false;
1158 bool is_line =
1159 (Geom::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1160 Geom::L2(node->pos - side->pos) < 1e-6);
1161 return is_line;
1162 }
1164 /**
1165 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1166 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1167 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1168 * If already cusp and set to cusp, retracts handles.
1169 */
1170 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1171 {
1172 if (type == Inkscape::NodePath::NODE_AUTO) {
1173 if (node->p.other != NULL)
1174 node->code = NR_CURVETO;
1175 if (node->n.other != NULL)
1176 node->n.other->code = NR_CURVETO;
1177 }
1179 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1181 /*
1182 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1184 if (two_handles) {
1185 // do nothing, adjust_handles called via set_node_type will line them up
1186 } else if (one_handle) {
1187 if (opposite_to_handle_is_line) {
1188 if (lined_up) {
1189 // already half-smooth; pull opposite handle too making it fully smooth
1190 } else {
1191 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1192 }
1193 } else {
1194 // pull opposite handle in line with the existing one
1195 }
1196 } else if (no_handles) {
1197 if (both_segments_are_lines OR both_segments_are_curves) {
1198 //pull both handles
1199 } else {
1200 // pull the handle opposite to line segment, making node half-smooth
1201 }
1202 }
1203 */
1204 bool p_has_handle = (Geom::L2(node->pos - node->p.pos) > 1e-6);
1205 bool n_has_handle = (Geom::L2(node->pos - node->n.pos) > 1e-6);
1206 bool p_is_line = sp_node_side_is_line(node, &node->p);
1207 bool n_is_line = sp_node_side_is_line(node, &node->n);
1209 if (p_has_handle && n_has_handle) {
1210 // do nothing, adjust_handles will line them up
1211 } else if (p_has_handle || n_has_handle) {
1212 if (p_has_handle && n_is_line) {
1213 Radial line (node->n.other->pos - node->pos);
1214 Radial handle (node->pos - node->p.pos);
1215 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1216 // already half-smooth; pull opposite handle too making it fully smooth
1217 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1218 } else {
1219 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1220 }
1221 } else if (n_has_handle && p_is_line) {
1222 Radial line (node->p.other->pos - node->pos);
1223 Radial handle (node->pos - node->n.pos);
1224 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1225 // already half-smooth; pull opposite handle too making it fully smooth
1226 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1227 } else {
1228 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1229 }
1230 } else if (p_has_handle && node->n.other) {
1231 // pull n handle
1232 node->n.other->code = NR_CURVETO;
1233 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1234 Geom::L2(node->p.pos - node->pos) :
1235 Geom::L2(node->n.other->pos - node->pos) / 3;
1236 node->n.pos = node->pos - (len / Geom::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1237 } else if (n_has_handle && node->p.other) {
1238 // pull p handle
1239 node->code = NR_CURVETO;
1240 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1241 Geom::L2(node->n.pos - node->pos) :
1242 Geom::L2(node->p.other->pos - node->pos) / 3;
1243 node->p.pos = node->pos - (len / Geom::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1244 }
1245 } else if (!p_has_handle && !n_has_handle) {
1246 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1247 // no handles, but both segments are either lnes or curves:
1248 //pull both handles
1250 // convert both to curves:
1251 node->code = NR_CURVETO;
1252 node->n.other->code = NR_CURVETO;
1254 sp_node_adjust_handles_auto(node);
1255 } else {
1256 // pull the handle opposite to line segment, making it half-smooth
1257 if (p_is_line && node->n.other) {
1258 if (type != Inkscape::NodePath::NODE_SYMM) {
1259 // pull n handle
1260 node->n.other->code = NR_CURVETO;
1261 double len = Geom::L2(node->n.other->pos - node->pos) / 3;
1262 node->n.pos = node->pos + (len / Geom::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1263 }
1264 } else if (n_is_line && node->p.other) {
1265 if (type != Inkscape::NodePath::NODE_SYMM) {
1266 // pull p handle
1267 node->code = NR_CURVETO;
1268 double len = Geom::L2(node->p.other->pos - node->pos) / 3;
1269 node->p.pos = node->pos + (len / Geom::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1270 }
1271 }
1272 }
1273 }
1274 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1275 // cusping a cusp: retract nodes
1276 node->p.pos = node->pos;
1277 node->n.pos = node->pos;
1278 }
1280 sp_nodepath_set_node_type (node, type);
1281 }
1283 /**
1284 * Move node to point, and adjust its and neighbouring handles.
1285 */
1286 void sp_node_moveto(Inkscape::NodePath::Node *node, Geom::Point p)
1287 {
1288 if (node->type == Inkscape::NodePath::NODE_AUTO) {
1289 node->pos = p;
1290 sp_node_adjust_handles_auto(node);
1291 } else {
1292 Geom::Point delta = p - node->pos;
1293 node->pos = p;
1295 node->p.pos += delta;
1296 node->n.pos += delta;
1297 }
1299 Inkscape::NodePath::Node *node_p = NULL;
1300 Inkscape::NodePath::Node *node_n = NULL;
1302 if (node->p.other) {
1303 if (node->code == NR_LINETO) {
1304 sp_node_adjust_handle(node, 1);
1305 sp_node_adjust_handle(node->p.other, -1);
1306 node_p = node->p.other;
1307 }
1308 if (!node->p.other->selected && node->p.other->type == Inkscape::NodePath::NODE_AUTO) {
1309 sp_node_adjust_handles_auto(node->p.other);
1310 node_p = node->p.other;
1311 }
1312 }
1313 if (node->n.other) {
1314 if (node->n.other->code == NR_LINETO) {
1315 sp_node_adjust_handle(node, -1);
1316 sp_node_adjust_handle(node->n.other, 1);
1317 node_n = node->n.other;
1318 }
1319 if (!node->n.other->selected && node->n.other->type == Inkscape::NodePath::NODE_AUTO) {
1320 sp_node_adjust_handles_auto(node->n.other);
1321 node_n = node->n.other;
1322 }
1323 }
1325 // this function is only called from batch movers that will update display at the end
1326 // themselves, so here we just move all the knots without emitting move signals, for speed
1327 sp_node_update_handles(node, false);
1328 if (node_n) {
1329 sp_node_update_handles(node_n, false);
1330 }
1331 if (node_p) {
1332 sp_node_update_handles(node_p, false);
1333 }
1334 }
1336 /**
1337 * Call sp_node_moveto() for node selection and handle possible snapping.
1338 */
1339 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, Geom::Coord dx, Geom::Coord dy,
1340 bool const snap, bool constrained = false,
1341 Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point())
1342 {
1343 Geom::Point delta(dx, dy);
1344 Geom::Point best_pt = delta;
1345 Inkscape::SnappedPoint best;
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 // When only the node closest to the mouse pointer is to be snapped
1367 // then we will not even try to snap to other points and discard those immediately
1368 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1369 bool closest_only = prefs->getBool("/options/snapclosestonly/value", false);
1371 Inkscape::NodePath::Node *closest_node = NULL;
1372 Geom::Coord closest_dist = NR_HUGE;
1374 if (closest_only) {
1375 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1376 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1377 Geom::Coord dist = Geom::L2(nodepath->drag_origin_mouse - n->origin);
1378 if (dist < closest_dist) {
1379 closest_node = n;
1380 closest_dist = dist;
1381 }
1382 }
1383 }
1385 // Iterate through all selected nodes
1386 m.setup(nodepath->desktop, false, SP_PATH(nodepath->item), &unselected_nodes);
1387 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1388 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1389 if (!closest_only || n == closest_node) { //try to snap either all selected nodes or only the closest one
1390 Inkscape::SnappedPoint s;
1391 if (constrained) {
1392 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1393 dedicated_constraint.setPoint(n->pos);
1394 s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), dedicated_constraint);
1395 } else {
1396 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta));
1397 }
1399 if (s.getSnapped()) {
1400 s.setPointerDistance(Geom::L2(nodepath->drag_origin_mouse - n->origin));
1401 if (!s.isOtherSnapBetter(best, true)) {
1402 best = s;
1403 best_pt = from_2geom(s.getPoint()) - n->pos;
1404 }
1405 }
1406 }
1407 }
1409 if (best.getSnapped()) {
1410 nodepath->desktop->snapindicator->set_new_snaptarget(best);
1411 } else {
1412 nodepath->desktop->snapindicator->remove_snaptarget();
1413 }
1414 }
1416 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1417 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1418 sp_node_moveto(n, n->pos + best_pt);
1419 }
1421 // do not update repr here so that node dragging is acceptably fast
1422 update_object(nodepath);
1423 }
1425 /**
1426 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1427 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1428 near x = 0.
1429 */
1430 double
1431 sculpt_profile (double x, double alpha, guint profile)
1432 {
1433 double result = 1;
1435 if (x >= 1) {
1436 result = 0;
1437 } else if (x <= 0) {
1438 result = 1;
1439 } else {
1440 switch (profile) {
1441 case SCULPT_PROFILE_LINEAR:
1442 result = 1 - x;
1443 break;
1444 case SCULPT_PROFILE_BELL:
1445 result = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1446 break;
1447 case SCULPT_PROFILE_ELLIPTIC:
1448 result = sqrt(1 - x*x);
1449 break;
1450 default:
1451 g_assert_not_reached();
1452 }
1453 }
1455 return result;
1456 }
1458 double
1459 bezier_length (Geom::Point a, Geom::Point ah, Geom::Point bh, Geom::Point b)
1460 {
1461 // extremely primitive for now, don't have time to look for the real one
1462 double lower = Geom::L2(b - a);
1463 double upper = Geom::L2(ah - a) + Geom::L2(bh - ah) + Geom::L2(bh - b);
1464 return (lower + upper)/2;
1465 }
1467 void
1468 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, Geom::Point delta, Geom::Point delta_n, Geom::Point delta_p)
1469 {
1470 n->pos = n->origin + delta;
1471 n->n.pos = n->n.origin + delta_n;
1472 n->p.pos = n->p.origin + delta_p;
1473 sp_node_adjust_handles(n);
1474 sp_node_update_handles(n, false);
1475 }
1477 /**
1478 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1479 * on how far they are from the dragged node n.
1480 */
1481 static void
1482 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, Geom::Point delta)
1483 {
1484 g_assert (n);
1485 g_assert (nodepath);
1486 g_assert (n->subpath->nodepath == nodepath);
1488 double pressure = n->knot->pressure;
1489 if (pressure == 0)
1490 pressure = 0.5; // default
1491 pressure = CLAMP (pressure, 0.2, 0.8);
1493 // map pressure to alpha = 1/5 ... 5
1494 double alpha = 1 - 2 * fabs(pressure - 0.5);
1495 if (pressure > 0.5)
1496 alpha = 1/alpha;
1498 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1499 guint profile = prefs->getInt("/tools/nodes/sculpting_profile", SCULPT_PROFILE_BELL);
1501 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1502 // Only one subpath has selected nodes:
1503 // use linear mode, where the distance from n to node being dragged is calculated along the path
1505 double n_sel_range = 0, p_sel_range = 0;
1506 guint n_nodes = 0, p_nodes = 0;
1507 guint n_sel_nodes = 0, p_sel_nodes = 0;
1509 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1510 {
1511 double n_range = 0, p_range = 0;
1512 bool n_going = true, p_going = true;
1513 Inkscape::NodePath::Node *n_node = n;
1514 Inkscape::NodePath::Node *p_node = n;
1515 do {
1516 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1517 if (n_node && n_going)
1518 n_node = n_node->n.other;
1519 if (n_node == NULL) {
1520 n_going = false;
1521 } else {
1522 n_nodes ++;
1523 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1524 if (n_node->selected) {
1525 n_sel_nodes ++;
1526 n_sel_range = n_range;
1527 }
1528 if (n_node == p_node) {
1529 n_going = false;
1530 p_going = false;
1531 }
1532 }
1533 if (p_node && p_going)
1534 p_node = p_node->p.other;
1535 if (p_node == NULL) {
1536 p_going = false;
1537 } else {
1538 p_nodes ++;
1539 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1540 if (p_node->selected) {
1541 p_sel_nodes ++;
1542 p_sel_range = p_range;
1543 }
1544 if (p_node == n_node) {
1545 n_going = false;
1546 p_going = false;
1547 }
1548 }
1549 } while (n_going || p_going);
1550 }
1552 // Second pass: actually move nodes in this subpath
1553 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1554 {
1555 double n_range = 0, p_range = 0;
1556 bool n_going = true, p_going = true;
1557 Inkscape::NodePath::Node *n_node = n;
1558 Inkscape::NodePath::Node *p_node = n;
1559 do {
1560 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1561 if (n_node && n_going)
1562 n_node = n_node->n.other;
1563 if (n_node == NULL) {
1564 n_going = false;
1565 } else {
1566 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1567 if (n_node->selected) {
1568 sp_nodepath_move_node_and_handles (n_node,
1569 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1570 sculpt_profile ((n_range + Geom::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1571 sculpt_profile ((n_range - Geom::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1572 }
1573 if (n_node == p_node) {
1574 n_going = false;
1575 p_going = false;
1576 }
1577 }
1578 if (p_node && p_going)
1579 p_node = p_node->p.other;
1580 if (p_node == NULL) {
1581 p_going = false;
1582 } else {
1583 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1584 if (p_node->selected) {
1585 sp_nodepath_move_node_and_handles (p_node,
1586 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1587 sculpt_profile ((p_range - Geom::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1588 sculpt_profile ((p_range + Geom::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1589 }
1590 if (p_node == n_node) {
1591 n_going = false;
1592 p_going = false;
1593 }
1594 }
1595 } while (n_going || p_going);
1596 }
1598 } else {
1599 // Multiple subpaths have selected nodes:
1600 // use spatial mode, where the distance from n to node being dragged is measured directly as Geom::L2.
1601 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1602 // fix the pear-like shape when sculpting e.g. a ring
1604 // First pass: calculate range
1605 gdouble direct_range = 0;
1606 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1607 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1608 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1609 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1610 if (node->selected) {
1611 direct_range = MAX(direct_range, Geom::L2(node->origin - n->origin));
1612 }
1613 }
1614 }
1616 // Second pass: actually move nodes
1617 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1618 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1619 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1620 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1621 if (node->selected) {
1622 if (direct_range > 1e-6) {
1623 sp_nodepath_move_node_and_handles (node,
1624 sculpt_profile (Geom::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1625 sculpt_profile (Geom::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1626 sculpt_profile (Geom::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1627 } else {
1628 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1629 }
1631 }
1632 }
1633 }
1634 }
1636 // do not update repr here so that node dragging is acceptably fast
1637 update_object(nodepath);
1638 }
1641 /**
1642 * Move node selection to point, adjust its and neighbouring handles,
1643 * handle possible snapping, and commit the change with possible undo.
1644 */
1645 void
1646 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1647 {
1648 if (!nodepath) return;
1650 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1652 if (dx == 0) {
1653 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1654 } else if (dy == 0) {
1655 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1656 } else {
1657 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1658 }
1659 }
1661 /**
1662 * Move node selection off screen and commit the change.
1663 */
1664 void
1665 sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1666 {
1667 // borrowed from sp_selection_move_screen in selection-chemistry.c
1668 // we find out the current zoom factor and divide deltas by it
1670 gdouble zoom = desktop->current_zoom();
1671 gdouble zdx = dx / zoom;
1672 gdouble zdy = dy / zoom;
1674 if (!nodepath) return;
1676 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1678 if (dx == 0) {
1679 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1680 } else if (dy == 0) {
1681 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1682 } else {
1683 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1684 }
1685 }
1687 /**
1688 * Move selected nodes to the absolute position given
1689 */
1690 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
1691 {
1692 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1693 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1694 Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
1695 sp_node_moveto(n, npos);
1696 }
1698 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1699 }
1701 /**
1702 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return Geom::Nothing
1703 */
1704 boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1705 {
1706 boost::optional<Geom::Coord> no_coord;
1707 g_return_val_if_fail(nodepath->selected, no_coord);
1709 // determine coordinate of first selected node
1710 GList *nsel = nodepath->selected;
1711 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1712 Geom::Coord coord = n->pos[axis];
1713 bool coincide = true;
1715 // compare it to the coordinates of all the other selected nodes
1716 for (GList *l = nsel->next; l != NULL; l = l->next) {
1717 n = (Inkscape::NodePath::Node *) l->data;
1718 if (n->pos[axis] != coord) {
1719 coincide = false;
1720 }
1721 }
1722 if (coincide) {
1723 return coord;
1724 } else {
1725 Geom::Rect bbox = sp_node_selected_bbox(nodepath);
1726 // currently we return the coordinate of the bounding box midpoint because I don't know how
1727 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1728 return bbox.midpoint()[axis];
1729 }
1730 }
1732 /** If they don't yet exist, creates knot and line for the given side of the node */
1733 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1734 {
1735 if (!side->knot) {
1736 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"));
1738 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1739 side->knot->setSize (7);
1740 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1741 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1742 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1743 sp_knot_update_ctrl(side->knot);
1745 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1746 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1747 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1748 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1749 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1750 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1751 }
1753 if (!side->line) {
1754 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1755 SP_TYPE_CTRLLINE, NULL);
1756 }
1757 }
1759 /**
1760 * Ensure the given handle of the node is visible/invisible, update its screen position
1761 */
1762 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1763 {
1764 g_assert(node != NULL);
1766 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1767 NRPathcode code = sp_node_path_code_from_side(node, side);
1769 show_handle = show_handle && (code == NR_CURVETO) && (Geom::L2(side->pos - node->pos) > 1e-6);
1771 if (show_handle) {
1772 if (!side->knot) { // No handle knot at all
1773 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1774 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1775 side->knot->pos = side->pos;
1776 if (side->knot->item)
1777 SP_CTRL(side->knot->item)->moveto(side->pos);
1778 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1779 sp_knot_show(side->knot);
1780 } else {
1781 if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1782 if (fire_move_signals) {
1783 sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1784 } else {
1785 sp_knot_moveto(side->knot, side->pos);
1786 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1787 }
1788 }
1789 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1790 sp_knot_show(side->knot);
1791 }
1792 }
1793 sp_canvas_item_show(side->line);
1794 } else {
1795 if (side->knot) {
1796 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1797 sp_knot_hide(side->knot);
1798 }
1799 }
1800 if (side->line) {
1801 sp_canvas_item_hide(side->line);
1802 }
1803 }
1804 }
1806 /**
1807 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1808 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1809 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1810 * updated; otherwise, just move the knots silently (used in batch moves).
1811 */
1812 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1813 {
1814 g_assert(node != NULL);
1816 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1817 sp_knot_show(node->knot);
1818 }
1820 if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1821 if (fire_move_signals)
1822 sp_knot_set_position(node->knot, node->pos, 0);
1823 else
1824 sp_knot_moveto(node->knot, node->pos);
1825 }
1827 gboolean show_handles = node->selected;
1828 if (node->p.other != NULL) {
1829 if (node->p.other->selected) show_handles = TRUE;
1830 }
1831 if (node->n.other != NULL) {
1832 if (node->n.other->selected) show_handles = TRUE;
1833 }
1835 if (node->subpath->nodepath->show_handles == false)
1836 show_handles = FALSE;
1838 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1839 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1840 }
1842 /**
1843 * Call sp_node_update_handles() for all nodes on subpath.
1844 */
1845 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1846 {
1847 g_assert(subpath != NULL);
1849 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1850 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1851 }
1852 }
1854 /**
1855 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1856 */
1857 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1858 {
1859 g_assert(nodepath != NULL);
1861 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1862 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1863 }
1864 }
1866 void
1867 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1868 {
1869 if (nodepath) {
1870 nodepath->show_handles = show;
1871 sp_nodepath_update_handles(nodepath);
1872 }
1873 }
1875 /**
1876 * Adds all selected nodes in nodepath to list.
1877 */
1878 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1879 {
1880 StlConv<Node *>::list(l, selected);
1881 /// \todo this adds a copying, rework when the selection becomes a stl list
1882 }
1884 /**
1885 * Align selected nodes on the specified axis.
1886 */
1887 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1888 {
1889 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1890 return;
1891 }
1893 if ( !nodepath->selected->next ) { // only one node selected
1894 return;
1895 }
1896 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1897 Geom::Point dest(pNode->pos);
1898 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1899 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1900 if (pNode) {
1901 dest[axis] = pNode->pos[axis];
1902 sp_node_moveto(pNode, dest);
1903 }
1904 }
1906 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1907 }
1909 /// Helper struct.
1910 struct NodeSort
1911 {
1912 Inkscape::NodePath::Node *_node;
1913 Geom::Coord _coord;
1914 /// \todo use vectorof pointers instead of calling copy ctor
1915 NodeSort(Inkscape::NodePath::Node *node, Geom::Dim2 axis) :
1916 _node(node), _coord(node->pos[axis])
1917 {}
1919 };
1921 static bool operator<(NodeSort const &a, NodeSort const &b)
1922 {
1923 return (a._coord < b._coord);
1924 }
1926 /**
1927 * Distribute selected nodes on the specified axis.
1928 */
1929 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1930 {
1931 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1932 return;
1933 }
1935 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1936 return;
1937 }
1939 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1940 std::vector<NodeSort> sorted;
1941 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1942 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1943 if (pNode) {
1944 NodeSort n(pNode, axis);
1945 sorted.push_back(n);
1946 //dest[axis] = pNode->pos[axis];
1947 //sp_node_moveto(pNode, dest);
1948 }
1949 }
1950 std::sort(sorted.begin(), sorted.end());
1951 unsigned int len = sorted.size();
1952 //overall bboxes span
1953 float dist = (sorted.back()._coord -
1954 sorted.front()._coord);
1955 //new distance between each bbox
1956 float step = (dist) / (len - 1);
1957 float pos = sorted.front()._coord;
1958 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1959 it < sorted.end();
1960 it ++ )
1961 {
1962 Geom::Point dest((*it)._node->pos);
1963 dest[axis] = pos;
1964 sp_node_moveto((*it)._node, dest);
1965 pos += step;
1966 }
1968 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1969 }
1972 /**
1973 * Call sp_nodepath_line_add_node() for all selected segments.
1974 */
1975 void
1976 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1977 {
1978 if (!nodepath) {
1979 return;
1980 }
1982 GList *nl = NULL;
1984 int n_added = 0;
1986 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1987 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1988 g_assert(t->selected);
1989 if (t->p.other && t->p.other->selected) {
1990 nl = g_list_prepend(nl, t);
1991 }
1992 }
1994 while (nl) {
1995 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1996 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1997 sp_nodepath_node_select(n, TRUE, FALSE);
1998 n_added ++;
1999 nl = g_list_remove(nl, t);
2000 }
2002 /** \todo fixme: adjust ? */
2003 sp_nodepath_update_handles(nodepath);
2005 if (n_added > 1) {
2006 sp_nodepath_update_repr(nodepath, _("Add nodes"));
2007 } else if (n_added > 0) {
2008 sp_nodepath_update_repr(nodepath, _("Add node"));
2009 }
2011 sp_nodepath_update_statusbar(nodepath);
2012 }
2014 /**
2015 * Select segment nearest to point
2016 */
2017 void
2018 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p, bool toggle)
2019 {
2020 if (!nodepath) {
2021 return;
2022 }
2024 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2025 Geom::PathVector const &pathv = curve->get_pathvector();
2026 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2027 if (!pvpos) {
2028 g_print ("Possible error?\n");
2029 return;
2030 }
2032 // calculate index for nodepath's representation.
2033 unsigned int segment_index = floor(pvpos->t) + 1;
2034 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2035 segment_index += pathv[i].size() + 1;
2036 if (pathv[i].closed()) {
2037 segment_index += 1;
2038 }
2039 }
2041 curve->unref();
2043 //find segment to segment
2044 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2046 //fixme: this can return NULL, so check before proceeding.
2047 g_return_if_fail(e != NULL);
2049 gboolean force = FALSE;
2050 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
2051 force = TRUE;
2052 }
2053 sp_nodepath_node_select(e, (gboolean) toggle, force);
2054 if (e->p.other)
2055 sp_nodepath_node_select(e->p.other, TRUE, force);
2057 sp_nodepath_update_handles(nodepath);
2059 sp_nodepath_update_statusbar(nodepath);
2060 }
2062 /**
2063 * Add a node nearest to point
2064 */
2065 void
2066 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p)
2067 {
2068 if (!nodepath) {
2069 return;
2070 }
2072 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2073 Geom::PathVector const &pathv = curve->get_pathvector();
2074 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2075 if (!pvpos) {
2076 g_print ("Possible error?\n");
2077 return;
2078 }
2080 // calculate index for nodepath's representation.
2081 double int_part;
2082 double t = std::modf(pvpos->t, &int_part);
2083 unsigned int segment_index = (unsigned int)int_part + 1;
2084 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2085 segment_index += pathv[i].size() + 1;
2086 if (pathv[i].closed()) {
2087 segment_index += 1;
2088 }
2089 }
2091 curve->unref();
2093 //find segment to split
2094 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2095 if (!e) {
2096 return;
2097 }
2099 //don't know why but t seems to flip for lines
2100 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2101 t = 1.0 - t;
2102 }
2104 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2105 sp_nodepath_node_select(n, FALSE, TRUE);
2107 /* fixme: adjust ? */
2108 sp_nodepath_update_handles(nodepath);
2110 sp_nodepath_update_repr(nodepath, _("Add node"));
2112 sp_nodepath_update_statusbar(nodepath);
2113 }
2115 /*
2116 * Adjusts a segment so that t moves by a certain delta for dragging
2117 * converts lines to curves
2118 *
2119 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2120 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2121 */
2122 void
2123 sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, Geom::Point delta)
2124 {
2125 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node);
2127 //fixme: e and e->p can be NULL, so check for those before proceeding
2128 g_return_if_fail(e != NULL);
2129 g_return_if_fail(&e->p != NULL);
2131 if (e->type == Inkscape::NodePath::NODE_AUTO) {
2132 e->type = Inkscape::NodePath::NODE_SMOOTH;
2133 sp_nodepath_update_node_knot (e);
2134 }
2135 if (e->p.other->type == Inkscape::NodePath::NODE_AUTO) {
2136 e->p.other->type = Inkscape::NodePath::NODE_SMOOTH;
2137 sp_nodepath_update_node_knot (e->p.other);
2138 }
2140 /* feel good is an arbitrary parameter that distributes the delta between handles
2141 * if t of the drag point is less than 1/6 distance form the endpoint only
2142 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2143 */
2144 double feel_good;
2145 if (t <= 1.0 / 6.0)
2146 feel_good = 0;
2147 else if (t <= 0.5)
2148 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2149 else if (t <= 5.0 / 6.0)
2150 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2151 else
2152 feel_good = 1;
2154 //if we're dragging a line convert it to a curve
2155 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2156 sp_nodepath_set_line_type(e, NR_CURVETO);
2157 }
2159 Geom::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2160 Geom::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2161 e->p.other->n.pos += offsetcoord0;
2162 e->p.pos += offsetcoord1;
2164 // adjust handles of adjacent nodes where necessary
2165 sp_node_adjust_handle(e,1);
2166 sp_node_adjust_handle(e->p.other,-1);
2168 sp_nodepath_update_handles(e->subpath->nodepath);
2170 update_object(e->subpath->nodepath);
2172 sp_nodepath_update_statusbar(e->subpath->nodepath);
2173 }
2176 /**
2177 * Call sp_nodepath_break() for all selected segments.
2178 */
2179 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2180 {
2181 if (!nodepath) return;
2183 GList *tempin = g_list_copy(nodepath->selected);
2184 GList *temp = NULL;
2185 for (GList *l = tempin; l != NULL; l = l->next) {
2186 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2187 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2188 if (nn == NULL) continue; // no break, no new node
2189 temp = g_list_prepend(temp, nn);
2190 }
2191 g_list_free(tempin);
2193 if (temp) {
2194 sp_nodepath_deselect(nodepath);
2195 }
2196 for (GList *l = temp; l != NULL; l = l->next) {
2197 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2198 }
2200 sp_nodepath_update_handles(nodepath);
2202 sp_nodepath_update_repr(nodepath, _("Break path"));
2203 }
2205 /**
2206 * Duplicate the selected node(s).
2207 */
2208 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2209 {
2210 if (!nodepath) {
2211 return;
2212 }
2214 GList *temp = NULL;
2215 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2216 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2217 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2218 if (nn == NULL) continue; // could not duplicate
2219 temp = g_list_prepend(temp, nn);
2220 }
2222 if (temp) {
2223 sp_nodepath_deselect(nodepath);
2224 }
2225 for (GList *l = temp; l != NULL; l = l->next) {
2226 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2227 }
2229 sp_nodepath_update_handles(nodepath);
2231 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2232 }
2234 /**
2235 * Internal function to join two nodes by merging them into one.
2236 */
2237 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2238 {
2239 /* a and b are endpoints */
2241 // if one of the two nodes is mouseovered, fix its position
2242 Geom::Point c;
2243 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2244 c = a->pos;
2245 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2246 c = b->pos;
2247 } else {
2248 // otherwise, move joined node to the midpoint
2249 c = (a->pos + b->pos) / 2;
2250 }
2252 if (a->subpath == b->subpath) {
2253 Inkscape::NodePath::SubPath *sp = a->subpath;
2254 sp_nodepath_subpath_close(sp);
2255 sp_node_moveto (sp->first, c);
2257 sp_nodepath_update_handles(sp->nodepath);
2258 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2259 return;
2260 }
2262 /* a and b are separate subpaths */
2263 Inkscape::NodePath::SubPath *sa = a->subpath;
2264 Inkscape::NodePath::SubPath *sb = b->subpath;
2265 Geom::Point p;
2266 Inkscape::NodePath::Node *n;
2267 NRPathcode code;
2268 if (a == sa->first) {
2269 // we will now reverse sa, so that a is its last node, not first, and drop that node
2270 p = sa->first->n.pos;
2271 code = (NRPathcode)sa->first->n.other->code;
2272 // create new subpath
2273 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2274 // create a first moveto node on it
2275 n = sa->last;
2276 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2277 n = n->p.other;
2278 if (n == sa->first) n = NULL;
2279 while (n) {
2280 // copy the rest of the nodes from sa to t, going backwards
2281 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2282 n = n->p.other;
2283 if (n == sa->first) n = NULL;
2284 }
2285 // replace sa with t
2286 sp_nodepath_subpath_destroy(sa);
2287 sa = t;
2288 } else if (a == sa->last) {
2289 // a is already last, just drop it
2290 p = sa->last->p.pos;
2291 code = (NRPathcode)sa->last->code;
2292 sp_nodepath_node_destroy(sa->last);
2293 } else {
2294 code = NR_END;
2295 g_assert_not_reached();
2296 }
2298 if (b == sb->first) {
2299 // copy all nodes from b to a, forward
2300 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2301 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2302 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2303 }
2304 } else if (b == sb->last) {
2305 // copy all nodes from b to a, backward
2306 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2307 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2308 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2309 }
2310 } else {
2311 g_assert_not_reached();
2312 }
2313 /* and now destroy sb */
2315 sp_nodepath_subpath_destroy(sb);
2317 sp_nodepath_update_handles(sa->nodepath);
2319 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2321 sp_nodepath_update_statusbar(nodepath);
2322 }
2324 /**
2325 * Internal function to join two nodes by adding a segment between them.
2326 */
2327 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2328 {
2329 if (a->subpath == b->subpath) {
2330 Inkscape::NodePath::SubPath *sp = a->subpath;
2332 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2333 sp->closed = TRUE;
2335 sp->first->p.other = sp->last;
2336 sp->last->n.other = sp->first;
2338 sp_node_handle_mirror_p_to_n(sp->last);
2339 sp_node_handle_mirror_n_to_p(sp->first);
2341 sp->first->code = sp->last->code;
2342 sp->first = sp->last;
2344 sp_nodepath_update_handles(sp->nodepath);
2346 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2348 return;
2349 }
2351 /* a and b are separate subpaths */
2352 Inkscape::NodePath::SubPath *sa = a->subpath;
2353 Inkscape::NodePath::SubPath *sb = b->subpath;
2355 Inkscape::NodePath::Node *n;
2356 Geom::Point p;
2357 NRPathcode code;
2358 if (a == sa->first) {
2359 code = (NRPathcode) sa->first->n.other->code;
2360 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2361 n = sa->last;
2362 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2363 for (n = n->p.other; n != NULL; n = n->p.other) {
2364 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2365 }
2366 sp_nodepath_subpath_destroy(sa);
2367 sa = t;
2368 } else if (a == sa->last) {
2369 code = (NRPathcode)sa->last->code;
2370 } else {
2371 code = NR_END;
2372 g_assert_not_reached();
2373 }
2375 if (b == sb->first) {
2376 n = sb->first;
2377 sp_node_handle_mirror_p_to_n(sa->last);
2378 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2379 sp_node_handle_mirror_n_to_p(sa->last);
2380 for (n = n->n.other; n != NULL; n = n->n.other) {
2381 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2382 }
2383 } else if (b == sb->last) {
2384 n = sb->last;
2385 sp_node_handle_mirror_p_to_n(sa->last);
2386 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2387 sp_node_handle_mirror_n_to_p(sa->last);
2388 for (n = n->p.other; n != NULL; n = n->p.other) {
2389 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2390 }
2391 } else {
2392 g_assert_not_reached();
2393 }
2394 /* and now destroy sb */
2396 sp_nodepath_subpath_destroy(sb);
2398 sp_nodepath_update_handles(sa->nodepath);
2400 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2401 }
2403 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2405 /**
2406 * Internal function to handle joining two nodes.
2407 */
2408 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2409 {
2410 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2412 if (g_list_length(nodepath->selected) != 2) {
2413 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2414 return;
2415 }
2417 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2418 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2420 g_assert(a != b);
2421 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2422 // someone tried to join an orphan node (i.e. a single-node subpath).
2423 // this is not worth an error message, just fail silently.
2424 return;
2425 }
2427 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2428 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2429 return;
2430 }
2432 switch(mode) {
2433 case NODE_JOIN_ENDPOINTS:
2434 do_node_selected_join(nodepath, a, b);
2435 break;
2436 case NODE_JOIN_SEGMENT:
2437 do_node_selected_join_segment(nodepath, a, b);
2438 break;
2439 }
2440 }
2442 /**
2443 * Join two nodes by merging them into one.
2444 */
2445 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2446 {
2447 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2448 }
2450 /**
2451 * Join two nodes by adding a segment between them.
2452 */
2453 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2454 {
2455 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2456 }
2458 /**
2459 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2460 */
2461 void sp_node_delete_preserve(GList *nodes_to_delete)
2462 {
2463 GSList *nodepaths = NULL;
2465 while (nodes_to_delete) {
2466 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2467 Inkscape::NodePath::SubPath *sp = node->subpath;
2468 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2469 Inkscape::NodePath::Node *sample_cursor = NULL;
2470 Inkscape::NodePath::Node *sample_end = NULL;
2471 Inkscape::NodePath::Node *delete_cursor = node;
2472 bool just_delete = false;
2474 //find the start of this contiguous selection
2475 //move left to the first node that is not selected
2476 //or the start of the non-closed path
2477 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2478 delete_cursor = curr;
2479 }
2481 //just delete at the beginning of an open path
2482 if (!delete_cursor->p.other) {
2483 sample_cursor = delete_cursor;
2484 just_delete = true;
2485 } else {
2486 sample_cursor = delete_cursor->p.other;
2487 }
2489 //calculate points for each segment
2490 int rate = 5;
2491 float period = 1.0 / rate;
2492 std::vector<Geom::Point> data;
2493 if (!just_delete) {
2494 data.push_back(sample_cursor->pos);
2495 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2496 //just delete at the end of an open path
2497 if (!sp->closed && curr == sp->last) {
2498 just_delete = true;
2499 break;
2500 }
2502 //sample points on the contiguous selected segment
2503 Geom::Point *bez;
2504 bez = new Geom::Point [4];
2505 bez[0] = curr->pos;
2506 bez[1] = curr->n.pos;
2507 bez[2] = curr->n.other->p.pos;
2508 bez[3] = curr->n.other->pos;
2509 for (int i=1; i<rate; i++) {
2510 gdouble t = i * period;
2511 Geom::Point p = bezier_pt(3, bez, t);
2512 data.push_back(p);
2513 }
2514 data.push_back(curr->n.other->pos);
2516 sample_end = curr->n.other;
2517 //break if we've come full circle or hit the end of the selection
2518 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2519 break;
2520 }
2521 }
2522 }
2524 if (!just_delete) {
2525 //calculate the best fitting single segment and adjust the endpoints
2526 Geom::Point *adata;
2527 adata = new Geom::Point [data.size()];
2528 copy(data.begin(), data.end(), adata);
2530 Geom::Point *bez;
2531 bez = new Geom::Point [4];
2532 //would decreasing error create a better fitting approximation?
2533 gdouble error = 1.0;
2534 gint ret;
2535 ret = Geom::bezier_fit_cubic (bez, adata, data.size(), error);
2537 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2538 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2539 //the resulting nodes behave as expected.
2540 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2541 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2542 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2543 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2545 //adjust endpoints
2546 sample_cursor->n.pos = bez[1];
2547 sample_end->p.pos = bez[2];
2548 }
2550 //destroy this contiguous selection
2551 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2552 Inkscape::NodePath::Node *temp = delete_cursor;
2553 if (delete_cursor->n.other == delete_cursor) {
2554 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2555 delete_cursor = NULL;
2556 } else {
2557 delete_cursor = delete_cursor->n.other;
2558 }
2559 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2560 sp_nodepath_node_destroy(temp);
2561 }
2563 sp_nodepath_update_handles(nodepath);
2565 if (!g_slist_find(nodepaths, nodepath))
2566 nodepaths = g_slist_prepend (nodepaths, nodepath);
2567 }
2569 for (GSList *i = nodepaths; i; i = i->next) {
2570 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2571 // different nodepaths will give us one undo event per nodepath
2572 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2574 // if the entire nodepath is removed, delete the selected object.
2575 if (nodepath->subpaths == NULL ||
2576 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2577 //at least 2
2578 sp_nodepath_get_node_count(nodepath) < 2) {
2579 SPDocument *document = sp_desktop_document (nodepath->desktop);
2580 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2581 //delete this nodepath's object, not the entire selection! (though at this time, this
2582 //does not matter)
2583 sp_selection_delete(nodepath->desktop);
2584 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2585 _("Delete nodes"));
2586 } else {
2587 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2588 sp_nodepath_update_statusbar(nodepath);
2589 }
2590 }
2592 g_slist_free (nodepaths);
2593 }
2595 /**
2596 * Delete one or more selected nodes.
2597 */
2598 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2599 {
2600 if (!nodepath) return;
2601 if (!nodepath->selected) return;
2603 /** \todo fixme: do it the right way */
2604 while (nodepath->selected) {
2605 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2606 sp_nodepath_node_destroy(node);
2607 }
2610 //clean up the nodepath (such as for trivial subpaths)
2611 sp_nodepath_cleanup(nodepath);
2613 sp_nodepath_update_handles(nodepath);
2615 // if the entire nodepath is removed, delete the selected object.
2616 if (nodepath->subpaths == NULL ||
2617 sp_nodepath_get_node_count(nodepath) < 2) {
2618 SPDocument *document = sp_desktop_document (nodepath->desktop);
2619 sp_selection_delete(nodepath->desktop);
2620 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2621 _("Delete nodes"));
2622 return;
2623 }
2625 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2627 sp_nodepath_update_statusbar(nodepath);
2628 }
2630 /**
2631 * Delete one or more segments between two selected nodes.
2632 * This is the code for 'split'.
2633 */
2634 void
2635 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2636 {
2637 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2638 Inkscape::NodePath::Node *curr, *next; //Iterators
2640 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2642 if (g_list_length(nodepath->selected) != 2) {
2643 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2644 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2645 return;
2646 }
2648 //Selected nodes, not inclusive
2649 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2650 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2652 if ( ( a==b) || //same node
2653 (a->subpath != b->subpath ) || //not the same path
2654 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2655 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2656 {
2657 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2658 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2659 return;
2660 }
2662 //###########################################
2663 //# BEGIN EDITS
2664 //###########################################
2665 //##################################
2666 //# CLOSED PATH
2667 //##################################
2668 if (a->subpath->closed) {
2671 gboolean reversed = FALSE;
2673 //Since we can go in a circle, we need to find the shorter distance.
2674 // a->b or b->a
2675 start = end = NULL;
2676 int distance = 0;
2677 int minDistance = 0;
2678 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2679 if (curr==b) {
2680 //printf("a to b:%d\n", distance);
2681 start = a;//go from a to b
2682 end = b;
2683 minDistance = distance;
2684 //printf("A to B :\n");
2685 break;
2686 }
2687 distance++;
2688 }
2690 //try again, the other direction
2691 distance = 0;
2692 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2693 if (curr==a) {
2694 //printf("b to a:%d\n", distance);
2695 if (distance < minDistance) {
2696 start = b; //we go from b to a
2697 end = a;
2698 reversed = TRUE;
2699 //printf("B to A\n");
2700 }
2701 break;
2702 }
2703 distance++;
2704 }
2707 //Copy everything from 'end' to 'start' to a new subpath
2708 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2709 for (curr=end ; curr ; curr=curr->n.other) {
2710 NRPathcode code = (NRPathcode) curr->code;
2711 if (curr == end)
2712 code = NR_MOVETO;
2713 sp_nodepath_node_new(t, NULL,
2714 (Inkscape::NodePath::NodeType)curr->type, code,
2715 &curr->p.pos, &curr->pos, &curr->n.pos);
2716 if (curr == start)
2717 break;
2718 }
2719 sp_nodepath_subpath_destroy(a->subpath);
2722 }
2726 //##################################
2727 //# OPEN PATH
2728 //##################################
2729 else {
2731 //We need to get the direction of the list between A and B
2732 //Can we walk from a to b?
2733 start = end = NULL;
2734 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2735 if (curr==b) {
2736 start = a; //did it! we go from a to b
2737 end = b;
2738 //printf("A to B\n");
2739 break;
2740 }
2741 }
2742 if (!start) {//didn't work? let's try the other direction
2743 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2744 if (curr==a) {
2745 start = b; //did it! we go from b to a
2746 end = a;
2747 //printf("B to A\n");
2748 break;
2749 }
2750 }
2751 }
2752 if (!start) {
2753 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2754 _("Cannot find path between nodes."));
2755 return;
2756 }
2760 //Copy everything after 'end' to a new subpath
2761 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2762 for (curr=end ; curr ; curr=curr->n.other) {
2763 NRPathcode code = (NRPathcode) curr->code;
2764 if (curr == end)
2765 code = NR_MOVETO;
2766 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2767 &curr->p.pos, &curr->pos, &curr->n.pos);
2768 }
2770 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2771 for (curr = start->n.other ; curr ; curr=next) {
2772 next = curr->n.other;
2773 sp_nodepath_node_destroy(curr);
2774 }
2776 }
2777 //###########################################
2778 //# END EDITS
2779 //###########################################
2781 //clean up the nodepath (such as for trivial subpaths)
2782 sp_nodepath_cleanup(nodepath);
2784 sp_nodepath_update_handles(nodepath);
2786 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2788 sp_nodepath_update_statusbar(nodepath);
2789 }
2791 /**
2792 * Call sp_nodepath_set_line() for all selected segments.
2793 */
2794 void
2795 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2796 {
2797 if (nodepath == NULL) return;
2799 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2800 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2801 g_assert(n->selected);
2802 if (n->p.other && n->p.other->selected) {
2803 sp_nodepath_set_line_type(n, code);
2804 }
2805 }
2807 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2808 }
2810 /**
2811 * Call sp_nodepath_convert_node_type() for all selected nodes.
2812 */
2813 void
2814 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2815 {
2816 if (nodepath == NULL) return;
2818 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2820 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2821 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2822 }
2824 sp_nodepath_update_repr(nodepath, _("Change node type"));
2825 }
2827 /**
2828 * Change select status of node, update its own and neighbour handles.
2829 */
2830 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2831 {
2832 node->selected = selected;
2834 if (selected) {
2835 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 11 : 9);
2836 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2837 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2838 sp_knot_update_ctrl(node->knot);
2839 } else {
2840 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 9 : 7);
2841 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2842 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2843 sp_knot_update_ctrl(node->knot);
2844 }
2846 sp_node_update_handles(node);
2847 if (node->n.other) sp_node_update_handles(node->n.other);
2848 if (node->p.other) sp_node_update_handles(node->p.other);
2849 }
2851 /**
2852 \brief Select a node
2853 \param node The node to select
2854 \param incremental If true, add to selection, otherwise deselect others
2855 \param override If true, always select this node, otherwise toggle selected status
2856 */
2857 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2858 {
2859 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2861 if (incremental) {
2862 if (override) {
2863 if (!g_list_find(nodepath->selected, node)) {
2864 nodepath->selected = g_list_prepend(nodepath->selected, node);
2865 }
2866 sp_node_set_selected(node, TRUE);
2867 } else { // toggle
2868 if (node->selected) {
2869 g_assert(g_list_find(nodepath->selected, node));
2870 nodepath->selected = g_list_remove(nodepath->selected, node);
2871 } else {
2872 g_assert(!g_list_find(nodepath->selected, node));
2873 nodepath->selected = g_list_prepend(nodepath->selected, node);
2874 }
2875 sp_node_set_selected(node, !node->selected);
2876 }
2877 } else {
2878 sp_nodepath_deselect(nodepath);
2879 nodepath->selected = g_list_prepend(nodepath->selected, node);
2880 sp_node_set_selected(node, TRUE);
2881 }
2883 sp_nodepath_update_statusbar(nodepath);
2884 }
2887 /**
2888 \brief Deselect all nodes in the nodepath
2889 */
2890 void
2891 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2892 {
2893 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2895 while (nodepath->selected) {
2896 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2897 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2898 }
2899 sp_nodepath_update_statusbar(nodepath);
2900 }
2902 /**
2903 \brief Select or invert selection of all nodes in the nodepath
2904 */
2905 void
2906 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2907 {
2908 if (!nodepath) return;
2910 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2911 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2912 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2913 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2914 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2915 }
2916 }
2917 }
2919 /**
2920 * If nothing selected, does the same as sp_nodepath_select_all();
2921 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2922 * (i.e., similar to "select all in layer", with the "selected" subpaths
2923 * being treated as "layers" in the path).
2924 */
2925 void
2926 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2927 {
2928 if (!nodepath) return;
2930 if (g_list_length (nodepath->selected) == 0) {
2931 sp_nodepath_select_all (nodepath, invert);
2932 return;
2933 }
2935 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2936 GSList *subpaths = NULL;
2938 for (GList *l = copy; l != NULL; l = l->next) {
2939 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2940 Inkscape::NodePath::SubPath *subpath = n->subpath;
2941 if (!g_slist_find (subpaths, subpath))
2942 subpaths = g_slist_prepend (subpaths, subpath);
2943 }
2945 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2946 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2947 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2948 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2949 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2950 }
2951 }
2953 g_slist_free (subpaths);
2954 g_list_free (copy);
2955 }
2957 /**
2958 * \brief Select the node after the last selected; if none is selected,
2959 * select the first within path.
2960 */
2961 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2962 {
2963 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2965 Inkscape::NodePath::Node *last = NULL;
2966 if (nodepath->selected) {
2967 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2968 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2969 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2970 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2971 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2972 if (node->selected) {
2973 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2974 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2975 if (spl->next) { // there's a next subpath
2976 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2977 last = subpath_next->first;
2978 } else if (spl->prev) { // there's a previous subpath
2979 last = NULL; // to be set later to the first node of first subpath
2980 } else {
2981 last = node->n.other;
2982 }
2983 } else {
2984 last = node->n.other;
2985 }
2986 } else {
2987 if (node->n.other) {
2988 last = node->n.other;
2989 } else {
2990 if (spl->next) { // there's a next subpath
2991 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2992 last = subpath_next->first;
2993 } else if (spl->prev) { // there's a previous subpath
2994 last = NULL; // to be set later to the first node of first subpath
2995 } else {
2996 last = (Inkscape::NodePath::Node *) subpath->first;
2997 }
2998 }
2999 }
3000 }
3001 }
3002 }
3003 sp_nodepath_deselect(nodepath);
3004 }
3006 if (last) { // there's at least one more node after selected
3007 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
3008 } else { // no more nodes, select the first one in first subpath
3009 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
3010 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
3011 }
3012 }
3014 /**
3015 * \brief Select the node before the first selected; if none is selected,
3016 * select the last within path
3017 */
3018 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
3019 {
3020 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
3022 Inkscape::NodePath::Node *last = NULL;
3023 if (nodepath->selected) {
3024 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
3025 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3026 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
3027 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3028 if (node->selected) {
3029 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
3030 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
3031 if (spl->prev) { // there's a prev subpath
3032 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3033 last = subpath_prev->last;
3034 } else if (spl->next) { // there's a next subpath
3035 last = NULL; // to be set later to the last node of last subpath
3036 } else {
3037 last = node->p.other;
3038 }
3039 } else {
3040 last = node->p.other;
3041 }
3042 } else {
3043 if (node->p.other) {
3044 last = node->p.other;
3045 } else {
3046 if (spl->prev) { // there's a prev subpath
3047 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3048 last = subpath_prev->last;
3049 } else if (spl->next) { // there's a next subpath
3050 last = NULL; // to be set later to the last node of last subpath
3051 } else {
3052 last = (Inkscape::NodePath::Node *) subpath->last;
3053 }
3054 }
3055 }
3056 }
3057 }
3058 }
3059 sp_nodepath_deselect(nodepath);
3060 }
3062 if (last) { // there's at least one more node before selected
3063 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
3064 } else { // no more nodes, select the last one in last subpath
3065 GList *spl = g_list_last(nodepath->subpaths);
3066 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3067 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
3068 }
3069 }
3071 /**
3072 * \brief Select all nodes that are within the rectangle.
3073 */
3074 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, Geom::Rect const &b, gboolean incremental)
3075 {
3076 if (!incremental) {
3077 sp_nodepath_deselect(nodepath);
3078 }
3080 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3081 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3082 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3083 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3085 if (b.contains(node->pos)) {
3086 sp_nodepath_node_select(node, TRUE, TRUE);
3087 }
3088 }
3089 }
3090 }
3093 void
3094 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3095 {
3096 g_assert (n);
3097 g_assert (nodepath);
3098 g_assert (n->subpath->nodepath == nodepath);
3100 if (g_list_length (nodepath->selected) == 0) {
3101 if (grow > 0) {
3102 sp_nodepath_node_select(n, TRUE, TRUE);
3103 }
3104 return;
3105 }
3107 if (g_list_length (nodepath->selected) == 1) {
3108 if (grow < 0) {
3109 sp_nodepath_deselect (nodepath);
3110 return;
3111 }
3112 }
3114 double n_sel_range = 0, p_sel_range = 0;
3115 Inkscape::NodePath::Node *farthest_n_node = n;
3116 Inkscape::NodePath::Node *farthest_p_node = n;
3118 // Calculate ranges
3119 {
3120 double n_range = 0, p_range = 0;
3121 bool n_going = true, p_going = true;
3122 Inkscape::NodePath::Node *n_node = n;
3123 Inkscape::NodePath::Node *p_node = n;
3124 do {
3125 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3126 if (n_node && n_going)
3127 n_node = n_node->n.other;
3128 if (n_node == NULL) {
3129 n_going = false;
3130 } else {
3131 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3132 if (n_node->selected) {
3133 n_sel_range = n_range;
3134 farthest_n_node = n_node;
3135 }
3136 if (n_node == p_node) {
3137 n_going = false;
3138 p_going = false;
3139 }
3140 }
3141 if (p_node && p_going)
3142 p_node = p_node->p.other;
3143 if (p_node == NULL) {
3144 p_going = false;
3145 } else {
3146 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3147 if (p_node->selected) {
3148 p_sel_range = p_range;
3149 farthest_p_node = p_node;
3150 }
3151 if (p_node == n_node) {
3152 n_going = false;
3153 p_going = false;
3154 }
3155 }
3156 } while (n_going || p_going);
3157 }
3159 if (grow > 0) {
3160 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3161 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3162 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3163 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3164 }
3165 } else {
3166 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3167 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3168 } else if (farthest_p_node && farthest_p_node->selected) {
3169 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3170 }
3171 }
3172 }
3174 void
3175 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3176 {
3177 g_assert (n);
3178 g_assert (nodepath);
3179 g_assert (n->subpath->nodepath == nodepath);
3181 if (g_list_length (nodepath->selected) == 0) {
3182 if (grow > 0) {
3183 sp_nodepath_node_select(n, TRUE, TRUE);
3184 }
3185 return;
3186 }
3188 if (g_list_length (nodepath->selected) == 1) {
3189 if (grow < 0) {
3190 sp_nodepath_deselect (nodepath);
3191 return;
3192 }
3193 }
3195 Inkscape::NodePath::Node *farthest_selected = NULL;
3196 double farthest_dist = 0;
3198 Inkscape::NodePath::Node *closest_unselected = NULL;
3199 double closest_dist = NR_HUGE;
3201 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3202 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3203 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3204 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3205 if (node == n)
3206 continue;
3207 if (node->selected) {
3208 if (Geom::L2(node->pos - n->pos) > farthest_dist) {
3209 farthest_dist = Geom::L2(node->pos - n->pos);
3210 farthest_selected = node;
3211 }
3212 } else {
3213 if (Geom::L2(node->pos - n->pos) < closest_dist) {
3214 closest_dist = Geom::L2(node->pos - n->pos);
3215 closest_unselected = node;
3216 }
3217 }
3218 }
3219 }
3221 if (grow > 0) {
3222 if (closest_unselected) {
3223 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3224 }
3225 } else {
3226 if (farthest_selected) {
3227 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3228 }
3229 }
3230 }
3233 /**
3234 \brief Saves all nodes' and handles' current positions in their origin members
3235 */
3236 void
3237 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3238 {
3239 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3240 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3241 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3242 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3243 n->origin = n->pos;
3244 n->p.origin = n->p.pos;
3245 n->n.origin = n->n.pos;
3246 }
3247 }
3248 }
3250 /**
3251 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3252 */
3253 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3254 {
3255 GList *r = NULL;
3256 if (nodepath->selected) {
3257 guint i = 0;
3258 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3259 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3260 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3261 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3262 i++;
3263 if (node->selected) {
3264 r = g_list_append(r, GINT_TO_POINTER(i));
3265 }
3266 }
3267 }
3268 }
3269 return r;
3270 }
3272 /**
3273 \brief Restores selection by selecting nodes whose positions are in the list
3274 */
3275 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3276 {
3277 sp_nodepath_deselect(nodepath);
3279 guint i = 0;
3280 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3281 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3282 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3283 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3284 i++;
3285 if (g_list_find(r, GINT_TO_POINTER(i))) {
3286 sp_nodepath_node_select(node, TRUE, TRUE);
3287 }
3288 }
3289 }
3290 }
3293 /**
3294 \brief Adjusts handle according to node type and line code.
3295 */
3296 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3297 {
3298 g_assert(node);
3300 // nothing to do for auto nodes (sp_node_adjust_handles() does the job)
3301 if (node->type == Inkscape::NodePath::NODE_AUTO)
3302 return;
3304 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3305 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3307 // nothing to do if we are an end node
3308 if (me->other == NULL) return;
3309 if (other->other == NULL) return;
3311 // nothing to do if we are a cusp node
3312 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3314 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3315 NRPathcode mecode;
3316 if (which_adjust == 1) {
3317 mecode = (NRPathcode)me->other->code;
3318 } else {
3319 mecode = (NRPathcode)node->code;
3320 }
3321 if (mecode == NR_LINETO) return;
3323 if (sp_node_side_is_line(node, other)) {
3324 // other is a line, and we are either smooth or symm
3325 Inkscape::NodePath::Node *othernode = other->other;
3326 double len = Geom::L2(me->pos - node->pos);
3327 Geom::Point delta = node->pos - othernode->pos;
3328 double linelen = Geom::L2(delta);
3329 if (linelen < 1e-18)
3330 return;
3331 me->pos = node->pos + (len / linelen)*delta;
3332 return;
3333 }
3335 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3336 // symmetrize
3337 me->pos = 2 * node->pos - other->pos;
3338 return;
3339 } else {
3340 // smoothify
3341 double len = Geom::L2(me->pos - node->pos);
3342 Geom::Point delta = other->pos - node->pos;
3343 double otherlen = Geom::L2(delta);
3344 if (otherlen < 1e-18) return;
3345 me->pos = node->pos - (len / otherlen) * delta;
3346 }
3347 }
3349 /**
3350 \brief Adjusts both handles according to node type and line code
3351 */
3352 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3353 {
3354 g_assert(node);
3356 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3358 /* we are either smooth or symm */
3360 if (node->p.other == NULL) return;
3361 if (node->n.other == NULL) return;
3363 if (node->type == Inkscape::NodePath::NODE_AUTO) {
3364 sp_node_adjust_handles_auto(node);
3365 return;
3366 }
3368 if (sp_node_side_is_line(node, &node->p)) {
3369 sp_node_adjust_handle(node, 1);
3370 return;
3371 }
3373 if (sp_node_side_is_line(node, &node->n)) {
3374 sp_node_adjust_handle(node, -1);
3375 return;
3376 }
3378 /* both are curves */
3379 Geom::Point const delta( node->n.pos - node->p.pos );
3381 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3382 node->p.pos = node->pos - delta / 2;
3383 node->n.pos = node->pos + delta / 2;
3384 return;
3385 }
3387 /* We are smooth */
3388 double plen = Geom::L2(node->p.pos - node->pos);
3389 if (plen < 1e-18) return;
3390 double nlen = Geom::L2(node->n.pos - node->pos);
3391 if (nlen < 1e-18) return;
3392 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3393 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3394 }
3396 static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node)
3397 {
3398 if (node->p.other == NULL || node->n.other == NULL) {
3399 node->p.pos = node->pos;
3400 node->n.pos = node->pos;
3401 return;
3402 }
3404 Geom::Point leg_prev = to_2geom(node->p.other->pos - node->pos);
3405 Geom::Point leg_next = to_2geom(node->n.other->pos - node->pos);
3407 double norm_leg_prev = Geom::L2(leg_prev);
3408 double norm_leg_next = Geom::L2(leg_next);
3410 Geom::Point delta;
3411 if (norm_leg_next > 0.0) {
3412 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
3413 delta.normalize();
3414 }
3416 node->p.pos = node->pos - norm_leg_prev / 3 * delta;
3417 node->n.pos = node->pos + norm_leg_next / 3 * delta;
3418 }
3420 /**
3421 * Node event callback.
3422 */
3423 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3424 {
3425 gboolean ret = FALSE;
3426 switch (event->type) {
3427 case GDK_ENTER_NOTIFY:
3428 Inkscape::NodePath::Path::active_node = n;
3429 break;
3430 case GDK_LEAVE_NOTIFY:
3431 Inkscape::NodePath::Path::active_node = NULL;
3432 break;
3433 case GDK_SCROLL:
3434 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3435 switch (event->scroll.direction) {
3436 case GDK_SCROLL_UP:
3437 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3438 break;
3439 case GDK_SCROLL_DOWN:
3440 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3441 break;
3442 default:
3443 break;
3444 }
3445 ret = TRUE;
3446 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3447 switch (event->scroll.direction) {
3448 case GDK_SCROLL_UP:
3449 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3450 break;
3451 case GDK_SCROLL_DOWN:
3452 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3453 break;
3454 default:
3455 break;
3456 }
3457 ret = TRUE;
3458 }
3459 break;
3460 case GDK_KEY_PRESS:
3461 switch (get_group0_keyval (&event->key)) {
3462 case GDK_space:
3463 if (event->key.state & GDK_BUTTON1_MASK) {
3464 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3465 stamp_repr(nodepath);
3466 ret = TRUE;
3467 }
3468 break;
3469 case GDK_Page_Up:
3470 if (event->key.state & GDK_CONTROL_MASK) {
3471 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3472 } else {
3473 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3474 }
3475 break;
3476 case GDK_Page_Down:
3477 if (event->key.state & GDK_CONTROL_MASK) {
3478 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3479 } else {
3480 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3481 }
3482 break;
3483 default:
3484 break;
3485 }
3486 break;
3487 default:
3488 break;
3489 }
3491 return ret;
3492 }
3494 /**
3495 * Handle keypress on node; directly called.
3496 */
3497 gboolean node_key(GdkEvent *event)
3498 {
3499 Inkscape::NodePath::Path *np;
3501 // there is no way to verify nodes so set active_node to nil when deleting!!
3502 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3504 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3505 gint ret = FALSE;
3506 switch (get_group0_keyval (&event->key)) {
3507 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3508 case GDK_BackSpace:
3509 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3510 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3511 sp_nodepath_update_repr(np, _("Delete node"));
3512 Inkscape::NodePath::Path::active_node = NULL;
3513 ret = TRUE;
3514 break;
3515 case GDK_c:
3516 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3517 ret = TRUE;
3518 break;
3519 case GDK_s:
3520 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3521 ret = TRUE;
3522 break;
3523 case GDK_a:
3524 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_AUTO);
3525 ret = TRUE;
3526 break;
3527 case GDK_y:
3528 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3529 ret = TRUE;
3530 break;
3531 case GDK_b:
3532 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3533 ret = TRUE;
3534 break;
3535 }
3536 return ret;
3537 }
3538 return FALSE;
3539 }
3541 /**
3542 * Mouseclick on node callback.
3543 */
3544 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3545 {
3546 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3548 if (state & GDK_CONTROL_MASK) {
3549 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3551 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3552 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3553 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3554 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3555 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3556 } else if (n->type == Inkscape::NodePath::NODE_SYMM) {
3557 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_AUTO);
3558 } else {
3559 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3560 }
3561 sp_nodepath_update_repr(nodepath, _("Change node type"));
3562 sp_nodepath_update_statusbar(nodepath);
3564 } else { //ctrl+alt+click: delete node
3565 GList *node_to_delete = NULL;
3566 node_to_delete = g_list_append(node_to_delete, n);
3567 sp_node_delete_preserve(node_to_delete);
3568 }
3570 } else {
3571 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3572 }
3573 }
3575 /**
3576 * Mouse grabbed node callback.
3577 */
3578 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3579 {
3580 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3582 if (!n->selected) {
3583 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3584 }
3586 n->is_dragging = true;
3587 sp_canvas_set_snap_delay_active(n->subpath->nodepath->desktop->canvas, true);
3588 // Reconstruct and store the location of the mouse pointer at the time when we started dragging (needed for snapping)
3589 n->subpath->nodepath->drag_origin_mouse = knot->grabbed_rel_pos + knot->drag_origin;
3591 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3593 sp_nodepath_remember_origins (n->subpath->nodepath);
3594 }
3596 /**
3597 * Mouse ungrabbed node callback.
3598 */
3599 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3600 {
3601 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3603 n->dragging_out = NULL;
3604 n->is_dragging = false;
3605 sp_canvas_set_snap_delay_active(n->subpath->nodepath->desktop->canvas, false);
3606 n->subpath->nodepath->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE);
3607 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3609 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3610 }
3612 /**
3613 * The point on a line, given by its angle, closest to the given point.
3614 * \param p A point.
3615 * \param a Angle of the line; it is assumed to go through coordinate origin.
3616 * \param closest Pointer to the point struct where the result is stored.
3617 * \todo FIXME: use dot product perhaps?
3618 */
3619 static void point_line_closest(Geom::Point *p, double a, Geom::Point *closest)
3620 {
3621 if (a == HUGE_VAL) { // vertical
3622 *closest = Geom::Point(0, (*p)[Geom::Y]);
3623 } else {
3624 (*closest)[Geom::X] = ( a * (*p)[Geom::Y] + (*p)[Geom::X]) / (a*a + 1);
3625 (*closest)[Geom::Y] = a * (*closest)[Geom::X];
3626 }
3627 }
3629 /**
3630 * Distance from the point to a line given by its angle.
3631 * \param p A point.
3632 * \param a Angle of the line; it is assumed to go through coordinate origin.
3633 */
3634 static double point_line_distance(Geom::Point *p, double a)
3635 {
3636 Geom::Point c;
3637 point_line_closest(p, a, &c);
3638 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]));
3639 }
3641 /**
3642 * Callback for node "request" signal.
3643 * \todo fixme: This goes to "moved" event? (lauris)
3644 */
3645 static gboolean
3646 node_request(SPKnot */*knot*/, Geom::Point const &p, guint state, gpointer data)
3647 {
3648 double yn, xn, yp, xp;
3649 double an, ap, na, pa;
3650 double d_an, d_ap, d_na, d_pa;
3651 gboolean collinear = FALSE;
3652 Geom::Point c;
3653 Geom::Point pr;
3655 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3657 n->subpath->nodepath->desktop->snapindicator->remove_snaptarget();
3659 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3660 if ( (!n->subpath->nodepath->straight_path) &&
3661 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3662 || n->dragging_out ) )
3663 {
3664 Geom::Point mouse = p;
3666 if (!n->dragging_out) {
3667 // This is the first drag-out event; find out which handle to drag out
3668 double appr_n = (n->n.other ? Geom::L2(n->n.other->pos - n->pos) - Geom::L2(n->n.other->pos - p) : -HUGE_VAL);
3669 double appr_p = (n->p.other ? Geom::L2(n->p.other->pos - n->pos) - Geom::L2(n->p.other->pos - p) : -HUGE_VAL);
3671 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3672 return FALSE;
3674 Inkscape::NodePath::NodeSide *opposite;
3675 if (appr_p > appr_n) { // closer to p
3676 n->dragging_out = &n->p;
3677 opposite = &n->n;
3678 n->code = NR_CURVETO;
3679 } else if (appr_p < appr_n) { // closer to n
3680 n->dragging_out = &n->n;
3681 opposite = &n->p;
3682 n->n.other->code = NR_CURVETO;
3683 } else { // p and n nodes are the same
3684 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3685 n->dragging_out = &n->p;
3686 opposite = &n->n;
3687 n->code = NR_CURVETO;
3688 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3689 n->dragging_out = &n->n;
3690 opposite = &n->p;
3691 n->n.other->code = NR_CURVETO;
3692 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3693 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);
3694 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);
3695 if (appr_other_p > appr_other_n) { // closer to other's p handle
3696 n->dragging_out = &n->n;
3697 opposite = &n->p;
3698 n->n.other->code = NR_CURVETO;
3699 } else { // closer to other's n handle
3700 n->dragging_out = &n->p;
3701 opposite = &n->n;
3702 n->code = NR_CURVETO;
3703 }
3704 }
3705 }
3707 // if there's another handle, make sure the one we drag out starts parallel to it
3708 if (opposite->pos != n->pos) {
3709 mouse = n->pos - Geom::L2(mouse - n->pos) * Geom::unit_vector(opposite->pos - n->pos);
3710 }
3712 // knots might not be created yet!
3713 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3714 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3715 }
3717 // pass this on to the handle-moved callback
3718 node_handle_moved(n->dragging_out->knot, mouse, state, (gpointer) n);
3719 sp_node_update_handles(n);
3720 return TRUE;
3721 }
3723 if (state & GDK_CONTROL_MASK) { // constrained motion
3725 // calculate relative distances of handles
3726 // n handle:
3727 yn = n->n.pos[Geom::Y] - n->pos[Geom::Y];
3728 xn = n->n.pos[Geom::X] - n->pos[Geom::X];
3729 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3730 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3731 if (n->n.other) { // if there is the next point
3732 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3733 yn = n->n.other->origin[Geom::Y] - n->origin[Geom::Y]; // use origin because otherwise the direction will change as you drag
3734 xn = n->n.other->origin[Geom::X] - n->origin[Geom::X];
3735 }
3736 }
3737 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3738 if (yn < 0) { xn = -xn; yn = -yn; }
3740 // p handle:
3741 yp = n->p.pos[Geom::Y] - n->pos[Geom::Y];
3742 xp = n->p.pos[Geom::X] - n->pos[Geom::X];
3743 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3744 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3745 if (n->p.other) {
3746 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3747 yp = n->p.other->origin[Geom::Y] - n->origin[Geom::Y];
3748 xp = n->p.other->origin[Geom::X] - n->origin[Geom::X];
3749 }
3750 }
3751 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3752 if (yp < 0) { xp = -xp; yp = -yp; }
3754 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3755 // sliding on handles, only if at least one of the handles is non-vertical
3756 // (otherwise it's the same as ctrl+drag anyway)
3758 // calculate angles of the handles
3759 if (xn == 0) {
3760 if (yn == 0) { // no handle, consider it the continuation of the other one
3761 an = 0;
3762 collinear = TRUE;
3763 }
3764 else an = 0; // vertical; set the angle to horizontal
3765 } else an = yn/xn;
3767 if (xp == 0) {
3768 if (yp == 0) { // no handle, consider it the continuation of the other one
3769 ap = an;
3770 }
3771 else ap = 0; // vertical; set the angle to horizontal
3772 } else ap = yp/xp;
3774 if (collinear) an = ap;
3776 // angles of the perpendiculars; HUGE_VAL means vertical
3777 if (an == 0) na = HUGE_VAL; else na = -1/an;
3778 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3780 // mouse point relative to the node's original pos
3781 pr = p - n->origin;
3783 // distances to the four lines (two handles and two perpendiculars)
3784 d_an = point_line_distance(&pr, an);
3785 d_na = point_line_distance(&pr, na);
3786 d_ap = point_line_distance(&pr, ap);
3787 d_pa = point_line_distance(&pr, pa);
3789 // find out which line is the closest, save its closest point in c
3790 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3791 point_line_closest(&pr, an, &c);
3792 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3793 point_line_closest(&pr, ap, &c);
3794 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3795 point_line_closest(&pr, na, &c);
3796 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3797 point_line_closest(&pr, pa, &c);
3798 }
3800 // move the node to the closest point
3801 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3802 n->origin[Geom::X] + c[Geom::X] - n->pos[Geom::X],
3803 n->origin[Geom::Y] + c[Geom::Y] - n->pos[Geom::Y],
3804 true);
3806 } else { // constraining to hor/vert
3808 if (fabs(p[Geom::X] - n->origin[Geom::X]) > fabs(p[Geom::Y] - n->origin[Geom::Y])) { // snap to hor
3809 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3810 p[Geom::X] - n->pos[Geom::X],
3811 n->origin[Geom::Y] - n->pos[Geom::Y],
3812 true,
3813 true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::X]));
3814 } else { // snap to vert
3815 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3816 n->origin[Geom::X] - n->pos[Geom::X],
3817 p[Geom::Y] - n->pos[Geom::Y],
3818 true,
3819 true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::Y]));
3820 }
3821 }
3822 } else { // move freely
3823 if (n->is_dragging) {
3824 if (state & GDK_MOD1_MASK) { // sculpt
3825 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, p - n->origin);
3826 } else {
3827 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3828 p[Geom::X] - n->pos[Geom::X],
3829 p[Geom::Y] - n->pos[Geom::Y],
3830 (state & GDK_SHIFT_MASK) == 0);
3831 }
3832 }
3833 }
3835 n->subpath->nodepath->desktop->scroll_to_point(p);
3837 return TRUE;
3838 }
3840 /**
3841 * Node handle clicked callback.
3842 */
3843 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3844 {
3845 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3847 if (state & GDK_CONTROL_MASK) { // "delete" handle
3848 if (n->p.knot == knot) {
3849 n->p.pos = n->pos;
3850 } else if (n->n.knot == knot) {
3851 n->n.pos = n->pos;
3852 }
3853 sp_node_update_handles(n);
3854 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3855 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3856 sp_nodepath_update_statusbar(nodepath);
3858 } else { // just select or add to selection, depending in Shift
3859 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3860 }
3861 }
3863 /**
3864 * Node handle grabbed callback.
3865 */
3866 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3867 {
3868 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3870 // convert auto -> smooth when dragging handle
3871 if (n->type == Inkscape::NodePath::NODE_AUTO) {
3872 n->type = Inkscape::NodePath::NODE_SMOOTH;
3873 sp_nodepath_update_node_knot (n);
3874 }
3876 if (!n->selected) {
3877 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3878 }
3880 // remember the origin point of the handle
3881 if (n->p.knot == knot) {
3882 n->p.origin_radial = n->p.pos - n->pos;
3883 } else if (n->n.knot == knot) {
3884 n->n.origin_radial = n->n.pos - n->pos;
3885 } else {
3886 g_assert_not_reached();
3887 }
3889 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3890 }
3892 /**
3893 * Node handle ungrabbed callback.
3894 */
3895 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3896 {
3897 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3899 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3900 if (n->p.knot == knot) {
3901 n->p.origin_radial.a = 0;
3902 sp_knot_set_position(knot, n->p.pos, state);
3903 } else if (n->n.knot == knot) {
3904 n->n.origin_radial.a = 0;
3905 sp_knot_set_position(knot, n->n.pos, state);
3906 } else {
3907 g_assert_not_reached();
3908 }
3910 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3911 }
3913 /**
3914 * Node handle "request" signal callback.
3915 */
3916 static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, gpointer data)
3917 {
3918 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3920 Inkscape::NodePath::NodeSide *me, *opposite;
3921 gint which;
3922 if (n->p.knot == knot) {
3923 me = &n->p;
3924 opposite = &n->n;
3925 which = -1;
3926 } else if (n->n.knot == knot) {
3927 me = &n->n;
3928 opposite = &n->p;
3929 which = 1;
3930 } else {
3931 me = opposite = NULL;
3932 which = 0;
3933 g_assert_not_reached();
3934 }
3936 SPDesktop *desktop = n->subpath->nodepath->desktop;
3937 SnapManager &m = desktop->namedview->snap_manager;
3938 m.setup(desktop, true, n->subpath->nodepath->item);
3939 Inkscape::SnappedPoint s;
3941 if ((state & GDK_SHIFT_MASK) != 0) {
3942 // We will not try to snap when the shift-key is pressed
3943 // so remove the old snap indicator and don't wait for it to time-out
3944 desktop->snapindicator->remove_snaptarget();
3945 }
3947 Inkscape::NodePath::Node *othernode = opposite->other;
3948 if (othernode) {
3949 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3950 /* We are smooth node adjacent with line */
3951 Geom::Point const delta = p - n->pos;
3952 Geom::Coord const len = Geom::L2(delta);
3953 Inkscape::NodePath::Node *othernode = opposite->other;
3954 Geom::Point const ndelta = n->pos - othernode->pos;
3955 Geom::Coord const linelen = Geom::L2(ndelta);
3956 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3957 Geom::Coord const scal = dot(delta, ndelta) / linelen;
3958 p = n->pos + (scal / linelen) * ndelta;
3959 }
3960 if ((state & GDK_SHIFT_MASK) == 0) {
3961 s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::Snapper::ConstraintLine(p, ndelta));
3962 }
3963 } else {
3964 if ((state & GDK_SHIFT_MASK) == 0) {
3965 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p);
3966 }
3967 }
3968 } else {
3969 if ((state & GDK_SHIFT_MASK) == 0) {
3970 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p);
3971 }
3972 }
3974 s.getPoint(p);
3976 sp_node_adjust_handle(n, -which);
3978 return FALSE;
3979 }
3981 /**
3982 * Node handle moved callback.
3983 */
3984 static void node_handle_moved(SPKnot *knot, Geom::Point const &p, guint state, gpointer data)
3985 {
3986 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3987 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
3989 Inkscape::NodePath::NodeSide *me;
3990 Inkscape::NodePath::NodeSide *other;
3991 if (n->p.knot == knot) {
3992 me = &n->p;
3993 other = &n->n;
3994 } else if (n->n.knot == knot) {
3995 me = &n->n;
3996 other = &n->p;
3997 } else {
3998 me = NULL;
3999 other = NULL;
4000 g_assert_not_reached();
4001 }
4003 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
4004 Radial rme(me->pos - n->pos);
4005 Radial rother(other->pos - n->pos);
4006 Radial rnew(p - n->pos);
4008 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
4009 int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
4010 /* 0 interpreted as "no snapping". */
4012 // 1. Snap to the closest PI/snaps angle, starting from zero.
4013 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
4015 // 2. Snap to the original angle, its opposite and perpendiculars
4016 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
4017 /* The closest PI/2 angle, starting from original angle */
4018 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
4020 // Snap to the closest.
4021 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
4022 ? a_snapped
4023 : a_ortho );
4024 }
4026 // 3. Snap to the angle of the opposite line, if any
4027 Inkscape::NodePath::Node *othernode = other->other;
4028 if (othernode) {
4029 Geom::Point other_to_snap(0,0);
4030 if (sp_node_side_is_line(n, other)) {
4031 other_to_snap = othernode->pos - n->pos;
4032 } else {
4033 other_to_snap = other->pos - n->pos;
4034 }
4035 if (Geom::L2(other_to_snap) > 1e-3) {
4036 Radial rother_to_snap(other_to_snap);
4037 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
4038 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
4040 // Snap to the closest.
4041 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
4042 ? a_snapped
4043 : a_oppo );
4044 }
4045 }
4047 rnew.a = a_snapped;
4048 }
4050 if (state & GDK_MOD1_MASK) {
4051 // lock handle length
4052 rnew.r = me->origin_radial.r;
4053 }
4055 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
4056 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
4057 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
4058 rother.a += rnew.a - rme.a;
4059 other->pos = Geom::Point(rother) + n->pos;
4060 if (other->knot) {
4061 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
4062 sp_knot_moveto(other->knot, other->pos);
4063 }
4064 }
4066 me->pos = Geom::Point(rnew) + n->pos;
4067 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
4069 // move knot, but without emitting the signal:
4070 // we cannot emit a "moved" signal because we're now processing it
4071 sp_knot_moveto(me->knot, me->pos);
4073 update_object(n->subpath->nodepath);
4075 /* status text */
4076 SPDesktop *desktop = n->subpath->nodepath->desktop;
4077 if (!desktop) return;
4078 SPEventContext *ec = desktop->event_context;
4079 if (!ec) return;
4081 Inkscape::MessageContext *mc = get_message_context(ec);
4083 if (!mc) return;
4085 double degrees = 180 / M_PI * rnew.a;
4086 if (degrees > 180) degrees -= 360;
4087 if (degrees < -180) degrees += 360;
4088 if (prefs->getBool("/options/compassangledisplay/value"))
4089 degrees = angle_to_compass (degrees);
4091 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
4093 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
4094 _("<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);
4096 g_string_free(length, TRUE);
4097 }
4099 /**
4100 * Node handle event callback.
4101 */
4102 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
4103 {
4104 gboolean ret = FALSE;
4105 switch (event->type) {
4106 case GDK_KEY_PRESS:
4107 switch (get_group0_keyval (&event->key)) {
4108 case GDK_space:
4109 if (event->key.state & GDK_BUTTON1_MASK) {
4110 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
4111 stamp_repr(nodepath);
4112 ret = TRUE;
4113 }
4114 break;
4115 default:
4116 break;
4117 }
4118 break;
4119 case GDK_ENTER_NOTIFY:
4120 // we use an experimentally determined threshold that seems to work fine
4121 if (Geom::L2(n->pos - knot->pos) < 0.75)
4122 Inkscape::NodePath::Path::active_node = n;
4123 break;
4124 case GDK_LEAVE_NOTIFY:
4125 // we use an experimentally determined threshold that seems to work fine
4126 if (Geom::L2(n->pos - knot->pos) < 0.75)
4127 Inkscape::NodePath::Path::active_node = NULL;
4128 break;
4129 default:
4130 break;
4131 }
4133 return ret;
4134 }
4136 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
4137 Radial &rme, Radial &rother, gboolean const both)
4138 {
4139 rme.a += angle;
4140 if ( both
4141 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4142 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4143 {
4144 rother.a += angle;
4145 }
4146 }
4148 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
4149 Radial &rme, Radial &rother, gboolean const both)
4150 {
4151 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4153 gdouble r;
4154 if ( both
4155 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4156 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4157 {
4158 r = MAX(rme.r, rother.r);
4159 } else {
4160 r = rme.r;
4161 }
4163 gdouble const weird_angle = atan2(norm_angle, r);
4164 /* Bulia says norm_angle is just the visible distance that the
4165 * object's end must travel on the screen. Left as 'angle' for want of
4166 * a better name.*/
4168 rme.a += weird_angle;
4169 if ( both
4170 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4171 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4172 {
4173 rother.a += weird_angle;
4174 }
4175 }
4177 /**
4178 * Rotate one node.
4179 */
4180 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4181 {
4182 Inkscape::NodePath::NodeSide *me, *other;
4183 bool both = false;
4185 double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4186 double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4188 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4189 me = &(n->p);
4190 other = &(n->n);
4191 } else if (!n->p.other) {
4192 me = &(n->n);
4193 other = &(n->p);
4194 } else {
4195 if (which > 0) { // right handle
4196 if (xn > xp) {
4197 me = &(n->n);
4198 other = &(n->p);
4199 } else {
4200 me = &(n->p);
4201 other = &(n->n);
4202 }
4203 } else if (which < 0){ // left handle
4204 if (xn <= xp) {
4205 me = &(n->n);
4206 other = &(n->p);
4207 } else {
4208 me = &(n->p);
4209 other = &(n->n);
4210 }
4211 } else { // both handles
4212 me = &(n->n);
4213 other = &(n->p);
4214 both = true;
4215 }
4216 }
4218 Radial rme(me->pos - n->pos);
4219 Radial rother(other->pos - n->pos);
4221 if (screen) {
4222 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4223 } else {
4224 node_rotate_one_internal (*n, angle, rme, rother, both);
4225 }
4227 me->pos = n->pos + Geom::Point(rme);
4229 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4230 other->pos = n->pos + Geom::Point(rother);
4231 }
4233 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4234 // so here we just move all the knots without emitting move signals, for speed
4235 sp_node_update_handles(n, false);
4236 }
4238 /**
4239 * Rotate selected nodes.
4240 */
4241 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4242 {
4243 if (!nodepath || !nodepath->selected) return;
4245 if (g_list_length(nodepath->selected) == 1) {
4246 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4247 node_rotate_one (n, angle, which, screen);
4248 } else {
4249 // rotate as an object:
4251 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4252 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4253 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4254 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4255 box.expandTo (n->pos); // contain all selected nodes
4256 }
4258 gdouble rot;
4259 if (screen) {
4260 gdouble const zoom = nodepath->desktop->current_zoom();
4261 gdouble const zmove = angle / zoom;
4262 gdouble const r = Geom::L2(box.max() - box.midpoint());
4263 rot = atan2(zmove, r);
4264 } else {
4265 rot = angle;
4266 }
4268 Geom::Point rot_center;
4269 if (Inkscape::NodePath::Path::active_node == NULL)
4270 rot_center = box.midpoint();
4271 else
4272 rot_center = Inkscape::NodePath::Path::active_node->pos;
4274 Geom::Matrix t =
4275 Geom::Matrix (Geom::Translate(-rot_center)) *
4276 Geom::Matrix (Geom::Rotate(rot)) *
4277 Geom::Matrix (Geom::Translate(rot_center));
4279 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4280 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4281 n->pos *= t;
4282 n->n.pos *= t;
4283 n->p.pos *= t;
4284 sp_node_update_handles(n, false);
4285 }
4286 }
4288 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4289 }
4291 /**
4292 * Scale one node.
4293 */
4294 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4295 {
4296 bool both = false;
4297 Inkscape::NodePath::NodeSide *me, *other;
4299 double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4300 double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4302 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4303 me = &(n->p);
4304 other = &(n->n);
4305 n->code = NR_CURVETO;
4306 } else if (!n->p.other) {
4307 me = &(n->n);
4308 other = &(n->p);
4309 if (n->n.other)
4310 n->n.other->code = NR_CURVETO;
4311 } else {
4312 if (which > 0) { // right handle
4313 if (xn > xp) {
4314 me = &(n->n);
4315 other = &(n->p);
4316 if (n->n.other)
4317 n->n.other->code = NR_CURVETO;
4318 } else {
4319 me = &(n->p);
4320 other = &(n->n);
4321 n->code = NR_CURVETO;
4322 }
4323 } else if (which < 0){ // left handle
4324 if (xn <= xp) {
4325 me = &(n->n);
4326 other = &(n->p);
4327 if (n->n.other)
4328 n->n.other->code = NR_CURVETO;
4329 } else {
4330 me = &(n->p);
4331 other = &(n->n);
4332 n->code = NR_CURVETO;
4333 }
4334 } else { // both handles
4335 me = &(n->n);
4336 other = &(n->p);
4337 both = true;
4338 n->code = NR_CURVETO;
4339 if (n->n.other)
4340 n->n.other->code = NR_CURVETO;
4341 }
4342 }
4344 Radial rme(me->pos - n->pos);
4345 Radial rother(other->pos - n->pos);
4347 rme.r += grow;
4348 if (rme.r < 0) rme.r = 0;
4349 if (rme.a == HUGE_VAL) {
4350 if (me->other) { // if direction is unknown, initialize it towards the next node
4351 Radial rme_next(me->other->pos - n->pos);
4352 rme.a = rme_next.a;
4353 } else { // if there's no next, initialize to 0
4354 rme.a = 0;
4355 }
4356 }
4357 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4358 rother.r += grow;
4359 if (rother.r < 0) rother.r = 0;
4360 if (rother.a == HUGE_VAL) {
4361 rother.a = rme.a + M_PI;
4362 }
4363 }
4365 me->pos = n->pos + Geom::Point(rme);
4367 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4368 other->pos = n->pos + Geom::Point(rother);
4369 }
4371 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4372 // so here we just move all the knots without emitting move signals, for speed
4373 sp_node_update_handles(n, false);
4374 }
4376 /**
4377 * Scale selected nodes.
4378 */
4379 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4380 {
4381 if (!nodepath || !nodepath->selected) return;
4383 if (g_list_length(nodepath->selected) == 1) {
4384 // scale handles of the single selected node
4385 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4386 node_scale_one (n, grow, which);
4387 } else {
4388 // scale nodes as an "object":
4390 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4391 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4392 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4393 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4394 box.expandTo (n->pos); // contain all selected nodes
4395 }
4397 if ( Geom::are_near(box.maxExtent(), 0) ) {
4398 SPEventContext *ec = nodepath->desktop->event_context;
4399 if (!ec) return;
4400 Inkscape::MessageContext *mc = get_message_context(ec);
4401 if (!mc) return;
4402 mc->setF(Inkscape::WARNING_MESSAGE,
4403 _("Cannot scale nodes when all are at the same location."));
4404 return;
4405 }
4406 double scale = (box.maxExtent() + grow)/box.maxExtent();
4409 Geom::Point scale_center;
4410 if (Inkscape::NodePath::Path::active_node == NULL)
4411 scale_center = box.midpoint();
4412 else
4413 scale_center = Inkscape::NodePath::Path::active_node->pos;
4415 Geom::Matrix t =
4416 Geom::Translate(-scale_center) *
4417 Geom::Scale(scale, scale) *
4418 Geom::Translate(scale_center);
4420 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4421 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4422 n->pos *= t;
4423 n->n.pos *= t;
4424 n->p.pos *= t;
4425 sp_node_update_handles(n, false);
4426 }
4427 }
4429 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4430 }
4432 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4433 {
4434 if (!nodepath) return;
4435 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4436 }
4438 /**
4439 * Flip selected nodes horizontally/vertically.
4440 */
4441 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis, boost::optional<Geom::Point> center)
4442 {
4443 if (!nodepath || !nodepath->selected) return;
4445 if (g_list_length(nodepath->selected) == 1 && !center) {
4446 // flip handles of the single selected node
4447 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4448 double temp = n->p.pos[axis];
4449 n->p.pos[axis] = n->n.pos[axis];
4450 n->n.pos[axis] = temp;
4451 sp_node_update_handles(n, false);
4452 } else {
4453 // scale nodes as an "object":
4455 Geom::Rect box = sp_node_selected_bbox (nodepath);
4456 if (!center) {
4457 center = box.midpoint();
4458 }
4459 Geom::Matrix t =
4460 Geom::Matrix (Geom::Translate(- *center)) *
4461 Geom::Matrix ((axis == Geom::X)? Geom::Scale(-1, 1) : Geom::Scale(1, -1)) *
4462 Geom::Matrix (Geom::Translate(*center));
4464 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4465 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4466 n->pos *= t;
4467 n->n.pos *= t;
4468 n->p.pos *= t;
4469 sp_node_update_handles(n, false);
4470 }
4471 }
4473 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4474 }
4476 Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4477 {
4478 g_assert (nodepath->selected);
4480 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4481 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4482 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4483 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4484 box.expandTo (n->pos); // contain all selected nodes
4485 }
4486 return box;
4487 }
4489 //-----------------------------------------------
4490 /**
4491 * Return new subpath under given nodepath.
4492 */
4493 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4494 {
4495 g_assert(nodepath);
4496 g_assert(nodepath->desktop);
4498 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4500 s->nodepath = nodepath;
4501 s->closed = FALSE;
4502 s->nodes = NULL;
4503 s->first = NULL;
4504 s->last = NULL;
4506 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4507 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4508 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4510 return s;
4511 }
4513 /**
4514 * Destroy nodes in subpath, then subpath itself.
4515 */
4516 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4517 {
4518 g_assert(subpath);
4519 g_assert(subpath->nodepath);
4520 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4522 while (subpath->nodes) {
4523 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4524 }
4526 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4528 g_free(subpath);
4529 }
4531 /**
4532 * Link head to tail in subpath.
4533 */
4534 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4535 {
4536 g_assert(!sp->closed);
4537 g_assert(sp->last != sp->first);
4538 g_assert(sp->first->code == NR_MOVETO);
4540 sp->closed = TRUE;
4542 //Link the head to the tail
4543 sp->first->p.other = sp->last;
4544 sp->last->n.other = sp->first;
4545 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4546 sp->first = sp->last;
4548 //Remove the extra end node
4549 sp_nodepath_node_destroy(sp->last->n.other);
4550 }
4552 /**
4553 * Open closed (loopy) subpath at node.
4554 */
4555 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4556 {
4557 g_assert(sp->closed);
4558 g_assert(n->subpath == sp);
4559 g_assert(sp->first == sp->last);
4561 /* We create new startpoint, current node will become last one */
4563 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4564 &n->pos, &n->pos, &n->n.pos);
4567 sp->closed = FALSE;
4569 //Unlink to make a head and tail
4570 sp->first = new_path;
4571 sp->last = n;
4572 n->n.other = NULL;
4573 new_path->p.other = NULL;
4574 }
4576 /**
4577 * Return new node in subpath with given properties.
4578 * \param pos Position of node.
4579 * \param ppos Handle position in previous direction
4580 * \param npos Handle position in previous direction
4581 */
4582 Inkscape::NodePath::Node *
4583 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)
4584 {
4585 g_assert(sp);
4586 g_assert(sp->nodepath);
4587 g_assert(sp->nodepath->desktop);
4589 if (nodechunk == NULL)
4590 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4592 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4594 n->subpath = sp;
4596 if (type != Inkscape::NodePath::NODE_NONE) {
4597 // use the type from sodipodi:nodetypes
4598 n->type = type;
4599 } else {
4600 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4601 // points are (almost) collinear
4602 if (Geom::L2(*pos - *ppos) < 1e-6 || Geom::L2(*pos - *npos) < 1e-6) {
4603 // endnode, or a node with a retracted handle
4604 n->type = Inkscape::NodePath::NODE_CUSP;
4605 } else {
4606 n->type = Inkscape::NodePath::NODE_SMOOTH;
4607 }
4608 } else {
4609 n->type = Inkscape::NodePath::NODE_CUSP;
4610 }
4611 }
4613 n->code = code;
4614 n->selected = FALSE;
4615 n->pos = *pos;
4616 n->p.pos = *ppos;
4617 n->n.pos = *npos;
4619 n->dragging_out = NULL;
4621 Inkscape::NodePath::Node *prev;
4622 if (next) {
4623 //g_assert(g_list_find(sp->nodes, next));
4624 prev = next->p.other;
4625 } else {
4626 prev = sp->last;
4627 }
4629 if (prev)
4630 prev->n.other = n;
4631 else
4632 sp->first = n;
4634 if (next)
4635 next->p.other = n;
4636 else
4637 sp->last = n;
4639 n->p.other = prev;
4640 n->n.other = next;
4642 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"));
4643 sp_knot_set_position(n->knot, *pos, 0);
4645 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4646 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4647 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4649 sp_nodepath_update_node_knot(n);
4651 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4652 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4653 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4654 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4655 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4656 sp_knot_show(n->knot);
4658 // We only create handle knots and lines on demand
4659 n->p.knot = NULL;
4660 n->p.line = NULL;
4661 n->n.knot = NULL;
4662 n->n.line = NULL;
4664 sp->nodes = g_list_prepend(sp->nodes, n);
4666 return n;
4667 }
4669 /**
4670 * Destroy node and its knots, link neighbors in subpath.
4671 */
4672 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4673 {
4674 g_assert(node);
4675 g_assert(node->subpath);
4676 g_assert(SP_IS_KNOT(node->knot));
4678 Inkscape::NodePath::SubPath *sp = node->subpath;
4680 if (node->selected) { // first, deselect
4681 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4682 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4683 }
4685 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4687 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4688 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4689 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4690 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4691 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4692 g_object_unref(G_OBJECT(node->knot));
4694 if (node->p.knot) {
4695 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4696 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4697 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4698 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4699 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4700 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4701 g_object_unref(G_OBJECT(node->p.knot));
4702 node->p.knot = NULL;
4703 }
4705 if (node->n.knot) {
4706 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4707 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4708 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4709 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4710 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4711 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4712 g_object_unref(G_OBJECT(node->n.knot));
4713 node->n.knot = NULL;
4714 }
4716 if (node->p.line)
4717 gtk_object_destroy(GTK_OBJECT(node->p.line));
4718 if (node->n.line)
4719 gtk_object_destroy(GTK_OBJECT(node->n.line));
4721 if (sp->nodes) { // there are others nodes on the subpath
4722 if (sp->closed) {
4723 if (sp->first == node) {
4724 g_assert(sp->last == node);
4725 sp->first = node->n.other;
4726 sp->last = sp->first;
4727 }
4728 node->p.other->n.other = node->n.other;
4729 node->n.other->p.other = node->p.other;
4730 } else {
4731 if (sp->first == node) {
4732 sp->first = node->n.other;
4733 sp->first->code = NR_MOVETO;
4734 }
4735 if (sp->last == node) sp->last = node->p.other;
4736 if (node->p.other) node->p.other->n.other = node->n.other;
4737 if (node->n.other) node->n.other->p.other = node->p.other;
4738 }
4739 } else { // this was the last node on subpath
4740 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4741 }
4743 g_mem_chunk_free(nodechunk, node);
4744 }
4746 /**
4747 * Returns one of the node's two sides.
4748 * \param which Indicates which side.
4749 * \return Pointer to previous node side if which==-1, next if which==1.
4750 */
4751 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4752 {
4753 g_assert(node);
4754 Inkscape::NodePath::NodeSide * result = 0;
4755 switch (which) {
4756 case -1:
4757 result = &node->p;
4758 break;
4759 case 1:
4760 result = &node->n;
4761 break;
4762 default:
4763 g_assert_not_reached();
4764 }
4766 return result;
4767 }
4769 /**
4770 * Return the other side of the node, given one of its sides.
4771 */
4772 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4773 {
4774 g_assert(node);
4775 Inkscape::NodePath::NodeSide *result = 0;
4777 if (me == &node->p) {
4778 result = &node->n;
4779 } else if (me == &node->n) {
4780 result = &node->p;
4781 } else {
4782 g_assert_not_reached();
4783 }
4785 return result;
4786 }
4788 /**
4789 * Return NRPathcode on the given side of the node.
4790 */
4791 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4792 {
4793 g_assert(node);
4795 NRPathcode result = NR_END;
4796 if (me == &node->p) {
4797 if (node->p.other) {
4798 result = (NRPathcode)node->code;
4799 } else {
4800 result = NR_MOVETO;
4801 }
4802 } else if (me == &node->n) {
4803 if (node->n.other) {
4804 result = (NRPathcode)node->n.other->code;
4805 } else {
4806 result = NR_MOVETO;
4807 }
4808 } else {
4809 g_assert_not_reached();
4810 }
4812 return result;
4813 }
4815 /**
4816 * Return node with the given index
4817 */
4818 Inkscape::NodePath::Node *
4819 sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index)
4820 {
4821 Inkscape::NodePath::Node *e = NULL;
4823 if (!nodepath) {
4824 return e;
4825 }
4827 //find segment
4828 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4830 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4831 int n = g_list_length(sp->nodes);
4832 if (sp->closed) {
4833 n++;
4834 }
4836 //if the piece belongs to this subpath grab it
4837 //otherwise move onto the next subpath
4838 if (index < n) {
4839 e = sp->first;
4840 for (int i = 0; i < index; ++i) {
4841 e = e->n.other;
4842 }
4843 break;
4844 } else {
4845 if (sp->closed) {
4846 index -= (n+1);
4847 } else {
4848 index -= n;
4849 }
4850 }
4851 }
4853 return e;
4854 }
4856 /**
4857 * Returns plain text meaning of node type.
4858 */
4859 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4860 {
4861 unsigned retracted = 0;
4862 bool endnode = false;
4864 for (int which = -1; which <= 1; which += 2) {
4865 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4866 if (side->other && Geom::L2(side->pos - node->pos) < 1e-6)
4867 retracted ++;
4868 if (!side->other)
4869 endnode = true;
4870 }
4872 if (retracted == 0) {
4873 if (endnode) {
4874 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4875 return _("end node");
4876 } else {
4877 switch (node->type) {
4878 case Inkscape::NodePath::NODE_CUSP:
4879 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4880 return _("cusp");
4881 case Inkscape::NodePath::NODE_SMOOTH:
4882 // TRANSLATORS: "smooth" is an adjective here
4883 return _("smooth");
4884 case Inkscape::NodePath::NODE_AUTO:
4885 return _("auto");
4886 case Inkscape::NodePath::NODE_SYMM:
4887 return _("symmetric");
4888 }
4889 }
4890 } else if (retracted == 1) {
4891 if (endnode) {
4892 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4893 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4894 } else {
4895 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4896 }
4897 } else {
4898 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4899 }
4901 return NULL;
4902 }
4904 /**
4905 * Handles content of statusbar as long as node tool is active.
4906 */
4907 void
4908 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4909 {
4910 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");
4911 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4913 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4914 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4915 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4916 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4918 SPDesktop *desktop = NULL;
4919 if (nodepath) {
4920 desktop = nodepath->desktop;
4921 } else {
4922 desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above
4923 }
4925 SPEventContext *ec = desktop->event_context;
4926 if (!ec) return;
4928 Inkscape::MessageContext *mc = get_message_context(ec);
4929 if (!mc) return;
4931 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4933 if (selected_nodes == 0) {
4934 Inkscape::Selection *sel = desktop->selection;
4935 if (!sel || sel->isEmpty()) {
4936 mc->setF(Inkscape::NORMAL_MESSAGE,
4937 _("Select a single object to edit its nodes or handles."));
4938 } else {
4939 if (nodepath) {
4940 mc->setF(Inkscape::NORMAL_MESSAGE,
4941 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.",
4942 "<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.",
4943 total_nodes),
4944 total_nodes);
4945 } else {
4946 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4947 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4948 } else {
4949 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4950 }
4951 }
4952 }
4953 } else if (nodepath && selected_nodes == 1) {
4954 mc->setF(Inkscape::NORMAL_MESSAGE,
4955 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4956 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4957 total_nodes),
4958 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4959 } else {
4960 if (selected_subpaths > 1) {
4961 mc->setF(Inkscape::NORMAL_MESSAGE,
4962 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4963 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4964 total_nodes),
4965 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4966 } else {
4967 mc->setF(Inkscape::NORMAL_MESSAGE,
4968 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4969 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4970 total_nodes),
4971 selected_nodes, total_nodes, when_selected);
4972 }
4973 }
4974 }
4976 /*
4977 * returns a *copy* of the curve of that object.
4978 */
4979 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4980 if (!object)
4981 return NULL;
4983 SPCurve *curve = NULL;
4984 if (SP_IS_PATH(object)) {
4985 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4986 curve = curve_new->copy();
4987 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4988 const gchar *svgd = object->repr->attribute(key);
4989 if (svgd) {
4990 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4991 SPCurve *curve_new = new SPCurve(pv);
4992 if (curve_new) {
4993 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4994 }
4995 }
4996 }
4998 return curve;
4999 }
5001 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
5002 if (!np || !np->object || !curve)
5003 return;
5005 if (SP_IS_PATH(np->object)) {
5006 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
5007 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
5008 } else {
5009 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
5010 }
5011 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
5012 Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(np->object)->get_lpe();
5013 if (lpe) {
5014 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( lpe->getParameter(np->repr_key) );
5015 if (pathparam) {
5016 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
5017 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
5018 }
5019 }
5020 }
5021 }
5023 /*
5024 SPCanvasItem *
5025 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
5026 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
5027 }
5028 */
5030 /*
5031 SPCanvasItem *
5032 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
5033 SPCurve *flash_curve = curve->copy();
5034 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
5035 flash_curve->transform(i2d);
5036 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
5037 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
5038 // unless we also flash the nodes...
5039 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
5040 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
5041 sp_canvas_item_show(canvasitem);
5042 flash_curve->unref();
5043 return canvasitem;
5044 }
5046 SPCanvasItem *
5047 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
5048 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
5049 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
5050 prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff));
5051 }
5052 */
5054 SPCanvasItem *
5055 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
5056 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
5057 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
5058 flash_curve->transform(i2d);
5059 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
5060 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
5061 // unless we also flash the nodes...
5062 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
5063 guint32 color = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
5064 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
5065 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
5066 sp_canvas_item_show(canvasitem);
5067 flash_curve->unref();
5068 return canvasitem;
5069 }
5071 // TODO: Merge this with sp_nodepath_make_helper_item()!
5072 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
5073 np->show_helperpath = show;
5075 if (show) {
5076 SPCurve *helper_curve = np->curve->copy();
5077 helper_curve->transform(np->i2d);
5078 if (!np->helper_path) {
5079 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
5081 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
5082 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);
5083 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
5084 sp_canvas_item_move_to_z(np->helper_path, 0);
5085 sp_canvas_item_show(np->helper_path);
5086 } else {
5087 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
5088 }
5089 helper_curve->unref();
5090 } else {
5091 if (np->helper_path) {
5092 GtkObject *temp = np->helper_path;
5093 np->helper_path = NULL;
5094 gtk_object_destroy(temp);
5095 }
5096 }
5097 }
5099 /* sp_nodepath_make_straight_path:
5100 * Prevents user from curving the path by dragging a segment or activating handles etc.
5101 * The resulting path is a linear interpolation between nodal points, with only straight segments.
5102 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
5103 */
5104 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
5105 np->straight_path = true;
5106 np->show_handles = false;
5107 g_message("add code to make the path straight.");
5108 // do sp_nodepath_convert_node_type on all nodes?
5109 // coding tip: search for this text : "Make selected segments lines"
5110 }
5112 /*
5113 Local Variables:
5114 mode:c++
5115 c-file-style:"stroustrup"
5116 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
5117 indent-tabs-mode:nil
5118 fill-column:99
5119 End:
5120 */
5121 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :