1 #define __SP_NODEPATH_C__
3 /** \file
4 * Path handler in node edit mode
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * Portions of this code are in public domain; node sculpting functions written by bulia byak are under GNU GPL
11 */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "display/canvas-bpath.h"
19 #include "display/curve.h"
20 #include "display/sp-ctrlline.h"
21 #include "display/sodipodi-ctrl.h"
22 #include "display/sp-canvas-util.h"
23 #include <glibmm/i18n.h>
24 #include "2geom/pathvector.h"
25 #include "2geom/sbasis-to-bezier.h"
26 #include "2geom/bezier-curve.h"
27 #include "2geom/hvlinesegment.h"
28 #include "helper/units.h"
29 #include "helper/geom.h"
30 #include "knot.h"
31 #include "inkscape.h"
32 #include "document.h"
33 #include "sp-namedview.h"
34 #include "desktop.h"
35 #include "desktop-handles.h"
36 #include "snap.h"
37 #include "message-stack.h"
38 #include "message-context.h"
39 #include "node-context.h"
40 #include "lpe-tool-context.h"
41 #include "shape-editor.h"
42 #include "selection-chemistry.h"
43 #include "selection.h"
44 #include "xml/repr.h"
45 #include "preferences.h"
46 #include "sp-metrics.h"
47 #include "sp-path.h"
48 #include "libnr/nr-matrix-ops.h"
49 #include "svg/svg.h"
50 #include "verbs.h"
51 #include "display/bezier-utils.h"
52 #include <vector>
53 #include <algorithm>
54 #include <cstring>
55 #include <cmath>
56 #include "live_effects/lpeobject.h"
57 #include "live_effects/lpeobject-reference.h"
58 #include "live_effects/effect.h"
59 #include "live_effects/parameter/parameter.h"
60 #include "live_effects/parameter/path.h"
61 #include "util/mathfns.h"
62 #include "display/snap-indicator.h"
63 #include "snapped-point.h"
65 namespace Geom { class Matrix; }
67 /// \todo
68 /// evil evil evil. FIXME: conflict of two different Path classes!
69 /// There is a conflict in the namespace between two classes named Path.
70 /// #include "sp-flowtext.h"
71 /// #include "sp-flowregion.h"
73 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
74 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
75 GType sp_flowregion_get_type (void);
76 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
77 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
78 GType sp_flowtext_get_type (void);
79 // end evil workaround
81 #include "helper/stlport.h"
84 /// \todo fixme: Implement these via preferences */
86 #define NODE_FILL 0xbfbfbf00
87 #define NODE_STROKE 0x000000ff
88 #define NODE_FILL_HI 0xff000000
89 #define NODE_STROKE_HI 0x000000ff
90 #define NODE_FILL_SEL 0x0000ffff
91 #define NODE_STROKE_SEL 0x000000ff
92 #define NODE_FILL_SEL_HI 0xff000000
93 #define NODE_STROKE_SEL_HI 0x000000ff
94 #define KNOT_FILL 0xffffffff
95 #define KNOT_STROKE 0x000000ff
96 #define KNOT_FILL_HI 0xff000000
97 #define KNOT_STROKE_HI 0x000000ff
99 static GMemChunk *nodechunk = NULL;
101 /* Creation from object */
103 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t);
104 static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length);
106 /* Object updating */
108 static void stamp_repr(Inkscape::NodePath::Path *np);
109 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
110 static gchar *create_typestr(Inkscape::NodePath::Path *np);
112 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
114 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
116 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
118 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
120 /* Adjust handle placement, if the node or the other handle is moved */
121 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
122 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
123 static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node);
125 /* Node event callbacks */
126 static void node_clicked(SPKnot *knot, guint state, gpointer data);
127 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
128 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
129 static gboolean node_request(SPKnot *knot, Geom::Point 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 const &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 sp_node_adjust_handle(start, -1);
1069 sp_node_adjust_handle(end, 1);
1071 } else {
1072 Geom::Point delta = end->pos - start->pos;
1073 start->n.pos = start->pos + delta / 3;
1074 end->p.pos = end->pos - delta / 3;
1075 sp_node_adjust_handle(start, 1);
1076 sp_node_adjust_handle(end, -1);
1077 }
1079 sp_node_update_handles(start);
1080 sp_node_update_handles(end);
1081 }
1082 }
1084 static void
1085 sp_nodepath_update_node_knot(Inkscape::NodePath::Node *node)
1086 {
1087 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1088 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1089 node->knot->setSize (node->selected? 11 : 9);
1090 sp_knot_update_ctrl(node->knot);
1091 } else if (node->type == Inkscape::NodePath::NODE_AUTO) {
1092 node->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1093 node->knot->setSize (node->selected? 11 : 9);
1094 sp_knot_update_ctrl(node->knot);
1095 } else {
1096 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1097 node->knot->setSize (node->selected? 9 : 7);
1098 sp_knot_update_ctrl(node->knot);
1099 }
1100 }
1103 /**
1104 * Change node type, and its handles accordingly.
1105 */
1106 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1107 {
1108 g_assert(node);
1109 g_assert(node->subpath);
1111 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1112 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1113 type =Inkscape::NodePath::NODE_CUSP;
1114 }
1115 }
1117 node->type = type;
1119 sp_nodepath_update_node_knot(node);
1121 // if one of handles is mouseovered, preserve its position
1122 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1123 sp_node_adjust_handle(node, 1);
1124 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1125 sp_node_adjust_handle(node, -1);
1126 } else {
1127 sp_node_adjust_handles(node);
1128 }
1130 sp_node_update_handles(node);
1132 sp_nodepath_update_statusbar(node->subpath->nodepath);
1134 return node;
1135 }
1137 bool
1138 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1139 {
1140 // TODO clean up multiple returns
1141 Inkscape::NodePath::Node *othernode = side->other;
1142 if (!othernode)
1143 return false;
1144 NRPathcode const code = sp_node_path_code_from_side(node, side);
1145 if (code == NR_LINETO)
1146 return true;
1147 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1148 if (&node->p == side) {
1149 other_to_me = &othernode->n;
1150 } else if (&node->n == side) {
1151 other_to_me = &othernode->p;
1152 }
1153 if (!other_to_me)
1154 return false;
1155 bool is_line =
1156 (Geom::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1157 Geom::L2(node->pos - side->pos) < 1e-6);
1158 return is_line;
1159 }
1161 /**
1162 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1163 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1164 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1165 * If already cusp and set to cusp, retracts handles.
1166 */
1167 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1168 {
1169 if (type == Inkscape::NodePath::NODE_AUTO) {
1170 if (node->p.other != NULL)
1171 node->code = NR_CURVETO;
1172 if (node->n.other != NULL)
1173 node->n.other->code = NR_CURVETO;
1174 }
1176 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1178 /*
1179 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1181 if (two_handles) {
1182 // do nothing, adjust_handles called via set_node_type will line them up
1183 } else if (one_handle) {
1184 if (opposite_to_handle_is_line) {
1185 if (lined_up) {
1186 // already half-smooth; pull opposite handle too making it fully smooth
1187 } else {
1188 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1189 }
1190 } else {
1191 // pull opposite handle in line with the existing one
1192 }
1193 } else if (no_handles) {
1194 if (both_segments_are_lines OR both_segments_are_curves) {
1195 //pull both handles
1196 } else {
1197 // pull the handle opposite to line segment, making node half-smooth
1198 }
1199 }
1200 */
1201 bool p_has_handle = (Geom::L2(node->pos - node->p.pos) > 1e-6);
1202 bool n_has_handle = (Geom::L2(node->pos - node->n.pos) > 1e-6);
1203 bool p_is_line = sp_node_side_is_line(node, &node->p);
1204 bool n_is_line = sp_node_side_is_line(node, &node->n);
1206 if (p_has_handle && n_has_handle) {
1207 // do nothing, adjust_handles will line them up
1208 } else if (p_has_handle || n_has_handle) {
1209 if (p_has_handle && n_is_line) {
1210 Radial line (node->n.other->pos - node->pos);
1211 Radial handle (node->pos - node->p.pos);
1212 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1213 // already half-smooth; pull opposite handle too making it fully smooth
1214 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1215 } else {
1216 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1217 }
1218 } else if (n_has_handle && p_is_line) {
1219 Radial line (node->p.other->pos - node->pos);
1220 Radial handle (node->pos - node->n.pos);
1221 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1222 // already half-smooth; pull opposite handle too making it fully smooth
1223 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1224 } else {
1225 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1226 }
1227 } else if (p_has_handle && node->n.other) {
1228 // pull n handle
1229 node->n.other->code = NR_CURVETO;
1230 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1231 Geom::L2(node->p.pos - node->pos) :
1232 Geom::L2(node->n.other->pos - node->pos) / 3;
1233 node->n.pos = node->pos - (len / Geom::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1234 } else if (n_has_handle && node->p.other) {
1235 // pull p handle
1236 node->code = NR_CURVETO;
1237 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1238 Geom::L2(node->n.pos - node->pos) :
1239 Geom::L2(node->p.other->pos - node->pos) / 3;
1240 node->p.pos = node->pos - (len / Geom::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1241 }
1242 } else if (!p_has_handle && !n_has_handle) {
1243 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1244 // no handles, but both segments are either lnes or curves:
1245 //pull both handles
1247 // convert both to curves:
1248 node->code = NR_CURVETO;
1249 node->n.other->code = NR_CURVETO;
1251 sp_node_adjust_handles_auto(node);
1252 } else {
1253 // pull the handle opposite to line segment, making it half-smooth
1254 if (p_is_line && node->n.other) {
1255 if (type != Inkscape::NodePath::NODE_SYMM) {
1256 // pull n handle
1257 node->n.other->code = NR_CURVETO;
1258 double len = Geom::L2(node->n.other->pos - node->pos) / 3;
1259 node->n.pos = node->pos + (len / Geom::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1260 }
1261 } else if (n_is_line && node->p.other) {
1262 if (type != Inkscape::NodePath::NODE_SYMM) {
1263 // pull p handle
1264 node->code = NR_CURVETO;
1265 double len = Geom::L2(node->p.other->pos - node->pos) / 3;
1266 node->p.pos = node->pos + (len / Geom::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1267 }
1268 }
1269 }
1270 }
1271 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1272 // cusping a cusp: retract nodes
1273 node->p.pos = node->pos;
1274 node->n.pos = node->pos;
1275 }
1277 sp_nodepath_set_node_type (node, type);
1278 }
1280 /**
1281 * Move node to point, and adjust its and neighbouring handles.
1282 */
1283 void sp_node_moveto(Inkscape::NodePath::Node *node, Geom::Point p)
1284 {
1285 if (node->type == Inkscape::NodePath::NODE_AUTO) {
1286 node->pos = p;
1287 sp_node_adjust_handles_auto(node);
1288 } else {
1289 Geom::Point delta = p - node->pos;
1290 node->pos = p;
1292 node->p.pos += delta;
1293 node->n.pos += delta;
1294 }
1296 Inkscape::NodePath::Node *node_p = NULL;
1297 Inkscape::NodePath::Node *node_n = NULL;
1299 if (node->p.other) {
1300 if (node->code == NR_LINETO) {
1301 sp_node_adjust_handle(node, 1);
1302 sp_node_adjust_handle(node->p.other, -1);
1303 node_p = node->p.other;
1304 }
1305 if (!node->p.other->selected && node->p.other->type == Inkscape::NodePath::NODE_AUTO) {
1306 sp_node_adjust_handles_auto(node->p.other);
1307 node_p = node->p.other;
1308 }
1309 }
1310 if (node->n.other) {
1311 if (node->n.other->code == NR_LINETO) {
1312 sp_node_adjust_handle(node, -1);
1313 sp_node_adjust_handle(node->n.other, 1);
1314 node_n = node->n.other;
1315 }
1316 if (!node->n.other->selected && node->n.other->type == Inkscape::NodePath::NODE_AUTO) {
1317 sp_node_adjust_handles_auto(node->n.other);
1318 node_n = node->n.other;
1319 }
1320 }
1322 // this function is only called from batch movers that will update display at the end
1323 // themselves, so here we just move all the knots without emitting move signals, for speed
1324 sp_node_update_handles(node, false);
1325 if (node_n) {
1326 sp_node_update_handles(node_n, false);
1327 }
1328 if (node_p) {
1329 sp_node_update_handles(node_p, false);
1330 }
1331 }
1333 /**
1334 * Call sp_node_moveto() for node selection and handle possible snapping.
1335 */
1336 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, Geom::Coord dx, Geom::Coord dy,
1337 bool const snap, bool constrained = false,
1338 Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point())
1339 {
1340 Geom::Point delta(dx, dy);
1341 Geom::Point best_pt = delta;
1342 Inkscape::SnappedPoint best;
1344 if (snap) {
1345 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1346 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1347 * must provide that information. */
1349 // Build a list of the unselected nodes to which the snapper should snap
1350 std::vector<Geom::Point> unselected_nodes;
1351 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1352 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1353 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1354 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1355 if (!node->selected) {
1356 unselected_nodes.push_back(to_2geom(node->pos));
1357 }
1358 }
1359 }
1361 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1363 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1364 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1365 m.setup(nodepath->desktop, false, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1366 Inkscape::SnappedPoint s;
1368 if (constrained) {
1369 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1370 dedicated_constraint.setPoint(n->pos);
1371 s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), dedicated_constraint);
1372 } else {
1373 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta));
1374 }
1376 if (s.getSnapped()) {
1377 s.setPointerDistance(Geom::L2(nodepath->drag_origin_mouse - n->origin));
1378 if (!s.isOtherOneBetter(best, true)) {
1379 best = s;
1380 best_pt = from_2geom(s.getPoint()) - n->pos;
1381 }
1382 }
1383 }
1385 if (best.getSnapped()) {
1386 nodepath->desktop->snapindicator->set_new_snappoint(best);
1387 } else {
1388 nodepath->desktop->snapindicator->remove_snappoint();
1389 }
1390 }
1392 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1393 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1394 sp_node_moveto(n, n->pos + best_pt);
1395 }
1397 // do not update repr here so that node dragging is acceptably fast
1398 update_object(nodepath);
1399 }
1401 /**
1402 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1403 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1404 near x = 0.
1405 */
1406 double
1407 sculpt_profile (double x, double alpha, guint profile)
1408 {
1409 double result = 1;
1411 if (x >= 1) {
1412 result = 0;
1413 } else if (x <= 0) {
1414 result = 1;
1415 } else {
1416 switch (profile) {
1417 case SCULPT_PROFILE_LINEAR:
1418 result = 1 - x;
1419 break;
1420 case SCULPT_PROFILE_BELL:
1421 result = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1422 break;
1423 case SCULPT_PROFILE_ELLIPTIC:
1424 result = sqrt(1 - x*x);
1425 break;
1426 default:
1427 g_assert_not_reached();
1428 }
1429 }
1431 return result;
1432 }
1434 double
1435 bezier_length (Geom::Point a, Geom::Point ah, Geom::Point bh, Geom::Point b)
1436 {
1437 // extremely primitive for now, don't have time to look for the real one
1438 double lower = Geom::L2(b - a);
1439 double upper = Geom::L2(ah - a) + Geom::L2(bh - ah) + Geom::L2(bh - b);
1440 return (lower + upper)/2;
1441 }
1443 void
1444 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, Geom::Point delta, Geom::Point delta_n, Geom::Point delta_p)
1445 {
1446 n->pos = n->origin + delta;
1447 n->n.pos = n->n.origin + delta_n;
1448 n->p.pos = n->p.origin + delta_p;
1449 sp_node_adjust_handles(n);
1450 sp_node_update_handles(n, false);
1451 }
1453 /**
1454 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1455 * on how far they are from the dragged node n.
1456 */
1457 static void
1458 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, Geom::Point delta)
1459 {
1460 g_assert (n);
1461 g_assert (nodepath);
1462 g_assert (n->subpath->nodepath == nodepath);
1464 double pressure = n->knot->pressure;
1465 if (pressure == 0)
1466 pressure = 0.5; // default
1467 pressure = CLAMP (pressure, 0.2, 0.8);
1469 // map pressure to alpha = 1/5 ... 5
1470 double alpha = 1 - 2 * fabs(pressure - 0.5);
1471 if (pressure > 0.5)
1472 alpha = 1/alpha;
1474 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1475 guint profile = prefs->getInt("/tools/nodes/sculpting_profile", SCULPT_PROFILE_BELL);
1477 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1478 // Only one subpath has selected nodes:
1479 // use linear mode, where the distance from n to node being dragged is calculated along the path
1481 double n_sel_range = 0, p_sel_range = 0;
1482 guint n_nodes = 0, p_nodes = 0;
1483 guint n_sel_nodes = 0, p_sel_nodes = 0;
1485 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1486 {
1487 double n_range = 0, p_range = 0;
1488 bool n_going = true, p_going = true;
1489 Inkscape::NodePath::Node *n_node = n;
1490 Inkscape::NodePath::Node *p_node = n;
1491 do {
1492 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1493 if (n_node && n_going)
1494 n_node = n_node->n.other;
1495 if (n_node == NULL) {
1496 n_going = false;
1497 } else {
1498 n_nodes ++;
1499 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1500 if (n_node->selected) {
1501 n_sel_nodes ++;
1502 n_sel_range = n_range;
1503 }
1504 if (n_node == p_node) {
1505 n_going = false;
1506 p_going = false;
1507 }
1508 }
1509 if (p_node && p_going)
1510 p_node = p_node->p.other;
1511 if (p_node == NULL) {
1512 p_going = false;
1513 } else {
1514 p_nodes ++;
1515 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1516 if (p_node->selected) {
1517 p_sel_nodes ++;
1518 p_sel_range = p_range;
1519 }
1520 if (p_node == n_node) {
1521 n_going = false;
1522 p_going = false;
1523 }
1524 }
1525 } while (n_going || p_going);
1526 }
1528 // Second pass: actually move nodes in this subpath
1529 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1530 {
1531 double n_range = 0, p_range = 0;
1532 bool n_going = true, p_going = true;
1533 Inkscape::NodePath::Node *n_node = n;
1534 Inkscape::NodePath::Node *p_node = n;
1535 do {
1536 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1537 if (n_node && n_going)
1538 n_node = n_node->n.other;
1539 if (n_node == NULL) {
1540 n_going = false;
1541 } else {
1542 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1543 if (n_node->selected) {
1544 sp_nodepath_move_node_and_handles (n_node,
1545 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1546 sculpt_profile ((n_range + Geom::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1547 sculpt_profile ((n_range - Geom::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1548 }
1549 if (n_node == p_node) {
1550 n_going = false;
1551 p_going = false;
1552 }
1553 }
1554 if (p_node && p_going)
1555 p_node = p_node->p.other;
1556 if (p_node == NULL) {
1557 p_going = false;
1558 } else {
1559 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1560 if (p_node->selected) {
1561 sp_nodepath_move_node_and_handles (p_node,
1562 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1563 sculpt_profile ((p_range - Geom::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1564 sculpt_profile ((p_range + Geom::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1565 }
1566 if (p_node == n_node) {
1567 n_going = false;
1568 p_going = false;
1569 }
1570 }
1571 } while (n_going || p_going);
1572 }
1574 } else {
1575 // Multiple subpaths have selected nodes:
1576 // use spatial mode, where the distance from n to node being dragged is measured directly as Geom::L2.
1577 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1578 // fix the pear-like shape when sculpting e.g. a ring
1580 // First pass: calculate range
1581 gdouble direct_range = 0;
1582 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1583 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1584 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1585 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1586 if (node->selected) {
1587 direct_range = MAX(direct_range, Geom::L2(node->origin - n->origin));
1588 }
1589 }
1590 }
1592 // Second pass: actually move nodes
1593 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1594 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1595 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1596 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1597 if (node->selected) {
1598 if (direct_range > 1e-6) {
1599 sp_nodepath_move_node_and_handles (node,
1600 sculpt_profile (Geom::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1601 sculpt_profile (Geom::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1602 sculpt_profile (Geom::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1603 } else {
1604 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1605 }
1607 }
1608 }
1609 }
1610 }
1612 // do not update repr here so that node dragging is acceptably fast
1613 update_object(nodepath);
1614 }
1617 /**
1618 * Move node selection to point, adjust its and neighbouring handles,
1619 * handle possible snapping, and commit the change with possible undo.
1620 */
1621 void
1622 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1623 {
1624 if (!nodepath) return;
1626 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1628 if (dx == 0) {
1629 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1630 } else if (dy == 0) {
1631 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1632 } else {
1633 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1634 }
1635 }
1637 /**
1638 * Move node selection off screen and commit the change.
1639 */
1640 void
1641 sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1642 {
1643 // borrowed from sp_selection_move_screen in selection-chemistry.c
1644 // we find out the current zoom factor and divide deltas by it
1646 gdouble zoom = desktop->current_zoom();
1647 gdouble zdx = dx / zoom;
1648 gdouble zdy = dy / zoom;
1650 if (!nodepath) return;
1652 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1654 if (dx == 0) {
1655 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1656 } else if (dy == 0) {
1657 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1658 } else {
1659 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1660 }
1661 }
1663 /**
1664 * Move selected nodes to the absolute position given
1665 */
1666 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
1667 {
1668 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1669 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1670 Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
1671 sp_node_moveto(n, npos);
1672 }
1674 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1675 }
1677 /**
1678 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return Geom::Nothing
1679 */
1680 boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1681 {
1682 boost::optional<Geom::Coord> no_coord;
1683 g_return_val_if_fail(nodepath->selected, no_coord);
1685 // determine coordinate of first selected node
1686 GList *nsel = nodepath->selected;
1687 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1688 Geom::Coord coord = n->pos[axis];
1689 bool coincide = true;
1691 // compare it to the coordinates of all the other selected nodes
1692 for (GList *l = nsel->next; l != NULL; l = l->next) {
1693 n = (Inkscape::NodePath::Node *) l->data;
1694 if (n->pos[axis] != coord) {
1695 coincide = false;
1696 }
1697 }
1698 if (coincide) {
1699 return coord;
1700 } else {
1701 Geom::Rect bbox = sp_node_selected_bbox(nodepath);
1702 // currently we return the coordinate of the bounding box midpoint because I don't know how
1703 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1704 return bbox.midpoint()[axis];
1705 }
1706 }
1708 /** If they don't yet exist, creates knot and line for the given side of the node */
1709 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1710 {
1711 if (!side->knot) {
1712 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"));
1714 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1715 side->knot->setSize (7);
1716 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1717 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1718 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1719 sp_knot_update_ctrl(side->knot);
1721 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1722 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1723 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1724 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1725 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1726 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1727 }
1729 if (!side->line) {
1730 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1731 SP_TYPE_CTRLLINE, NULL);
1732 }
1733 }
1735 /**
1736 * Ensure the given handle of the node is visible/invisible, update its screen position
1737 */
1738 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1739 {
1740 g_assert(node != NULL);
1742 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1743 NRPathcode code = sp_node_path_code_from_side(node, side);
1745 show_handle = show_handle && (code == NR_CURVETO) && (Geom::L2(side->pos - node->pos) > 1e-6);
1747 if (show_handle) {
1748 if (!side->knot) { // No handle knot at all
1749 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1750 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1751 side->knot->pos = side->pos;
1752 if (side->knot->item)
1753 SP_CTRL(side->knot->item)->moveto(side->pos);
1754 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1755 sp_knot_show(side->knot);
1756 } else {
1757 if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1758 if (fire_move_signals) {
1759 sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1760 } else {
1761 sp_knot_moveto(side->knot, side->pos);
1762 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1763 }
1764 }
1765 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1766 sp_knot_show(side->knot);
1767 }
1768 }
1769 sp_canvas_item_show(side->line);
1770 } else {
1771 if (side->knot) {
1772 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1773 sp_knot_hide(side->knot);
1774 }
1775 }
1776 if (side->line) {
1777 sp_canvas_item_hide(side->line);
1778 }
1779 }
1780 }
1782 /**
1783 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1784 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1785 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1786 * updated; otherwise, just move the knots silently (used in batch moves).
1787 */
1788 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1789 {
1790 g_assert(node != NULL);
1792 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1793 sp_knot_show(node->knot);
1794 }
1796 if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1797 if (fire_move_signals)
1798 sp_knot_set_position(node->knot, node->pos, 0);
1799 else
1800 sp_knot_moveto(node->knot, node->pos);
1801 }
1803 gboolean show_handles = node->selected;
1804 if (node->p.other != NULL) {
1805 if (node->p.other->selected) show_handles = TRUE;
1806 }
1807 if (node->n.other != NULL) {
1808 if (node->n.other->selected) show_handles = TRUE;
1809 }
1811 if (node->subpath->nodepath->show_handles == false)
1812 show_handles = FALSE;
1814 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1815 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1816 }
1818 /**
1819 * Call sp_node_update_handles() for all nodes on subpath.
1820 */
1821 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1822 {
1823 g_assert(subpath != NULL);
1825 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1826 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1827 }
1828 }
1830 /**
1831 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1832 */
1833 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1834 {
1835 g_assert(nodepath != NULL);
1837 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1838 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1839 }
1840 }
1842 void
1843 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1844 {
1845 if (nodepath) {
1846 nodepath->show_handles = show;
1847 sp_nodepath_update_handles(nodepath);
1848 }
1849 }
1851 /**
1852 * Adds all selected nodes in nodepath to list.
1853 */
1854 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1855 {
1856 StlConv<Node *>::list(l, selected);
1857 /// \todo this adds a copying, rework when the selection becomes a stl list
1858 }
1860 /**
1861 * Align selected nodes on the specified axis.
1862 */
1863 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1864 {
1865 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1866 return;
1867 }
1869 if ( !nodepath->selected->next ) { // only one node selected
1870 return;
1871 }
1872 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1873 Geom::Point dest(pNode->pos);
1874 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1875 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1876 if (pNode) {
1877 dest[axis] = pNode->pos[axis];
1878 sp_node_moveto(pNode, dest);
1879 }
1880 }
1882 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1883 }
1885 /// Helper struct.
1886 struct NodeSort
1887 {
1888 Inkscape::NodePath::Node *_node;
1889 Geom::Coord _coord;
1890 /// \todo use vectorof pointers instead of calling copy ctor
1891 NodeSort(Inkscape::NodePath::Node *node, Geom::Dim2 axis) :
1892 _node(node), _coord(node->pos[axis])
1893 {}
1895 };
1897 static bool operator<(NodeSort const &a, NodeSort const &b)
1898 {
1899 return (a._coord < b._coord);
1900 }
1902 /**
1903 * Distribute selected nodes on the specified axis.
1904 */
1905 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1906 {
1907 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1908 return;
1909 }
1911 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1912 return;
1913 }
1915 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1916 std::vector<NodeSort> sorted;
1917 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1918 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1919 if (pNode) {
1920 NodeSort n(pNode, axis);
1921 sorted.push_back(n);
1922 //dest[axis] = pNode->pos[axis];
1923 //sp_node_moveto(pNode, dest);
1924 }
1925 }
1926 std::sort(sorted.begin(), sorted.end());
1927 unsigned int len = sorted.size();
1928 //overall bboxes span
1929 float dist = (sorted.back()._coord -
1930 sorted.front()._coord);
1931 //new distance between each bbox
1932 float step = (dist) / (len - 1);
1933 float pos = sorted.front()._coord;
1934 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1935 it < sorted.end();
1936 it ++ )
1937 {
1938 Geom::Point dest((*it)._node->pos);
1939 dest[axis] = pos;
1940 sp_node_moveto((*it)._node, dest);
1941 pos += step;
1942 }
1944 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1945 }
1948 /**
1949 * Call sp_nodepath_line_add_node() for all selected segments.
1950 */
1951 void
1952 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1953 {
1954 if (!nodepath) {
1955 return;
1956 }
1958 GList *nl = NULL;
1960 int n_added = 0;
1962 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1963 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1964 g_assert(t->selected);
1965 if (t->p.other && t->p.other->selected) {
1966 nl = g_list_prepend(nl, t);
1967 }
1968 }
1970 while (nl) {
1971 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1972 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1973 sp_nodepath_node_select(n, TRUE, FALSE);
1974 n_added ++;
1975 nl = g_list_remove(nl, t);
1976 }
1978 /** \todo fixme: adjust ? */
1979 sp_nodepath_update_handles(nodepath);
1981 if (n_added > 1) {
1982 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1983 } else if (n_added > 0) {
1984 sp_nodepath_update_repr(nodepath, _("Add node"));
1985 }
1987 sp_nodepath_update_statusbar(nodepath);
1988 }
1990 /**
1991 * Select segment nearest to point
1992 */
1993 void
1994 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p, bool toggle)
1995 {
1996 if (!nodepath) {
1997 return;
1998 }
2000 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2001 Geom::PathVector const &pathv = curve->get_pathvector();
2002 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2003 if (!pvpos) {
2004 g_print ("Possible error?\n");
2005 return;
2006 }
2008 // calculate index for nodepath's representation.
2009 unsigned int segment_index = floor(pvpos->t) + 1;
2010 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2011 segment_index += pathv[i].size() + 1;
2012 if (pathv[i].closed()) {
2013 segment_index += 1;
2014 }
2015 }
2017 curve->unref();
2019 //find segment to segment
2020 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2022 //fixme: this can return NULL, so check before proceeding.
2023 g_return_if_fail(e != NULL);
2025 gboolean force = FALSE;
2026 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
2027 force = TRUE;
2028 }
2029 sp_nodepath_node_select(e, (gboolean) toggle, force);
2030 if (e->p.other)
2031 sp_nodepath_node_select(e->p.other, TRUE, force);
2033 sp_nodepath_update_handles(nodepath);
2035 sp_nodepath_update_statusbar(nodepath);
2036 }
2038 /**
2039 * Add a node nearest to point
2040 */
2041 void
2042 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p)
2043 {
2044 if (!nodepath) {
2045 return;
2046 }
2048 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2049 Geom::PathVector const &pathv = curve->get_pathvector();
2050 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2051 if (!pvpos) {
2052 g_print ("Possible error?\n");
2053 return;
2054 }
2056 // calculate index for nodepath's representation.
2057 double int_part;
2058 double t = std::modf(pvpos->t, &int_part);
2059 unsigned int segment_index = (unsigned int)int_part + 1;
2060 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2061 segment_index += pathv[i].size() + 1;
2062 if (pathv[i].closed()) {
2063 segment_index += 1;
2064 }
2065 }
2067 curve->unref();
2069 //find segment to split
2070 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2071 if (!e) {
2072 return;
2073 }
2075 //don't know why but t seems to flip for lines
2076 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2077 t = 1.0 - t;
2078 }
2080 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2081 sp_nodepath_node_select(n, FALSE, TRUE);
2083 /* fixme: adjust ? */
2084 sp_nodepath_update_handles(nodepath);
2086 sp_nodepath_update_repr(nodepath, _("Add node"));
2088 sp_nodepath_update_statusbar(nodepath);
2089 }
2091 /*
2092 * Adjusts a segment so that t moves by a certain delta for dragging
2093 * converts lines to curves
2094 *
2095 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2096 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2097 */
2098 void
2099 sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, Geom::Point delta)
2100 {
2101 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node);
2103 //fixme: e and e->p can be NULL, so check for those before proceeding
2104 g_return_if_fail(e != NULL);
2105 g_return_if_fail(&e->p != NULL);
2107 if (e->type == Inkscape::NodePath::NODE_AUTO) {
2108 e->type = Inkscape::NodePath::NODE_SMOOTH;
2109 sp_nodepath_update_node_knot (e);
2110 }
2111 if (e->p.other->type == Inkscape::NodePath::NODE_AUTO) {
2112 e->p.other->type = Inkscape::NodePath::NODE_SMOOTH;
2113 sp_nodepath_update_node_knot (e->p.other);
2114 }
2116 /* feel good is an arbitrary parameter that distributes the delta between handles
2117 * if t of the drag point is less than 1/6 distance form the endpoint only
2118 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2119 */
2120 double feel_good;
2121 if (t <= 1.0 / 6.0)
2122 feel_good = 0;
2123 else if (t <= 0.5)
2124 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2125 else if (t <= 5.0 / 6.0)
2126 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2127 else
2128 feel_good = 1;
2130 //if we're dragging a line convert it to a curve
2131 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2132 sp_nodepath_set_line_type(e, NR_CURVETO);
2133 }
2135 Geom::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2136 Geom::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2137 e->p.other->n.pos += offsetcoord0;
2138 e->p.pos += offsetcoord1;
2140 // adjust handles of adjacent nodes where necessary
2141 sp_node_adjust_handle(e,1);
2142 sp_node_adjust_handle(e->p.other,-1);
2144 sp_nodepath_update_handles(e->subpath->nodepath);
2146 update_object(e->subpath->nodepath);
2148 sp_nodepath_update_statusbar(e->subpath->nodepath);
2149 }
2152 /**
2153 * Call sp_nodepath_break() for all selected segments.
2154 */
2155 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2156 {
2157 if (!nodepath) return;
2159 GList *tempin = g_list_copy(nodepath->selected);
2160 GList *temp = NULL;
2161 for (GList *l = tempin; l != NULL; l = l->next) {
2162 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2163 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2164 if (nn == NULL) continue; // no break, no new node
2165 temp = g_list_prepend(temp, nn);
2166 }
2167 g_list_free(tempin);
2169 if (temp) {
2170 sp_nodepath_deselect(nodepath);
2171 }
2172 for (GList *l = temp; l != NULL; l = l->next) {
2173 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2174 }
2176 sp_nodepath_update_handles(nodepath);
2178 sp_nodepath_update_repr(nodepath, _("Break path"));
2179 }
2181 /**
2182 * Duplicate the selected node(s).
2183 */
2184 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2185 {
2186 if (!nodepath) {
2187 return;
2188 }
2190 GList *temp = NULL;
2191 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2192 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2193 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2194 if (nn == NULL) continue; // could not duplicate
2195 temp = g_list_prepend(temp, nn);
2196 }
2198 if (temp) {
2199 sp_nodepath_deselect(nodepath);
2200 }
2201 for (GList *l = temp; l != NULL; l = l->next) {
2202 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2203 }
2205 sp_nodepath_update_handles(nodepath);
2207 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2208 }
2210 /**
2211 * Internal function to join two nodes by merging them into one.
2212 */
2213 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2214 {
2215 /* a and b are endpoints */
2217 // if one of the two nodes is mouseovered, fix its position
2218 Geom::Point c;
2219 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2220 c = a->pos;
2221 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2222 c = b->pos;
2223 } else {
2224 // otherwise, move joined node to the midpoint
2225 c = (a->pos + b->pos) / 2;
2226 }
2228 if (a->subpath == b->subpath) {
2229 Inkscape::NodePath::SubPath *sp = a->subpath;
2230 sp_nodepath_subpath_close(sp);
2231 sp_node_moveto (sp->first, c);
2233 sp_nodepath_update_handles(sp->nodepath);
2234 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2235 return;
2236 }
2238 /* a and b are separate subpaths */
2239 Inkscape::NodePath::SubPath *sa = a->subpath;
2240 Inkscape::NodePath::SubPath *sb = b->subpath;
2241 Geom::Point p;
2242 Inkscape::NodePath::Node *n;
2243 NRPathcode code;
2244 if (a == sa->first) {
2245 // we will now reverse sa, so that a is its last node, not first, and drop that node
2246 p = sa->first->n.pos;
2247 code = (NRPathcode)sa->first->n.other->code;
2248 // create new subpath
2249 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2250 // create a first moveto node on it
2251 n = sa->last;
2252 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2253 n = n->p.other;
2254 if (n == sa->first) n = NULL;
2255 while (n) {
2256 // copy the rest of the nodes from sa to t, going backwards
2257 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2258 n = n->p.other;
2259 if (n == sa->first) n = NULL;
2260 }
2261 // replace sa with t
2262 sp_nodepath_subpath_destroy(sa);
2263 sa = t;
2264 } else if (a == sa->last) {
2265 // a is already last, just drop it
2266 p = sa->last->p.pos;
2267 code = (NRPathcode)sa->last->code;
2268 sp_nodepath_node_destroy(sa->last);
2269 } else {
2270 code = NR_END;
2271 g_assert_not_reached();
2272 }
2274 if (b == sb->first) {
2275 // copy all nodes from b to a, forward
2276 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2277 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2278 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2279 }
2280 } else if (b == sb->last) {
2281 // copy all nodes from b to a, backward
2282 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2283 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2284 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2285 }
2286 } else {
2287 g_assert_not_reached();
2288 }
2289 /* and now destroy sb */
2291 sp_nodepath_subpath_destroy(sb);
2293 sp_nodepath_update_handles(sa->nodepath);
2295 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2297 sp_nodepath_update_statusbar(nodepath);
2298 }
2300 /**
2301 * Internal function to join two nodes by adding a segment between them.
2302 */
2303 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2304 {
2305 if (a->subpath == b->subpath) {
2306 Inkscape::NodePath::SubPath *sp = a->subpath;
2308 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2309 sp->closed = TRUE;
2311 sp->first->p.other = sp->last;
2312 sp->last->n.other = sp->first;
2314 sp_node_handle_mirror_p_to_n(sp->last);
2315 sp_node_handle_mirror_n_to_p(sp->first);
2317 sp->first->code = sp->last->code;
2318 sp->first = sp->last;
2320 sp_nodepath_update_handles(sp->nodepath);
2322 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2324 return;
2325 }
2327 /* a and b are separate subpaths */
2328 Inkscape::NodePath::SubPath *sa = a->subpath;
2329 Inkscape::NodePath::SubPath *sb = b->subpath;
2331 Inkscape::NodePath::Node *n;
2332 Geom::Point p;
2333 NRPathcode code;
2334 if (a == sa->first) {
2335 code = (NRPathcode) sa->first->n.other->code;
2336 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2337 n = sa->last;
2338 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2339 for (n = n->p.other; n != NULL; n = n->p.other) {
2340 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2341 }
2342 sp_nodepath_subpath_destroy(sa);
2343 sa = t;
2344 } else if (a == sa->last) {
2345 code = (NRPathcode)sa->last->code;
2346 } else {
2347 code = NR_END;
2348 g_assert_not_reached();
2349 }
2351 if (b == sb->first) {
2352 n = sb->first;
2353 sp_node_handle_mirror_p_to_n(sa->last);
2354 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2355 sp_node_handle_mirror_n_to_p(sa->last);
2356 for (n = n->n.other; n != NULL; n = n->n.other) {
2357 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2358 }
2359 } else if (b == sb->last) {
2360 n = sb->last;
2361 sp_node_handle_mirror_p_to_n(sa->last);
2362 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2363 sp_node_handle_mirror_n_to_p(sa->last);
2364 for (n = n->p.other; n != NULL; n = n->p.other) {
2365 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2366 }
2367 } else {
2368 g_assert_not_reached();
2369 }
2370 /* and now destroy sb */
2372 sp_nodepath_subpath_destroy(sb);
2374 sp_nodepath_update_handles(sa->nodepath);
2376 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2377 }
2379 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2381 /**
2382 * Internal function to handle joining two nodes.
2383 */
2384 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2385 {
2386 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2388 if (g_list_length(nodepath->selected) != 2) {
2389 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2390 return;
2391 }
2393 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2394 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2396 g_assert(a != b);
2397 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2398 // someone tried to join an orphan node (i.e. a single-node subpath).
2399 // this is not worth an error message, just fail silently.
2400 return;
2401 }
2403 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2404 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2405 return;
2406 }
2408 switch(mode) {
2409 case NODE_JOIN_ENDPOINTS:
2410 do_node_selected_join(nodepath, a, b);
2411 break;
2412 case NODE_JOIN_SEGMENT:
2413 do_node_selected_join_segment(nodepath, a, b);
2414 break;
2415 }
2416 }
2418 /**
2419 * Join two nodes by merging them into one.
2420 */
2421 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2422 {
2423 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2424 }
2426 /**
2427 * Join two nodes by adding a segment between them.
2428 */
2429 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2430 {
2431 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2432 }
2434 /**
2435 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2436 */
2437 void sp_node_delete_preserve(GList *nodes_to_delete)
2438 {
2439 GSList *nodepaths = NULL;
2441 while (nodes_to_delete) {
2442 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2443 Inkscape::NodePath::SubPath *sp = node->subpath;
2444 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2445 Inkscape::NodePath::Node *sample_cursor = NULL;
2446 Inkscape::NodePath::Node *sample_end = NULL;
2447 Inkscape::NodePath::Node *delete_cursor = node;
2448 bool just_delete = false;
2450 //find the start of this contiguous selection
2451 //move left to the first node that is not selected
2452 //or the start of the non-closed path
2453 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2454 delete_cursor = curr;
2455 }
2457 //just delete at the beginning of an open path
2458 if (!delete_cursor->p.other) {
2459 sample_cursor = delete_cursor;
2460 just_delete = true;
2461 } else {
2462 sample_cursor = delete_cursor->p.other;
2463 }
2465 //calculate points for each segment
2466 int rate = 5;
2467 float period = 1.0 / rate;
2468 std::vector<Geom::Point> data;
2469 if (!just_delete) {
2470 data.push_back(sample_cursor->pos);
2471 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2472 //just delete at the end of an open path
2473 if (!sp->closed && curr == sp->last) {
2474 just_delete = true;
2475 break;
2476 }
2478 //sample points on the contiguous selected segment
2479 Geom::Point *bez;
2480 bez = new Geom::Point [4];
2481 bez[0] = curr->pos;
2482 bez[1] = curr->n.pos;
2483 bez[2] = curr->n.other->p.pos;
2484 bez[3] = curr->n.other->pos;
2485 for (int i=1; i<rate; i++) {
2486 gdouble t = i * period;
2487 Geom::Point p = bezier_pt(3, bez, t);
2488 data.push_back(p);
2489 }
2490 data.push_back(curr->n.other->pos);
2492 sample_end = curr->n.other;
2493 //break if we've come full circle or hit the end of the selection
2494 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2495 break;
2496 }
2497 }
2498 }
2500 if (!just_delete) {
2501 //calculate the best fitting single segment and adjust the endpoints
2502 Geom::Point *adata;
2503 adata = new Geom::Point [data.size()];
2504 copy(data.begin(), data.end(), adata);
2506 Geom::Point *bez;
2507 bez = new Geom::Point [4];
2508 //would decreasing error create a better fitting approximation?
2509 gdouble error = 1.0;
2510 gint ret;
2511 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2513 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2514 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2515 //the resulting nodes behave as expected.
2516 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2517 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2518 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2519 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2521 //adjust endpoints
2522 sample_cursor->n.pos = bez[1];
2523 sample_end->p.pos = bez[2];
2524 }
2526 //destroy this contiguous selection
2527 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2528 Inkscape::NodePath::Node *temp = delete_cursor;
2529 if (delete_cursor->n.other == delete_cursor) {
2530 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2531 delete_cursor = NULL;
2532 } else {
2533 delete_cursor = delete_cursor->n.other;
2534 }
2535 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2536 sp_nodepath_node_destroy(temp);
2537 }
2539 sp_nodepath_update_handles(nodepath);
2541 if (!g_slist_find(nodepaths, nodepath))
2542 nodepaths = g_slist_prepend (nodepaths, nodepath);
2543 }
2545 for (GSList *i = nodepaths; i; i = i->next) {
2546 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2547 // different nodepaths will give us one undo event per nodepath
2548 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2550 // if the entire nodepath is removed, delete the selected object.
2551 if (nodepath->subpaths == NULL ||
2552 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2553 //at least 2
2554 sp_nodepath_get_node_count(nodepath) < 2) {
2555 SPDocument *document = sp_desktop_document (nodepath->desktop);
2556 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2557 //delete this nodepath's object, not the entire selection! (though at this time, this
2558 //does not matter)
2559 sp_selection_delete(nodepath->desktop);
2560 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2561 _("Delete nodes"));
2562 } else {
2563 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2564 sp_nodepath_update_statusbar(nodepath);
2565 }
2566 }
2568 g_slist_free (nodepaths);
2569 }
2571 /**
2572 * Delete one or more selected nodes.
2573 */
2574 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2575 {
2576 if (!nodepath) return;
2577 if (!nodepath->selected) return;
2579 /** \todo fixme: do it the right way */
2580 while (nodepath->selected) {
2581 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2582 sp_nodepath_node_destroy(node);
2583 }
2586 //clean up the nodepath (such as for trivial subpaths)
2587 sp_nodepath_cleanup(nodepath);
2589 sp_nodepath_update_handles(nodepath);
2591 // if the entire nodepath is removed, delete the selected object.
2592 if (nodepath->subpaths == NULL ||
2593 sp_nodepath_get_node_count(nodepath) < 2) {
2594 SPDocument *document = sp_desktop_document (nodepath->desktop);
2595 sp_selection_delete(nodepath->desktop);
2596 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2597 _("Delete nodes"));
2598 return;
2599 }
2601 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2603 sp_nodepath_update_statusbar(nodepath);
2604 }
2606 /**
2607 * Delete one or more segments between two selected nodes.
2608 * This is the code for 'split'.
2609 */
2610 void
2611 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2612 {
2613 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2614 Inkscape::NodePath::Node *curr, *next; //Iterators
2616 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2618 if (g_list_length(nodepath->selected) != 2) {
2619 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2620 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2621 return;
2622 }
2624 //Selected nodes, not inclusive
2625 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2626 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2628 if ( ( a==b) || //same node
2629 (a->subpath != b->subpath ) || //not the same path
2630 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2631 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2632 {
2633 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2634 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2635 return;
2636 }
2638 //###########################################
2639 //# BEGIN EDITS
2640 //###########################################
2641 //##################################
2642 //# CLOSED PATH
2643 //##################################
2644 if (a->subpath->closed) {
2647 gboolean reversed = FALSE;
2649 //Since we can go in a circle, we need to find the shorter distance.
2650 // a->b or b->a
2651 start = end = NULL;
2652 int distance = 0;
2653 int minDistance = 0;
2654 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2655 if (curr==b) {
2656 //printf("a to b:%d\n", distance);
2657 start = a;//go from a to b
2658 end = b;
2659 minDistance = distance;
2660 //printf("A to B :\n");
2661 break;
2662 }
2663 distance++;
2664 }
2666 //try again, the other direction
2667 distance = 0;
2668 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2669 if (curr==a) {
2670 //printf("b to a:%d\n", distance);
2671 if (distance < minDistance) {
2672 start = b; //we go from b to a
2673 end = a;
2674 reversed = TRUE;
2675 //printf("B to A\n");
2676 }
2677 break;
2678 }
2679 distance++;
2680 }
2683 //Copy everything from 'end' to 'start' to a new subpath
2684 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2685 for (curr=end ; curr ; curr=curr->n.other) {
2686 NRPathcode code = (NRPathcode) curr->code;
2687 if (curr == end)
2688 code = NR_MOVETO;
2689 sp_nodepath_node_new(t, NULL,
2690 (Inkscape::NodePath::NodeType)curr->type, code,
2691 &curr->p.pos, &curr->pos, &curr->n.pos);
2692 if (curr == start)
2693 break;
2694 }
2695 sp_nodepath_subpath_destroy(a->subpath);
2698 }
2702 //##################################
2703 //# OPEN PATH
2704 //##################################
2705 else {
2707 //We need to get the direction of the list between A and B
2708 //Can we walk from a to b?
2709 start = end = NULL;
2710 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2711 if (curr==b) {
2712 start = a; //did it! we go from a to b
2713 end = b;
2714 //printf("A to B\n");
2715 break;
2716 }
2717 }
2718 if (!start) {//didn't work? let's try the other direction
2719 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2720 if (curr==a) {
2721 start = b; //did it! we go from b to a
2722 end = a;
2723 //printf("B to A\n");
2724 break;
2725 }
2726 }
2727 }
2728 if (!start) {
2729 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2730 _("Cannot find path between nodes."));
2731 return;
2732 }
2736 //Copy everything after 'end' to a new subpath
2737 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2738 for (curr=end ; curr ; curr=curr->n.other) {
2739 NRPathcode code = (NRPathcode) curr->code;
2740 if (curr == end)
2741 code = NR_MOVETO;
2742 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2743 &curr->p.pos, &curr->pos, &curr->n.pos);
2744 }
2746 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2747 for (curr = start->n.other ; curr ; curr=next) {
2748 next = curr->n.other;
2749 sp_nodepath_node_destroy(curr);
2750 }
2752 }
2753 //###########################################
2754 //# END EDITS
2755 //###########################################
2757 //clean up the nodepath (such as for trivial subpaths)
2758 sp_nodepath_cleanup(nodepath);
2760 sp_nodepath_update_handles(nodepath);
2762 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2764 sp_nodepath_update_statusbar(nodepath);
2765 }
2767 /**
2768 * Call sp_nodepath_set_line() for all selected segments.
2769 */
2770 void
2771 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2772 {
2773 if (nodepath == NULL) return;
2775 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2776 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2777 g_assert(n->selected);
2778 if (n->p.other && n->p.other->selected) {
2779 sp_nodepath_set_line_type(n, code);
2780 }
2781 }
2783 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2784 }
2786 /**
2787 * Call sp_nodepath_convert_node_type() for all selected nodes.
2788 */
2789 void
2790 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2791 {
2792 if (nodepath == NULL) return;
2794 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2796 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2797 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2798 }
2800 sp_nodepath_update_repr(nodepath, _("Change node type"));
2801 }
2803 /**
2804 * Change select status of node, update its own and neighbour handles.
2805 */
2806 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2807 {
2808 node->selected = selected;
2810 if (selected) {
2811 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 11 : 9);
2812 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2813 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2814 sp_knot_update_ctrl(node->knot);
2815 } else {
2816 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 9 : 7);
2817 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2818 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2819 sp_knot_update_ctrl(node->knot);
2820 }
2822 sp_node_update_handles(node);
2823 if (node->n.other) sp_node_update_handles(node->n.other);
2824 if (node->p.other) sp_node_update_handles(node->p.other);
2825 }
2827 /**
2828 \brief Select a node
2829 \param node The node to select
2830 \param incremental If true, add to selection, otherwise deselect others
2831 \param override If true, always select this node, otherwise toggle selected status
2832 */
2833 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2834 {
2835 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2837 if (incremental) {
2838 if (override) {
2839 if (!g_list_find(nodepath->selected, node)) {
2840 nodepath->selected = g_list_prepend(nodepath->selected, node);
2841 }
2842 sp_node_set_selected(node, TRUE);
2843 } else { // toggle
2844 if (node->selected) {
2845 g_assert(g_list_find(nodepath->selected, node));
2846 nodepath->selected = g_list_remove(nodepath->selected, node);
2847 } else {
2848 g_assert(!g_list_find(nodepath->selected, node));
2849 nodepath->selected = g_list_prepend(nodepath->selected, node);
2850 }
2851 sp_node_set_selected(node, !node->selected);
2852 }
2853 } else {
2854 sp_nodepath_deselect(nodepath);
2855 nodepath->selected = g_list_prepend(nodepath->selected, node);
2856 sp_node_set_selected(node, TRUE);
2857 }
2859 sp_nodepath_update_statusbar(nodepath);
2860 }
2863 /**
2864 \brief Deselect all nodes in the nodepath
2865 */
2866 void
2867 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2868 {
2869 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2871 while (nodepath->selected) {
2872 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2873 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2874 }
2875 sp_nodepath_update_statusbar(nodepath);
2876 }
2878 /**
2879 \brief Select or invert selection of all nodes in the nodepath
2880 */
2881 void
2882 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2883 {
2884 if (!nodepath) return;
2886 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2887 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2888 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2889 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2890 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2891 }
2892 }
2893 }
2895 /**
2896 * If nothing selected, does the same as sp_nodepath_select_all();
2897 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2898 * (i.e., similar to "select all in layer", with the "selected" subpaths
2899 * being treated as "layers" in the path).
2900 */
2901 void
2902 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2903 {
2904 if (!nodepath) return;
2906 if (g_list_length (nodepath->selected) == 0) {
2907 sp_nodepath_select_all (nodepath, invert);
2908 return;
2909 }
2911 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2912 GSList *subpaths = NULL;
2914 for (GList *l = copy; l != NULL; l = l->next) {
2915 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2916 Inkscape::NodePath::SubPath *subpath = n->subpath;
2917 if (!g_slist_find (subpaths, subpath))
2918 subpaths = g_slist_prepend (subpaths, subpath);
2919 }
2921 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2922 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2923 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2924 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2925 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2926 }
2927 }
2929 g_slist_free (subpaths);
2930 g_list_free (copy);
2931 }
2933 /**
2934 * \brief Select the node after the last selected; if none is selected,
2935 * select the first within path.
2936 */
2937 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2938 {
2939 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2941 Inkscape::NodePath::Node *last = NULL;
2942 if (nodepath->selected) {
2943 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2944 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2945 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2946 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2947 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2948 if (node->selected) {
2949 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2950 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2951 if (spl->next) { // there's a next subpath
2952 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2953 last = subpath_next->first;
2954 } else if (spl->prev) { // there's a previous subpath
2955 last = NULL; // to be set later to the first node of first subpath
2956 } else {
2957 last = node->n.other;
2958 }
2959 } else {
2960 last = node->n.other;
2961 }
2962 } else {
2963 if (node->n.other) {
2964 last = node->n.other;
2965 } else {
2966 if (spl->next) { // there's a next subpath
2967 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2968 last = subpath_next->first;
2969 } else if (spl->prev) { // there's a previous subpath
2970 last = NULL; // to be set later to the first node of first subpath
2971 } else {
2972 last = (Inkscape::NodePath::Node *) subpath->first;
2973 }
2974 }
2975 }
2976 }
2977 }
2978 }
2979 sp_nodepath_deselect(nodepath);
2980 }
2982 if (last) { // there's at least one more node after selected
2983 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2984 } else { // no more nodes, select the first one in first subpath
2985 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2986 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2987 }
2988 }
2990 /**
2991 * \brief Select the node before the first selected; if none is selected,
2992 * select the last within path
2993 */
2994 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2995 {
2996 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2998 Inkscape::NodePath::Node *last = NULL;
2999 if (nodepath->selected) {
3000 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
3001 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3002 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
3003 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3004 if (node->selected) {
3005 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
3006 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
3007 if (spl->prev) { // there's a prev subpath
3008 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3009 last = subpath_prev->last;
3010 } else if (spl->next) { // there's a next subpath
3011 last = NULL; // to be set later to the last node of last subpath
3012 } else {
3013 last = node->p.other;
3014 }
3015 } else {
3016 last = node->p.other;
3017 }
3018 } else {
3019 if (node->p.other) {
3020 last = node->p.other;
3021 } else {
3022 if (spl->prev) { // there's a prev subpath
3023 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3024 last = subpath_prev->last;
3025 } else if (spl->next) { // there's a next subpath
3026 last = NULL; // to be set later to the last node of last subpath
3027 } else {
3028 last = (Inkscape::NodePath::Node *) subpath->last;
3029 }
3030 }
3031 }
3032 }
3033 }
3034 }
3035 sp_nodepath_deselect(nodepath);
3036 }
3038 if (last) { // there's at least one more node before selected
3039 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
3040 } else { // no more nodes, select the last one in last subpath
3041 GList *spl = g_list_last(nodepath->subpaths);
3042 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3043 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
3044 }
3045 }
3047 /**
3048 * \brief Select all nodes that are within the rectangle.
3049 */
3050 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, Geom::Rect const &b, gboolean incremental)
3051 {
3052 if (!incremental) {
3053 sp_nodepath_deselect(nodepath);
3054 }
3056 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3057 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3058 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3059 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3061 if (b.contains(node->pos)) {
3062 sp_nodepath_node_select(node, TRUE, TRUE);
3063 }
3064 }
3065 }
3066 }
3069 void
3070 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3071 {
3072 g_assert (n);
3073 g_assert (nodepath);
3074 g_assert (n->subpath->nodepath == nodepath);
3076 if (g_list_length (nodepath->selected) == 0) {
3077 if (grow > 0) {
3078 sp_nodepath_node_select(n, TRUE, TRUE);
3079 }
3080 return;
3081 }
3083 if (g_list_length (nodepath->selected) == 1) {
3084 if (grow < 0) {
3085 sp_nodepath_deselect (nodepath);
3086 return;
3087 }
3088 }
3090 double n_sel_range = 0, p_sel_range = 0;
3091 Inkscape::NodePath::Node *farthest_n_node = n;
3092 Inkscape::NodePath::Node *farthest_p_node = n;
3094 // Calculate ranges
3095 {
3096 double n_range = 0, p_range = 0;
3097 bool n_going = true, p_going = true;
3098 Inkscape::NodePath::Node *n_node = n;
3099 Inkscape::NodePath::Node *p_node = n;
3100 do {
3101 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3102 if (n_node && n_going)
3103 n_node = n_node->n.other;
3104 if (n_node == NULL) {
3105 n_going = false;
3106 } else {
3107 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3108 if (n_node->selected) {
3109 n_sel_range = n_range;
3110 farthest_n_node = n_node;
3111 }
3112 if (n_node == p_node) {
3113 n_going = false;
3114 p_going = false;
3115 }
3116 }
3117 if (p_node && p_going)
3118 p_node = p_node->p.other;
3119 if (p_node == NULL) {
3120 p_going = false;
3121 } else {
3122 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3123 if (p_node->selected) {
3124 p_sel_range = p_range;
3125 farthest_p_node = p_node;
3126 }
3127 if (p_node == n_node) {
3128 n_going = false;
3129 p_going = false;
3130 }
3131 }
3132 } while (n_going || p_going);
3133 }
3135 if (grow > 0) {
3136 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3137 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3138 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3139 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3140 }
3141 } else {
3142 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3143 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3144 } else if (farthest_p_node && farthest_p_node->selected) {
3145 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3146 }
3147 }
3148 }
3150 void
3151 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3152 {
3153 g_assert (n);
3154 g_assert (nodepath);
3155 g_assert (n->subpath->nodepath == nodepath);
3157 if (g_list_length (nodepath->selected) == 0) {
3158 if (grow > 0) {
3159 sp_nodepath_node_select(n, TRUE, TRUE);
3160 }
3161 return;
3162 }
3164 if (g_list_length (nodepath->selected) == 1) {
3165 if (grow < 0) {
3166 sp_nodepath_deselect (nodepath);
3167 return;
3168 }
3169 }
3171 Inkscape::NodePath::Node *farthest_selected = NULL;
3172 double farthest_dist = 0;
3174 Inkscape::NodePath::Node *closest_unselected = NULL;
3175 double closest_dist = NR_HUGE;
3177 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3178 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3179 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3180 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3181 if (node == n)
3182 continue;
3183 if (node->selected) {
3184 if (Geom::L2(node->pos - n->pos) > farthest_dist) {
3185 farthest_dist = Geom::L2(node->pos - n->pos);
3186 farthest_selected = node;
3187 }
3188 } else {
3189 if (Geom::L2(node->pos - n->pos) < closest_dist) {
3190 closest_dist = Geom::L2(node->pos - n->pos);
3191 closest_unselected = node;
3192 }
3193 }
3194 }
3195 }
3197 if (grow > 0) {
3198 if (closest_unselected) {
3199 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3200 }
3201 } else {
3202 if (farthest_selected) {
3203 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3204 }
3205 }
3206 }
3209 /**
3210 \brief Saves all nodes' and handles' current positions in their origin members
3211 */
3212 void
3213 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3214 {
3215 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3216 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3217 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3218 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3219 n->origin = n->pos;
3220 n->p.origin = n->p.pos;
3221 n->n.origin = n->n.pos;
3222 }
3223 }
3224 }
3226 /**
3227 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3228 */
3229 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3230 {
3231 GList *r = NULL;
3232 if (nodepath->selected) {
3233 guint i = 0;
3234 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3235 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3236 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3237 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3238 i++;
3239 if (node->selected) {
3240 r = g_list_append(r, GINT_TO_POINTER(i));
3241 }
3242 }
3243 }
3244 }
3245 return r;
3246 }
3248 /**
3249 \brief Restores selection by selecting nodes whose positions are in the list
3250 */
3251 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3252 {
3253 sp_nodepath_deselect(nodepath);
3255 guint i = 0;
3256 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3257 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3258 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3259 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3260 i++;
3261 if (g_list_find(r, GINT_TO_POINTER(i))) {
3262 sp_nodepath_node_select(node, TRUE, TRUE);
3263 }
3264 }
3265 }
3266 }
3269 /**
3270 \brief Adjusts handle according to node type and line code.
3271 */
3272 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3273 {
3274 g_assert(node);
3276 // nothing to do for auto nodes (sp_node_adjust_handles() does the job)
3277 if (node->type == Inkscape::NodePath::NODE_AUTO)
3278 return;
3280 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3281 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3283 // nothing to do if we are an end node
3284 if (me->other == NULL) return;
3285 if (other->other == NULL) return;
3287 // nothing to do if we are a cusp node
3288 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3290 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3291 NRPathcode mecode;
3292 if (which_adjust == 1) {
3293 mecode = (NRPathcode)me->other->code;
3294 } else {
3295 mecode = (NRPathcode)node->code;
3296 }
3297 if (mecode == NR_LINETO) return;
3299 if (sp_node_side_is_line(node, other)) {
3300 // other is a line, and we are either smooth or symm
3301 Inkscape::NodePath::Node *othernode = other->other;
3302 double len = Geom::L2(me->pos - node->pos);
3303 Geom::Point delta = node->pos - othernode->pos;
3304 double linelen = Geom::L2(delta);
3305 if (linelen < 1e-18)
3306 return;
3307 me->pos = node->pos + (len / linelen)*delta;
3308 return;
3309 }
3311 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3312 // symmetrize
3313 me->pos = 2 * node->pos - other->pos;
3314 return;
3315 } else {
3316 // smoothify
3317 double len = Geom::L2(me->pos - node->pos);
3318 Geom::Point delta = other->pos - node->pos;
3319 double otherlen = Geom::L2(delta);
3320 if (otherlen < 1e-18) return;
3321 me->pos = node->pos - (len / otherlen) * delta;
3322 }
3323 }
3325 /**
3326 \brief Adjusts both handles according to node type and line code
3327 */
3328 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3329 {
3330 g_assert(node);
3332 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3334 /* we are either smooth or symm */
3336 if (node->p.other == NULL) return;
3337 if (node->n.other == NULL) return;
3339 if (node->type == Inkscape::NodePath::NODE_AUTO) {
3340 sp_node_adjust_handles_auto(node);
3341 return;
3342 }
3344 if (sp_node_side_is_line(node, &node->p)) {
3345 sp_node_adjust_handle(node, 1);
3346 return;
3347 }
3349 if (sp_node_side_is_line(node, &node->n)) {
3350 sp_node_adjust_handle(node, -1);
3351 return;
3352 }
3354 /* both are curves */
3355 Geom::Point const delta( node->n.pos - node->p.pos );
3357 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3358 node->p.pos = node->pos - delta / 2;
3359 node->n.pos = node->pos + delta / 2;
3360 return;
3361 }
3363 /* We are smooth */
3364 double plen = Geom::L2(node->p.pos - node->pos);
3365 if (plen < 1e-18) return;
3366 double nlen = Geom::L2(node->n.pos - node->pos);
3367 if (nlen < 1e-18) return;
3368 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3369 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3370 }
3372 static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node)
3373 {
3374 if (node->p.other == NULL || node->n.other == NULL) {
3375 node->p.pos = node->pos;
3376 node->n.pos = node->pos;
3377 return;
3378 }
3380 Geom::Point leg_prev = to_2geom(node->p.other->pos - node->pos);
3381 Geom::Point leg_next = to_2geom(node->n.other->pos - node->pos);
3383 double norm_leg_prev = Geom::L2(leg_prev);
3384 double norm_leg_next = Geom::L2(leg_next);
3386 Geom::Point delta;
3387 if (norm_leg_next > 0.0) {
3388 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
3389 delta.normalize();
3390 }
3392 node->p.pos = node->pos - norm_leg_prev / 3 * delta;
3393 node->n.pos = node->pos + norm_leg_next / 3 * delta;
3394 }
3396 /**
3397 * Node event callback.
3398 */
3399 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3400 {
3401 gboolean ret = FALSE;
3402 switch (event->type) {
3403 case GDK_ENTER_NOTIFY:
3404 Inkscape::NodePath::Path::active_node = n;
3405 break;
3406 case GDK_LEAVE_NOTIFY:
3407 Inkscape::NodePath::Path::active_node = NULL;
3408 break;
3409 case GDK_SCROLL:
3410 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3411 switch (event->scroll.direction) {
3412 case GDK_SCROLL_UP:
3413 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3414 break;
3415 case GDK_SCROLL_DOWN:
3416 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3417 break;
3418 default:
3419 break;
3420 }
3421 ret = TRUE;
3422 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3423 switch (event->scroll.direction) {
3424 case GDK_SCROLL_UP:
3425 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3426 break;
3427 case GDK_SCROLL_DOWN:
3428 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3429 break;
3430 default:
3431 break;
3432 }
3433 ret = TRUE;
3434 }
3435 break;
3436 case GDK_KEY_PRESS:
3437 switch (get_group0_keyval (&event->key)) {
3438 case GDK_space:
3439 if (event->key.state & GDK_BUTTON1_MASK) {
3440 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3441 stamp_repr(nodepath);
3442 ret = TRUE;
3443 }
3444 break;
3445 case GDK_Page_Up:
3446 if (event->key.state & GDK_CONTROL_MASK) {
3447 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3448 } else {
3449 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3450 }
3451 break;
3452 case GDK_Page_Down:
3453 if (event->key.state & GDK_CONTROL_MASK) {
3454 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3455 } else {
3456 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3457 }
3458 break;
3459 default:
3460 break;
3461 }
3462 break;
3463 default:
3464 break;
3465 }
3467 return ret;
3468 }
3470 /**
3471 * Handle keypress on node; directly called.
3472 */
3473 gboolean node_key(GdkEvent *event)
3474 {
3475 Inkscape::NodePath::Path *np;
3477 // there is no way to verify nodes so set active_node to nil when deleting!!
3478 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3480 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3481 gint ret = FALSE;
3482 switch (get_group0_keyval (&event->key)) {
3483 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3484 case GDK_BackSpace:
3485 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3486 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3487 sp_nodepath_update_repr(np, _("Delete node"));
3488 Inkscape::NodePath::Path::active_node = NULL;
3489 ret = TRUE;
3490 break;
3491 case GDK_c:
3492 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3493 ret = TRUE;
3494 break;
3495 case GDK_s:
3496 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3497 ret = TRUE;
3498 break;
3499 case GDK_a:
3500 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_AUTO);
3501 ret = TRUE;
3502 break;
3503 case GDK_y:
3504 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3505 ret = TRUE;
3506 break;
3507 case GDK_b:
3508 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3509 ret = TRUE;
3510 break;
3511 }
3512 return ret;
3513 }
3514 return FALSE;
3515 }
3517 /**
3518 * Mouseclick on node callback.
3519 */
3520 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3521 {
3522 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3524 if (state & GDK_CONTROL_MASK) {
3525 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3527 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3528 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3529 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3530 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3531 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3532 } else if (n->type == Inkscape::NodePath::NODE_SYMM) {
3533 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_AUTO);
3534 } else {
3535 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3536 }
3537 sp_nodepath_update_repr(nodepath, _("Change node type"));
3538 sp_nodepath_update_statusbar(nodepath);
3540 } else { //ctrl+alt+click: delete node
3541 GList *node_to_delete = NULL;
3542 node_to_delete = g_list_append(node_to_delete, n);
3543 sp_node_delete_preserve(node_to_delete);
3544 }
3546 } else {
3547 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3548 }
3549 }
3551 /**
3552 * Mouse grabbed node callback.
3553 */
3554 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3555 {
3556 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3558 if (!n->selected) {
3559 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3560 }
3562 n->is_dragging = true;
3563 // Reconstruct and store the location of the mouse pointer at the time when we started dragging (needed for snapping)
3564 n->subpath->nodepath->drag_origin_mouse = knot->grabbed_rel_pos + knot->drag_origin;
3566 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3568 sp_nodepath_remember_origins (n->subpath->nodepath);
3569 }
3571 /**
3572 * Mouse ungrabbed node callback.
3573 */
3574 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3575 {
3576 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3578 n->dragging_out = NULL;
3579 n->is_dragging = false;
3580 n->subpath->nodepath->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE);
3581 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3583 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3584 }
3586 /**
3587 * The point on a line, given by its angle, closest to the given point.
3588 * \param p A point.
3589 * \param a Angle of the line; it is assumed to go through coordinate origin.
3590 * \param closest Pointer to the point struct where the result is stored.
3591 * \todo FIXME: use dot product perhaps?
3592 */
3593 static void point_line_closest(Geom::Point *p, double a, Geom::Point *closest)
3594 {
3595 if (a == HUGE_VAL) { // vertical
3596 *closest = Geom::Point(0, (*p)[Geom::Y]);
3597 } else {
3598 (*closest)[Geom::X] = ( a * (*p)[Geom::Y] + (*p)[Geom::X]) / (a*a + 1);
3599 (*closest)[Geom::Y] = a * (*closest)[Geom::X];
3600 }
3601 }
3603 /**
3604 * Distance from the point to a line given by its angle.
3605 * \param p A point.
3606 * \param a Angle of the line; it is assumed to go through coordinate origin.
3607 */
3608 static double point_line_distance(Geom::Point *p, double a)
3609 {
3610 Geom::Point c;
3611 point_line_closest(p, a, &c);
3612 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]));
3613 }
3615 /**
3616 * Callback for node "request" signal.
3617 * \todo fixme: This goes to "moved" event? (lauris)
3618 */
3619 static gboolean
3620 node_request(SPKnot */*knot*/, Geom::Point const &p, guint state, gpointer data)
3621 {
3622 double yn, xn, yp, xp;
3623 double an, ap, na, pa;
3624 double d_an, d_ap, d_na, d_pa;
3625 gboolean collinear = FALSE;
3626 Geom::Point c;
3627 Geom::Point pr;
3629 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3631 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3633 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3634 if ( (!n->subpath->nodepath->straight_path) &&
3635 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3636 || n->dragging_out ) )
3637 {
3638 Geom::Point mouse = p;
3640 if (!n->dragging_out) {
3641 // This is the first drag-out event; find out which handle to drag out
3642 double appr_n = (n->n.other ? Geom::L2(n->n.other->pos - n->pos) - Geom::L2(n->n.other->pos - p) : -HUGE_VAL);
3643 double appr_p = (n->p.other ? Geom::L2(n->p.other->pos - n->pos) - Geom::L2(n->p.other->pos - p) : -HUGE_VAL);
3645 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3646 return FALSE;
3648 Inkscape::NodePath::NodeSide *opposite;
3649 if (appr_p > appr_n) { // closer to p
3650 n->dragging_out = &n->p;
3651 opposite = &n->n;
3652 n->code = NR_CURVETO;
3653 } else if (appr_p < appr_n) { // closer to n
3654 n->dragging_out = &n->n;
3655 opposite = &n->p;
3656 n->n.other->code = NR_CURVETO;
3657 } else { // p and n nodes are the same
3658 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3659 n->dragging_out = &n->p;
3660 opposite = &n->n;
3661 n->code = NR_CURVETO;
3662 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3663 n->dragging_out = &n->n;
3664 opposite = &n->p;
3665 n->n.other->code = NR_CURVETO;
3666 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3667 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);
3668 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);
3669 if (appr_other_p > appr_other_n) { // closer to other's p handle
3670 n->dragging_out = &n->n;
3671 opposite = &n->p;
3672 n->n.other->code = NR_CURVETO;
3673 } else { // closer to other's n handle
3674 n->dragging_out = &n->p;
3675 opposite = &n->n;
3676 n->code = NR_CURVETO;
3677 }
3678 }
3679 }
3681 // if there's another handle, make sure the one we drag out starts parallel to it
3682 if (opposite->pos != n->pos) {
3683 mouse = n->pos - Geom::L2(mouse - n->pos) * Geom::unit_vector(opposite->pos - n->pos);
3684 }
3686 // knots might not be created yet!
3687 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3688 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3689 }
3691 // pass this on to the handle-moved callback
3692 node_handle_moved(n->dragging_out->knot, mouse, state, (gpointer) n);
3693 sp_node_update_handles(n);
3694 return TRUE;
3695 }
3697 if (state & GDK_CONTROL_MASK) { // constrained motion
3699 // calculate relative distances of handles
3700 // n handle:
3701 yn = n->n.pos[Geom::Y] - n->pos[Geom::Y];
3702 xn = n->n.pos[Geom::X] - n->pos[Geom::X];
3703 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3704 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3705 if (n->n.other) { // if there is the next point
3706 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3707 yn = n->n.other->origin[Geom::Y] - n->origin[Geom::Y]; // use origin because otherwise the direction will change as you drag
3708 xn = n->n.other->origin[Geom::X] - n->origin[Geom::X];
3709 }
3710 }
3711 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3712 if (yn < 0) { xn = -xn; yn = -yn; }
3714 // p handle:
3715 yp = n->p.pos[Geom::Y] - n->pos[Geom::Y];
3716 xp = n->p.pos[Geom::X] - n->pos[Geom::X];
3717 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3718 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3719 if (n->p.other) {
3720 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3721 yp = n->p.other->origin[Geom::Y] - n->origin[Geom::Y];
3722 xp = n->p.other->origin[Geom::X] - n->origin[Geom::X];
3723 }
3724 }
3725 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3726 if (yp < 0) { xp = -xp; yp = -yp; }
3728 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3729 // sliding on handles, only if at least one of the handles is non-vertical
3730 // (otherwise it's the same as ctrl+drag anyway)
3732 // calculate angles of the handles
3733 if (xn == 0) {
3734 if (yn == 0) { // no handle, consider it the continuation of the other one
3735 an = 0;
3736 collinear = TRUE;
3737 }
3738 else an = 0; // vertical; set the angle to horizontal
3739 } else an = yn/xn;
3741 if (xp == 0) {
3742 if (yp == 0) { // no handle, consider it the continuation of the other one
3743 ap = an;
3744 }
3745 else ap = 0; // vertical; set the angle to horizontal
3746 } else ap = yp/xp;
3748 if (collinear) an = ap;
3750 // angles of the perpendiculars; HUGE_VAL means vertical
3751 if (an == 0) na = HUGE_VAL; else na = -1/an;
3752 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3754 // mouse point relative to the node's original pos
3755 pr = p - n->origin;
3757 // distances to the four lines (two handles and two perpendiculars)
3758 d_an = point_line_distance(&pr, an);
3759 d_na = point_line_distance(&pr, na);
3760 d_ap = point_line_distance(&pr, ap);
3761 d_pa = point_line_distance(&pr, pa);
3763 // find out which line is the closest, save its closest point in c
3764 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3765 point_line_closest(&pr, an, &c);
3766 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3767 point_line_closest(&pr, ap, &c);
3768 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3769 point_line_closest(&pr, na, &c);
3770 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3771 point_line_closest(&pr, pa, &c);
3772 }
3774 // move the node to the closest point
3775 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3776 n->origin[Geom::X] + c[Geom::X] - n->pos[Geom::X],
3777 n->origin[Geom::Y] + c[Geom::Y] - n->pos[Geom::Y],
3778 true);
3780 } else { // constraining to hor/vert
3782 if (fabs(p[Geom::X] - n->origin[Geom::X]) > fabs(p[Geom::Y] - n->origin[Geom::Y])) { // snap to hor
3783 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3784 p[Geom::X] - n->pos[Geom::X],
3785 n->origin[Geom::Y] - n->pos[Geom::Y],
3786 true,
3787 true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::X]));
3788 } else { // snap to vert
3789 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3790 n->origin[Geom::X] - n->pos[Geom::X],
3791 p[Geom::Y] - n->pos[Geom::Y],
3792 true,
3793 true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::Y]));
3794 }
3795 }
3796 } else { // move freely
3797 if (n->is_dragging) {
3798 if (state & GDK_MOD1_MASK) { // sculpt
3799 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, p - n->origin);
3800 } else {
3801 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3802 p[Geom::X] - n->pos[Geom::X],
3803 p[Geom::Y] - n->pos[Geom::Y],
3804 (state & GDK_SHIFT_MASK) == 0);
3805 }
3806 }
3807 }
3809 n->subpath->nodepath->desktop->scroll_to_point(p);
3811 return TRUE;
3812 }
3814 /**
3815 * Node handle clicked callback.
3816 */
3817 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3818 {
3819 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3821 if (state & GDK_CONTROL_MASK) { // "delete" handle
3822 if (n->p.knot == knot) {
3823 n->p.pos = n->pos;
3824 } else if (n->n.knot == knot) {
3825 n->n.pos = n->pos;
3826 }
3827 sp_node_update_handles(n);
3828 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3829 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3830 sp_nodepath_update_statusbar(nodepath);
3832 } else { // just select or add to selection, depending in Shift
3833 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3834 }
3835 }
3837 /**
3838 * Node handle grabbed callback.
3839 */
3840 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3841 {
3842 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3844 // convert auto -> smooth when dragging handle
3845 if (n->type == Inkscape::NodePath::NODE_AUTO) {
3846 n->type = Inkscape::NodePath::NODE_SMOOTH;
3847 sp_nodepath_update_node_knot (n);
3848 }
3850 if (!n->selected) {
3851 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3852 }
3854 // remember the origin point of the handle
3855 if (n->p.knot == knot) {
3856 n->p.origin_radial = n->p.pos - n->pos;
3857 } else if (n->n.knot == knot) {
3858 n->n.origin_radial = n->n.pos - n->pos;
3859 } else {
3860 g_assert_not_reached();
3861 }
3863 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3864 }
3866 /**
3867 * Node handle ungrabbed callback.
3868 */
3869 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3870 {
3871 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3873 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3874 if (n->p.knot == knot) {
3875 n->p.origin_radial.a = 0;
3876 sp_knot_set_position(knot, n->p.pos, state);
3877 } else if (n->n.knot == knot) {
3878 n->n.origin_radial.a = 0;
3879 sp_knot_set_position(knot, n->n.pos, state);
3880 } else {
3881 g_assert_not_reached();
3882 }
3884 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3885 }
3887 /**
3888 * Node handle "request" signal callback.
3889 */
3890 static gboolean node_handle_request(SPKnot *knot, Geom::Point const &p, guint state, gpointer data)
3891 {
3892 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3894 Inkscape::NodePath::NodeSide *me, *opposite;
3895 gint which;
3896 if (n->p.knot == knot) {
3897 me = &n->p;
3898 opposite = &n->n;
3899 which = -1;
3900 } else if (n->n.knot == knot) {
3901 me = &n->n;
3902 opposite = &n->p;
3903 which = 1;
3904 } else {
3905 me = opposite = NULL;
3906 which = 0;
3907 g_assert_not_reached();
3908 }
3910 SPDesktop *desktop = n->subpath->nodepath->desktop;
3911 SnapManager &m = desktop->namedview->snap_manager;
3912 m.setup(desktop, true, n->subpath->nodepath->item);
3913 Inkscape::SnappedPoint s;
3915 if ((state & GDK_SHIFT_MASK) != 0) {
3916 // We will not try to snap when the shift-key is pressed
3917 // so remove the old snap indicator and don't wait for it to time-out
3918 desktop->snapindicator->remove_snappoint();
3919 }
3921 Inkscape::NodePath::Node *othernode = opposite->other;
3922 if (othernode) {
3923 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3924 /* We are smooth node adjacent with line */
3925 Geom::Point const delta = p - n->pos;
3926 Geom::Coord const len = Geom::L2(delta);
3927 Inkscape::NodePath::Node *othernode = opposite->other;
3928 Geom::Point const ndelta = n->pos - othernode->pos;
3929 Geom::Coord const linelen = Geom::L2(ndelta);
3930 Geom::Point ptemp = p;
3931 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3932 Geom::Coord const scal = dot(delta, ndelta) / linelen;
3933 ptemp = n->pos + (scal / linelen) * ndelta;
3934 }
3935 if ((state & GDK_SHIFT_MASK) == 0) {
3936 s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, ptemp, Inkscape::Snapper::ConstraintLine(ptemp, ndelta));
3937 }
3938 } else {
3939 if ((state & GDK_SHIFT_MASK) == 0) {
3940 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p);
3941 }
3942 }
3943 } else {
3944 if ((state & GDK_SHIFT_MASK) == 0) {
3945 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p);
3946 }
3947 }
3949 sp_node_adjust_handle(n, -which);
3951 return FALSE;
3952 }
3954 /**
3955 * Node handle moved callback.
3956 */
3957 static void node_handle_moved(SPKnot *knot, Geom::Point const &p, guint state, gpointer data)
3958 {
3959 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3960 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
3962 Inkscape::NodePath::NodeSide *me;
3963 Inkscape::NodePath::NodeSide *other;
3964 if (n->p.knot == knot) {
3965 me = &n->p;
3966 other = &n->n;
3967 } else if (n->n.knot == knot) {
3968 me = &n->n;
3969 other = &n->p;
3970 } else {
3971 me = NULL;
3972 other = NULL;
3973 g_assert_not_reached();
3974 }
3976 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3977 Radial rme(me->pos - n->pos);
3978 Radial rother(other->pos - n->pos);
3979 Radial rnew(p - n->pos);
3981 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3982 int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
3983 /* 0 interpreted as "no snapping". */
3985 // 1. Snap to the closest PI/snaps angle, starting from zero.
3986 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3988 // 2. Snap to the original angle, its opposite and perpendiculars
3989 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3990 /* The closest PI/2 angle, starting from original angle */
3991 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3993 // Snap to the closest.
3994 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3995 ? a_snapped
3996 : a_ortho );
3997 }
3999 // 3. Snap to the angle of the opposite line, if any
4000 Inkscape::NodePath::Node *othernode = other->other;
4001 if (othernode) {
4002 Geom::Point other_to_snap(0,0);
4003 if (sp_node_side_is_line(n, other)) {
4004 other_to_snap = othernode->pos - n->pos;
4005 } else {
4006 other_to_snap = other->pos - n->pos;
4007 }
4008 if (Geom::L2(other_to_snap) > 1e-3) {
4009 Radial rother_to_snap(other_to_snap);
4010 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
4011 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
4013 // Snap to the closest.
4014 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
4015 ? a_snapped
4016 : a_oppo );
4017 }
4018 }
4020 rnew.a = a_snapped;
4021 }
4023 if (state & GDK_MOD1_MASK) {
4024 // lock handle length
4025 rnew.r = me->origin_radial.r;
4026 }
4028 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
4029 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
4030 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
4031 rother.a += rnew.a - rme.a;
4032 other->pos = Geom::Point(rother) + n->pos;
4033 if (other->knot) {
4034 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
4035 sp_knot_moveto(other->knot, other->pos);
4036 }
4037 }
4039 me->pos = Geom::Point(rnew) + n->pos;
4040 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
4042 // move knot, but without emitting the signal:
4043 // we cannot emit a "moved" signal because we're now processing it
4044 sp_knot_moveto(me->knot, me->pos);
4046 update_object(n->subpath->nodepath);
4048 /* status text */
4049 SPDesktop *desktop = n->subpath->nodepath->desktop;
4050 if (!desktop) return;
4051 SPEventContext *ec = desktop->event_context;
4052 if (!ec) return;
4054 Inkscape::MessageContext *mc = get_message_context(ec);
4056 if (!mc) return;
4058 double degrees = 180 / M_PI * rnew.a;
4059 if (degrees > 180) degrees -= 360;
4060 if (degrees < -180) degrees += 360;
4061 if (prefs->getBool("/options/compassangledisplay/value"))
4062 degrees = angle_to_compass (degrees);
4064 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
4066 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
4067 _("<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);
4069 g_string_free(length, TRUE);
4070 }
4072 /**
4073 * Node handle event callback.
4074 */
4075 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
4076 {
4077 gboolean ret = FALSE;
4078 switch (event->type) {
4079 case GDK_KEY_PRESS:
4080 switch (get_group0_keyval (&event->key)) {
4081 case GDK_space:
4082 if (event->key.state & GDK_BUTTON1_MASK) {
4083 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
4084 stamp_repr(nodepath);
4085 ret = TRUE;
4086 }
4087 break;
4088 default:
4089 break;
4090 }
4091 break;
4092 case GDK_ENTER_NOTIFY:
4093 // we use an experimentally determined threshold that seems to work fine
4094 if (Geom::L2(n->pos - knot->pos) < 0.75)
4095 Inkscape::NodePath::Path::active_node = n;
4096 break;
4097 case GDK_LEAVE_NOTIFY:
4098 // we use an experimentally determined threshold that seems to work fine
4099 if (Geom::L2(n->pos - knot->pos) < 0.75)
4100 Inkscape::NodePath::Path::active_node = NULL;
4101 break;
4102 default:
4103 break;
4104 }
4106 return ret;
4107 }
4109 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
4110 Radial &rme, Radial &rother, gboolean const both)
4111 {
4112 rme.a += angle;
4113 if ( both
4114 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4115 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4116 {
4117 rother.a += angle;
4118 }
4119 }
4121 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
4122 Radial &rme, Radial &rother, gboolean const both)
4123 {
4124 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4126 gdouble r;
4127 if ( both
4128 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4129 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4130 {
4131 r = MAX(rme.r, rother.r);
4132 } else {
4133 r = rme.r;
4134 }
4136 gdouble const weird_angle = atan2(norm_angle, r);
4137 /* Bulia says norm_angle is just the visible distance that the
4138 * object's end must travel on the screen. Left as 'angle' for want of
4139 * a better name.*/
4141 rme.a += weird_angle;
4142 if ( both
4143 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4144 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4145 {
4146 rother.a += weird_angle;
4147 }
4148 }
4150 /**
4151 * Rotate one node.
4152 */
4153 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4154 {
4155 Inkscape::NodePath::NodeSide *me, *other;
4156 bool both = false;
4158 double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4159 double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4161 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4162 me = &(n->p);
4163 other = &(n->n);
4164 } else if (!n->p.other) {
4165 me = &(n->n);
4166 other = &(n->p);
4167 } else {
4168 if (which > 0) { // right handle
4169 if (xn > xp) {
4170 me = &(n->n);
4171 other = &(n->p);
4172 } else {
4173 me = &(n->p);
4174 other = &(n->n);
4175 }
4176 } else if (which < 0){ // left handle
4177 if (xn <= xp) {
4178 me = &(n->n);
4179 other = &(n->p);
4180 } else {
4181 me = &(n->p);
4182 other = &(n->n);
4183 }
4184 } else { // both handles
4185 me = &(n->n);
4186 other = &(n->p);
4187 both = true;
4188 }
4189 }
4191 Radial rme(me->pos - n->pos);
4192 Radial rother(other->pos - n->pos);
4194 if (screen) {
4195 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4196 } else {
4197 node_rotate_one_internal (*n, angle, rme, rother, both);
4198 }
4200 me->pos = n->pos + Geom::Point(rme);
4202 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4203 other->pos = n->pos + Geom::Point(rother);
4204 }
4206 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4207 // so here we just move all the knots without emitting move signals, for speed
4208 sp_node_update_handles(n, false);
4209 }
4211 /**
4212 * Rotate selected nodes.
4213 */
4214 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4215 {
4216 if (!nodepath || !nodepath->selected) return;
4218 if (g_list_length(nodepath->selected) == 1) {
4219 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4220 node_rotate_one (n, angle, which, screen);
4221 } else {
4222 // rotate as an object:
4224 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4225 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4226 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4227 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4228 box.expandTo (n->pos); // contain all selected nodes
4229 }
4231 gdouble rot;
4232 if (screen) {
4233 gdouble const zoom = nodepath->desktop->current_zoom();
4234 gdouble const zmove = angle / zoom;
4235 gdouble const r = Geom::L2(box.max() - box.midpoint());
4236 rot = atan2(zmove, r);
4237 } else {
4238 rot = angle;
4239 }
4241 Geom::Point rot_center;
4242 if (Inkscape::NodePath::Path::active_node == NULL)
4243 rot_center = box.midpoint();
4244 else
4245 rot_center = Inkscape::NodePath::Path::active_node->pos;
4247 Geom::Matrix t =
4248 Geom::Matrix (Geom::Translate(-rot_center)) *
4249 Geom::Matrix (Geom::Rotate(rot)) *
4250 Geom::Matrix (Geom::Translate(rot_center));
4252 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4253 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4254 n->pos *= t;
4255 n->n.pos *= t;
4256 n->p.pos *= t;
4257 sp_node_update_handles(n, false);
4258 }
4259 }
4261 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4262 }
4264 /**
4265 * Scale one node.
4266 */
4267 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4268 {
4269 bool both = false;
4270 Inkscape::NodePath::NodeSide *me, *other;
4272 double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4273 double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4275 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4276 me = &(n->p);
4277 other = &(n->n);
4278 n->code = NR_CURVETO;
4279 } else if (!n->p.other) {
4280 me = &(n->n);
4281 other = &(n->p);
4282 if (n->n.other)
4283 n->n.other->code = NR_CURVETO;
4284 } else {
4285 if (which > 0) { // right handle
4286 if (xn > xp) {
4287 me = &(n->n);
4288 other = &(n->p);
4289 if (n->n.other)
4290 n->n.other->code = NR_CURVETO;
4291 } else {
4292 me = &(n->p);
4293 other = &(n->n);
4294 n->code = NR_CURVETO;
4295 }
4296 } else if (which < 0){ // left handle
4297 if (xn <= xp) {
4298 me = &(n->n);
4299 other = &(n->p);
4300 if (n->n.other)
4301 n->n.other->code = NR_CURVETO;
4302 } else {
4303 me = &(n->p);
4304 other = &(n->n);
4305 n->code = NR_CURVETO;
4306 }
4307 } else { // both handles
4308 me = &(n->n);
4309 other = &(n->p);
4310 both = true;
4311 n->code = NR_CURVETO;
4312 if (n->n.other)
4313 n->n.other->code = NR_CURVETO;
4314 }
4315 }
4317 Radial rme(me->pos - n->pos);
4318 Radial rother(other->pos - n->pos);
4320 rme.r += grow;
4321 if (rme.r < 0) rme.r = 0;
4322 if (rme.a == HUGE_VAL) {
4323 if (me->other) { // if direction is unknown, initialize it towards the next node
4324 Radial rme_next(me->other->pos - n->pos);
4325 rme.a = rme_next.a;
4326 } else { // if there's no next, initialize to 0
4327 rme.a = 0;
4328 }
4329 }
4330 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4331 rother.r += grow;
4332 if (rother.r < 0) rother.r = 0;
4333 if (rother.a == HUGE_VAL) {
4334 rother.a = rme.a + M_PI;
4335 }
4336 }
4338 me->pos = n->pos + Geom::Point(rme);
4340 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4341 other->pos = n->pos + Geom::Point(rother);
4342 }
4344 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4345 // so here we just move all the knots without emitting move signals, for speed
4346 sp_node_update_handles(n, false);
4347 }
4349 /**
4350 * Scale selected nodes.
4351 */
4352 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4353 {
4354 if (!nodepath || !nodepath->selected) return;
4356 if (g_list_length(nodepath->selected) == 1) {
4357 // scale handles of the single selected node
4358 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4359 node_scale_one (n, grow, which);
4360 } else {
4361 // scale nodes as an "object":
4363 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4364 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4365 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4366 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4367 box.expandTo (n->pos); // contain all selected nodes
4368 }
4370 if ( Geom::are_near(box.maxExtent(), 0) ) {
4371 SPEventContext *ec = nodepath->desktop->event_context;
4372 if (!ec) return;
4373 Inkscape::MessageContext *mc = get_message_context(ec);
4374 if (!mc) return;
4375 mc->setF(Inkscape::WARNING_MESSAGE,
4376 _("Cannot scale nodes when all are at the same location."));
4377 return;
4378 }
4379 double scale = (box.maxExtent() + grow)/box.maxExtent();
4382 Geom::Point scale_center;
4383 if (Inkscape::NodePath::Path::active_node == NULL)
4384 scale_center = box.midpoint();
4385 else
4386 scale_center = Inkscape::NodePath::Path::active_node->pos;
4388 Geom::Matrix t =
4389 Geom::Translate(-scale_center) *
4390 Geom::Scale(scale, scale) *
4391 Geom::Translate(scale_center);
4393 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4394 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4395 n->pos *= t;
4396 n->n.pos *= t;
4397 n->p.pos *= t;
4398 sp_node_update_handles(n, false);
4399 }
4400 }
4402 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4403 }
4405 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4406 {
4407 if (!nodepath) return;
4408 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4409 }
4411 /**
4412 * Flip selected nodes horizontally/vertically.
4413 */
4414 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis, boost::optional<Geom::Point> center)
4415 {
4416 if (!nodepath || !nodepath->selected) return;
4418 if (g_list_length(nodepath->selected) == 1 && !center) {
4419 // flip handles of the single selected node
4420 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4421 double temp = n->p.pos[axis];
4422 n->p.pos[axis] = n->n.pos[axis];
4423 n->n.pos[axis] = temp;
4424 sp_node_update_handles(n, false);
4425 } else {
4426 // scale nodes as an "object":
4428 Geom::Rect box = sp_node_selected_bbox (nodepath);
4429 if (!center) {
4430 center = box.midpoint();
4431 }
4432 Geom::Matrix t =
4433 Geom::Matrix (Geom::Translate(- *center)) *
4434 Geom::Matrix ((axis == Geom::X)? Geom::Scale(-1, 1) : Geom::Scale(1, -1)) *
4435 Geom::Matrix (Geom::Translate(*center));
4437 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4438 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4439 n->pos *= t;
4440 n->n.pos *= t;
4441 n->p.pos *= t;
4442 sp_node_update_handles(n, false);
4443 }
4444 }
4446 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4447 }
4449 Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4450 {
4451 g_assert (nodepath->selected);
4453 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4454 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4455 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4456 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4457 box.expandTo (n->pos); // contain all selected nodes
4458 }
4459 return box;
4460 }
4462 //-----------------------------------------------
4463 /**
4464 * Return new subpath under given nodepath.
4465 */
4466 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4467 {
4468 g_assert(nodepath);
4469 g_assert(nodepath->desktop);
4471 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4473 s->nodepath = nodepath;
4474 s->closed = FALSE;
4475 s->nodes = NULL;
4476 s->first = NULL;
4477 s->last = NULL;
4479 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4480 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4481 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4483 return s;
4484 }
4486 /**
4487 * Destroy nodes in subpath, then subpath itself.
4488 */
4489 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4490 {
4491 g_assert(subpath);
4492 g_assert(subpath->nodepath);
4493 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4495 while (subpath->nodes) {
4496 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4497 }
4499 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4501 g_free(subpath);
4502 }
4504 /**
4505 * Link head to tail in subpath.
4506 */
4507 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4508 {
4509 g_assert(!sp->closed);
4510 g_assert(sp->last != sp->first);
4511 g_assert(sp->first->code == NR_MOVETO);
4513 sp->closed = TRUE;
4515 //Link the head to the tail
4516 sp->first->p.other = sp->last;
4517 sp->last->n.other = sp->first;
4518 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4519 sp->first = sp->last;
4521 //Remove the extra end node
4522 sp_nodepath_node_destroy(sp->last->n.other);
4523 }
4525 /**
4526 * Open closed (loopy) subpath at node.
4527 */
4528 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4529 {
4530 g_assert(sp->closed);
4531 g_assert(n->subpath == sp);
4532 g_assert(sp->first == sp->last);
4534 /* We create new startpoint, current node will become last one */
4536 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4537 &n->pos, &n->pos, &n->n.pos);
4540 sp->closed = FALSE;
4542 //Unlink to make a head and tail
4543 sp->first = new_path;
4544 sp->last = n;
4545 n->n.other = NULL;
4546 new_path->p.other = NULL;
4547 }
4549 /**
4550 * Return new node in subpath with given properties.
4551 * \param pos Position of node.
4552 * \param ppos Handle position in previous direction
4553 * \param npos Handle position in previous direction
4554 */
4555 Inkscape::NodePath::Node *
4556 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)
4557 {
4558 g_assert(sp);
4559 g_assert(sp->nodepath);
4560 g_assert(sp->nodepath->desktop);
4562 if (nodechunk == NULL)
4563 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4565 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4567 n->subpath = sp;
4569 if (type != Inkscape::NodePath::NODE_NONE) {
4570 // use the type from sodipodi:nodetypes
4571 n->type = type;
4572 } else {
4573 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4574 // points are (almost) collinear
4575 if (Geom::L2(*pos - *ppos) < 1e-6 || Geom::L2(*pos - *npos) < 1e-6) {
4576 // endnode, or a node with a retracted handle
4577 n->type = Inkscape::NodePath::NODE_CUSP;
4578 } else {
4579 n->type = Inkscape::NodePath::NODE_SMOOTH;
4580 }
4581 } else {
4582 n->type = Inkscape::NodePath::NODE_CUSP;
4583 }
4584 }
4586 n->code = code;
4587 n->selected = FALSE;
4588 n->pos = *pos;
4589 n->p.pos = *ppos;
4590 n->n.pos = *npos;
4592 n->dragging_out = NULL;
4594 Inkscape::NodePath::Node *prev;
4595 if (next) {
4596 //g_assert(g_list_find(sp->nodes, next));
4597 prev = next->p.other;
4598 } else {
4599 prev = sp->last;
4600 }
4602 if (prev)
4603 prev->n.other = n;
4604 else
4605 sp->first = n;
4607 if (next)
4608 next->p.other = n;
4609 else
4610 sp->last = n;
4612 n->p.other = prev;
4613 n->n.other = next;
4615 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"));
4616 sp_knot_set_position(n->knot, *pos, 0);
4618 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4619 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4620 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4622 sp_nodepath_update_node_knot(n);
4624 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4625 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4626 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4627 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4628 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4629 sp_knot_show(n->knot);
4631 // We only create handle knots and lines on demand
4632 n->p.knot = NULL;
4633 n->p.line = NULL;
4634 n->n.knot = NULL;
4635 n->n.line = NULL;
4637 sp->nodes = g_list_prepend(sp->nodes, n);
4639 return n;
4640 }
4642 /**
4643 * Destroy node and its knots, link neighbors in subpath.
4644 */
4645 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4646 {
4647 g_assert(node);
4648 g_assert(node->subpath);
4649 g_assert(SP_IS_KNOT(node->knot));
4651 Inkscape::NodePath::SubPath *sp = node->subpath;
4653 if (node->selected) { // first, deselect
4654 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4655 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4656 }
4658 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4660 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4661 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4662 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4663 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4664 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4665 g_object_unref(G_OBJECT(node->knot));
4667 if (node->p.knot) {
4668 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4669 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4670 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4671 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4672 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4673 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4674 g_object_unref(G_OBJECT(node->p.knot));
4675 node->p.knot = NULL;
4676 }
4678 if (node->n.knot) {
4679 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4680 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4681 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4682 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4683 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4684 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4685 g_object_unref(G_OBJECT(node->n.knot));
4686 node->n.knot = NULL;
4687 }
4689 if (node->p.line)
4690 gtk_object_destroy(GTK_OBJECT(node->p.line));
4691 if (node->n.line)
4692 gtk_object_destroy(GTK_OBJECT(node->n.line));
4694 if (sp->nodes) { // there are others nodes on the subpath
4695 if (sp->closed) {
4696 if (sp->first == node) {
4697 g_assert(sp->last == node);
4698 sp->first = node->n.other;
4699 sp->last = sp->first;
4700 }
4701 node->p.other->n.other = node->n.other;
4702 node->n.other->p.other = node->p.other;
4703 } else {
4704 if (sp->first == node) {
4705 sp->first = node->n.other;
4706 sp->first->code = NR_MOVETO;
4707 }
4708 if (sp->last == node) sp->last = node->p.other;
4709 if (node->p.other) node->p.other->n.other = node->n.other;
4710 if (node->n.other) node->n.other->p.other = node->p.other;
4711 }
4712 } else { // this was the last node on subpath
4713 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4714 }
4716 g_mem_chunk_free(nodechunk, node);
4717 }
4719 /**
4720 * Returns one of the node's two sides.
4721 * \param which Indicates which side.
4722 * \return Pointer to previous node side if which==-1, next if which==1.
4723 */
4724 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4725 {
4726 g_assert(node);
4727 Inkscape::NodePath::NodeSide * result = 0;
4728 switch (which) {
4729 case -1:
4730 result = &node->p;
4731 break;
4732 case 1:
4733 result = &node->n;
4734 break;
4735 default:
4736 g_assert_not_reached();
4737 }
4739 return result;
4740 }
4742 /**
4743 * Return the other side of the node, given one of its sides.
4744 */
4745 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4746 {
4747 g_assert(node);
4748 Inkscape::NodePath::NodeSide *result = 0;
4750 if (me == &node->p) {
4751 result = &node->n;
4752 } else if (me == &node->n) {
4753 result = &node->p;
4754 } else {
4755 g_assert_not_reached();
4756 }
4758 return result;
4759 }
4761 /**
4762 * Return NRPathcode on the given side of the node.
4763 */
4764 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4765 {
4766 g_assert(node);
4768 NRPathcode result = NR_END;
4769 if (me == &node->p) {
4770 if (node->p.other) {
4771 result = (NRPathcode)node->code;
4772 } else {
4773 result = NR_MOVETO;
4774 }
4775 } else if (me == &node->n) {
4776 if (node->n.other) {
4777 result = (NRPathcode)node->n.other->code;
4778 } else {
4779 result = NR_MOVETO;
4780 }
4781 } else {
4782 g_assert_not_reached();
4783 }
4785 return result;
4786 }
4788 /**
4789 * Return node with the given index
4790 */
4791 Inkscape::NodePath::Node *
4792 sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index)
4793 {
4794 Inkscape::NodePath::Node *e = NULL;
4796 if (!nodepath) {
4797 return e;
4798 }
4800 //find segment
4801 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4803 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4804 int n = g_list_length(sp->nodes);
4805 if (sp->closed) {
4806 n++;
4807 }
4809 //if the piece belongs to this subpath grab it
4810 //otherwise move onto the next subpath
4811 if (index < n) {
4812 e = sp->first;
4813 for (int i = 0; i < index; ++i) {
4814 e = e->n.other;
4815 }
4816 break;
4817 } else {
4818 if (sp->closed) {
4819 index -= (n+1);
4820 } else {
4821 index -= n;
4822 }
4823 }
4824 }
4826 return e;
4827 }
4829 /**
4830 * Returns plain text meaning of node type.
4831 */
4832 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4833 {
4834 unsigned retracted = 0;
4835 bool endnode = false;
4837 for (int which = -1; which <= 1; which += 2) {
4838 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4839 if (side->other && Geom::L2(side->pos - node->pos) < 1e-6)
4840 retracted ++;
4841 if (!side->other)
4842 endnode = true;
4843 }
4845 if (retracted == 0) {
4846 if (endnode) {
4847 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4848 return _("end node");
4849 } else {
4850 switch (node->type) {
4851 case Inkscape::NodePath::NODE_CUSP:
4852 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4853 return _("cusp");
4854 case Inkscape::NodePath::NODE_SMOOTH:
4855 // TRANSLATORS: "smooth" is an adjective here
4856 return _("smooth");
4857 case Inkscape::NodePath::NODE_AUTO:
4858 return _("auto");
4859 case Inkscape::NodePath::NODE_SYMM:
4860 return _("symmetric");
4861 }
4862 }
4863 } else if (retracted == 1) {
4864 if (endnode) {
4865 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4866 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4867 } else {
4868 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4869 }
4870 } else {
4871 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4872 }
4874 return NULL;
4875 }
4877 /**
4878 * Handles content of statusbar as long as node tool is active.
4879 */
4880 void
4881 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4882 {
4883 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");
4884 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4886 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4887 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4888 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4889 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4891 SPDesktop *desktop = NULL;
4892 if (nodepath) {
4893 desktop = nodepath->desktop;
4894 } else {
4895 desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above
4896 }
4898 SPEventContext *ec = desktop->event_context;
4899 if (!ec) return;
4901 Inkscape::MessageContext *mc = get_message_context(ec);
4902 if (!mc) return;
4904 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4906 if (selected_nodes == 0) {
4907 Inkscape::Selection *sel = desktop->selection;
4908 if (!sel || sel->isEmpty()) {
4909 mc->setF(Inkscape::NORMAL_MESSAGE,
4910 _("Select a single object to edit its nodes or handles."));
4911 } else {
4912 if (nodepath) {
4913 mc->setF(Inkscape::NORMAL_MESSAGE,
4914 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.",
4915 "<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.",
4916 total_nodes),
4917 total_nodes);
4918 } else {
4919 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4920 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4921 } else {
4922 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4923 }
4924 }
4925 }
4926 } else if (nodepath && selected_nodes == 1) {
4927 mc->setF(Inkscape::NORMAL_MESSAGE,
4928 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4929 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4930 total_nodes),
4931 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4932 } else {
4933 if (selected_subpaths > 1) {
4934 mc->setF(Inkscape::NORMAL_MESSAGE,
4935 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4936 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4937 total_nodes),
4938 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4939 } else {
4940 mc->setF(Inkscape::NORMAL_MESSAGE,
4941 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4942 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4943 total_nodes),
4944 selected_nodes, total_nodes, when_selected);
4945 }
4946 }
4947 }
4949 /*
4950 * returns a *copy* of the curve of that object.
4951 */
4952 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4953 if (!object)
4954 return NULL;
4956 SPCurve *curve = NULL;
4957 if (SP_IS_PATH(object)) {
4958 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4959 curve = curve_new->copy();
4960 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4961 const gchar *svgd = object->repr->attribute(key);
4962 if (svgd) {
4963 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4964 SPCurve *curve_new = new SPCurve(pv);
4965 if (curve_new) {
4966 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4967 }
4968 }
4969 }
4971 return curve;
4972 }
4974 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4975 if (!np || !np->object || !curve)
4976 return;
4978 if (SP_IS_PATH(np->object)) {
4979 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4980 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4981 } else {
4982 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4983 }
4984 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4985 Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(np->object)->get_lpe();
4986 if (lpe) {
4987 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( lpe->getParameter(np->repr_key) );
4988 if (pathparam) {
4989 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
4990 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4991 }
4992 }
4993 }
4994 }
4996 /*
4997 SPCanvasItem *
4998 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
4999 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
5000 }
5001 */
5003 /*
5004 SPCanvasItem *
5005 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
5006 SPCurve *flash_curve = curve->copy();
5007 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
5008 flash_curve->transform(i2d);
5009 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
5010 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
5011 // unless we also flash the nodes...
5012 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
5013 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
5014 sp_canvas_item_show(canvasitem);
5015 flash_curve->unref();
5016 return canvasitem;
5017 }
5019 SPCanvasItem *
5020 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
5021 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
5022 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
5023 prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff));
5024 }
5025 */
5027 SPCanvasItem *
5028 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
5029 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
5030 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
5031 flash_curve->transform(i2d);
5032 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
5033 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
5034 // unless we also flash the nodes...
5035 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
5036 guint32 color = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
5037 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
5038 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
5039 sp_canvas_item_show(canvasitem);
5040 flash_curve->unref();
5041 return canvasitem;
5042 }
5044 // TODO: Merge this with sp_nodepath_make_helper_item()!
5045 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
5046 np->show_helperpath = show;
5048 if (show) {
5049 SPCurve *helper_curve = np->curve->copy();
5050 helper_curve->transform(np->i2d);
5051 if (!np->helper_path) {
5052 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
5054 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
5055 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);
5056 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
5057 sp_canvas_item_move_to_z(np->helper_path, 0);
5058 sp_canvas_item_show(np->helper_path);
5059 } else {
5060 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
5061 }
5062 helper_curve->unref();
5063 } else {
5064 if (np->helper_path) {
5065 GtkObject *temp = np->helper_path;
5066 np->helper_path = NULL;
5067 gtk_object_destroy(temp);
5068 }
5069 }
5070 }
5072 /* sp_nodepath_make_straight_path:
5073 * Prevents user from curving the path by dragging a segment or activating handles etc.
5074 * The resulting path is a linear interpolation between nodal points, with only straight segments.
5075 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
5076 */
5077 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
5078 np->straight_path = true;
5079 np->show_handles = false;
5080 g_message("add code to make the path straight.");
5081 // do sp_nodepath_convert_node_type on all nodes?
5082 // coding tip: search for this text : "Make selected segments lines"
5083 }
5085 /*
5086 Local Variables:
5087 mode:c++
5088 c-file-style:"stroustrup"
5089 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
5090 indent-tabs-mode:nil
5091 fill-column:99
5092 End:
5093 */
5094 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :