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