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