Code

Node tool: fix handle retraction with non-cusp nodes
[inkscape.git] / src / ui / tool / curve-drag-point.cpp
1 /** @file
2  * Control point that is dragged during path drag
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 <glib/gi18n.h>
12 #include <2geom/bezier-curve.h>
13 #include "desktop.h"
14 #include "ui/tool/control-point-selection.h"
15 #include "ui/tool/curve-drag-point.h"
16 #include "ui/tool/event-utils.h"
17 #include "ui/tool/multi-path-manipulator.h"
18 #include "ui/tool/path-manipulator.h"
19 #include "ui/tool/node.h"
21 namespace Inkscape {
22 namespace UI {
24 /**
25  * @class CurveDragPoint
26  * An invisible point used to drag curves. This point is used by PathManipulator to allow editing
27  * of path segments by dragging them. It is defined in a separate file so that the node tool
28  * can check if the mouseovered control point is a curve drag point and update the cursor
29  * accordingly, without the need to drag in the full PathManipulator header.
30  */
32 // This point should be invisible to the user - use the invisible_cset from control-point.h
33 // TODO make some methods from path-manipulator.cpp public so that this point doesn't have
34 // to be declared as a friend
36 bool CurveDragPoint::_drags_stroke = false;
38 CurveDragPoint::CurveDragPoint(PathManipulator &pm)
39     : ControlPoint(pm._multi_path_manipulator._path_data.node_data.desktop, Geom::Point(),
40         Gtk::ANCHOR_CENTER, SP_CTRL_SHAPE_CIRCLE, 1.0, &invisible_cset,
41         pm._multi_path_manipulator._path_data.dragpoint_group)
42     , _pm(pm)
43 {
44     setVisible(false);
45 }
47 bool CurveDragPoint::_eventHandler(GdkEvent *event)
48 {
49     // do not process any events when the manipulator is empty
50     if (_pm.empty()) {
51         setVisible(false);
52         return false;
53     }
54     return ControlPoint::_eventHandler(event);
55 }
57 bool CurveDragPoint::grabbed(GdkEventMotion */*event*/)
58 {
59     _pm._selection.hideTransformHandles();
60     NodeList::iterator second = first.next();
62     // move the handles to 1/3 the length of the segment for line segments
63     if (first->front()->isDegenerate() && second->back()->isDegenerate()) {
65         // delta is a vector equal 1/3 of distance from first to second
66         Geom::Point delta = (second->position() - first->position()) / 3.0;
67         first->front()->move(first->front()->position() + delta);
68         second->back()->move(second->back()->position() - delta);
70         _pm.update();
71     }
72     return false;
73 }
75 void CurveDragPoint::dragged(Geom::Point &new_pos, GdkEventMotion *)
76 {
77     NodeList::iterator second = first.next();
78     // Magic Bezier Drag Equations follow!
79     // "weight" describes how the influence of the drag should be distributed
80     // among the handles; 0 = front handle only, 1 = back handle only.
81     double weight, t = _t;
82     if (t <= 1.0 / 6.0) weight = 0;
83     else if (t <= 0.5) weight = (pow((6 * t - 1) / 2.0, 3)) / 2;
84     else if (t <= 5.0 / 6.0) weight = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
85     else weight = 1;
87     Geom::Point delta = new_pos - position();
88     Geom::Point offset0 = ((1-weight)/(3*t*(1-t)*(1-t))) * delta;
89     Geom::Point offset1 = (weight/(3*t*t*(1-t))) * delta;
91     first->front()->move(first->front()->position() + offset0);
92     second->back()->move(second->back()->position() + offset1);
94     _pm.update();
95 }
97 void CurveDragPoint::ungrabbed(GdkEventButton *)
98 {
99     _pm._updateDragPoint(_desktop->d2w(position()));
100     _pm._commit(_("Drag curve"));
101     _pm._selection.restoreTransformHandles();
104 bool CurveDragPoint::clicked(GdkEventButton *event)
106     // This check is probably redundant
107     if (!first || event->button != 1) return false;
108     // the next iterator can be invalid if we click very near the end of path
109     NodeList::iterator second = first.next();
110     if (!second) return false;
112     // insert nodes on Ctrl+Alt+click
113     if (held_control(*event) && held_alt(*event)) {
114         _insertNode(false);
115         return true;
116     }
118     if (held_shift(*event)) {
119         // if both nodes of the segment are selected, deselect;
120         // otherwise add to selection
121         if (first->selected() && second->selected())  {
122             _pm._selection.erase(first.ptr());
123             _pm._selection.erase(second.ptr());
124         } else {
125             _pm._selection.insert(first.ptr());
126             _pm._selection.insert(second.ptr());
127         }
128     } else {
129         // without Shift, take selection
130         _pm._selection.clear();
131         _pm._selection.insert(first.ptr());
132         _pm._selection.insert(second.ptr());
133     }
134     return true;
137 bool CurveDragPoint::doubleclicked(GdkEventButton *event)
139     if (event->button != 1 || !first || !first.next()) return false;
140     _insertNode(true);
141     return true;
144 void CurveDragPoint::_insertNode(bool take_selection)
146     // The purpose of this call is to make way for the just created node.
147     // Otherwise clicks on the new node would only work after the user moves the mouse a bit.
148     // PathManipulator will restore visibility when necessary.
149     setVisible(false);
150     NodeList::iterator inserted = _pm.subdivideSegment(first, _t);
151     if (take_selection) {
152         _pm._selection.clear();
153     }
154     _pm._selection.insert(inserted.ptr());
156     _pm.update();
157     _pm._commit(_("Add node"));
160 Glib::ustring CurveDragPoint::_getTip(unsigned state)
162     if (_pm.empty()) return "";
163     if (!first || !first.next()) return "";
164     bool linear = first->front()->isDegenerate() && first.next()->back()->isDegenerate();
165     if (state_held_shift(state)) {
166         return C_("Path segment tip",
167             "<b>Shift</b>: click to toggle segment selection");
168     }
169     if (state_held_control(state) && state_held_alt(state)) {
170         return C_("Path segment tip",
171             "<b>Ctrl+Alt</b>: click to insert a node");
172     }
173     if (linear) {
174         return C_("Path segment tip",
175             "<b>Linear segment</b>: drag to convert to a Bezier segment, "
176             "doubleclick to insert node, click to select (more: Shift, Ctrl+Alt)");
177     } else {
178         return C_("Path segment tip",
179             "<b>Bezier segment</b>: drag to shape the segment, doubleclick to insert node, "
180             "click to select (more: Shift, Ctrl+Alt)");
181     }
184 } // namespace UI
185 } // namespace Inkscape
187 /*
188   Local Variables:
189   mode:c++
190   c-file-style:"stroustrup"
191   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
192   indent-tabs-mode:nil
193   fill-column:99
194   End:
195 */
196 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :