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