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.other->code = NR_CURVETO;
1223 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1224 } else {
1225 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1226 }
1227 } else if (n_has_handle && p_is_line) {
1228 Radial line (node->p.other->pos - node->pos);
1229 Radial handle (node->pos - node->n.pos);
1230 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1231 // already half-smooth; pull opposite handle too making it fully smooth
1232 node->code = NR_CURVETO;
1233 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1234 } else {
1235 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1236 }
1237 } else if (p_has_handle && node->n.other) {
1238 // pull n handle
1239 node->n.other->code = NR_CURVETO;
1240 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1241 Geom::L2(node->p.pos - node->pos) :
1242 Geom::L2(node->n.other->pos - node->pos) / 3;
1243 node->n.pos = node->pos - (len / Geom::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1244 } else if (n_has_handle && node->p.other) {
1245 // pull p handle
1246 node->code = NR_CURVETO;
1247 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1248 Geom::L2(node->n.pos - node->pos) :
1249 Geom::L2(node->p.other->pos - node->pos) / 3;
1250 node->p.pos = node->pos - (len / Geom::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1251 }
1252 } else if (!p_has_handle && !n_has_handle) {
1253 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1254 // no handles, but both segments are either lnes or curves:
1255 //pull both handles
1257 // convert both to curves:
1258 node->code = NR_CURVETO;
1259 node->n.other->code = NR_CURVETO;
1261 sp_node_adjust_handles_auto(node);
1262 } else {
1263 // pull the handle opposite to line segment, making it half-smooth
1264 if (p_is_line && node->n.other) {
1265 if (type != Inkscape::NodePath::NODE_SYMM) {
1266 // pull n handle
1267 node->n.other->code = NR_CURVETO;
1268 double len = Geom::L2(node->n.other->pos - node->pos) / 3;
1269 node->n.pos = node->pos + (len / Geom::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1270 }
1271 } else if (n_is_line && node->p.other) {
1272 if (type != Inkscape::NodePath::NODE_SYMM) {
1273 // pull p handle
1274 node->code = NR_CURVETO;
1275 double len = Geom::L2(node->p.other->pos - node->pos) / 3;
1276 node->p.pos = node->pos + (len / Geom::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1277 }
1278 }
1279 }
1280 }
1281 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1282 // cusping a cusp: retract nodes
1283 node->p.pos = node->pos;
1284 node->n.pos = node->pos;
1285 }
1287 sp_nodepath_set_node_type (node, type);
1288 }
1290 /**
1291 * Move node to point, and adjust its and neighbouring handles.
1292 */
1293 void sp_node_moveto(Inkscape::NodePath::Node *node, Geom::Point p)
1294 {
1295 if (node->type == Inkscape::NodePath::NODE_AUTO) {
1296 node->pos = p;
1297 sp_node_adjust_handles_auto(node);
1298 } else {
1299 Geom::Point delta = p - node->pos;
1300 node->pos = p;
1302 node->p.pos += delta;
1303 node->n.pos += delta;
1304 }
1306 Inkscape::NodePath::Node *node_p = NULL;
1307 Inkscape::NodePath::Node *node_n = NULL;
1309 if (node->p.other) {
1310 if (node->code == NR_LINETO) {
1311 sp_node_adjust_handle(node, 1);
1312 sp_node_adjust_handle(node->p.other, -1);
1313 node_p = node->p.other;
1314 }
1315 if (!node->p.other->selected && node->p.other->type == Inkscape::NodePath::NODE_AUTO) {
1316 sp_node_adjust_handles_auto(node->p.other);
1317 node_p = node->p.other;
1318 }
1319 }
1320 if (node->n.other) {
1321 if (node->n.other->code == NR_LINETO) {
1322 sp_node_adjust_handle(node, -1);
1323 sp_node_adjust_handle(node->n.other, 1);
1324 node_n = node->n.other;
1325 }
1326 if (!node->n.other->selected && node->n.other->type == Inkscape::NodePath::NODE_AUTO) {
1327 sp_node_adjust_handles_auto(node->n.other);
1328 node_n = node->n.other;
1329 }
1330 }
1332 // this function is only called from batch movers that will update display at the end
1333 // themselves, so here we just move all the knots without emitting move signals, for speed
1334 sp_node_update_handles(node, false);
1335 if (node_n) {
1336 sp_node_update_handles(node_n, false);
1337 }
1338 if (node_p) {
1339 sp_node_update_handles(node_p, false);
1340 }
1341 }
1343 /**
1344 * Call sp_node_moveto() for node selection and handle possible snapping.
1345 */
1346 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, Geom::Coord dx, Geom::Coord dy,
1347 bool const snap, bool constrained = false,
1348 Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point())
1349 {
1350 Geom::Point delta(dx, dy);
1351 Geom::Point best_pt = delta;
1352 Inkscape::SnappedPoint best;
1354 if (snap) {
1355 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1356 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1357 * must provide that information. */
1359 // Build a list of the unselected nodes to which the snapper should snap
1360 std::vector<std::pair<Geom::Point, int> > unselected_nodes;
1361 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1362 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1363 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1364 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1365 if (!node->selected) {
1366 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));
1367 }
1368 }
1369 }
1371 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1373 // When only the node closest to the mouse pointer is to be snapped
1374 // then we will not even try to snap to other points and discard those immediately
1375 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1376 bool closest_only = prefs->getBool("/options/snapclosestonly/value", false);
1378 Inkscape::NodePath::Node *closest_node = NULL;
1379 Geom::Coord closest_dist = NR_HUGE;
1381 if (closest_only) {
1382 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1383 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1384 Geom::Coord dist = Geom::L2(nodepath->drag_origin_mouse - n->origin);
1385 if (dist < closest_dist) {
1386 closest_node = n;
1387 closest_dist = dist;
1388 }
1389 }
1390 }
1392 // Iterate through all selected nodes
1393 m.setup(nodepath->desktop, false, nodepath->item, &unselected_nodes);
1394 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1395 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1396 if (!closest_only || n == closest_node) { //try to snap either all selected nodes or only the closest one
1397 Inkscape::SnappedPoint s;
1398 Inkscape::SnapSourceType source_type = (n->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPSOURCE_NODE_SMOOTH : Inkscape::SNAPSOURCE_NODE_CUSP);
1399 if (constrained) {
1400 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1401 dedicated_constraint.setPoint(n->pos);
1402 s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), source_type, dedicated_constraint, false);
1403 } else {
1404 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), source_type);
1405 }
1407 if (s.getSnapped()) {
1408 s.setPointerDistance(Geom::L2(nodepath->drag_origin_mouse - n->origin));
1409 if (!s.isOtherSnapBetter(best, true)) {
1410 best = s;
1411 best_pt = from_2geom(s.getPoint()) - n->pos;
1412 }
1413 }
1414 }
1415 }
1417 if (best.getSnapped()) {
1418 nodepath->desktop->snapindicator->set_new_snaptarget(best);
1419 } else {
1420 nodepath->desktop->snapindicator->remove_snaptarget();
1421 }
1422 }
1424 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1425 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1426 sp_node_moveto(n, n->pos + best_pt);
1427 }
1429 // do not update repr here so that node dragging is acceptably fast
1430 update_object(nodepath);
1431 }
1433 /**
1434 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1435 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1436 near x = 0.
1437 */
1438 double
1439 sculpt_profile (double x, double alpha, guint profile)
1440 {
1441 double result = 1;
1443 if (x >= 1) {
1444 result = 0;
1445 } else if (x <= 0) {
1446 result = 1;
1447 } else {
1448 switch (profile) {
1449 case SCULPT_PROFILE_LINEAR:
1450 result = 1 - x;
1451 break;
1452 case SCULPT_PROFILE_BELL:
1453 result = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1454 break;
1455 case SCULPT_PROFILE_ELLIPTIC:
1456 result = sqrt(1 - x*x);
1457 break;
1458 default:
1459 g_assert_not_reached();
1460 }
1461 }
1463 return result;
1464 }
1466 double
1467 bezier_length (Geom::Point a, Geom::Point ah, Geom::Point bh, Geom::Point b)
1468 {
1469 // extremely primitive for now, don't have time to look for the real one
1470 double lower = Geom::L2(b - a);
1471 double upper = Geom::L2(ah - a) + Geom::L2(bh - ah) + Geom::L2(bh - b);
1472 return (lower + upper)/2;
1473 }
1475 void
1476 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, Geom::Point delta, Geom::Point delta_n, Geom::Point delta_p)
1477 {
1478 n->pos = n->origin + delta;
1479 n->n.pos = n->n.origin + delta_n;
1480 n->p.pos = n->p.origin + delta_p;
1481 sp_node_adjust_handles(n);
1482 sp_node_update_handles(n, false);
1483 }
1485 /**
1486 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1487 * on how far they are from the dragged node n.
1488 */
1489 static void
1490 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, Geom::Point delta)
1491 {
1492 g_assert (n);
1493 g_assert (nodepath);
1494 g_assert (n->subpath->nodepath == nodepath);
1496 double pressure = n->knot->pressure;
1497 if (pressure == 0)
1498 pressure = 0.5; // default
1499 pressure = CLAMP (pressure, 0.2, 0.8);
1501 // map pressure to alpha = 1/5 ... 5
1502 double alpha = 1 - 2 * fabs(pressure - 0.5);
1503 if (pressure > 0.5)
1504 alpha = 1/alpha;
1506 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1507 guint profile = prefs->getInt("/tools/nodes/sculpting_profile", SCULPT_PROFILE_BELL);
1509 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1510 // Only one subpath has selected nodes:
1511 // use linear mode, where the distance from n to node being dragged is calculated along the path
1513 double n_sel_range = 0, p_sel_range = 0;
1514 guint n_nodes = 0, p_nodes = 0;
1515 guint n_sel_nodes = 0, p_sel_nodes = 0;
1517 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1518 {
1519 double n_range = 0, p_range = 0;
1520 bool n_going = true, p_going = true;
1521 Inkscape::NodePath::Node *n_node = n;
1522 Inkscape::NodePath::Node *p_node = n;
1523 do {
1524 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1525 if (n_node && n_going)
1526 n_node = n_node->n.other;
1527 if (n_node == NULL) {
1528 n_going = false;
1529 } else {
1530 n_nodes ++;
1531 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1532 if (n_node->selected) {
1533 n_sel_nodes ++;
1534 n_sel_range = n_range;
1535 }
1536 if (n_node == p_node) {
1537 n_going = false;
1538 p_going = false;
1539 }
1540 }
1541 if (p_node && p_going)
1542 p_node = p_node->p.other;
1543 if (p_node == NULL) {
1544 p_going = false;
1545 } else {
1546 p_nodes ++;
1547 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1548 if (p_node->selected) {
1549 p_sel_nodes ++;
1550 p_sel_range = p_range;
1551 }
1552 if (p_node == n_node) {
1553 n_going = false;
1554 p_going = false;
1555 }
1556 }
1557 } while (n_going || p_going);
1558 }
1560 // Second pass: actually move nodes in this subpath
1561 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1562 {
1563 double n_range = 0, p_range = 0;
1564 bool n_going = true, p_going = true;
1565 Inkscape::NodePath::Node *n_node = n;
1566 Inkscape::NodePath::Node *p_node = n;
1567 do {
1568 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1569 if (n_node && n_going)
1570 n_node = n_node->n.other;
1571 if (n_node == NULL) {
1572 n_going = false;
1573 } else {
1574 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1575 if (n_node->selected) {
1576 sp_nodepath_move_node_and_handles (n_node,
1577 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1578 sculpt_profile ((n_range + Geom::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1579 sculpt_profile ((n_range - Geom::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1580 }
1581 if (n_node == p_node) {
1582 n_going = false;
1583 p_going = false;
1584 }
1585 }
1586 if (p_node && p_going)
1587 p_node = p_node->p.other;
1588 if (p_node == NULL) {
1589 p_going = false;
1590 } else {
1591 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1592 if (p_node->selected) {
1593 sp_nodepath_move_node_and_handles (p_node,
1594 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1595 sculpt_profile ((p_range - Geom::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1596 sculpt_profile ((p_range + Geom::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1597 }
1598 if (p_node == n_node) {
1599 n_going = false;
1600 p_going = false;
1601 }
1602 }
1603 } while (n_going || p_going);
1604 }
1606 } else {
1607 // Multiple subpaths have selected nodes:
1608 // use spatial mode, where the distance from n to node being dragged is measured directly as Geom::L2.
1609 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1610 // fix the pear-like shape when sculpting e.g. a ring
1612 // First pass: calculate range
1613 gdouble direct_range = 0;
1614 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1615 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1616 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1617 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1618 if (node->selected) {
1619 direct_range = MAX(direct_range, Geom::L2(node->origin - n->origin));
1620 }
1621 }
1622 }
1624 // Second pass: actually move nodes
1625 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1626 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1627 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1628 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1629 if (node->selected) {
1630 if (direct_range > 1e-6) {
1631 sp_nodepath_move_node_and_handles (node,
1632 sculpt_profile (Geom::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1633 sculpt_profile (Geom::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1634 sculpt_profile (Geom::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1635 } else {
1636 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1637 }
1639 }
1640 }
1641 }
1642 }
1644 // do not update repr here so that node dragging is acceptably fast
1645 update_object(nodepath);
1646 }
1649 /**
1650 * Move node selection to point, adjust its and neighbouring handles,
1651 * handle possible snapping, and commit the change with possible undo.
1652 */
1653 void
1654 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1655 {
1656 if (!nodepath) return;
1658 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1660 if (dx == 0) {
1661 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1662 } else if (dy == 0) {
1663 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1664 } else {
1665 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1666 }
1667 }
1669 /**
1670 * Move node selection off screen and commit the change.
1671 */
1672 void
1673 sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1674 {
1675 // borrowed from sp_selection_move_screen in selection-chemistry.c
1676 // we find out the current zoom factor and divide deltas by it
1678 gdouble zoom = desktop->current_zoom();
1679 gdouble zdx = dx / zoom;
1680 gdouble zdy = dy / zoom;
1682 if (!nodepath) return;
1684 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1686 if (dx == 0) {
1687 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1688 } else if (dy == 0) {
1689 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1690 } else {
1691 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1692 }
1693 }
1695 /**
1696 * Move selected nodes to the absolute position given
1697 */
1698 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
1699 {
1700 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1701 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1702 Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
1703 sp_node_moveto(n, npos);
1704 }
1706 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1707 }
1709 /**
1710 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return Geom::Nothing
1711 */
1712 boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1713 {
1714 boost::optional<Geom::Coord> no_coord;
1715 g_return_val_if_fail(nodepath->selected, no_coord);
1717 // determine coordinate of first selected node
1718 GList *nsel = nodepath->selected;
1719 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1720 Geom::Coord coord = n->pos[axis];
1721 bool coincide = true;
1723 // compare it to the coordinates of all the other selected nodes
1724 for (GList *l = nsel->next; l != NULL; l = l->next) {
1725 n = (Inkscape::NodePath::Node *) l->data;
1726 if (n->pos[axis] != coord) {
1727 coincide = false;
1728 }
1729 }
1730 if (coincide) {
1731 return coord;
1732 } else {
1733 Geom::Rect bbox = sp_node_selected_bbox(nodepath);
1734 // currently we return the coordinate of the bounding box midpoint because I don't know how
1735 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1736 return bbox.midpoint()[axis];
1737 }
1738 }
1740 /** If they don't yet exist, creates knot and line for the given side of the node */
1741 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1742 {
1743 if (!side->knot) {
1744 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"));
1746 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1747 side->knot->setSize (7);
1748 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1749 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1750 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1751 sp_knot_update_ctrl(side->knot);
1753 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1754 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1755 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1756 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1757 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1758 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1759 }
1761 if (!side->line) {
1762 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1763 SP_TYPE_CTRLLINE, NULL);
1764 }
1765 }
1767 /**
1768 * Ensure the given handle of the node is visible/invisible, update its screen position
1769 */
1770 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1771 {
1772 g_assert(node != NULL);
1774 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1775 NRPathcode code = sp_node_path_code_from_side(node, side);
1777 show_handle = show_handle && (code == NR_CURVETO) && (Geom::L2(side->pos - node->pos) > 1e-6);
1779 if (show_handle) {
1780 if (!side->knot) { // No handle knot at all
1781 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1782 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1783 side->knot->pos = side->pos;
1784 if (side->knot->item)
1785 SP_CTRL(side->knot->item)->moveto(side->pos);
1786 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1787 sp_knot_show(side->knot);
1788 } else {
1789 if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1790 if (fire_move_signals) {
1791 sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1792 } else {
1793 sp_knot_moveto(side->knot, side->pos);
1794 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1795 }
1796 }
1797 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1798 sp_knot_show(side->knot);
1799 }
1800 }
1801 sp_canvas_item_show(side->line);
1802 } else {
1803 if (side->knot) {
1804 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1805 sp_knot_hide(side->knot);
1806 }
1807 }
1808 if (side->line) {
1809 sp_canvas_item_hide(side->line);
1810 }
1811 }
1812 }
1814 /**
1815 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1816 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1817 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1818 * updated; otherwise, just move the knots silently (used in batch moves).
1819 */
1820 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1821 {
1822 g_assert(node != NULL);
1824 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1825 sp_knot_show(node->knot);
1826 }
1828 if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1829 if (fire_move_signals)
1830 sp_knot_set_position(node->knot, node->pos, 0);
1831 else
1832 sp_knot_moveto(node->knot, node->pos);
1833 }
1835 gboolean show_handles = node->selected;
1836 if (node->p.other != NULL) {
1837 if (node->p.other->selected) show_handles = TRUE;
1838 }
1839 if (node->n.other != NULL) {
1840 if (node->n.other->selected) show_handles = TRUE;
1841 }
1843 if (node->subpath->nodepath->show_handles == false)
1844 show_handles = FALSE;
1846 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1847 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1848 }
1850 /**
1851 * Call sp_node_update_handles() for all nodes on subpath.
1852 */
1853 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1854 {
1855 g_assert(subpath != NULL);
1857 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1858 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1859 }
1860 }
1862 /**
1863 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1864 */
1865 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1866 {
1867 g_assert(nodepath != NULL);
1869 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1870 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1871 }
1872 }
1874 void
1875 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1876 {
1877 if (nodepath) {
1878 nodepath->show_handles = show;
1879 sp_nodepath_update_handles(nodepath);
1880 }
1881 }
1883 /**
1884 * Adds all selected nodes in nodepath to list.
1885 */
1886 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1887 {
1888 StlConv<Node *>::list(l, selected);
1889 /// \todo this adds a copying, rework when the selection becomes a stl list
1890 }
1892 /**
1893 * Align selected nodes on the specified axis.
1894 */
1895 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1896 {
1897 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1898 return;
1899 }
1901 if ( !nodepath->selected->next ) { // only one node selected
1902 return;
1903 }
1904 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1905 Geom::Point dest(pNode->pos);
1906 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1907 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1908 if (pNode) {
1909 dest[axis] = pNode->pos[axis];
1910 sp_node_moveto(pNode, dest);
1911 }
1912 }
1914 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1915 }
1917 /// Helper struct.
1918 struct NodeSort
1919 {
1920 Inkscape::NodePath::Node *_node;
1921 Geom::Coord _coord;
1922 /// \todo use vectorof pointers instead of calling copy ctor
1923 NodeSort(Inkscape::NodePath::Node *node, Geom::Dim2 axis) :
1924 _node(node), _coord(node->pos[axis])
1925 {}
1927 };
1929 static bool operator<(NodeSort const &a, NodeSort const &b)
1930 {
1931 return (a._coord < b._coord);
1932 }
1934 /**
1935 * Distribute selected nodes on the specified axis.
1936 */
1937 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1938 {
1939 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1940 return;
1941 }
1943 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1944 return;
1945 }
1947 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1948 std::vector<NodeSort> sorted;
1949 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1950 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1951 if (pNode) {
1952 NodeSort n(pNode, axis);
1953 sorted.push_back(n);
1954 //dest[axis] = pNode->pos[axis];
1955 //sp_node_moveto(pNode, dest);
1956 }
1957 }
1958 std::sort(sorted.begin(), sorted.end());
1959 unsigned int len = sorted.size();
1960 //overall bboxes span
1961 float dist = (sorted.back()._coord -
1962 sorted.front()._coord);
1963 //new distance between each bbox
1964 float step = (dist) / (len - 1);
1965 float pos = sorted.front()._coord;
1966 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1967 it < sorted.end();
1968 it ++ )
1969 {
1970 Geom::Point dest((*it)._node->pos);
1971 dest[axis] = pos;
1972 sp_node_moveto((*it)._node, dest);
1973 pos += step;
1974 }
1976 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1977 }
1980 /**
1981 * Call sp_nodepath_line_add_node() for all selected segments.
1982 */
1983 void
1984 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1985 {
1986 if (!nodepath) {
1987 return;
1988 }
1990 GList *nl = NULL;
1992 int n_added = 0;
1994 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1995 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1996 g_assert(t->selected);
1997 if (t->p.other && t->p.other->selected) {
1998 nl = g_list_prepend(nl, t);
1999 }
2000 }
2002 while (nl) {
2003 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
2004 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
2005 sp_nodepath_node_select(n, TRUE, FALSE);
2006 n_added ++;
2007 nl = g_list_remove(nl, t);
2008 }
2010 /** \todo fixme: adjust ? */
2011 sp_nodepath_update_handles(nodepath);
2013 if (n_added > 1) {
2014 sp_nodepath_update_repr(nodepath, _("Add nodes"));
2015 } else if (n_added > 0) {
2016 sp_nodepath_update_repr(nodepath, _("Add node"));
2017 }
2019 sp_nodepath_update_statusbar(nodepath);
2020 }
2022 /**
2023 * Select segment nearest to point
2024 */
2025 void
2026 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p, bool toggle)
2027 {
2028 if (!nodepath) {
2029 return;
2030 }
2032 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2033 Geom::PathVector const &pathv = curve->get_pathvector();
2034 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2035 if (!pvpos) {
2036 g_print ("Possible error?\n");
2037 return;
2038 }
2040 // calculate index for nodepath's representation.
2041 unsigned int segment_index = floor(pvpos->t) + 1;
2042 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2043 segment_index += pathv[i].size() + 1;
2044 if (pathv[i].closed()) {
2045 segment_index += 1;
2046 }
2047 }
2049 curve->unref();
2051 //find segment to segment
2052 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2054 //fixme: this can return NULL, so check before proceeding.
2055 g_return_if_fail(e != NULL);
2057 gboolean force = FALSE;
2058 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
2059 force = TRUE;
2060 }
2061 sp_nodepath_node_select(e, (gboolean) toggle, force);
2062 if (e->p.other)
2063 sp_nodepath_node_select(e->p.other, TRUE, force);
2065 sp_nodepath_update_handles(nodepath);
2067 sp_nodepath_update_statusbar(nodepath);
2068 }
2070 /**
2071 * Add a node nearest to point
2072 */
2073 void
2074 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p)
2075 {
2076 if (!nodepath) {
2077 return;
2078 }
2080 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2081 Geom::PathVector const &pathv = curve->get_pathvector();
2082 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2083 if (!pvpos) {
2084 g_print ("Possible error?\n");
2085 return;
2086 }
2088 // calculate index for nodepath's representation.
2089 double int_part;
2090 double t = std::modf(pvpos->t, &int_part);
2091 unsigned int segment_index = (unsigned int)int_part + 1;
2092 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2093 segment_index += pathv[i].size() + 1;
2094 if (pathv[i].closed()) {
2095 segment_index += 1;
2096 }
2097 }
2099 curve->unref();
2101 //find segment to split
2102 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2103 if (!e) {
2104 return;
2105 }
2107 //don't know why but t seems to flip for lines
2108 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2109 t = 1.0 - t;
2110 }
2112 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2113 sp_nodepath_node_select(n, FALSE, TRUE);
2115 /* fixme: adjust ? */
2116 sp_nodepath_update_handles(nodepath);
2118 sp_nodepath_update_repr(nodepath, _("Add node"));
2120 sp_nodepath_update_statusbar(nodepath);
2121 }
2123 /*
2124 * Adjusts a segment so that t moves by a certain delta for dragging
2125 * converts lines to curves
2126 *
2127 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2128 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2129 */
2130 void
2131 sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, Geom::Point delta)
2132 {
2133 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node);
2135 //fixme: e and e->p can be NULL, so check for those before proceeding
2136 g_return_if_fail(e != NULL);
2137 g_return_if_fail(&e->p != NULL);
2139 if (e->type == Inkscape::NodePath::NODE_AUTO) {
2140 e->type = Inkscape::NodePath::NODE_SMOOTH;
2141 sp_nodepath_update_node_knot (e);
2142 }
2143 if (e->p.other->type == Inkscape::NodePath::NODE_AUTO) {
2144 e->p.other->type = Inkscape::NodePath::NODE_SMOOTH;
2145 sp_nodepath_update_node_knot (e->p.other);
2146 }
2148 /* feel good is an arbitrary parameter that distributes the delta between handles
2149 * if t of the drag point is less than 1/6 distance form the endpoint only
2150 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2151 */
2152 double feel_good;
2153 if (t <= 1.0 / 6.0)
2154 feel_good = 0;
2155 else if (t <= 0.5)
2156 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2157 else if (t <= 5.0 / 6.0)
2158 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2159 else
2160 feel_good = 1;
2162 //if we're dragging a line convert it to a curve
2163 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2164 sp_nodepath_set_line_type(e, NR_CURVETO);
2165 }
2167 Geom::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2168 Geom::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2169 e->p.other->n.pos += offsetcoord0;
2170 e->p.pos += offsetcoord1;
2172 // adjust handles of adjacent nodes where necessary
2173 sp_node_adjust_handle(e,1);
2174 sp_node_adjust_handle(e->p.other,-1);
2176 sp_nodepath_update_handles(e->subpath->nodepath);
2178 update_object(e->subpath->nodepath);
2180 sp_nodepath_update_statusbar(e->subpath->nodepath);
2181 }
2184 /**
2185 * Call sp_nodepath_break() for all selected segments.
2186 */
2187 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2188 {
2189 if (!nodepath) return;
2191 GList *tempin = g_list_copy(nodepath->selected);
2192 GList *temp = NULL;
2193 for (GList *l = tempin; l != NULL; l = l->next) {
2194 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2195 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2196 if (nn == NULL) continue; // no break, no new node
2197 temp = g_list_prepend(temp, nn);
2198 }
2199 g_list_free(tempin);
2201 if (temp) {
2202 sp_nodepath_deselect(nodepath);
2203 }
2204 for (GList *l = temp; l != NULL; l = l->next) {
2205 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2206 }
2208 sp_nodepath_update_handles(nodepath);
2210 sp_nodepath_update_repr(nodepath, _("Break path"));
2211 }
2213 /**
2214 * Duplicate the selected node(s).
2215 */
2216 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2217 {
2218 if (!nodepath) {
2219 return;
2220 }
2222 GList *temp = NULL;
2223 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2224 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2225 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2226 if (nn == NULL) continue; // could not duplicate
2227 temp = g_list_prepend(temp, nn);
2228 }
2230 if (temp) {
2231 sp_nodepath_deselect(nodepath);
2232 }
2233 for (GList *l = temp; l != NULL; l = l->next) {
2234 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2235 }
2237 sp_nodepath_update_handles(nodepath);
2239 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2240 }
2242 /**
2243 * Internal function to join two nodes by merging them into one.
2244 */
2245 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2246 {
2247 /* a and b are endpoints */
2249 // if one of the two nodes is mouseovered, fix its position
2250 Geom::Point c;
2251 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2252 c = a->pos;
2253 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2254 c = b->pos;
2255 } else {
2256 // otherwise, move joined node to the midpoint
2257 c = (a->pos + b->pos) / 2;
2258 }
2260 if (a->subpath == b->subpath) {
2261 Inkscape::NodePath::SubPath *sp = a->subpath;
2262 sp_nodepath_subpath_close(sp);
2263 sp_node_moveto (sp->first, c);
2265 sp_nodepath_update_handles(sp->nodepath);
2266 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2267 return;
2268 }
2270 /* a and b are separate subpaths */
2271 Inkscape::NodePath::SubPath *sa = a->subpath;
2272 Inkscape::NodePath::SubPath *sb = b->subpath;
2273 Geom::Point p;
2274 Inkscape::NodePath::Node *n;
2275 NRPathcode code;
2276 if (a == sa->first) {
2277 // we will now reverse sa, so that a is its last node, not first, and drop that node
2278 p = sa->first->n.pos;
2279 code = (NRPathcode)sa->first->n.other->code;
2280 // create new subpath
2281 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2282 // create a first moveto node on it
2283 n = sa->last;
2284 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2285 n = n->p.other;
2286 if (n == sa->first) n = NULL;
2287 while (n) {
2288 // copy the rest of the nodes from sa to t, going backwards
2289 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2290 n = n->p.other;
2291 if (n == sa->first) n = NULL;
2292 }
2293 // replace sa with t
2294 sp_nodepath_subpath_destroy(sa);
2295 sa = t;
2296 } else if (a == sa->last) {
2297 // a is already last, just drop it
2298 p = sa->last->p.pos;
2299 code = (NRPathcode)sa->last->code;
2300 sp_nodepath_node_destroy(sa->last);
2301 } else {
2302 code = NR_END;
2303 g_assert_not_reached();
2304 }
2306 if (b == sb->first) {
2307 // copy all nodes from b to a, forward
2308 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2309 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2310 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2311 }
2312 } else if (b == sb->last) {
2313 // copy all nodes from b to a, backward
2314 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2315 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2316 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2317 }
2318 } else {
2319 g_assert_not_reached();
2320 }
2321 /* and now destroy sb */
2323 sp_nodepath_subpath_destroy(sb);
2325 sp_nodepath_update_handles(sa->nodepath);
2327 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2329 sp_nodepath_update_statusbar(nodepath);
2330 }
2332 /**
2333 * Internal function to join two nodes by adding a segment between them.
2334 */
2335 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2336 {
2337 if (a->subpath == b->subpath) {
2338 Inkscape::NodePath::SubPath *sp = a->subpath;
2340 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2341 sp->closed = TRUE;
2343 sp->first->p.other = sp->last;
2344 sp->last->n.other = sp->first;
2346 sp_node_handle_mirror_p_to_n(sp->last);
2347 sp_node_handle_mirror_n_to_p(sp->first);
2349 sp->first->code = sp->last->code;
2350 sp->first = sp->last;
2352 sp_nodepath_update_handles(sp->nodepath);
2354 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2356 return;
2357 }
2359 /* a and b are separate subpaths */
2360 Inkscape::NodePath::SubPath *sa = a->subpath;
2361 Inkscape::NodePath::SubPath *sb = b->subpath;
2363 Inkscape::NodePath::Node *n;
2364 Geom::Point p;
2365 NRPathcode code;
2366 if (a == sa->first) {
2367 code = (NRPathcode) sa->first->n.other->code;
2368 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2369 n = sa->last;
2370 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2371 for (n = n->p.other; n != NULL; n = n->p.other) {
2372 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2373 }
2374 sp_nodepath_subpath_destroy(sa);
2375 sa = t;
2376 } else if (a == sa->last) {
2377 code = (NRPathcode)sa->last->code;
2378 } else {
2379 code = NR_END;
2380 g_assert_not_reached();
2381 }
2383 if (b == sb->first) {
2384 n = sb->first;
2385 sp_node_handle_mirror_p_to_n(sa->last);
2386 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2387 sp_node_handle_mirror_n_to_p(sa->last);
2388 for (n = n->n.other; n != NULL; n = n->n.other) {
2389 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2390 }
2391 } else if (b == sb->last) {
2392 n = sb->last;
2393 sp_node_handle_mirror_p_to_n(sa->last);
2394 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2395 sp_node_handle_mirror_n_to_p(sa->last);
2396 for (n = n->p.other; n != NULL; n = n->p.other) {
2397 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2398 }
2399 } else {
2400 g_assert_not_reached();
2401 }
2402 /* and now destroy sb */
2404 sp_nodepath_subpath_destroy(sb);
2406 sp_nodepath_update_handles(sa->nodepath);
2408 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2409 }
2411 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2413 /**
2414 * Internal function to handle joining two nodes.
2415 */
2416 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2417 {
2418 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2420 if (g_list_length(nodepath->selected) != 2) {
2421 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2422 return;
2423 }
2425 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2426 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2428 g_assert(a != b);
2429 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2430 // someone tried to join an orphan node (i.e. a single-node subpath).
2431 // this is not worth an error message, just fail silently.
2432 return;
2433 }
2435 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2436 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2437 return;
2438 }
2440 switch(mode) {
2441 case NODE_JOIN_ENDPOINTS:
2442 do_node_selected_join(nodepath, a, b);
2443 break;
2444 case NODE_JOIN_SEGMENT:
2445 do_node_selected_join_segment(nodepath, a, b);
2446 break;
2447 }
2448 }
2450 /**
2451 * Join two nodes by merging them into one.
2452 */
2453 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2454 {
2455 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2456 }
2458 /**
2459 * Join two nodes by adding a segment between them.
2460 */
2461 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2462 {
2463 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2464 }
2466 /**
2467 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2468 */
2469 void sp_node_delete_preserve(GList *nodes_to_delete)
2470 {
2471 GSList *nodepaths = NULL;
2473 while (nodes_to_delete) {
2474 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2475 Inkscape::NodePath::SubPath *sp = node->subpath;
2476 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2477 Inkscape::NodePath::Node *sample_cursor = NULL;
2478 Inkscape::NodePath::Node *sample_end = NULL;
2479 Inkscape::NodePath::Node *delete_cursor = node;
2480 bool just_delete = false;
2482 //find the start of this contiguous selection
2483 //move left to the first node that is not selected
2484 //or the start of the non-closed path
2485 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2486 delete_cursor = curr;
2487 }
2489 //just delete at the beginning of an open path
2490 if (!delete_cursor->p.other) {
2491 sample_cursor = delete_cursor;
2492 just_delete = true;
2493 } else {
2494 sample_cursor = delete_cursor->p.other;
2495 }
2497 //calculate points for each segment
2498 int rate = 5;
2499 float period = 1.0 / rate;
2500 std::vector<Geom::Point> data;
2501 if (!just_delete) {
2502 data.push_back(sample_cursor->pos);
2503 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2504 //just delete at the end of an open path
2505 if (!sp->closed && curr == sp->last) {
2506 just_delete = true;
2507 break;
2508 }
2510 //sample points on the contiguous selected segment
2511 Geom::Point *bez;
2512 bez = new Geom::Point [4];
2513 bez[0] = curr->pos;
2514 bez[1] = curr->n.pos;
2515 bez[2] = curr->n.other->p.pos;
2516 bez[3] = curr->n.other->pos;
2517 for (int i=1; i<rate; i++) {
2518 gdouble t = i * period;
2519 Geom::Point p = bezier_pt(3, bez, t);
2520 data.push_back(p);
2521 }
2522 data.push_back(curr->n.other->pos);
2524 sample_end = curr->n.other;
2525 //break if we've come full circle or hit the end of the selection
2526 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2527 break;
2528 }
2529 }
2530 }
2532 if (!just_delete) {
2533 //calculate the best fitting single segment and adjust the endpoints
2534 Geom::Point *adata;
2535 adata = new Geom::Point [data.size()];
2536 copy(data.begin(), data.end(), adata);
2538 Geom::Point *bez;
2539 bez = new Geom::Point [4];
2540 //would decreasing error create a better fitting approximation?
2541 gdouble error = 1.0;
2542 gint ret;
2543 ret = Geom::bezier_fit_cubic (bez, adata, data.size(), error);
2545 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2546 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2547 //the resulting nodes behave as expected.
2548 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2549 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2550 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2551 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2553 //adjust endpoints
2554 sample_cursor->n.pos = bez[1];
2555 sample_end->p.pos = bez[2];
2556 }
2558 //destroy this contiguous selection
2559 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2560 Inkscape::NodePath::Node *temp = delete_cursor;
2561 if (delete_cursor->n.other == delete_cursor) {
2562 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2563 delete_cursor = NULL;
2564 } else {
2565 delete_cursor = delete_cursor->n.other;
2566 }
2567 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2568 sp_nodepath_node_destroy(temp);
2569 }
2571 sp_nodepath_update_handles(nodepath);
2573 if (!g_slist_find(nodepaths, nodepath))
2574 nodepaths = g_slist_prepend (nodepaths, nodepath);
2575 }
2577 for (GSList *i = nodepaths; i; i = i->next) {
2578 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2579 // different nodepaths will give us one undo event per nodepath
2580 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2582 // if the entire nodepath is removed, delete the selected object.
2583 if (nodepath->subpaths == NULL ||
2584 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2585 //at least 2
2586 sp_nodepath_get_node_count(nodepath) < 2) {
2587 SPDocument *document = sp_desktop_document (nodepath->desktop);
2588 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2589 //delete this nodepath's object, not the entire selection! (though at this time, this
2590 //does not matter)
2591 sp_selection_delete(nodepath->desktop);
2592 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2593 _("Delete nodes"));
2594 } else {
2595 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2596 sp_nodepath_update_statusbar(nodepath);
2597 }
2598 }
2600 g_slist_free (nodepaths);
2601 }
2603 /**
2604 * Delete one or more selected nodes.
2605 */
2606 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2607 {
2608 if (!nodepath) return;
2609 if (!nodepath->selected) return;
2611 /** \todo fixme: do it the right way */
2612 while (nodepath->selected) {
2613 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2614 sp_nodepath_node_destroy(node);
2615 }
2618 //clean up the nodepath (such as for trivial subpaths)
2619 sp_nodepath_cleanup(nodepath);
2621 sp_nodepath_update_handles(nodepath);
2623 // if the entire nodepath is removed, delete the selected object.
2624 if (nodepath->subpaths == NULL ||
2625 sp_nodepath_get_node_count(nodepath) < 2) {
2626 SPDocument *document = sp_desktop_document (nodepath->desktop);
2627 sp_selection_delete(nodepath->desktop);
2628 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2629 _("Delete nodes"));
2630 return;
2631 }
2633 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2635 sp_nodepath_update_statusbar(nodepath);
2636 }
2638 /**
2639 * Delete one or more segments between two selected nodes.
2640 * This is the code for 'split'.
2641 */
2642 void
2643 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2644 {
2645 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2646 Inkscape::NodePath::Node *curr, *next; //Iterators
2648 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2650 if (g_list_length(nodepath->selected) != 2) {
2651 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2652 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2653 return;
2654 }
2656 //Selected nodes, not inclusive
2657 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2658 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2660 if ( ( a==b) || //same node
2661 (a->subpath != b->subpath ) || //not the same path
2662 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2663 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2664 {
2665 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2666 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2667 return;
2668 }
2670 //###########################################
2671 //# BEGIN EDITS
2672 //###########################################
2673 //##################################
2674 //# CLOSED PATH
2675 //##################################
2676 if (a->subpath->closed) {
2679 gboolean reversed = FALSE;
2681 //Since we can go in a circle, we need to find the shorter distance.
2682 // a->b or b->a
2683 start = end = NULL;
2684 int distance = 0;
2685 int minDistance = 0;
2686 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2687 if (curr==b) {
2688 //printf("a to b:%d\n", distance);
2689 start = a;//go from a to b
2690 end = b;
2691 minDistance = distance;
2692 //printf("A to B :\n");
2693 break;
2694 }
2695 distance++;
2696 }
2698 //try again, the other direction
2699 distance = 0;
2700 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2701 if (curr==a) {
2702 //printf("b to a:%d\n", distance);
2703 if (distance < minDistance) {
2704 start = b; //we go from b to a
2705 end = a;
2706 reversed = TRUE;
2707 //printf("B to A\n");
2708 }
2709 break;
2710 }
2711 distance++;
2712 }
2715 //Copy everything from 'end' to 'start' to a new subpath
2716 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2717 for (curr=end ; curr ; curr=curr->n.other) {
2718 NRPathcode code = (NRPathcode) curr->code;
2719 if (curr == end)
2720 code = NR_MOVETO;
2721 sp_nodepath_node_new(t, NULL,
2722 (Inkscape::NodePath::NodeType)curr->type, code,
2723 &curr->p.pos, &curr->pos, &curr->n.pos);
2724 if (curr == start)
2725 break;
2726 }
2727 sp_nodepath_subpath_destroy(a->subpath);
2730 }
2734 //##################################
2735 //# OPEN PATH
2736 //##################################
2737 else {
2739 //We need to get the direction of the list between A and B
2740 //Can we walk from a to b?
2741 start = end = NULL;
2742 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2743 if (curr==b) {
2744 start = a; //did it! we go from a to b
2745 end = b;
2746 //printf("A to B\n");
2747 break;
2748 }
2749 }
2750 if (!start) {//didn't work? let's try the other direction
2751 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2752 if (curr==a) {
2753 start = b; //did it! we go from b to a
2754 end = a;
2755 //printf("B to A\n");
2756 break;
2757 }
2758 }
2759 }
2760 if (!start) {
2761 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2762 _("Cannot find path between nodes."));
2763 return;
2764 }
2768 //Copy everything after 'end' to a new subpath
2769 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2770 for (curr=end ; curr ; curr=curr->n.other) {
2771 NRPathcode code = (NRPathcode) curr->code;
2772 if (curr == end)
2773 code = NR_MOVETO;
2774 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2775 &curr->p.pos, &curr->pos, &curr->n.pos);
2776 }
2778 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2779 for (curr = start->n.other ; curr ; curr=next) {
2780 next = curr->n.other;
2781 sp_nodepath_node_destroy(curr);
2782 }
2784 }
2785 //###########################################
2786 //# END EDITS
2787 //###########################################
2789 //clean up the nodepath (such as for trivial subpaths)
2790 sp_nodepath_cleanup(nodepath);
2792 sp_nodepath_update_handles(nodepath);
2794 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2796 sp_nodepath_update_statusbar(nodepath);
2797 }
2799 /**
2800 * Call sp_nodepath_set_line() for all selected segments.
2801 */
2802 void
2803 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2804 {
2805 if (nodepath == NULL) return;
2807 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2808 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2809 g_assert(n->selected);
2810 if (n->p.other && n->p.other->selected) {
2811 sp_nodepath_set_line_type(n, code);
2812 }
2813 }
2815 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2816 }
2818 /**
2819 * Call sp_nodepath_convert_node_type() for all selected nodes.
2820 */
2821 void
2822 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2823 {
2824 if (nodepath == NULL) return;
2826 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2828 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2829 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2830 }
2832 sp_nodepath_update_repr(nodepath, _("Change node type"));
2833 }
2835 /**
2836 * Change select status of node, update its own and neighbour handles.
2837 */
2838 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2839 {
2840 node->selected = selected;
2842 if (selected) {
2843 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 11 : 9);
2844 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2845 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2846 sp_knot_update_ctrl(node->knot);
2847 } else {
2848 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 9 : 7);
2849 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2850 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2851 sp_knot_update_ctrl(node->knot);
2852 }
2854 sp_node_update_handles(node);
2855 if (node->n.other) sp_node_update_handles(node->n.other);
2856 if (node->p.other) sp_node_update_handles(node->p.other);
2857 }
2859 /**
2860 \brief Select a node
2861 \param node The node to select
2862 \param incremental If true, add to selection, otherwise deselect others
2863 \param override If true, always select this node, otherwise toggle selected status
2864 */
2865 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2866 {
2867 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2869 if (incremental) {
2870 if (override) {
2871 if (!g_list_find(nodepath->selected, node)) {
2872 nodepath->selected = g_list_prepend(nodepath->selected, node);
2873 }
2874 sp_node_set_selected(node, TRUE);
2875 } else { // toggle
2876 if (node->selected) {
2877 g_assert(g_list_find(nodepath->selected, node));
2878 nodepath->selected = g_list_remove(nodepath->selected, node);
2879 } else {
2880 g_assert(!g_list_find(nodepath->selected, node));
2881 nodepath->selected = g_list_prepend(nodepath->selected, node);
2882 }
2883 sp_node_set_selected(node, !node->selected);
2884 }
2885 } else {
2886 sp_nodepath_deselect(nodepath);
2887 nodepath->selected = g_list_prepend(nodepath->selected, node);
2888 sp_node_set_selected(node, TRUE);
2889 }
2891 sp_nodepath_update_statusbar(nodepath);
2892 }
2895 /**
2896 \brief Deselect all nodes in the nodepath
2897 */
2898 void
2899 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2900 {
2901 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2903 while (nodepath->selected) {
2904 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2905 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2906 }
2907 sp_nodepath_update_statusbar(nodepath);
2908 }
2910 /**
2911 \brief Select or invert selection of all nodes in the nodepath
2912 */
2913 void
2914 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2915 {
2916 if (!nodepath) return;
2918 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2919 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2920 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2921 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2922 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2923 }
2924 }
2925 }
2927 /**
2928 * If nothing selected, does the same as sp_nodepath_select_all();
2929 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2930 * (i.e., similar to "select all in layer", with the "selected" subpaths
2931 * being treated as "layers" in the path).
2932 */
2933 void
2934 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2935 {
2936 if (!nodepath) return;
2938 if (g_list_length (nodepath->selected) == 0) {
2939 sp_nodepath_select_all (nodepath, invert);
2940 return;
2941 }
2943 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2944 GSList *subpaths = NULL;
2946 for (GList *l = copy; l != NULL; l = l->next) {
2947 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2948 Inkscape::NodePath::SubPath *subpath = n->subpath;
2949 if (!g_slist_find (subpaths, subpath))
2950 subpaths = g_slist_prepend (subpaths, subpath);
2951 }
2953 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2954 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2955 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2956 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2957 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2958 }
2959 }
2961 g_slist_free (subpaths);
2962 g_list_free (copy);
2963 }
2965 /**
2966 * \brief Select the node after the last selected; if none is selected,
2967 * select the first within path.
2968 */
2969 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2970 {
2971 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2973 Inkscape::NodePath::Node *last = NULL;
2974 if (nodepath->selected) {
2975 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2976 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2977 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2978 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2979 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2980 if (node->selected) {
2981 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2982 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2983 if (spl->next) { // there's a next subpath
2984 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2985 last = subpath_next->first;
2986 } else if (spl->prev) { // there's a previous subpath
2987 last = NULL; // to be set later to the first node of first subpath
2988 } else {
2989 last = node->n.other;
2990 }
2991 } else {
2992 last = node->n.other;
2993 }
2994 } else {
2995 if (node->n.other) {
2996 last = node->n.other;
2997 } else {
2998 if (spl->next) { // there's a next subpath
2999 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
3000 last = subpath_next->first;
3001 } else if (spl->prev) { // there's a previous subpath
3002 last = NULL; // to be set later to the first node of first subpath
3003 } else {
3004 last = (Inkscape::NodePath::Node *) subpath->first;
3005 }
3006 }
3007 }
3008 }
3009 }
3010 }
3011 sp_nodepath_deselect(nodepath);
3012 }
3014 if (last) { // there's at least one more node after selected
3015 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
3016 } else { // no more nodes, select the first one in first subpath
3017 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
3018 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
3019 }
3020 }
3022 /**
3023 * \brief Select the node before the first selected; if none is selected,
3024 * select the last within path
3025 */
3026 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
3027 {
3028 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
3030 Inkscape::NodePath::Node *last = NULL;
3031 if (nodepath->selected) {
3032 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
3033 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3034 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
3035 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3036 if (node->selected) {
3037 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
3038 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
3039 if (spl->prev) { // there's a prev subpath
3040 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3041 last = subpath_prev->last;
3042 } else if (spl->next) { // there's a next subpath
3043 last = NULL; // to be set later to the last node of last subpath
3044 } else {
3045 last = node->p.other;
3046 }
3047 } else {
3048 last = node->p.other;
3049 }
3050 } else {
3051 if (node->p.other) {
3052 last = node->p.other;
3053 } else {
3054 if (spl->prev) { // there's a prev subpath
3055 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3056 last = subpath_prev->last;
3057 } else if (spl->next) { // there's a next subpath
3058 last = NULL; // to be set later to the last node of last subpath
3059 } else {
3060 last = (Inkscape::NodePath::Node *) subpath->last;
3061 }
3062 }
3063 }
3064 }
3065 }
3066 }
3067 sp_nodepath_deselect(nodepath);
3068 }
3070 if (last) { // there's at least one more node before selected
3071 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
3072 } else { // no more nodes, select the last one in last subpath
3073 GList *spl = g_list_last(nodepath->subpaths);
3074 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3075 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
3076 }
3077 }
3079 /**
3080 * \brief Select all nodes that are within the rectangle.
3081 */
3082 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, Geom::Rect const &b, gboolean incremental)
3083 {
3084 if (!incremental) {
3085 sp_nodepath_deselect(nodepath);
3086 }
3088 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3089 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3090 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3091 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3093 if (b.contains(node->pos)) {
3094 sp_nodepath_node_select(node, TRUE, TRUE);
3095 }
3096 }
3097 }
3098 }
3101 void
3102 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3103 {
3104 g_assert (n);
3105 g_assert (nodepath);
3106 g_assert (n->subpath->nodepath == nodepath);
3108 if (g_list_length (nodepath->selected) == 0) {
3109 if (grow > 0) {
3110 sp_nodepath_node_select(n, TRUE, TRUE);
3111 }
3112 return;
3113 }
3115 if (g_list_length (nodepath->selected) == 1) {
3116 if (grow < 0) {
3117 sp_nodepath_deselect (nodepath);
3118 return;
3119 }
3120 }
3122 double n_sel_range = 0, p_sel_range = 0;
3123 Inkscape::NodePath::Node *farthest_n_node = n;
3124 Inkscape::NodePath::Node *farthest_p_node = n;
3126 // Calculate ranges
3127 {
3128 double n_range = 0, p_range = 0;
3129 bool n_going = true, p_going = true;
3130 Inkscape::NodePath::Node *n_node = n;
3131 Inkscape::NodePath::Node *p_node = n;
3132 do {
3133 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3134 if (n_node && n_going)
3135 n_node = n_node->n.other;
3136 if (n_node == NULL) {
3137 n_going = false;
3138 } else {
3139 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3140 if (n_node->selected) {
3141 n_sel_range = n_range;
3142 farthest_n_node = n_node;
3143 }
3144 if (n_node == p_node) {
3145 n_going = false;
3146 p_going = false;
3147 }
3148 }
3149 if (p_node && p_going)
3150 p_node = p_node->p.other;
3151 if (p_node == NULL) {
3152 p_going = false;
3153 } else {
3154 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3155 if (p_node->selected) {
3156 p_sel_range = p_range;
3157 farthest_p_node = p_node;
3158 }
3159 if (p_node == n_node) {
3160 n_going = false;
3161 p_going = false;
3162 }
3163 }
3164 } while (n_going || p_going);
3165 }
3167 if (grow > 0) {
3168 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3169 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3170 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3171 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3172 }
3173 } else {
3174 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3175 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3176 } else if (farthest_p_node && farthest_p_node->selected) {
3177 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3178 }
3179 }
3180 }
3182 void
3183 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3184 {
3185 g_assert (n);
3186 g_assert (nodepath);
3187 g_assert (n->subpath->nodepath == nodepath);
3189 if (g_list_length (nodepath->selected) == 0) {
3190 if (grow > 0) {
3191 sp_nodepath_node_select(n, TRUE, TRUE);
3192 }
3193 return;
3194 }
3196 if (g_list_length (nodepath->selected) == 1) {
3197 if (grow < 0) {
3198 sp_nodepath_deselect (nodepath);
3199 return;
3200 }
3201 }
3203 Inkscape::NodePath::Node *farthest_selected = NULL;
3204 double farthest_dist = 0;
3206 Inkscape::NodePath::Node *closest_unselected = NULL;
3207 double closest_dist = NR_HUGE;
3209 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3210 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3211 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3212 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3213 if (node == n)
3214 continue;
3215 if (node->selected) {
3216 if (Geom::L2(node->pos - n->pos) > farthest_dist) {
3217 farthest_dist = Geom::L2(node->pos - n->pos);
3218 farthest_selected = node;
3219 }
3220 } else {
3221 if (Geom::L2(node->pos - n->pos) < closest_dist) {
3222 closest_dist = Geom::L2(node->pos - n->pos);
3223 closest_unselected = node;
3224 }
3225 }
3226 }
3227 }
3229 if (grow > 0) {
3230 if (closest_unselected) {
3231 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3232 }
3233 } else {
3234 if (farthest_selected) {
3235 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3236 }
3237 }
3238 }
3241 /**
3242 \brief Saves all nodes' and handles' current positions in their origin members
3243 */
3244 void
3245 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3246 {
3247 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3248 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3249 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3250 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3251 n->origin = n->pos;
3252 n->p.origin = n->p.pos;
3253 n->n.origin = n->n.pos;
3254 }
3255 }
3256 }
3258 /**
3259 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3260 */
3261 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3262 {
3263 GList *r = NULL;
3264 if (nodepath->selected) {
3265 guint i = 0;
3266 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3267 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3268 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3269 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3270 i++;
3271 if (node->selected) {
3272 r = g_list_append(r, GINT_TO_POINTER(i));
3273 }
3274 }
3275 }
3276 }
3277 return r;
3278 }
3280 /**
3281 \brief Restores selection by selecting nodes whose positions are in the list
3282 */
3283 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3284 {
3285 sp_nodepath_deselect(nodepath);
3287 guint i = 0;
3288 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3289 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3290 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3291 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3292 i++;
3293 if (g_list_find(r, GINT_TO_POINTER(i))) {
3294 sp_nodepath_node_select(node, TRUE, TRUE);
3295 }
3296 }
3297 }
3298 }
3301 /**
3302 \brief Adjusts handle according to node type and line code.
3303 */
3304 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3305 {
3306 g_assert(node);
3308 // nothing to do for auto nodes (sp_node_adjust_handles() does the job)
3309 if (node->type == Inkscape::NodePath::NODE_AUTO)
3310 return;
3312 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3313 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3315 // nothing to do if we are an end node
3316 if (me->other == NULL) return;
3317 if (other->other == NULL) return;
3319 // nothing to do if we are a cusp node
3320 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3322 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3323 NRPathcode mecode;
3324 if (which_adjust == 1) {
3325 mecode = (NRPathcode)me->other->code;
3326 } else {
3327 mecode = (NRPathcode)node->code;
3328 }
3329 if (mecode == NR_LINETO) return;
3331 if (sp_node_side_is_line(node, other)) {
3332 // other is a line, and we are either smooth or symm
3333 Inkscape::NodePath::Node *othernode = other->other;
3334 double len = Geom::L2(me->pos - node->pos);
3335 Geom::Point delta = node->pos - othernode->pos;
3336 double linelen = Geom::L2(delta);
3337 if (linelen < 1e-18)
3338 return;
3339 me->pos = node->pos + (len / linelen)*delta;
3340 return;
3341 }
3343 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3344 // symmetrize
3345 me->pos = 2 * node->pos - other->pos;
3346 return;
3347 } else {
3348 // smoothify
3349 double len = Geom::L2(me->pos - node->pos);
3350 Geom::Point delta = other->pos - node->pos;
3351 double otherlen = Geom::L2(delta);
3352 if (otherlen < 1e-18) return;
3353 me->pos = node->pos - (len / otherlen) * delta;
3354 }
3355 }
3357 /**
3358 \brief Adjusts both handles according to node type and line code
3359 */
3360 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3361 {
3362 g_assert(node);
3364 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3366 /* we are either smooth or symm */
3368 if (node->p.other == NULL) return;
3369 if (node->n.other == NULL) return;
3371 if (node->type == Inkscape::NodePath::NODE_AUTO) {
3372 sp_node_adjust_handles_auto(node);
3373 return;
3374 }
3376 if (sp_node_side_is_line(node, &node->p)) {
3377 sp_node_adjust_handle(node, 1);
3378 return;
3379 }
3381 if (sp_node_side_is_line(node, &node->n)) {
3382 sp_node_adjust_handle(node, -1);
3383 return;
3384 }
3386 /* both are curves */
3387 Geom::Point const delta( node->n.pos - node->p.pos );
3389 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3390 node->p.pos = node->pos - delta / 2;
3391 node->n.pos = node->pos + delta / 2;
3392 return;
3393 }
3395 /* We are smooth */
3396 double plen = Geom::L2(node->p.pos - node->pos);
3397 if (plen < 1e-18) return;
3398 double nlen = Geom::L2(node->n.pos - node->pos);
3399 if (nlen < 1e-18) return;
3400 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3401 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3402 }
3404 static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node)
3405 {
3406 if (node->p.other == NULL || node->n.other == NULL) {
3407 node->p.pos = node->pos;
3408 node->n.pos = node->pos;
3409 return;
3410 }
3412 Geom::Point leg_prev = to_2geom(node->p.other->pos - node->pos);
3413 Geom::Point leg_next = to_2geom(node->n.other->pos - node->pos);
3415 double norm_leg_prev = Geom::L2(leg_prev);
3416 double norm_leg_next = Geom::L2(leg_next);
3418 Geom::Point delta;
3419 if (norm_leg_next > 0.0) {
3420 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
3421 delta.normalize();
3422 }
3424 node->p.pos = node->pos - norm_leg_prev / 3 * delta;
3425 node->n.pos = node->pos + norm_leg_next / 3 * delta;
3426 }
3428 /**
3429 * Node event callback.
3430 */
3431 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3432 {
3433 gboolean ret = FALSE;
3434 switch (event->type) {
3435 case GDK_ENTER_NOTIFY:
3436 Inkscape::NodePath::Path::active_node = n;
3437 break;
3438 case GDK_LEAVE_NOTIFY:
3439 Inkscape::NodePath::Path::active_node = NULL;
3440 break;
3441 case GDK_SCROLL:
3442 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3443 switch (event->scroll.direction) {
3444 case GDK_SCROLL_UP:
3445 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3446 break;
3447 case GDK_SCROLL_DOWN:
3448 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3449 break;
3450 default:
3451 break;
3452 }
3453 ret = TRUE;
3454 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3455 switch (event->scroll.direction) {
3456 case GDK_SCROLL_UP:
3457 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3458 break;
3459 case GDK_SCROLL_DOWN:
3460 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3461 break;
3462 default:
3463 break;
3464 }
3465 ret = TRUE;
3466 }
3467 break;
3468 case GDK_KEY_PRESS:
3469 switch (get_group0_keyval (&event->key)) {
3470 case GDK_space:
3471 if (event->key.state & GDK_BUTTON1_MASK) {
3472 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3473 stamp_repr(nodepath);
3474 ret = TRUE;
3475 }
3476 break;
3477 case GDK_Page_Up:
3478 if (event->key.state & GDK_CONTROL_MASK) {
3479 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3480 } else {
3481 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3482 }
3483 break;
3484 case GDK_Page_Down:
3485 if (event->key.state & GDK_CONTROL_MASK) {
3486 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3487 } else {
3488 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3489 }
3490 break;
3491 default:
3492 break;
3493 }
3494 break;
3495 default:
3496 break;
3497 }
3499 return ret;
3500 }
3502 /**
3503 * Handle keypress on node; directly called.
3504 */
3505 gboolean node_key(GdkEvent *event)
3506 {
3507 Inkscape::NodePath::Path *np;
3509 // there is no way to verify nodes so set active_node to nil when deleting!!
3510 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3512 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3513 gint ret = FALSE;
3514 switch (get_group0_keyval (&event->key)) {
3515 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3516 case GDK_BackSpace:
3517 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3518 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3519 sp_nodepath_update_repr(np, _("Delete node"));
3520 Inkscape::NodePath::Path::active_node = NULL;
3521 ret = TRUE;
3522 break;
3523 case GDK_c:
3524 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3525 ret = TRUE;
3526 break;
3527 case GDK_s:
3528 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3529 ret = TRUE;
3530 break;
3531 case GDK_a:
3532 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_AUTO);
3533 ret = TRUE;
3534 break;
3535 case GDK_y:
3536 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3537 ret = TRUE;
3538 break;
3539 case GDK_b:
3540 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3541 ret = TRUE;
3542 break;
3543 }
3544 return ret;
3545 }
3546 return FALSE;
3547 }
3549 /**
3550 * Mouseclick on node callback.
3551 */
3552 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3553 {
3554 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3556 if (state & GDK_CONTROL_MASK) {
3557 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3559 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3560 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3561 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3562 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3563 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3564 } else if (n->type == Inkscape::NodePath::NODE_SYMM) {
3565 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_AUTO);
3566 } else {
3567 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3568 }
3569 sp_nodepath_update_repr(nodepath, _("Change node type"));
3570 sp_nodepath_update_statusbar(nodepath);
3572 } else { //ctrl+alt+click: delete node
3573 GList *node_to_delete = NULL;
3574 node_to_delete = g_list_append(node_to_delete, n);
3575 sp_node_delete_preserve(node_to_delete);
3576 }
3578 } else {
3579 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3580 }
3581 }
3583 /**
3584 * Mouse grabbed node callback.
3585 */
3586 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3587 {
3588 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3590 if (!n->selected) {
3591 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3592 }
3594 n->is_dragging = true;
3595 // Reconstruct and store the location of the mouse pointer at the time when we started dragging (needed for snapping)
3596 n->subpath->nodepath->drag_origin_mouse = knot->grabbed_rel_pos + knot->drag_origin;
3598 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3600 sp_nodepath_remember_origins (n->subpath->nodepath);
3601 }
3603 /**
3604 * Mouse ungrabbed node callback.
3605 */
3606 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3607 {
3608 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3610 n->dragging_out = NULL;
3611 n->is_dragging = false;
3612 n->subpath->nodepath->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE);
3613 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3615 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3616 }
3618 /**
3619 * The point on a line, given by its angle, closest to the given point.
3620 * \param p A point.
3621 * \param a Angle of the line; it is assumed to go through coordinate origin.
3622 * \param closest Pointer to the point struct where the result is stored.
3623 * \todo FIXME: use dot product perhaps?
3624 */
3625 static void point_line_closest(Geom::Point *p, double a, Geom::Point *closest)
3626 {
3627 if (a == HUGE_VAL) { // vertical
3628 *closest = Geom::Point(0, (*p)[Geom::Y]);
3629 } else {
3630 (*closest)[Geom::X] = ( a * (*p)[Geom::Y] + (*p)[Geom::X]) / (a*a + 1);
3631 (*closest)[Geom::Y] = a * (*closest)[Geom::X];
3632 }
3633 }
3635 /**
3636 * Distance from the point to a line given by its angle.
3637 * \param p A point.
3638 * \param a Angle of the line; it is assumed to go through coordinate origin.
3639 */
3640 static double point_line_distance(Geom::Point *p, double a)
3641 {
3642 Geom::Point c;
3643 point_line_closest(p, a, &c);
3644 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]));
3645 }
3647 /**
3648 * Callback for node "request" signal.
3649 * \todo fixme: This goes to "moved" event? (lauris)
3650 */
3651 static gboolean
3652 node_request(SPKnot */*knot*/, Geom::Point const &p, guint state, gpointer data)
3653 {
3654 double yn, xn, yp, xp;
3655 double an, ap, na, pa;
3656 double d_an, d_ap, d_na, d_pa;
3657 gboolean collinear = FALSE;
3658 Geom::Point c;
3659 Geom::Point pr;
3661 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3663 n->subpath->nodepath->desktop->snapindicator->remove_snaptarget();
3665 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3666 if ( (!n->subpath->nodepath->straight_path) &&
3667 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3668 || n->dragging_out ) )
3669 {
3670 Geom::Point mouse = p;
3672 if (!n->dragging_out) {
3673 // This is the first drag-out event; find out which handle to drag out
3674 double appr_n = (n->n.other ? Geom::L2(n->n.other->pos - n->pos) - Geom::L2(n->n.other->pos - p) : -HUGE_VAL);
3675 double appr_p = (n->p.other ? Geom::L2(n->p.other->pos - n->pos) - Geom::L2(n->p.other->pos - p) : -HUGE_VAL);
3677 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3678 return FALSE;
3680 Inkscape::NodePath::NodeSide *opposite;
3681 if (appr_p > appr_n) { // closer to p
3682 n->dragging_out = &n->p;
3683 opposite = &n->n;
3684 n->code = NR_CURVETO;
3685 } else if (appr_p < appr_n) { // closer to n
3686 n->dragging_out = &n->n;
3687 opposite = &n->p;
3688 n->n.other->code = NR_CURVETO;
3689 } else { // p and n nodes are the same
3690 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3691 n->dragging_out = &n->p;
3692 opposite = &n->n;
3693 n->code = NR_CURVETO;
3694 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3695 n->dragging_out = &n->n;
3696 opposite = &n->p;
3697 n->n.other->code = NR_CURVETO;
3698 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3699 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);
3700 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);
3701 if (appr_other_p > appr_other_n) { // closer to other's p handle
3702 n->dragging_out = &n->n;
3703 opposite = &n->p;
3704 n->n.other->code = NR_CURVETO;
3705 } else { // closer to other's n handle
3706 n->dragging_out = &n->p;
3707 opposite = &n->n;
3708 n->code = NR_CURVETO;
3709 }
3710 }
3711 }
3713 // if there's another handle, make sure the one we drag out starts parallel to it
3714 if (opposite->pos != n->pos) {
3715 mouse = n->pos - Geom::L2(mouse - n->pos) * Geom::unit_vector(opposite->pos - n->pos);
3716 }
3718 // knots might not be created yet!
3719 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3720 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3721 }
3723 // pass this on to the handle-moved callback
3724 node_handle_moved(n->dragging_out->knot, mouse, state, (gpointer) n);
3725 sp_node_update_handles(n);
3726 return TRUE;
3727 }
3729 if (state & GDK_CONTROL_MASK) { // constrained motion
3731 // calculate relative distances of handles
3732 // n handle:
3733 yn = n->n.pos[Geom::Y] - n->pos[Geom::Y];
3734 xn = n->n.pos[Geom::X] - n->pos[Geom::X];
3735 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3736 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3737 if (n->n.other) { // if there is the next point
3738 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3739 yn = n->n.other->origin[Geom::Y] - n->origin[Geom::Y]; // use origin because otherwise the direction will change as you drag
3740 xn = n->n.other->origin[Geom::X] - n->origin[Geom::X];
3741 }
3742 }
3743 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3744 if (yn < 0) { xn = -xn; yn = -yn; }
3746 // p handle:
3747 yp = n->p.pos[Geom::Y] - n->pos[Geom::Y];
3748 xp = n->p.pos[Geom::X] - n->pos[Geom::X];
3749 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3750 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3751 if (n->p.other) {
3752 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3753 yp = n->p.other->origin[Geom::Y] - n->origin[Geom::Y];
3754 xp = n->p.other->origin[Geom::X] - n->origin[Geom::X];
3755 }
3756 }
3757 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3758 if (yp < 0) { xp = -xp; yp = -yp; }
3760 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3761 // sliding on handles, only if at least one of the handles is non-vertical
3762 // (otherwise it's the same as ctrl+drag anyway)
3764 // calculate angles of the handles
3765 if (xn == 0) {
3766 if (yn == 0) { // no handle, consider it the continuation of the other one
3767 an = 0;
3768 collinear = TRUE;
3769 }
3770 else an = 0; // vertical; set the angle to horizontal
3771 } else an = yn/xn;
3773 if (xp == 0) {
3774 if (yp == 0) { // no handle, consider it the continuation of the other one
3775 ap = an;
3776 }
3777 else ap = 0; // vertical; set the angle to horizontal
3778 } else ap = yp/xp;
3780 if (collinear) an = ap;
3782 // angles of the perpendiculars; HUGE_VAL means vertical
3783 if (an == 0) na = HUGE_VAL; else na = -1/an;
3784 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3786 // mouse point relative to the node's original pos
3787 pr = p - n->origin;
3789 // distances to the four lines (two handles and two perpendiculars)
3790 d_an = point_line_distance(&pr, an);
3791 d_na = point_line_distance(&pr, na);
3792 d_ap = point_line_distance(&pr, ap);
3793 d_pa = point_line_distance(&pr, pa);
3795 // find out which line is the closest, save its closest point in c
3796 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3797 point_line_closest(&pr, an, &c);
3798 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3799 point_line_closest(&pr, ap, &c);
3800 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3801 point_line_closest(&pr, na, &c);
3802 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3803 point_line_closest(&pr, pa, &c);
3804 }
3806 // move the node to the closest point
3807 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3808 n->origin[Geom::X] + c[Geom::X] - n->pos[Geom::X],
3809 n->origin[Geom::Y] + c[Geom::Y] - n->pos[Geom::Y],
3810 true);
3812 } else { // constraining to hor/vert
3814 if (fabs(p[Geom::X] - n->origin[Geom::X]) > fabs(p[Geom::Y] - n->origin[Geom::Y])) { // snap to hor
3815 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3816 p[Geom::X] - n->pos[Geom::X],
3817 n->origin[Geom::Y] - n->pos[Geom::Y],
3818 true,
3819 true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::X]));
3820 } else { // snap to vert
3821 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3822 n->origin[Geom::X] - n->pos[Geom::X],
3823 p[Geom::Y] - n->pos[Geom::Y],
3824 true,
3825 true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::Y]));
3826 }
3827 }
3828 } else { // move freely
3829 if (n->is_dragging) {
3830 if (state & GDK_MOD1_MASK) { // sculpt
3831 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, p - n->origin);
3832 } else {
3833 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3834 p[Geom::X] - n->pos[Geom::X],
3835 p[Geom::Y] - n->pos[Geom::Y],
3836 (state & GDK_SHIFT_MASK) == 0);
3837 }
3838 }
3839 }
3841 n->subpath->nodepath->desktop->scroll_to_point(p);
3843 return TRUE;
3844 }
3846 /**
3847 * Node handle clicked callback.
3848 */
3849 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3850 {
3851 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3853 if (state & GDK_CONTROL_MASK) { // "delete" handle
3854 if (n->p.knot == knot) {
3855 n->p.pos = n->pos;
3856 } else if (n->n.knot == knot) {
3857 n->n.pos = n->pos;
3858 }
3859 sp_node_update_handles(n);
3860 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3861 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3862 sp_nodepath_update_statusbar(nodepath);
3864 } else { // just select or add to selection, depending in Shift
3865 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3866 }
3867 }
3869 /**
3870 * Node handle grabbed callback.
3871 */
3872 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3873 {
3874 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3876 // convert auto -> smooth when dragging handle
3877 if (n->type == Inkscape::NodePath::NODE_AUTO) {
3878 n->type = Inkscape::NodePath::NODE_SMOOTH;
3879 sp_nodepath_update_node_knot (n);
3880 }
3882 if (!n->selected) {
3883 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3884 }
3886 // remember the origin point of the handle
3887 if (n->p.knot == knot) {
3888 n->p.origin_radial = n->p.pos - n->pos;
3889 } else if (n->n.knot == knot) {
3890 n->n.origin_radial = n->n.pos - n->pos;
3891 } else {
3892 g_assert_not_reached();
3893 }
3895 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3896 }
3898 /**
3899 * Node handle ungrabbed callback.
3900 */
3901 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3902 {
3903 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3905 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3906 if (n->p.knot == knot) {
3907 n->p.origin_radial.a = 0;
3908 sp_knot_set_position(knot, n->p.pos, state);
3909 } else if (n->n.knot == knot) {
3910 n->n.origin_radial.a = 0;
3911 sp_knot_set_position(knot, n->n.pos, state);
3912 } else {
3913 g_assert_not_reached();
3914 }
3916 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3917 }
3919 /**
3920 * Node handle "request" signal callback.
3921 */
3922 static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, gpointer data)
3923 {
3924 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3926 Inkscape::NodePath::NodeSide *me, *opposite;
3927 gint which;
3928 if (n->p.knot == knot) {
3929 me = &n->p;
3930 opposite = &n->n;
3931 which = -1;
3932 } else if (n->n.knot == knot) {
3933 me = &n->n;
3934 opposite = &n->p;
3935 which = 1;
3936 } else {
3937 me = opposite = NULL;
3938 which = 0;
3939 g_assert_not_reached();
3940 }
3942 SPDesktop *desktop = n->subpath->nodepath->desktop;
3943 SnapManager &m = desktop->namedview->snap_manager;
3944 m.setup(desktop, true, n->subpath->nodepath->item);
3945 Inkscape::SnappedPoint s;
3947 if ((state & GDK_SHIFT_MASK) != 0) {
3948 // We will not try to snap when the shift-key is pressed
3949 // so remove the old snap indicator and don't wait for it to time-out
3950 desktop->snapindicator->remove_snaptarget();
3951 }
3953 Inkscape::NodePath::Node *othernode = opposite->other;
3954 Inkscape::SnapSourceType source_type = (n->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPSOURCE_NODE_SMOOTH : Inkscape::SNAPSOURCE_NODE_CUSP);
3955 if (othernode) {
3956 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3957 /* We are smooth node adjacent with line */
3958 Geom::Point const delta = p - n->pos;
3959 Geom::Coord const len = Geom::L2(delta);
3960 Inkscape::NodePath::Node *othernode = opposite->other;
3961 Geom::Point const ndelta = n->pos - othernode->pos;
3962 Geom::Coord const linelen = Geom::L2(ndelta);
3963 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3964 Geom::Coord const scal = dot(delta, ndelta) / linelen;
3965 p = n->pos + (scal / linelen) * ndelta;
3966 }
3967 if ((state & GDK_SHIFT_MASK) == 0) {
3968 s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type, Inkscape::Snapper::ConstraintLine(p, ndelta), false);
3969 }
3970 } else {
3971 if ((state & GDK_SHIFT_MASK) == 0) {
3972 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type);
3973 }
3974 }
3975 } else {
3976 if ((state & GDK_SHIFT_MASK) == 0) {
3977 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type);
3978 }
3979 }
3981 s.getPoint(p);
3983 sp_node_adjust_handle(n, -which);
3985 return FALSE;
3986 }
3988 /**
3989 * Node handle moved callback.
3990 */
3991 static void node_handle_moved(SPKnot *knot, Geom::Point const &p, guint state, gpointer data)
3992 {
3993 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3994 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
3996 Inkscape::NodePath::NodeSide *me;
3997 Inkscape::NodePath::NodeSide *other;
3998 if (n->p.knot == knot) {
3999 me = &n->p;
4000 other = &n->n;
4001 } else if (n->n.knot == knot) {
4002 me = &n->n;
4003 other = &n->p;
4004 } else {
4005 me = NULL;
4006 other = NULL;
4007 g_assert_not_reached();
4008 }
4010 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
4011 Radial rme(me->pos - n->pos);
4012 Radial rother(other->pos - n->pos);
4013 Radial rnew(p - n->pos);
4015 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
4016 int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
4017 /* 0 interpreted as "no snapping". */
4019 // 1. Snap to the closest PI/snaps angle, starting from zero.
4020 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
4022 // 2. Snap to the original angle, its opposite and perpendiculars
4023 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
4024 /* The closest PI/2 angle, starting from original angle */
4025 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
4027 // Snap to the closest.
4028 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
4029 ? a_snapped
4030 : a_ortho );
4031 }
4033 // 3. Snap to the angle of the opposite line, if any
4034 Inkscape::NodePath::Node *othernode = other->other;
4035 if (othernode) {
4036 Geom::Point other_to_snap(0,0);
4037 if (sp_node_side_is_line(n, other)) {
4038 other_to_snap = othernode->pos - n->pos;
4039 } else {
4040 other_to_snap = other->pos - n->pos;
4041 }
4042 if (Geom::L2(other_to_snap) > 1e-3) {
4043 Radial rother_to_snap(other_to_snap);
4044 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
4045 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
4047 // Snap to the closest.
4048 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
4049 ? a_snapped
4050 : a_oppo );
4051 }
4052 }
4054 rnew.a = a_snapped;
4055 }
4057 if (state & GDK_MOD1_MASK) {
4058 // lock handle length
4059 rnew.r = me->origin_radial.r;
4060 }
4062 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (!n->dragging_out && (state & GDK_SHIFT_MASK)))
4063 && (rme.a != HUGE_VAL) && (rnew.a != HUGE_VAL) && ((fabs(rme.a - rnew.a) > 0.001) || (n->type ==Inkscape::NodePath::NODE_SYMM))) {
4064 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
4065 rother.a += rnew.a - rme.a;
4066 other->pos = Geom::Point(rother) + n->pos;
4067 if (other->knot) {
4068 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
4069 sp_knot_moveto(other->knot, other->pos);
4070 }
4071 }
4073 me->pos = Geom::Point(rnew) + n->pos;
4074 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
4076 // move knot, but without emitting the signal:
4077 // we cannot emit a "moved" signal because we're now processing it
4078 sp_knot_moveto(me->knot, me->pos);
4080 update_object(n->subpath->nodepath);
4082 /* status text */
4083 SPDesktop *desktop = n->subpath->nodepath->desktop;
4084 if (!desktop) return;
4085 SPEventContext *ec = desktop->event_context;
4086 if (!ec) return;
4088 Inkscape::MessageContext *mc = get_message_context(ec);
4090 if (!mc) return;
4092 double degrees = 180 / M_PI * rnew.a;
4093 if (degrees > 180) degrees -= 360;
4094 if (degrees < -180) degrees += 360;
4095 if (prefs->getBool("/options/compassangledisplay/value"))
4096 degrees = angle_to_compass (degrees);
4098 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
4100 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
4101 _("<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);
4103 g_string_free(length, TRUE);
4104 }
4106 /**
4107 * Node handle event callback.
4108 */
4109 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
4110 {
4111 gboolean ret = FALSE;
4112 switch (event->type) {
4113 case GDK_KEY_PRESS:
4114 switch (get_group0_keyval (&event->key)) {
4115 case GDK_space:
4116 if (event->key.state & GDK_BUTTON1_MASK) {
4117 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
4118 stamp_repr(nodepath);
4119 ret = TRUE;
4120 }
4121 break;
4122 default:
4123 break;
4124 }
4125 break;
4126 case GDK_ENTER_NOTIFY:
4127 // we use an experimentally determined threshold that seems to work fine
4128 if (Geom::L2(n->pos - knot->pos) < 0.75)
4129 Inkscape::NodePath::Path::active_node = n;
4130 break;
4131 case GDK_LEAVE_NOTIFY:
4132 // we use an experimentally determined threshold that seems to work fine
4133 if (Geom::L2(n->pos - knot->pos) < 0.75)
4134 Inkscape::NodePath::Path::active_node = NULL;
4135 break;
4136 default:
4137 break;
4138 }
4140 return ret;
4141 }
4143 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
4144 Radial &rme, Radial &rother, gboolean const both)
4145 {
4146 rme.a += angle;
4147 if ( both
4148 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4149 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4150 {
4151 rother.a += angle;
4152 }
4153 }
4155 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
4156 Radial &rme, Radial &rother, gboolean const both)
4157 {
4158 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4160 gdouble r;
4161 if ( both
4162 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4163 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4164 {
4165 r = MAX(rme.r, rother.r);
4166 } else {
4167 r = rme.r;
4168 }
4170 gdouble const weird_angle = atan2(norm_angle, r);
4171 /* Bulia says norm_angle is just the visible distance that the
4172 * object's end must travel on the screen. Left as 'angle' for want of
4173 * a better name.*/
4175 rme.a += weird_angle;
4176 if ( both
4177 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4178 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4179 {
4180 rother.a += weird_angle;
4181 }
4182 }
4184 /**
4185 * Rotate one node.
4186 */
4187 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4188 {
4189 Inkscape::NodePath::NodeSide *me, *other;
4190 bool both = false;
4192 double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4193 double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4195 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4196 me = &(n->p);
4197 other = &(n->n);
4198 } else if (!n->p.other) {
4199 me = &(n->n);
4200 other = &(n->p);
4201 } else {
4202 if (which > 0) { // right handle
4203 if (xn > xp) {
4204 me = &(n->n);
4205 other = &(n->p);
4206 } else {
4207 me = &(n->p);
4208 other = &(n->n);
4209 }
4210 } else if (which < 0){ // left handle
4211 if (xn <= xp) {
4212 me = &(n->n);
4213 other = &(n->p);
4214 } else {
4215 me = &(n->p);
4216 other = &(n->n);
4217 }
4218 } else { // both handles
4219 me = &(n->n);
4220 other = &(n->p);
4221 both = true;
4222 }
4223 }
4225 Radial rme(me->pos - n->pos);
4226 Radial rother(other->pos - n->pos);
4228 if (screen) {
4229 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4230 } else {
4231 node_rotate_one_internal (*n, angle, rme, rother, both);
4232 }
4234 me->pos = n->pos + Geom::Point(rme);
4236 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4237 other->pos = n->pos + Geom::Point(rother);
4238 }
4240 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4241 // so here we just move all the knots without emitting move signals, for speed
4242 sp_node_update_handles(n, false);
4243 }
4245 /**
4246 * Rotate selected nodes.
4247 */
4248 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4249 {
4250 if (!nodepath || !nodepath->selected) return;
4252 if (g_list_length(nodepath->selected) == 1) {
4253 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4254 node_rotate_one (n, angle, which, screen);
4255 } else {
4256 // rotate as an object:
4258 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4259 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4260 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4261 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4262 box.expandTo (n->pos); // contain all selected nodes
4263 }
4265 gdouble rot;
4266 if (screen) {
4267 gdouble const zoom = nodepath->desktop->current_zoom();
4268 gdouble const zmove = angle / zoom;
4269 gdouble const r = Geom::L2(box.max() - box.midpoint());
4270 rot = atan2(zmove, r);
4271 } else {
4272 rot = angle;
4273 }
4275 Geom::Point rot_center;
4276 if (Inkscape::NodePath::Path::active_node == NULL)
4277 rot_center = box.midpoint();
4278 else
4279 rot_center = Inkscape::NodePath::Path::active_node->pos;
4281 Geom::Matrix t =
4282 Geom::Matrix (Geom::Translate(-rot_center)) *
4283 Geom::Matrix (Geom::Rotate(rot)) *
4284 Geom::Matrix (Geom::Translate(rot_center));
4286 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4287 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4288 n->pos *= t;
4289 n->n.pos *= t;
4290 n->p.pos *= t;
4291 sp_node_update_handles(n, false);
4292 }
4293 }
4295 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4296 }
4298 /**
4299 * Scale one node.
4300 */
4301 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4302 {
4303 bool both = false;
4304 Inkscape::NodePath::NodeSide *me, *other;
4306 double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4307 double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4309 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4310 me = &(n->p);
4311 other = &(n->n);
4312 n->code = NR_CURVETO;
4313 } else if (!n->p.other) {
4314 me = &(n->n);
4315 other = &(n->p);
4316 if (n->n.other)
4317 n->n.other->code = NR_CURVETO;
4318 } else {
4319 if (which > 0) { // right handle
4320 if (xn > xp) {
4321 me = &(n->n);
4322 other = &(n->p);
4323 if (n->n.other)
4324 n->n.other->code = NR_CURVETO;
4325 } else {
4326 me = &(n->p);
4327 other = &(n->n);
4328 n->code = NR_CURVETO;
4329 }
4330 } else if (which < 0){ // left handle
4331 if (xn <= xp) {
4332 me = &(n->n);
4333 other = &(n->p);
4334 if (n->n.other)
4335 n->n.other->code = NR_CURVETO;
4336 } else {
4337 me = &(n->p);
4338 other = &(n->n);
4339 n->code = NR_CURVETO;
4340 }
4341 } else { // both handles
4342 me = &(n->n);
4343 other = &(n->p);
4344 both = true;
4345 n->code = NR_CURVETO;
4346 if (n->n.other)
4347 n->n.other->code = NR_CURVETO;
4348 }
4349 }
4351 Radial rme(me->pos - n->pos);
4352 Radial rother(other->pos - n->pos);
4354 rme.r += grow;
4355 if (rme.r < 0) rme.r = 0;
4356 if (rme.a == HUGE_VAL) {
4357 if (me->other) { // if direction is unknown, initialize it towards the next node
4358 Radial rme_next(me->other->pos - n->pos);
4359 rme.a = rme_next.a;
4360 } else { // if there's no next, initialize to 0
4361 rme.a = 0;
4362 }
4363 }
4364 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4365 rother.r += grow;
4366 if (rother.r < 0) rother.r = 0;
4367 if (rother.a == HUGE_VAL) {
4368 rother.a = rme.a + M_PI;
4369 }
4370 }
4372 me->pos = n->pos + Geom::Point(rme);
4374 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4375 other->pos = n->pos + Geom::Point(rother);
4376 }
4378 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4379 // so here we just move all the knots without emitting move signals, for speed
4380 sp_node_update_handles(n, false);
4381 }
4383 /**
4384 * Scale selected nodes.
4385 */
4386 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4387 {
4388 if (!nodepath || !nodepath->selected) return;
4390 if (g_list_length(nodepath->selected) == 1) {
4391 // scale handles of the single selected node
4392 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4393 node_scale_one (n, grow, which);
4394 } else {
4395 // scale nodes as an "object":
4397 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4398 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4399 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4400 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4401 box.expandTo (n->pos); // contain all selected nodes
4402 }
4404 if ( Geom::are_near(box.maxExtent(), 0) ) {
4405 SPEventContext *ec = nodepath->desktop->event_context;
4406 if (!ec) return;
4407 Inkscape::MessageContext *mc = get_message_context(ec);
4408 if (!mc) return;
4409 mc->setF(Inkscape::WARNING_MESSAGE,
4410 _("Cannot scale nodes when all are at the same location."));
4411 return;
4412 }
4413 double scale = (box.maxExtent() + grow)/box.maxExtent();
4416 Geom::Point scale_center;
4417 if (Inkscape::NodePath::Path::active_node == NULL)
4418 scale_center = box.midpoint();
4419 else
4420 scale_center = Inkscape::NodePath::Path::active_node->pos;
4422 Geom::Matrix t =
4423 Geom::Translate(-scale_center) *
4424 Geom::Scale(scale, scale) *
4425 Geom::Translate(scale_center);
4427 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4428 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4429 n->pos *= t;
4430 n->n.pos *= t;
4431 n->p.pos *= t;
4432 sp_node_update_handles(n, false);
4433 }
4434 }
4436 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4437 }
4439 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4440 {
4441 if (!nodepath) return;
4442 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4443 }
4445 /**
4446 * Flip selected nodes horizontally/vertically.
4447 */
4448 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis, boost::optional<Geom::Point> center)
4449 {
4450 if (!nodepath || !nodepath->selected) return;
4452 if (g_list_length(nodepath->selected) == 1 && !center) {
4453 // flip handles of the single selected node
4454 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4455 double temp = n->p.pos[axis];
4456 n->p.pos[axis] = n->n.pos[axis];
4457 n->n.pos[axis] = temp;
4458 sp_node_update_handles(n, false);
4459 } else {
4460 // scale nodes as an "object":
4462 Geom::Rect box = sp_node_selected_bbox (nodepath);
4463 if (!center) {
4464 center = box.midpoint();
4465 }
4466 Geom::Matrix t =
4467 Geom::Matrix (Geom::Translate(- *center)) *
4468 Geom::Matrix ((axis == Geom::X)? Geom::Scale(-1, 1) : Geom::Scale(1, -1)) *
4469 Geom::Matrix (Geom::Translate(*center));
4471 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4472 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4473 n->pos *= t;
4474 n->n.pos *= t;
4475 n->p.pos *= t;
4476 sp_node_update_handles(n, false);
4477 }
4478 }
4480 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4481 }
4483 Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4484 {
4485 g_assert (nodepath->selected);
4487 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4488 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4489 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4490 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4491 box.expandTo (n->pos); // contain all selected nodes
4492 }
4493 return box;
4494 }
4496 //-----------------------------------------------
4497 /**
4498 * Return new subpath under given nodepath.
4499 */
4500 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4501 {
4502 g_assert(nodepath);
4503 g_assert(nodepath->desktop);
4505 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4507 s->nodepath = nodepath;
4508 s->closed = FALSE;
4509 s->nodes = NULL;
4510 s->first = NULL;
4511 s->last = NULL;
4513 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4514 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4515 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4517 return s;
4518 }
4520 /**
4521 * Destroy nodes in subpath, then subpath itself.
4522 */
4523 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4524 {
4525 g_assert(subpath);
4526 g_assert(subpath->nodepath);
4527 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4529 while (subpath->nodes) {
4530 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4531 }
4533 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4535 g_free(subpath);
4536 }
4538 /**
4539 * Link head to tail in subpath.
4540 */
4541 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4542 {
4543 g_assert(!sp->closed);
4544 g_assert(sp->last != sp->first);
4545 g_assert(sp->first->code == NR_MOVETO);
4547 sp->closed = TRUE;
4549 //Link the head to the tail
4550 sp->first->p.other = sp->last;
4551 sp->last->n.other = sp->first;
4552 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4553 sp->first = sp->last;
4555 //Remove the extra end node
4556 sp_nodepath_node_destroy(sp->last->n.other);
4557 }
4559 /**
4560 * Open closed (loopy) subpath at node.
4561 */
4562 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4563 {
4564 g_assert(sp->closed);
4565 g_assert(n->subpath == sp);
4566 g_assert(sp->first == sp->last);
4568 /* We create new startpoint, current node will become last one */
4570 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4571 &n->pos, &n->pos, &n->n.pos);
4574 sp->closed = FALSE;
4576 //Unlink to make a head and tail
4577 sp->first = new_path;
4578 sp->last = n;
4579 n->n.other = NULL;
4580 new_path->p.other = NULL;
4581 }
4583 /**
4584 * Return new node in subpath with given properties.
4585 * \param pos Position of node.
4586 * \param ppos Handle position in previous direction
4587 * \param npos Handle position in previous direction
4588 */
4589 Inkscape::NodePath::Node *
4590 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)
4591 {
4592 g_assert(sp);
4593 g_assert(sp->nodepath);
4594 g_assert(sp->nodepath->desktop);
4596 if (nodechunk == NULL)
4597 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4599 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4601 n->subpath = sp;
4603 if (type != Inkscape::NodePath::NODE_NONE) {
4604 // use the type from sodipodi:nodetypes
4605 n->type = type;
4606 } else {
4607 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4608 // points are (almost) collinear
4609 if (Geom::L2(*pos - *ppos) < 1e-6 || Geom::L2(*pos - *npos) < 1e-6) {
4610 // endnode, or a node with a retracted handle
4611 n->type = Inkscape::NodePath::NODE_CUSP;
4612 } else {
4613 n->type = Inkscape::NodePath::NODE_SMOOTH;
4614 }
4615 } else {
4616 n->type = Inkscape::NodePath::NODE_CUSP;
4617 }
4618 }
4620 n->code = code;
4621 n->selected = FALSE;
4622 n->pos = *pos;
4623 n->p.pos = *ppos;
4624 n->n.pos = *npos;
4626 n->dragging_out = NULL;
4628 Inkscape::NodePath::Node *prev;
4629 if (next) {
4630 //g_assert(g_list_find(sp->nodes, next));
4631 prev = next->p.other;
4632 } else {
4633 prev = sp->last;
4634 }
4636 if (prev)
4637 prev->n.other = n;
4638 else
4639 sp->first = n;
4641 if (next)
4642 next->p.other = n;
4643 else
4644 sp->last = n;
4646 n->p.other = prev;
4647 n->n.other = next;
4649 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"));
4650 sp_knot_set_position(n->knot, *pos, 0);
4652 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4653 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4654 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4656 sp_nodepath_update_node_knot(n);
4658 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4659 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4660 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4661 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4662 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4663 sp_knot_show(n->knot);
4665 // We only create handle knots and lines on demand
4666 n->p.knot = NULL;
4667 n->p.line = NULL;
4668 n->n.knot = NULL;
4669 n->n.line = NULL;
4671 sp->nodes = g_list_prepend(sp->nodes, n);
4673 return n;
4674 }
4676 /**
4677 * Destroy node and its knots, link neighbors in subpath.
4678 */
4679 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4680 {
4681 g_assert(node);
4682 g_assert(node->subpath);
4683 g_assert(SP_IS_KNOT(node->knot));
4685 Inkscape::NodePath::SubPath *sp = node->subpath;
4687 if (node->selected) { // first, deselect
4688 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4689 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4690 }
4692 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4694 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4695 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4696 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4697 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4698 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4699 g_object_unref(G_OBJECT(node->knot));
4701 if (node->p.knot) {
4702 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4703 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4704 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4705 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4706 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4707 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4708 g_object_unref(G_OBJECT(node->p.knot));
4709 node->p.knot = NULL;
4710 }
4712 if (node->n.knot) {
4713 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4714 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4715 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4716 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4717 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4718 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4719 g_object_unref(G_OBJECT(node->n.knot));
4720 node->n.knot = NULL;
4721 }
4723 if (node->p.line)
4724 gtk_object_destroy(GTK_OBJECT(node->p.line));
4725 if (node->n.line)
4726 gtk_object_destroy(GTK_OBJECT(node->n.line));
4728 if (sp->nodes) { // there are others nodes on the subpath
4729 if (sp->closed) {
4730 if (sp->first == node) {
4731 g_assert(sp->last == node);
4732 sp->first = node->n.other;
4733 sp->last = sp->first;
4734 }
4735 node->p.other->n.other = node->n.other;
4736 node->n.other->p.other = node->p.other;
4737 } else {
4738 if (sp->first == node) {
4739 sp->first = node->n.other;
4740 sp->first->code = NR_MOVETO;
4741 }
4742 if (sp->last == node) sp->last = node->p.other;
4743 if (node->p.other) node->p.other->n.other = node->n.other;
4744 if (node->n.other) node->n.other->p.other = node->p.other;
4745 }
4746 } else { // this was the last node on subpath
4747 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4748 }
4750 g_mem_chunk_free(nodechunk, node);
4751 }
4753 /**
4754 * Returns one of the node's two sides.
4755 * \param which Indicates which side.
4756 * \return Pointer to previous node side if which==-1, next if which==1.
4757 */
4758 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4759 {
4760 g_assert(node);
4761 Inkscape::NodePath::NodeSide * result = 0;
4762 switch (which) {
4763 case -1:
4764 result = &node->p;
4765 break;
4766 case 1:
4767 result = &node->n;
4768 break;
4769 default:
4770 g_assert_not_reached();
4771 }
4773 return result;
4774 }
4776 /**
4777 * Return the other side of the node, given one of its sides.
4778 */
4779 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4780 {
4781 g_assert(node);
4782 Inkscape::NodePath::NodeSide *result = 0;
4784 if (me == &node->p) {
4785 result = &node->n;
4786 } else if (me == &node->n) {
4787 result = &node->p;
4788 } else {
4789 g_assert_not_reached();
4790 }
4792 return result;
4793 }
4795 /**
4796 * Return NRPathcode on the given side of the node.
4797 */
4798 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4799 {
4800 g_assert(node);
4802 NRPathcode result = NR_END;
4803 if (me == &node->p) {
4804 if (node->p.other) {
4805 result = (NRPathcode)node->code;
4806 } else {
4807 result = NR_MOVETO;
4808 }
4809 } else if (me == &node->n) {
4810 if (node->n.other) {
4811 result = (NRPathcode)node->n.other->code;
4812 } else {
4813 result = NR_MOVETO;
4814 }
4815 } else {
4816 g_assert_not_reached();
4817 }
4819 return result;
4820 }
4822 /**
4823 * Return node with the given index
4824 */
4825 Inkscape::NodePath::Node *
4826 sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index)
4827 {
4828 Inkscape::NodePath::Node *e = NULL;
4830 if (!nodepath) {
4831 return e;
4832 }
4834 //find segment
4835 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4837 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4838 int n = g_list_length(sp->nodes);
4839 if (sp->closed) {
4840 n++;
4841 }
4843 //if the piece belongs to this subpath grab it
4844 //otherwise move onto the next subpath
4845 if (index < n) {
4846 e = sp->first;
4847 for (int i = 0; i < index; ++i) {
4848 e = e->n.other;
4849 }
4850 break;
4851 } else {
4852 if (sp->closed) {
4853 index -= (n+1);
4854 } else {
4855 index -= n;
4856 }
4857 }
4858 }
4860 return e;
4861 }
4863 /**
4864 * Returns plain text meaning of node type.
4865 */
4866 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4867 {
4868 unsigned retracted = 0;
4869 bool endnode = false;
4871 for (int which = -1; which <= 1; which += 2) {
4872 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4873 if (side->other && Geom::L2(side->pos - node->pos) < 1e-6)
4874 retracted ++;
4875 if (!side->other)
4876 endnode = true;
4877 }
4879 if (retracted == 0) {
4880 if (endnode) {
4881 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4882 return _("end node");
4883 } else {
4884 switch (node->type) {
4885 case Inkscape::NodePath::NODE_CUSP:
4886 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4887 return _("cusp");
4888 case Inkscape::NodePath::NODE_SMOOTH:
4889 // TRANSLATORS: "smooth" is an adjective here
4890 return _("smooth");
4891 case Inkscape::NodePath::NODE_AUTO:
4892 return _("auto");
4893 case Inkscape::NodePath::NODE_SYMM:
4894 return _("symmetric");
4895 }
4896 }
4897 } else if (retracted == 1) {
4898 if (endnode) {
4899 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4900 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4901 } else {
4902 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4903 }
4904 } else {
4905 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4906 }
4908 return NULL;
4909 }
4911 /**
4912 * Handles content of statusbar as long as node tool is active.
4913 */
4914 void
4915 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4916 {
4917 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");
4918 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4920 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4921 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4922 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4923 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4925 SPDesktop *desktop = NULL;
4926 if (nodepath) {
4927 desktop = nodepath->desktop;
4928 } else {
4929 desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above
4930 }
4932 SPEventContext *ec = desktop->event_context;
4933 if (!ec) return;
4935 Inkscape::MessageContext *mc = get_message_context(ec);
4936 if (!mc) return;
4938 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4940 if (selected_nodes == 0) {
4941 Inkscape::Selection *sel = desktop->selection;
4942 if (!sel || sel->isEmpty()) {
4943 mc->setF(Inkscape::NORMAL_MESSAGE,
4944 _("Select a single object to edit its nodes or handles."));
4945 } else {
4946 if (nodepath) {
4947 mc->setF(Inkscape::NORMAL_MESSAGE,
4948 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.",
4949 "<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.",
4950 total_nodes),
4951 total_nodes);
4952 } else {
4953 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4954 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4955 } else {
4956 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4957 }
4958 }
4959 }
4960 } else if (nodepath && selected_nodes == 1) {
4961 mc->setF(Inkscape::NORMAL_MESSAGE,
4962 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4963 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4964 total_nodes),
4965 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4966 } else {
4967 if (selected_subpaths > 1) {
4968 mc->setF(Inkscape::NORMAL_MESSAGE,
4969 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4970 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4971 total_nodes),
4972 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4973 } else {
4974 mc->setF(Inkscape::NORMAL_MESSAGE,
4975 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4976 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4977 total_nodes),
4978 selected_nodes, total_nodes, when_selected);
4979 }
4980 }
4981 }
4983 /*
4984 * returns a *copy* of the curve of that object.
4985 */
4986 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4987 if (!object)
4988 return NULL;
4990 SPCurve *curve = NULL;
4991 if (SP_IS_PATH(object)) {
4992 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4993 curve = curve_new->copy();
4994 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4995 const gchar *svgd = object->repr->attribute(key);
4996 if (svgd) {
4997 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4998 SPCurve *curve_new = new SPCurve(pv);
4999 if (curve_new) {
5000 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
5001 }
5002 }
5003 }
5005 return curve;
5006 }
5008 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
5009 if (!np || !np->object || !curve)
5010 return;
5012 if (SP_IS_PATH(np->object)) {
5013 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
5014 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
5015 } else {
5016 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
5017 }
5018 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
5019 Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(np->object)->get_lpe();
5020 if (lpe) {
5021 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( lpe->getParameter(np->repr_key) );
5022 if (pathparam) {
5023 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
5024 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
5025 }
5026 }
5027 }
5028 }
5030 /*
5031 SPCanvasItem *
5032 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
5033 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
5034 }
5035 */
5038 /// \todo this code to generate a helper canvasitem from an spcurve should be moved to different file
5039 SPCanvasItem *
5040 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const Geom::Matrix & i2d, guint32 color = 0xff0000ff) {
5041 SPCurve *flash_curve = curve->copy();
5042 flash_curve->transform(i2d);
5043 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
5044 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
5045 // unless we also flash the nodes...
5046 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
5047 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
5048 sp_canvas_item_show(canvasitem);
5049 flash_curve->unref();
5050 return canvasitem;
5051 }
5053 SPCanvasItem *
5054 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPItem *item) {
5055 if (!item || !desktop) {
5056 return NULL;
5057 }
5059 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
5060 guint32 color = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
5062 Geom::Matrix i2d = sp_item_i2d_affine(item);
5064 SPCurve *curve = NULL;
5065 if (SP_IS_PATH(item)) {
5066 curve = sp_path_get_curve_for_edit(SP_PATH(item));
5067 } else if ( SP_IS_SHAPE(item) && SP_SHAPE(item)->curve ) {
5068 curve = sp_shape_get_curve (SP_SHAPE(item));
5069 } else if ( SP_IS_TEXT(item) ) {
5070 // do not display helperpath for text - we cannot do anything with it in Node tool anyway
5071 // curve = SP_TEXT(item)->getNormalizedBpath();
5072 return NULL;
5073 } else {
5074 g_warning ("-----> sp_nodepath_generate_helperpath(SPDesktop *desktop, SPItem *item): TODO: generate the helper path for this item type!\n");
5075 return NULL;
5076 }
5078 SPCanvasItem * helperpath = sp_nodepath_generate_helperpath(desktop, curve, i2d, color);
5080 curve->unref();
5082 return helperpath;
5083 }
5086 // TODO: Merge this with sp_nodepath_make_helper_item()!
5087 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
5088 np->show_helperpath = show;
5090 if (show) {
5091 SPCurve *helper_curve = np->curve->copy();
5092 helper_curve->transform(np->i2d);
5093 if (!np->helper_path) {
5094 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
5096 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
5097 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);
5098 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
5099 sp_canvas_item_move_to_z(np->helper_path, 0);
5100 sp_canvas_item_show(np->helper_path);
5101 } else {
5102 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
5103 }
5104 helper_curve->unref();
5105 } else {
5106 if (np->helper_path) {
5107 GtkObject *temp = np->helper_path;
5108 np->helper_path = NULL;
5109 gtk_object_destroy(temp);
5110 }
5111 }
5112 }
5114 /* sp_nodepath_make_straight_path:
5115 * Prevents user from curving the path by dragging a segment or activating handles etc.
5116 * The resulting path is a linear interpolation between nodal points, with only straight segments.
5117 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
5118 */
5119 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
5120 np->straight_path = true;
5121 np->show_handles = false;
5122 g_message("add code to make the path straight.");
5123 // do sp_nodepath_convert_node_type on all nodes?
5124 // coding tip: search for this text : "Make selected segments lines"
5125 }
5127 /*
5128 Local Variables:
5129 mode:c++
5130 c-file-style:"stroustrup"
5131 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
5132 indent-tabs-mode:nil
5133 fill-column:99
5134 End:
5135 */
5136 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :