Code

GSoC node tool
[inkscape.git] / src / ui / tool / node.cpp
1 /** @file
2  * Editable node - implementation
3  */
4 /* Authors:
5  *   Krzysztof Kosiński <tweenk.pl@gmail.com>
6  *
7  * Copyright (C) 2009 Authors
8  * Released under GNU GPL, read the file 'COPYING' for more information
9  */
11 #include <iostream>
12 #include <stdexcept>
13 #include <boost/utility.hpp>
14 #include <glib.h>
15 #include <glib/gi18n.h>
16 #include <2geom/transforms.h>
17 #include "ui/tool/event-utils.h"
18 #include "ui/tool/node.h"
19 #include "display/sp-ctrlline.h"
20 #include "display/sp-canvas.h"
21 #include "display/sp-canvas-util.h"
22 #include "desktop.h"
23 #include "desktop-handles.h"
24 #include "preferences.h"
25 #include "sp-metrics.h"
26 #include "sp-namedview.h"
28 namespace Inkscape {
29 namespace UI {   
31 static SelectableControlPoint::ColorSet node_colors = {
32     {
33         {0xbfbfbf00, 0x000000ff}, // normal fill, stroke
34         {0xff000000, 0x000000ff}, // mouseover fill, stroke
35         {0xff000000, 0x000000ff}  // clicked fill, stroke
36     },
37     {0x0000ffff, 0x000000ff}, // normal fill, stroke when selected
38     {0xff000000, 0x000000ff}, // mouseover fill, stroke when selected
39     {0xff000000, 0x000000ff}  // clicked fill, stroke when selected
40 };
42 static ControlPoint::ColorSet handle_colors = {
43     {0xffffffff, 0x000000ff}, // normal fill, stroke
44     {0xff000000, 0x000000ff}, // mouseover fill, stroke
45     {0xff000000, 0x000000ff}  // clicked fill, stroke
46 };
48 std::ostream &operator<<(std::ostream &out, NodeType type)
49 {
50     switch(type) {
51     case NODE_CUSP: out << 'c'; break;
52     case NODE_SMOOTH: out << 's'; break;
53     case NODE_AUTO: out << 'a'; break;
54     case NODE_SYMMETRIC: out << 'z'; break;
55     default: out << 'b'; break;
56     }
57     return out;
58 }
60 /** Computes an unit vector of the direction from first to second control point */
61 static Geom::Point direction(Geom::Point const &first, Geom::Point const &second) {
62     return Geom::unit_vector(second - first);
63 }
65 /**
66  * @class Handle
67  * Represents a control point of a cubic Bezier curve in a path.
68  */
70 double Handle::_saved_length = 0.0;
71 bool Handle::_drag_out = false;
73 Handle::Handle(NodeSharedData const &data, Geom::Point const &initial_pos, Node *parent)
74     : ControlPoint(data.desktop, initial_pos, Gtk::ANCHOR_CENTER, SP_CTRL_SHAPE_CIRCLE, 7.0,
75         &handle_colors, data.handle_group)
76     , _parent(parent)
77     , _degenerate(true)
78 {
79     _cset = &handle_colors;
80     _handle_line = sp_canvas_item_new(data.handle_line_group, SP_TYPE_CTRLLINE, NULL);
81     setVisible(false);
82     signal_grabbed.connect(
83         sigc::bind_return(
84             sigc::hide(
85                 sigc::mem_fun(*this, &Handle::_grabbedHandler)),
86             false));
87     signal_dragged.connect(
88         sigc::hide<0>(
89             sigc::mem_fun(*this, &Handle::_draggedHandler)));
90     signal_ungrabbed.connect(
91         sigc::hide(sigc::mem_fun(*this, &Handle::_ungrabbedHandler)));
92 }
93 Handle::~Handle()
94 {
95     sp_canvas_item_hide(_handle_line);
96     gtk_object_destroy(GTK_OBJECT(_handle_line));
97 }
99 void Handle::setVisible(bool v)
101     ControlPoint::setVisible(v);
102     if (v) sp_canvas_item_show(_handle_line);
103     else sp_canvas_item_hide(_handle_line);
106 void Handle::move(Geom::Point const &new_pos)
108     Handle *other, *towards, *towards_second;
109     Node *node_towards; // node in direction of this handle
110     Node *node_away; // node in the opposite direction
111     if (this == &_parent->_front) {
112         other = &_parent->_back;
113         node_towards = _parent->_next();
114         node_away = _parent->_prev();
115         towards = node_towards ? &node_towards->_back : 0;
116         towards_second = node_towards ? &node_towards->_front : 0;
117     } else {
118         other = &_parent->_front;
119         node_towards = _parent->_prev();
120         node_away = _parent->_next();
121         towards = node_towards ? &node_towards->_front : 0;
122         towards_second = node_towards ? &node_towards->_back : 0;
123     }
125     if (Geom::are_near(new_pos, _parent->position())) {
126         // The handle becomes degenerate. If the segment between it and the node
127         // in its direction becomes linear and there are smooth nodes
128         // at its ends, make their handles colinear with the segment
129         if (towards && towards->isDegenerate()) {
130             if (node_towards->type() == NODE_SMOOTH) {
131                 towards_second->setDirection(*_parent, *node_towards);
132             }
133             if (_parent->type() == NODE_SMOOTH) {
134                 other->setDirection(*node_towards, *_parent);
135             }
136         }
137         setPosition(new_pos);
138         return;
139     }
141     if (_parent->type() == NODE_SMOOTH && Node::_is_line_segment(_parent, node_away)) {
142         // restrict movement to the line joining the nodes
143         Geom::Point direction = _parent->position() - node_away->position();
144         Geom::Point delta = new_pos - _parent->position();
145         // project the relative position on the direction line
146         Geom::Point new_delta = (Geom::dot(delta, direction)
147             / Geom::L2sq(direction)) * direction;
148         setRelativePos(new_delta);
149         return;
150     }
152     switch (_parent->type()) {
153     case NODE_AUTO:
154         _parent->setType(NODE_SMOOTH, false);
155         // fall through - auto nodes degrade into smooth nodes
156     case NODE_SMOOTH: {
157         /* for smooth nodes, we need to rotate the other handle so that it's colinear
158          * with the dragged one while conserving length. */
159         other->setDirection(new_pos, *_parent);
160         } break;
161     case NODE_SYMMETRIC:
162         // for symmetric nodes, place the other handle on the opposite side
163         other->setRelativePos(-(new_pos - _parent->position()));
164         break;
165     default: break;
166     }
168     setPosition(new_pos);
171 void Handle::setPosition(Geom::Point const &p)
173     ControlPoint::setPosition(p);
174     sp_ctrlline_set_coords(SP_CTRLLINE(_handle_line), _parent->position(), position());
176     // update degeneration info and visibility
177     if (Geom::are_near(position(), _parent->position()))
178         _degenerate = true;
179     else _degenerate = false;
180     if (_parent->_handles_shown && _parent->visible() && !_degenerate) {
181         setVisible(true);
182     } else {
183         setVisible(false);
184     }
185     // If both handles become degenerate, convert to parent cusp node
186     if (_parent->isDegenerate()) {
187         _parent->setType(NODE_CUSP, false);
188     }
191 void Handle::setLength(double len)
193     if (isDegenerate()) return;
194     Geom::Point dir = Geom::unit_vector(relativePos());
195     setRelativePos(dir * len);
198 void Handle::retract()
200     setPosition(_parent->position());
203 void Handle::setDirection(Geom::Point const &from, Geom::Point const &to)
205     setDirection(to - from);
208 void Handle::setDirection(Geom::Point const &dir)
210     Geom::Point unitdir = Geom::unit_vector(dir);
211     setRelativePos(unitdir * length());
214 char const *Handle::handle_type_to_localized_string(NodeType type)
216     switch(type) {
217     case NODE_CUSP: return _("Cusp node handle");
218     case NODE_SMOOTH: return _("Smooth node handle");
219     case NODE_SYMMETRIC: return _("Symmetric node handle");
220     case NODE_AUTO: return _("Auto-smooth node handle");
221     default: return "";
222     }
225 void Handle::_grabbedHandler()
227     _saved_length = _drag_out ? 0 : length();
230 void Handle::_draggedHandler(Geom::Point &new_pos, GdkEventMotion *event)
232     Geom::Point parent_pos = _parent->position();
233     // with Alt, preserve length
234     if (held_alt(*event)) {
235         new_pos = parent_pos + Geom::unit_vector(new_pos - parent_pos) * _saved_length;
236     }
237     // with Ctrl, constrain to M_PI/rotationsnapsperpi increments.
238     if (held_control(*event)) {
239         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
240         int snaps = 2 * prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
241         Geom::Point origin = _last_drag_origin();
242         Geom::Point rel_origin = origin - parent_pos;
243         new_pos = parent_pos + Geom::constrain_angle(Geom::Point(0,0), new_pos - parent_pos, snaps,
244             _drag_out ? Geom::Point(1,0) : Geom::unit_vector(rel_origin));
245     }
246     signal_update.emit();
249 void Handle::_ungrabbedHandler()
251     // hide the handle if it's less than dragtolerance away from the node
252     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
253     int drag_tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
254     
255     Geom::Point dist = _desktop->d2w(_parent->position()) - _desktop->d2w(position());
256     if (dist.length() <= drag_tolerance) {
257         move(_parent->position());
258     }
259     _drag_out = false;
262 static double snap_increment_degrees() {
263     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
264     int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
265     return 180.0 / snaps;
268 Glib::ustring Handle::_getTip(unsigned state)
270     if (state_held_alt(state)) {
271         if (state_held_control(state)) {
272             return format_tip(C_("Path handle tip",
273                 "<b>Ctrl+Alt</b>: preserve length and snap rotation angle to %f° increments"),
274                 snap_increment_degrees());
275         } else {
276             return C_("Path handle tip",
277                 "<b>Alt:</b> preserve handle length while dragging");
278         }
279     } else {
280         if (state_held_control(state)) {
281             return format_tip(C_("Path handle tip",
282                 "<b>Ctrl:</b> snap rotation angle to %f° increments, click to retract"),
283                 snap_increment_degrees());
284         }
285     }
286     switch (_parent->type()) {
287     case NODE_AUTO:
288         return C_("Path handle tip",
289             "<b>Auto node handle:</b> drag to convert to smooth node");
290     default:
291         return format_tip(C_("Path handle tip", "<b>%s:</b> drag to shape the curve"),
292             handle_type_to_localized_string(_parent->type()));
293     }
296 Glib::ustring Handle::_getDragTip(GdkEventMotion *event)
298     Geom::Point dist = position() - _last_drag_origin();
299     // report angle in mathematical convention
300     double angle = Geom::angle_between(Geom::Point(-1,0), position() - _parent->position());
301     angle += M_PI; // angle is (-M_PI...M_PI] - offset by +pi and scale to 0...360
302     angle *= 360.0 / (2 * M_PI);
303     GString *x = SP_PX_TO_METRIC_STRING(dist[Geom::X], _desktop->namedview->getDefaultMetric());
304     GString *y = SP_PX_TO_METRIC_STRING(dist[Geom::Y], _desktop->namedview->getDefaultMetric());
305     GString *len = SP_PX_TO_METRIC_STRING(length(), _desktop->namedview->getDefaultMetric());
306     Glib::ustring ret = format_tip(C_("Path handle tip",
307         "Move by %s, %s; angle %.2f°, length %s"), x->str, y->str, angle, len->str);
308     g_string_free(x, TRUE);
309     g_string_free(y, TRUE);
310     g_string_free(len, TRUE);
311     return ret;
314 /**
315  * @class Node
316  * Represents a curve endpoint in an editable path.
317  */
319 Node::Node(NodeSharedData const &data, Geom::Point const &initial_pos)
320     : SelectableControlPoint(data.desktop, initial_pos, Gtk::ANCHOR_CENTER,
321         SP_CTRL_SHAPE_DIAMOND, 9.0, *data.selection, &node_colors, data.node_group)
322     , _front(data, initial_pos, this)
323     , _back(data, initial_pos, this)
324     , _type(NODE_CUSP)
325     , _handles_shown(false)
327     // NOTE we do not set type here, because the handles are still degenerate
328     // connect to own grabbed signal - dragging out handles
329     signal_grabbed.connect(
330         sigc::mem_fun(*this, &Node::_grabbedHandler));
331     signal_dragged.connect( sigc::hide<0>(
332         sigc::mem_fun(*this, &Node::_draggedHandler)));
335 // NOTE: not using iterators won't make this much quicker because iterators can be 100% inlined.
336 Node *Node::_next()
338     NodeList::iterator n = NodeList::get_iterator(this).next();
339     if (n) return n.ptr();
340     return NULL;
342 Node *Node::_prev()
344     NodeList::iterator p = NodeList::get_iterator(this).prev();
345     if (p) return p.ptr();
346     return NULL;
349 void Node::move(Geom::Point const &new_pos)
351     // move handles when the node moves.
352     Geom::Point old_pos = position();
353     Geom::Point delta = new_pos - position();
354     setPosition(new_pos);
355     _front.setPosition(_front.position() + delta);
356     _back.setPosition(_back.position() + delta);
358     // if the node has a smooth handle after a line segment, it should be kept colinear
359     // with the segment
360     _fixNeighbors(old_pos, new_pos);
363 void Node::transform(Geom::Matrix const &m)
365     Geom::Point old_pos = position();
366     setPosition(position() * m);
367     _front.setPosition(_front.position() * m);
368     _back.setPosition(_back.position() * m);
370     /* Affine transforms keep handle invariants for smooth and symmetric nodes,
371      * but smooth nodes at ends of linear segments and auto nodes need special treatment */
372     _fixNeighbors(old_pos, position());
375 Geom::Rect Node::bounds()
377     Geom::Rect b(position(), position());
378     b.expandTo(_front.position());
379     b.expandTo(_back.position());
380     return b;
383 void Node::_fixNeighbors(Geom::Point const &old_pos, Geom::Point const &new_pos)
385     /* This method restores handle invariants for neighboring nodes,
386      * and invariants that are based on positions of those nodes for this one. */
387     
388     /* Fix auto handles */
389     if (_type == NODE_AUTO) _updateAutoHandles();
390     if (old_pos != new_pos) {
391         if (_next() && _next()->_type == NODE_AUTO) _next()->_updateAutoHandles();
392         if (_prev() && _prev()->_type == NODE_AUTO) _prev()->_updateAutoHandles();
393     }
394     
395     /* Fix smooth handles at the ends of linear segments.
396      * Rotate the appropriate handle to be colinear with the segment.
397      * If there is a smooth node at the other end of the segment, rotate it too. */
398     Handle *handle, *other_handle;
399     Node *other;
400     if (_is_line_segment(this, _next())) {
401         handle = &_back;
402         other = _next();
403         other_handle = &_next()->_front;
404     } else if (_is_line_segment(_prev(), this)) {
405         handle = &_front;
406         other = _prev();
407         other_handle = &_prev()->_back;
408     } else return;
410     if (_type == NODE_SMOOTH && !handle->isDegenerate()) {
411         handle->setDirection(other->position(), new_pos);
412         /*Geom::Point handle_delta = handle->position() - position();
413         Geom::Point new_delta = Geom::unit_vector(new_direction) * handle_delta.length();
414         handle->setPosition(position() + new_delta);*/
415     }
416     // also update the handle on the other end of the segment
417     if (other->_type == NODE_SMOOTH && !other_handle->isDegenerate()) {
418         other_handle->setDirection(new_pos, other->position());
419         /*
420         Geom::Point handle_delta2 = other_handle->position() - other->position();
421         Geom::Point new_delta2 = Geom::unit_vector(new_direction) * handle_delta2.length();
422         other_handle->setPosition(other->position() + new_delta2);*/
423     }
426 void Node::_updateAutoHandles()
428     // Recompute the position of automatic handles.
429     if (!_prev() || !_next()) {
430         _front.retract();
431         _back.retract();
432         return;
433     }
434     // TODO describe in detail what the code below does
435     Geom::Point vec_next = _next()->position() - position();
436     Geom::Point vec_prev = _prev()->position() - position();
437     double len_next = vec_next.length(), len_prev = vec_prev.length();
438     if (len_next > 0 && len_prev > 0) {
439         Geom::Point dir = Geom::unit_vector((len_prev / len_next) * vec_next - vec_prev);
440         _back.setRelativePos(-dir * (len_prev / 3));
441         _front.setRelativePos(dir * (len_next / 3));
442     } else {
443         _front.retract();
444         _back.retract();
445     }
448 void Node::showHandles(bool v)
450     _handles_shown = v;
451     if (!_front.isDegenerate()) _front.setVisible(v);
452     if (!_back.isDegenerate()) _back.setVisible(v);
455 /** Sets the node type and optionally restores the invariants associated with the given type.
456  * @param type The type to set
457  * @param update_handles Whether to restore invariants associated with the given type.
458  *                       Passing false is useful e.g. wen initially creating the path,
459  *                       and when making cusp nodes during some node algorithms.
460  *                       Pass true when used in response to an UI node type button.
461  */
462 void Node::setType(NodeType type, bool update_handles)
464     if (type == NODE_PICK_BEST) {
465         pickBestType();
466         updateState(); // The size of the control might have changed
467         return;
468     }
470     // if update_handles is true, adjust handle positions to match the node type
471     // handle degenerate handles appropriately
472     if (update_handles) {
473         switch (type) {
474         case NODE_CUSP:
475             // if the existing type is also NODE_CUSP, retract handles
476             if (_type == NODE_CUSP) {
477                 _front.retract();
478                 _back.retract();
479             }
480             break;
481         case NODE_AUTO:
482             // auto handles make no sense for endnodes
483             if (isEndNode()) return;
484             _updateAutoHandles();
485             break;
486         case NODE_SMOOTH: {
487             // rotate handles to be colinear
488             // for degenerate nodes set positions like auto handles
489             bool prev_line = _is_line_segment(_prev(), this);
490             bool next_line = _is_line_segment(this, _next());
491             if (isDegenerate()) {
492                 _updateAutoHandles();
493             } else if (_front.isDegenerate()) {
494                 // if the front handle is degenerate and this...next is a line segment,
495                 // make back colinear; otherwise pull out the other handle
496                 // to 1/3 of distance to prev
497                 if (next_line) {
498                     _back.setDirection(*_next(), *this);
499                 } else if (_prev()) {
500                     Geom::Point dir = direction(_back, *this);
501                     _front.setRelativePos((_prev()->position() - position()).length() / 3 * dir);
502                 }
503             } else if (_back.isDegenerate()) {
504                 if (prev_line) {
505                     _front.setDirection(*_prev(), *this);
506                 } else if (_next()) {
507                     Geom::Point dir = direction(_front, *this);
508                     _back.setRelativePos((_next()->position() - position()).length() / 3 * dir);
509                 }
510             } else {
511                 // both handles are extended. make colinear while keeping length
512                 // first make back colinear with the vector front ---> back,
513                 // then make front colinear with back ---> node
514                 // (not back ---> front because back's position was changed in the first call)
515                 _back.setDirection(_front, _back);
516                 _front.setDirection(_back, *this);
517             }
518             } break;
519         case NODE_SYMMETRIC:
520             if (isEndNode()) return; // symmetric handles make no sense for endnodes
521             if (isDegenerate()) {
522                 // similar to auto handles but set the same length for both
523                 Geom::Point vec_next = _next()->position() - position();
524                 Geom::Point vec_prev = _prev()->position() - position();
525                 double len_next = vec_next.length(), len_prev = vec_prev.length();
526                 double len = (len_next + len_prev) / 6; // take 1/3 of average
527                 if (len == 0) return;
528                 
529                 Geom::Point dir = Geom::unit_vector((len_prev / len_next) * vec_next - vec_prev);
530                 _back.setRelativePos(-dir * len);
531                 _front.setRelativePos(dir * len);
532             } else {
533                 // Both handles are extended. Compute average length, use direction from
534                 // back handle to front handle. This also works correctly for degenerates
535                 double len = (_front.length() + _back.length()) / 2;
536                 Geom::Point dir = direction(_back, _front);
537                 _front.setRelativePos(dir * len);
538                 _back.setRelativePos(-dir * len);
539             }
540             break;
541         default: break;
542         }
543     }
544     _type = type;
545     _setShape(_node_type_to_shape(type));
546     updateState();
549 void Node::pickBestType()
551     _type = NODE_CUSP;
552     bool front_degen = _front.isDegenerate();
553     bool back_degen = _back.isDegenerate();
554     bool both_degen = front_degen && back_degen;
555     bool neither_degen = !front_degen && !back_degen;
556     do {
557         // if both handles are degenerate, do nothing
558         if (both_degen) break;
559         // if neither are degenerate, check their respective positions
560         if (neither_degen) {
561             Geom::Point front_delta = _front.position() - position();
562             Geom::Point back_delta = _back.position() - position();
563             // for now do not automatically make nodes symmetric, it can be annoying
564             /*if (Geom::are_near(front_delta, -back_delta)) {
565                 _type = NODE_SYMMETRIC;
566                 break;
567             }*/
568             if (Geom::are_near(Geom::unit_vector(front_delta),
569                 Geom::unit_vector(-back_delta)))
570             {
571                 _type = NODE_SMOOTH;
572                 break;
573             }
574         }
575         // check whether the handle aligns with the previous line segment.
576         // we know that if front is degenerate, back isn't, because
577         // both_degen was false
578         if (front_degen && _next() && _next()->_back.isDegenerate()) {
579             Geom::Point segment_delta = Geom::unit_vector(_next()->position() - position());
580             Geom::Point handle_delta = Geom::unit_vector(_back.position() - position());
581             if (Geom::are_near(segment_delta, -handle_delta)) {
582                 _type = NODE_SMOOTH;
583                 break;
584             }
585         } else if (back_degen && _prev() && _prev()->_front.isDegenerate()) {
586             Geom::Point segment_delta = Geom::unit_vector(_prev()->position() - position());
587             Geom::Point handle_delta = Geom::unit_vector(_front.position() - position());
588             if (Geom::are_near(segment_delta, -handle_delta)) {
589                 _type = NODE_SMOOTH;
590                 break;
591             }
592         }
593     } while (false);
594     _setShape(_node_type_to_shape(_type));
595     updateState();
598 bool Node::isEndNode()
600     return !_prev() || !_next();
603 /** Move the node to the bottom of its canvas group. Useful for node break, to ensure that
604  * the selected nodes are above the unselected ones. */
605 void Node::sink()
607     sp_canvas_item_move_to_z(_canvas_item, 0);
610 NodeType Node::parse_nodetype(char x)
612     switch (x) {
613     case 'a': return NODE_AUTO;
614     case 'c': return NODE_CUSP;
615     case 's': return NODE_SMOOTH;
616     case 'z': return NODE_SYMMETRIC;
617     default: return NODE_PICK_BEST;
618     }
621 void Node::_setState(State state)
623     // change node size to match type and selection state
624     switch (_type) {
625     case NODE_AUTO:
626     case NODE_CUSP:
627         if (selected()) _setSize(11);
628         else _setSize(9);
629         break;
630     default:
631         if(selected()) _setSize(9);
632         else _setSize(7);
633         break;
634     }
635     SelectableControlPoint::_setState(state);
638 bool Node::_grabbedHandler(GdkEventMotion *event)
640     // dragging out handles
641     if (!held_shift(*event)) return false;
643     Handle *h;
644     Geom::Point evp = event_point(*event);
645     Geom::Point rel_evp = evp - _last_click_event_point();
647     // this should work even if dragtolerance is zero and evp coincides with node position
648     double angle_next = HUGE_VAL;
649     double angle_prev = HUGE_VAL;
650     bool has_degenerate = false;
651     // determine which handle to drag out based on degeneration and the direction of drag
652     if (_front.isDegenerate() && _next()) {
653         Geom::Point next_relpos = _desktop->d2w(_next()->position())
654             - _desktop->d2w(position());
655         angle_next = fabs(Geom::angle_between(rel_evp, next_relpos));
656         has_degenerate = true;
657     }
658     if (_back.isDegenerate() && _prev()) {
659         Geom::Point prev_relpos = _desktop->d2w(_prev()->position())
660             - _desktop->d2w(position());
661         angle_prev = fabs(Geom::angle_between(rel_evp, prev_relpos));
662         has_degenerate = true;
663     }
664     if (!has_degenerate) return false;
665     h = angle_next < angle_prev ? &_front : &_back;
667     h->setPosition(_desktop->w2d(evp));
668     h->setVisible(true);
669     h->transferGrab(this, event);
670     Handle::_drag_out = true;
671     return true;
674 void Node::_draggedHandler(Geom::Point &new_pos, GdkEventMotion *event)
676     if (!held_control(*event)) return;
677     if (held_alt(*event)) {
678         // with Ctrl+Alt, constrain to handle lines
679         // project the new position onto a handle line that is closer
680         Geom::Point origin = _last_drag_origin();
681         Geom::Line line_front(origin, origin + _front.relativePos());
682         Geom::Line line_back(origin, origin + _back.relativePos());
683         double dist_front, dist_back;
684         dist_front = Geom::distance(new_pos, line_front);
685         dist_back = Geom::distance(new_pos, line_back);
686         if (dist_front < dist_back) {
687             new_pos = Geom::projection(new_pos, line_front);
688         } else {
689             new_pos = Geom::projection(new_pos, line_back);
690         }
691     } else {
692         // with Ctrl, constrain to axes
693         // TODO this probably has to be separated into an AxisConstrainablePoint class
694         // TODO maybe add diagonals when the distance from origin is large enough?
695         Geom::Point origin = _last_drag_origin();
696         Geom::Point delta = new_pos - origin;
697         Geom::Dim2 d = (fabs(delta[Geom::X]) < fabs(delta[Geom::Y])) ? Geom::X : Geom::Y;
698         new_pos[d] = origin[d];
699     }
702 Glib::ustring Node::_getTip(unsigned state)
704     if (state_held_shift(state)) {
705         if ((_next() && _front.isDegenerate()) || (_prev() && _back.isDegenerate())) {
706             if (state_held_control(state)) {
707                 return format_tip(C_("Path node tip",
708                     "<b>Shift+Ctrl:</b> drag out a handle and snap its angle "
709                     "to %f° increments"), snap_increment_degrees());
710             }
711             return C_("Path node tip",
712                 "<b>Shift:</b> drag out a handle, click to toggle selection");
713         }
714         return C_("Path node statusbar tip", "<b>Shift:</b> click to toggle selection");
715     }
717     if (state_held_control(state)) {
718         if (state_held_alt(state)) {
719             return C_("Path node tip", "<b>Ctrl+Alt:</b> move along handle lines");
720         }
721         return C_("Path node tip",
722             "<b>Ctrl:</b> move along axes, click to change node type");
723     }
724     
725     // assemble tip from node name
726     char const *nodetype = node_type_to_localized_string(_type);
727     return format_tip(C_("Path node tip",
728         "<b>%s:</b> drag to shape the path, click to select this node"), nodetype);
731 Glib::ustring Node::_getDragTip(GdkEventMotion *event)
733     Geom::Point dist = position() - _last_drag_origin();
734     GString *x = SP_PX_TO_METRIC_STRING(dist[Geom::X], _desktop->namedview->getDefaultMetric());
735     GString *y = SP_PX_TO_METRIC_STRING(dist[Geom::Y], _desktop->namedview->getDefaultMetric());
736     Glib::ustring ret = format_tip(C_("Path node statusbar tip", "Move by %s, %s"),
737         x->str, y->str);
738     g_string_free(x, TRUE);
739     g_string_free(y, TRUE);
740     return ret;
743 char const *Node::node_type_to_localized_string(NodeType type)
745     switch (type) {
746     case NODE_CUSP: return _("Cusp node");
747     case NODE_SMOOTH: return _("Smooth node");
748     case NODE_SYMMETRIC: return _("Symmetric node");
749     case NODE_AUTO: return _("Auto-smooth node");
750     default: return "";
751     }
754 /** Determine whether two nodes are joined by a linear segment. */
755 bool Node::_is_line_segment(Node *first, Node *second)
757     if (!first || !second) return false;
758     if (first->_next() == second)
759         return first->_front.isDegenerate() && second->_back.isDegenerate();
760     if (second->_next() == first)
761         return second->_front.isDegenerate() && first->_back.isDegenerate();
762     return false;
765 SPCtrlShapeType Node::_node_type_to_shape(NodeType type)
767     switch(type) {
768     case NODE_CUSP: return SP_CTRL_SHAPE_DIAMOND;
769     case NODE_SMOOTH: return SP_CTRL_SHAPE_SQUARE;
770     case NODE_AUTO: return SP_CTRL_SHAPE_CIRCLE;
771     case NODE_SYMMETRIC: return SP_CTRL_SHAPE_SQUARE;
772     default: return SP_CTRL_SHAPE_DIAMOND;
773     }
777 /**
778  * @class NodeList
779  * @brief An editable list of nodes representing a subpath.
780  *
781  * It can optionally be cyclic to represent a closed path.
782  * The list has iterators that act like plain node iterators, but can also be used
783  * to obtain shared pointers to nodes.
784  *
785  * @todo Manage geometric representation to improve speed
786  */
788 NodeList::NodeList(SubpathList &splist)
789     : _list(splist)
790     , _closed(false)
792     this->list = this;
793     this->next = this;
794     this->prev = this;
797 NodeList::~NodeList()
799     clear();
802 bool NodeList::empty()
804     return next == this;
807 NodeList::size_type NodeList::size()
809     size_type sz = 0;
810     for (ListNode *ln = next; ln != this; ln = ln->next) ++sz;
811     return sz;
814 bool NodeList::closed()
816     return _closed;
819 /** A subpath is degenerate if it has no segments - either one node in an open path
820  * or no nodes in a closed path */
821 bool NodeList::degenerate()
823     return closed() ? empty() : ++begin() == end();
826 NodeList::iterator NodeList::before(double t, double *fracpart)
828     double intpart;
829     *fracpart = std::modf(t, &intpart);
830     int index = intpart;
832     iterator ret = begin();
833     std::advance(ret, index);
834     return ret;
837 // insert a node before i
838 NodeList::iterator NodeList::insert(iterator i, Node *x)
840     ListNode *ins = i._node;
841     x->next = ins;
842     x->prev = ins->prev;
843     ins->prev->next = x;
844     ins->prev = x;
845     x->ListNode::list = this;
846     _list.signal_insert_node.emit(x);
847     return iterator(x);
850 void NodeList::splice(iterator pos, NodeList &list)
852     splice(pos, list, list.begin(), list.end());
855 void NodeList::splice(iterator pos, NodeList &list, iterator i)
857     NodeList::iterator j = i;
858     ++j;
859     splice(pos, list, i, j);
862 void NodeList::splice(iterator pos, NodeList &list, iterator first, iterator last)
864     ListNode *ins_beg = first._node, *ins_end = last._node, *at = pos._node;
865     for (ListNode *ln = ins_beg; ln != ins_end; ln = ln->next) {
866         list._list.signal_remove_node.emit(static_cast<Node*>(ln));
867         ln->list = this;
868         _list.signal_insert_node.emit(static_cast<Node*>(ln));
869     }
870     ins_beg->prev->next = ins_end;
871     ins_end->prev->next = at;
872     at->prev->next = ins_beg;
874     ListNode *atprev = at->prev;
875     at->prev = ins_end->prev;
876     ins_end->prev = ins_beg->prev;
877     ins_beg->prev = atprev;
880 void NodeList::shift(int n)
882     // 1. make the list perfectly cyclic
883     next->prev = prev;
884     prev->next = next;
885     // 2. find new begin
886     ListNode *new_begin = next;
887     if (n > 0) {
888         for (; n > 0; --n) new_begin = new_begin->next;
889     } else {
890         for (; n < 0; ++n) new_begin = new_begin->prev;
891     }
892     // 3. relink begin to list
893     next = new_begin;
894     prev = new_begin->prev;
895     new_begin->prev->next = this;
896     new_begin->prev = this;
899 void NodeList::reverse()
901     for (ListNode *ln = next; ln != this; ln = ln->prev) {
902         std::swap(ln->next, ln->prev);
903         Node *node = static_cast<Node*>(ln);
904         Geom::Point save_pos = node->front()->position();
905         node->front()->setPosition(node->back()->position());
906         node->back()->setPosition(save_pos);
907     }
908     std::swap(next, prev);
911 void NodeList::clear()
913     for (iterator i = begin(); i != end();) erase (i++);
916 NodeList::iterator NodeList::erase(iterator i)
918     // some acrobatics are required to ensure that the node is valid when deleted;
919     // otherwise the code that updates handle visibility will break
920     Node *rm = static_cast<Node*>(i._node);
921     ListNode *rmnext = rm->next, *rmprev = rm->prev;
922     ++i;
923     _list.signal_remove_node.emit(rm);
924     delete rm;
925     rmprev->next = rmnext;
926     rmnext->prev = rmprev;
927     return i;
930 void NodeList::kill()
932     for (SubpathList::iterator i = _list.begin(); i != _list.end(); ++i) {
933         if (i->get() == this) {
934             _list.erase(i);
935             return;
936         }
937     }
940 NodeList &NodeList::get(Node *n) {
941     return *(n->list());
943 NodeList &NodeList::get(iterator const &i) {
944     return *(i._node->list);
948 /**
949  * @class SubpathList
950  * @brief Editable path composed of one or more subpaths
951  */
953 } // namespace UI
954 } // namespace Inkscape
956 /*
957   Local Variables:
958   mode:c++
959   c-file-style:"stroustrup"
960   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
961   indent-tabs-mode:nil
962   fill-column:99
963   End:
964 */
965 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :