summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorKrzysztof Kosi??ski <tweenk.pl@gmail.com>2010-02-09 02:20:18 +0000
committerKrzysztof Kosiński <tweenk.pl@gmail.com>2010-02-09 02:20:18 +0000
commit81f88ca0856da56bdf426cd065ff0acd3414567f (patch)
treec909cc475397273059093d0fc8157f26b83350c1 /src
parentTranslations. German translation update by uwesch. (diff)
downloadinkscape-81f88ca0856da56bdf426cd065ff0acd3414567f.tar.gz
inkscape-81f88ca0856da56bdf426cd065ff0acd3414567f.zip
Fix multiple minor problems in the node tool
(bzr r9070)
Diffstat (limited to 'src')
-rw-r--r--src/selection-chemistry.cpp8
-rw-r--r--src/ui/tool/control-point-selection.cpp132
-rw-r--r--src/ui/tool/control-point-selection.h11
-rw-r--r--src/ui/tool/curve-drag-point.cpp15
-rw-r--r--src/ui/tool/curve-drag-point.h3
-rw-r--r--src/ui/tool/node-tool.cpp82
-rw-r--r--src/ui/tool/node-tool.h1
-rw-r--r--src/ui/tool/node.cpp183
-rw-r--r--src/ui/tool/node.h2
-rw-r--r--src/ui/tool/path-manipulator.cpp2
-rw-r--r--src/ui/tool/selector.cpp4
11 files changed, 326 insertions, 117 deletions
diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp
index a5c6ae961..4dfb4597d 100644
--- a/src/selection-chemistry.cpp
+++ b/src/selection-chemistry.cpp
@@ -118,10 +118,12 @@ void SelectionHelper::selectAll(SPDesktop *dt)
{
if (tools_isactive(dt, TOOLS_NODES)) {
InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context);
- nt->_multipath->selectSubpaths();
- } else {
- sp_edit_select_all(dt);
+ if (!nt->_multipath->empty()) {
+ nt->_multipath->selectSubpaths();
+ return;
+ }
}
+ sp_edit_select_all(dt);
}
void SelectionHelper::selectAllInAll(SPDesktop *dt)
diff --git a/src/ui/tool/control-point-selection.cpp b/src/ui/tool/control-point-selection.cpp
index 638318dbf..df27c2a72 100644
--- a/src/ui/tool/control-point-selection.cpp
+++ b/src/ui/tool/control-point-selection.cpp
@@ -8,6 +8,7 @@
* Released under GNU GPL, read the file 'COPYING' for more information
*/
+#include <boost/none.hpp>
#include <2geom/transforms.h>
#include "desktop.h"
#include "preferences.h"
@@ -53,15 +54,13 @@ ControlPointSelection::ControlPointSelection(SPDesktop *d, SPCanvasGroup *th_gro
signal_update.connect( sigc::bind(
sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles),
true));
- signal_point_changed.connect(
- sigc::hide( sigc::hide(
- sigc::bind(
- sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles),
- false))));
+ ControlPoint::signal_mouseover_change.connect(
+ sigc::hide(
+ sigc::mem_fun(*this, &ControlPointSelection::_mouseoverChanged)));
_handles->signal_transform.connect(
sigc::mem_fun(*this, &ControlPointSelection::transform));
_handles->signal_commit.connect(
- sigc::mem_fun(*this, &ControlPointSelection::_commitTransform));
+ sigc::mem_fun(*this, &ControlPointSelection::_commitHandlesTransform));
}
ControlPointSelection::~ControlPointSelection()
@@ -81,8 +80,7 @@ std::pair<ControlPointSelection::iterator, bool> ControlPointSelection::insert(c
found = _points.insert(x).first;
x->updateState();
- _rot_radius.reset();
- signal_point_changed.emit(x, true);
+ _pointChanged(x, true);
return std::pair<iterator, bool>(found, true);
}
@@ -93,8 +91,7 @@ void ControlPointSelection::erase(iterator pos)
SelectableControlPoint *erased = *pos;
_points.erase(pos);
erased->updateState();
- _rot_radius.reset();
- signal_point_changed.emit(erased, false);
+ _pointChanged(erased, false);
}
ControlPointSelection::size_type ControlPointSelection::erase(const key_type &k)
{
@@ -175,8 +172,10 @@ void ControlPointSelection::transform(Geom::Matrix const &m)
SelectableControlPoint *cur = *i;
cur->transform(m);
}
+ _updateBounds();
// TODO preserving the rotation radius needs some rethinking...
if (_rot_radius) (*_rot_radius) *= m.descrim();
+ if (_mouseover_rot_radius) (*_mouseover_rot_radius) *= m.descrim();
signal_update.emit();
}
@@ -233,28 +232,12 @@ void ControlPointSelection::distribute(Geom::Dim2 d)
* or nothing if the selection is empty */
Geom::OptRect ControlPointSelection::pointwiseBounds()
{
- Geom::OptRect bound;
- for (iterator i = _points.begin(); i != _points.end(); ++i) {
- SelectableControlPoint *cur = (*i);
- Geom::Point p = cur->position();
- if (!bound) {
- bound = Geom::Rect(p, p);
- } else {
- bound->expandTo(p);
- }
- }
- return bound;
+ return _bounds;
}
Geom::OptRect ControlPointSelection::bounds()
{
- Geom::OptRect bound;
- for (iterator i = _points.begin(); i != _points.end(); ++i) {
- SelectableControlPoint *cur = (*i);
- Geom::OptRect r = cur->bounds();
- bound.unionWith(r);
- }
- return bound;
+ return size() == 1 ? (*_points.begin())->bounds() : _bounds;
}
void ControlPointSelection::showTransformHandles(bool v, bool one_node)
@@ -305,6 +288,7 @@ void ControlPointSelection::_pointUngrabbed()
{
_dragging = false;
_grabbed_point = NULL;
+ _updateBounds();
restoreTransformHandles();
signal_commit.emit(COMMIT_MOUSE_MOVE);
}
@@ -320,13 +304,42 @@ bool ControlPointSelection::_pointClicked(SelectableControlPoint *p, GdkEventBut
return false;
}
+void ControlPointSelection::_pointChanged(SelectableControlPoint *p, bool selected)
+{
+ _updateBounds();
+ _updateTransformHandles(false);
+ if (_bounds)
+ _handles->rotationCenter().move(_bounds->midpoint());
+
+ signal_point_changed.emit(p, selected);
+}
+
+void ControlPointSelection::_mouseoverChanged()
+{
+ _mouseover_rot_radius = boost::none;
+}
+
+void ControlPointSelection::_updateBounds()
+{
+ _rot_radius = boost::none;
+ _bounds = Geom::OptRect();
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ SelectableControlPoint *cur = (*i);
+ Geom::Point p = cur->position();
+ if (!_bounds) {
+ _bounds = Geom::Rect(p, p);
+ } else {
+ _bounds->expandTo(p);
+ }
+ }
+}
+
void ControlPointSelection::_updateTransformHandles(bool preserve_center)
{
if (_dragging) return;
if (_handles_visible && size() > 1) {
- Geom::OptRect b = pointwiseBounds();
- _handles->setBounds(*b, preserve_center);
+ _handles->setBounds(*bounds(), preserve_center);
_handles->setVisible(true);
} else if (_one_node_handles && size() == 1) { // only one control point in selection
SelectableControlPoint *p = *begin();
@@ -365,6 +378,20 @@ bool ControlPointSelection::_keyboardMove(GdkEventKey const &event, Geom::Point
return true;
}
+/** @brief Computes the distance to the farthest corner of the bounding box.
+ * Used to determine what it means to "rotate by one pixel". */
+double ControlPointSelection::_rotationRadius(Geom::Point const &rc)
+{
+ if (empty()) return 1.0; // some safe value
+ Geom::Rect b = *bounds();
+ double maxlen = 0;
+ for (unsigned i = 0; i < 4; ++i) {
+ double len = Geom::distance(b.corner(i), rc);
+ if (len > maxlen) maxlen = len;
+ }
+ return maxlen;
+}
+
/** Rotates the selected points in the given direction according to the modifier state
* from the supplied event.
* @param event Key event to take modifier state from
@@ -374,15 +401,25 @@ bool ControlPointSelection::_keyboardRotate(GdkEventKey const &event, int dir)
{
if (empty()) return false;
- Geom::Point rc = _handles->rotationCenter();
- if (!_rot_radius) {
- Geom::Rect b = *(size() == 1 ? bounds() : pointwiseBounds());
- double maxlen = 0;
- for (unsigned i = 0; i < 4; ++i) {
- double len = (b.corner(i) - rc).length();
- if (len > maxlen) maxlen = len;
+ Geom::Point rc;
+
+ // rotate around the mouseovered point, or the selection's rotation center
+ // if nothing is mouseovered
+ double radius;
+ SelectableControlPoint *scp =
+ dynamic_cast<SelectableControlPoint*>(ControlPoint::mouseovered_point);
+ if (scp) {
+ rc = scp->position();
+ if (!_mouseover_rot_radius) {
+ _mouseover_rot_radius = _rotationRadius(rc);
+ }
+ radius = *_mouseover_rot_radius;
+ } else {
+ rc = _handles->rotationCenter();
+ if (!_rot_radius) {
+ _rot_radius = _rotationRadius(rc);
}
- _rot_radius = maxlen;
+ radius = *_rot_radius;
}
double angle;
@@ -390,7 +427,7 @@ bool ControlPointSelection::_keyboardRotate(GdkEventKey const &event, int dir)
// Rotate by "one pixel". We interpret this as rotating by an angle that causes
// the topmost point of a circle circumscribed about the selection's bounding box
// to move on an arc 1 screen pixel long.
- angle = atan2(1.0 / _desktop->current_zoom(), *_rot_radius) * dir;
+ angle = atan2(1.0 / _desktop->current_zoom(), radius) * dir;
} else {
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
@@ -410,11 +447,17 @@ bool ControlPointSelection::_keyboardScale(GdkEventKey const &event, int dir)
{
if (empty()) return false;
- // TODO should the saved rotation center or the current center be used?
- Geom::Rect bound = (size() == 1 ? *bounds() : *pointwiseBounds());
- double maxext = bound.maxExtent();
+ double maxext = bounds()->maxExtent();
if (Geom::are_near(maxext, 0)) return false;
- Geom::Point center = _handles->rotationCenter().position();
+
+ Geom::Point center;
+ SelectableControlPoint *scp =
+ dynamic_cast<SelectableControlPoint*>(ControlPoint::mouseovered_point);
+ if (scp) {
+ center = scp->position();
+ } else {
+ center = _handles->rotationCenter().position();
+ }
double length_change;
if (held_alt(event)) {
@@ -455,8 +498,9 @@ bool ControlPointSelection::_keyboardFlip(Geom::Dim2 d)
return true;
}
-void ControlPointSelection::_commitTransform(CommitEvent ce)
+void ControlPointSelection::_commitHandlesTransform(CommitEvent ce)
{
+ _updateBounds();
_updateTransformHandles(true);
signal_commit.emit(ce);
}
diff --git a/src/ui/tool/control-point-selection.h b/src/ui/tool/control-point-selection.h
index 93fba56f5..48c25c285 100644
--- a/src/ui/tool/control-point-selection.h
+++ b/src/ui/tool/control-point-selection.h
@@ -19,6 +19,7 @@
#include <sigc++/sigc++.h>
#include <2geom/forward.h>
#include <2geom/point.h>
+#include <2geom/rect.h>
#include "display/display-forward.h"
#include "util/accumulators.h"
#include "util/hash.h"
@@ -96,6 +97,7 @@ public:
Geom::OptRect pointwiseBounds();
Geom::OptRect bounds();
+ bool transformHandlesEnabled() { return _handles_visible; }
void showTransformHandles(bool v, bool one_node);
// the two methods below do not modify the state; they are for use in manipulators
// that need to temporarily hide the handles, for example when moving a node
@@ -114,17 +116,24 @@ private:
void _pointDragged(Geom::Point const &, Geom::Point &, GdkEventMotion *);
void _pointUngrabbed();
bool _pointClicked(SelectableControlPoint *, GdkEventButton *);
+ void _pointChanged(SelectableControlPoint *, bool);
+ void _mouseoverChanged();
void _updateTransformHandles(bool preserve_center);
+ void _updateBounds();
bool _keyboardMove(GdkEventKey const &, Geom::Point const &);
bool _keyboardRotate(GdkEventKey const &, int);
bool _keyboardScale(GdkEventKey const &, int);
bool _keyboardFlip(Geom::Dim2);
void _keyboardTransform(Geom::Matrix const &);
- void _commitTransform(CommitEvent ce);
+ void _commitHandlesTransform(CommitEvent ce);
+ double _rotationRadius(Geom::Point const &);
+
set_type _points;
set_type _all_points;
boost::optional<double> _rot_radius;
+ boost::optional<double> _mouseover_rot_radius;
+ Geom::OptRect _bounds;
TransformHandleSet *_handles;
SelectableControlPoint *_grabbed_point;
unsigned _dragging : 1;
diff --git a/src/ui/tool/curve-drag-point.cpp b/src/ui/tool/curve-drag-point.cpp
index 88cb72ed5..e761daf20 100644
--- a/src/ui/tool/curve-drag-point.cpp
+++ b/src/ui/tool/curve-drag-point.cpp
@@ -44,6 +44,16 @@ CurveDragPoint::CurveDragPoint(PathManipulator &pm)
setVisible(false);
}
+bool CurveDragPoint::_eventHandler(GdkEvent *event)
+{
+ // do not process any events when the manipulator is empty
+ if (_pm.empty()) {
+ setVisible(false);
+ return false;
+ }
+ return ControlPoint::_eventHandler(event);
+}
+
bool CurveDragPoint::grabbed(GdkEventMotion */*event*/)
{
_pm._selection.hideTransformHandles();
@@ -149,6 +159,7 @@ void CurveDragPoint::_insertNode(bool take_selection)
Glib::ustring CurveDragPoint::_getTip(unsigned state)
{
+ if (_pm.empty()) return "";
if (!first || !first.next()) return "";
bool linear = first->front()->isDegenerate() && first.next()->back()->isDegenerate();
if (state_held_shift(state)) {
@@ -162,11 +173,11 @@ Glib::ustring CurveDragPoint::_getTip(unsigned state)
if (linear) {
return C_("Path segment tip",
"<b>Linear segment:</b> drag to convert to a Bezier segment, "
- "doubleclick to insert node, click to select");
+ "doubleclick to insert node, click to select (more: Shift, Ctrl+Alt)");
} else {
return C_("Path segment tip",
"<b>Bezier segment:</b> drag to shape the segment, doubleclick to insert node, "
- "click to select");
+ "click to select (more: Shift, Ctrl+Alt)");
}
}
diff --git a/src/ui/tool/curve-drag-point.h b/src/ui/tool/curve-drag-point.h
index 147a91837..288ae6a8e 100644
--- a/src/ui/tool/curve-drag-point.h
+++ b/src/ui/tool/curve-drag-point.h
@@ -27,15 +27,16 @@ public:
void setSize(double sz) { _setSize(sz); }
void setTimeValue(double t) { _t = t; }
void setIterator(NodeList::iterator i) { first = i; }
+ virtual bool _eventHandler(GdkEvent *event);
protected:
virtual Glib::ustring _getTip(unsigned state);
-private:
virtual void dragged(Geom::Point &, GdkEventMotion *);
virtual bool grabbed(GdkEventMotion *);
virtual void ungrabbed(GdkEventButton *);
virtual bool clicked(GdkEventButton *);
virtual bool doubleclicked(GdkEventButton *);
+private:
void _insertNode(bool take_selection);
double _t;
PathManipulator &_pm;
diff --git a/src/ui/tool/node-tool.cpp b/src/ui/tool/node-tool.cpp
index 0c4599e52..663504b52 100644
--- a/src/ui/tool/node-tool.cpp
+++ b/src/ui/tool/node-tool.cpp
@@ -280,7 +280,9 @@ void ink_node_tool_setup(SPEventContext *ec)
nt->single_node_transform_handles = false;
nt->flash_tempitem = NULL;
nt->flashed_item = NULL;
- // TODO remove this!
+ nt->_last_over = NULL;
+ // TODO long term, fold ShapeEditor into MultiPathManipulator and rename MPM
+ // to something better
nt->shape_editor = new ShapeEditor(nt->desktop);
// read prefs before adding items to selection to prevent momentarily showing the outline
@@ -442,11 +444,17 @@ gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event)
switch (event->type)
{
- case GDK_MOTION_NOTIFY:
- // create outline
- if (prefs->getBool("/tools/nodes/pathflash_enabled")) {
- SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button),
+ case GDK_MOTION_NOTIFY: {
+ combine_motion_events(desktop->canvas, event->motion, 0);
+ SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button),
FALSE, TRUE);
+ if (over_item != nt->_last_over) {
+ nt->_last_over = over_item;
+ ink_node_tool_update_tip(nt, event);
+ }
+
+ // create pathflash outline
+ if (prefs->getBool("/tools/nodes/pathflash_enabled")) {
if (over_item == nt->flashed_item) break;
if (!prefs->getBool("/tools/nodes/pathflash_selected") && selection->includes(over_item)) break;
if (nt->flash_tempitem) {
@@ -468,7 +476,7 @@ gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event)
prefs->getInt("/tools/nodes/pathflash_timeout", 500));
c->unref();
}
- return true;
+ } return true;
case GDK_KEY_PRESS:
switch (get_group0_keyval(&event->key))
{
@@ -481,14 +489,9 @@ gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event)
ink_node_tool_update_tip(nt, event);
return TRUE;
case GDK_a:
- if (held_control(event->key)) {
- if (held_alt(event->key)) {
- nt->_selected_nodes->selectAll();
- } else {
- // select all nodes in subpaths that have something selected
- // if nothing is selected, select everything
- nt->_multipath->selectSubpaths();
- }
+ if (held_control(event->key) && held_alt(event->key)) {
+ nt->_selected_nodes->selectAll();
+ // Ctrl+A is handled in selection-chemistry.cpp via verb
ink_node_tool_update_tip(nt, event);
return TRUE;
}
@@ -517,26 +520,49 @@ void ink_node_tool_update_tip(InkNodeTool *nt, GdkEvent *event)
unsigned new_state = state_after_event(event);
if (new_state == event->key.state) return;
if (state_held_shift(new_state)) {
- nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE,
- C_("Node tool tip", "<b>Shift:</b> drag to add nodes to the selection, "
- "click to toggle object selection"));
+ if (nt->_last_over) {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE,
+ C_("Node tool tip", "<b>Shift:</b> drag to add nodes to the selection, "
+ "click to toggle object selection"));
+ } else {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE,
+ C_("Node tool tip", "<b>Shift:</b> drag to add nodes to the selection"));
+ }
return;
}
}
unsigned sz = nt->_selected_nodes->size();
+ unsigned total = nt->_selected_nodes->allPoints().size();
if (sz != 0) {
- char *dyntip = g_strdup_printf(C_("Node tool tip",
- "Selected <b>%d nodes</b>. Drag to select nodes, click to select a single object "
- "or unselect all objects"), sz);
- nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, dyntip);
- g_free(dyntip);
- } else if (nt->_multipath->empty()) {
- nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE,
- C_("Node tool tip", "Drag or click to select objects to edit"));
+ if (nt->_last_over) {
+ char *dyntip = g_strdup_printf(C_("Node tool tip",
+ "<b>%u of %u nodes</b> selected. "
+ "Drag to select nodes, click to edit only this object (more: Shift)"), sz, total);
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, dyntip);
+ g_free(dyntip);
+ } else {
+ char *dyntip = g_strdup_printf(C_("Node tool tip",
+ "<b>%u of %u nodes</b> selected. "
+ "Drag to select nodes, click clear the selection"), sz, total);
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, dyntip);
+ g_free(dyntip);
+ }
+ } else if (!nt->_multipath->empty()) {
+ if (nt->_last_over) {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip",
+ "Drag to select nodes, click to edit only this object"));
+ } else {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip",
+ "Drag to select nodes, click to clear the selection"));
+ }
} else {
- nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE,
- C_("Node tool tip", "Drag to select nodes, click to select an object "
- "or clear the selection"));
+ if (nt->_last_over) {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip",
+ "Drag to select objects to edit, click to edit this object (more: Shift)"));
+ } else {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip",
+ "Drag to select objects to edit"));
+ }
}
}
diff --git a/src/ui/tool/node-tool.h b/src/ui/tool/node-tool.h
index 130e16198..baac642ac 100644
--- a/src/ui/tool/node-tool.h
+++ b/src/ui/tool/node-tool.h
@@ -55,6 +55,7 @@ struct InkNodeTool : public SPEventContext
SelectorPtr _selector;
PathSharedDataPtr _path_data;
SPCanvasGroup *_transform_handle_group;
+ SPItem *_last_over;
unsigned cursor_drag : 1;
unsigned show_outline : 1;
diff --git a/src/ui/tool/node.cpp b/src/ui/tool/node.cpp
index 9f1e25877..09ca99c6e 100644
--- a/src/ui/tool/node.cpp
+++ b/src/ui/tool/node.cpp
@@ -77,6 +77,7 @@ static Geom::Point direction(Geom::Point const &first, Geom::Point const &second
* Keeping the invariant on node moves is left to the %Node class.
*/
+Geom::Point Handle::_saved_other_pos(0, 0);
double Handle::_saved_length = 0.0;
bool Handle::_drag_out = false;
@@ -224,6 +225,7 @@ char const *Handle::handle_type_to_localized_string(NodeType type)
bool Handle::grabbed(GdkEventMotion *)
{
+ _saved_other_pos = other().position();
_saved_length = _drag_out ? 0 : length();
_pm()._handleGrabbed();
return false;
@@ -232,23 +234,45 @@ bool Handle::grabbed(GdkEventMotion *)
void Handle::dragged(Geom::Point &new_pos, GdkEventMotion *event)
{
Geom::Point parent_pos = _parent->position();
+ Geom::Point origin = _last_drag_origin();
// with Alt, preserve length
if (held_alt(*event)) {
new_pos = parent_pos + Geom::unit_vector(new_pos - parent_pos) * _saved_length;
}
- // with Ctrl, constrain to M_PI/rotationsnapsperpi increments.
+ // with Ctrl, constrain to M_PI/rotationsnapsperpi increments from vertical
+ // and the original position.
if (held_control(*event)) {
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
int snaps = 2 * prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
- Geom::Point origin = _last_drag_origin();
- Geom::Point rel_origin = origin - parent_pos;
- new_pos = parent_pos + Geom::constrain_angle(Geom::Point(0,0), new_pos - parent_pos, snaps,
- _drag_out ? Geom::Point(1,0) : Geom::unit_vector(rel_origin));
+
+ // note: if snapping to the original position is only desired in the original
+ // direction of the handle, change 2nd line below to Ray instead of Line
+ Geom::Line original_line(parent_pos, origin);
+ Geom::Point snap_pos = parent_pos + Geom::constrain_angle(
+ Geom::Point(0,0), new_pos - parent_pos, snaps, Geom::Point(1,0));
+ Geom::Point orig_pos = original_line.pointAt(original_line.nearestPoint(new_pos));
+
+ if (Geom::distance(snap_pos, new_pos) < Geom::distance(orig_pos, new_pos)) {
+ new_pos = snap_pos;
+ } else {
+ new_pos = orig_pos;
+ }
+ }
+ // with Shift, if the node is cusp, rotate the other handle as well
+ if (_parent->type() == NODE_CUSP && !_drag_out) {
+ if (held_shift(*event)) {
+ Geom::Point other_relpos = _saved_other_pos - parent_pos;
+ other_relpos *= Geom::Rotate(Geom::angle_between(origin - parent_pos, new_pos - parent_pos));
+ other().setRelativePos(other_relpos);
+ } else {
+ // restore the position
+ other().setPosition(_saved_other_pos);
+ }
}
_pm().update();
}
-void Handle::ungrabbed(GdkEventButton *)
+void Handle::ungrabbed(GdkEventButton *event)
{
// hide the handle if it's less than dragtolerance away from the node
// TODO is this actually desired?
@@ -259,6 +283,12 @@ void Handle::ungrabbed(GdkEventButton *)
if (dist.length() <= drag_tolerance) {
move(_parent->position());
}
+
+ // HACK: If the handle was dragged out, call parent's ungrabbed handler,
+ // so that transform handles reappear
+ if (_drag_out) {
+ _parent->ungrabbed(event);
+ }
_drag_out = false;
_pm()._handleUngrabbed();
@@ -270,6 +300,12 @@ bool Handle::clicked(GdkEventButton *event)
return true;
}
+Handle &Handle::other()
+{
+ if (this == &_parent->_front) return _parent->_back;
+ return _parent->_front;
+}
+
static double snap_increment_degrees() {
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
@@ -278,29 +314,59 @@ static double snap_increment_degrees() {
Glib::ustring Handle::_getTip(unsigned state)
{
+ char const *more;
+ bool can_shift_rotate = _parent->type() == NODE_CUSP && !other().isDegenerate();
+ if (can_shift_rotate) {
+ more = C_("Path handle tip", "more: Ctrl, Alt, Ctrl+Alt, Shift");
+ } else {
+ more = C_("Path handle tip", "more: Ctrl, Alt, Ctrl+Alt");
+ }
if (state_held_alt(state)) {
if (state_held_control(state)) {
- return format_tip(C_("Path handle tip",
- "<b>Ctrl+Alt</b>: preserve length and snap rotation angle to %f° increments"),
- snap_increment_degrees());
+ if (state_held_shift(state) && can_shift_rotate) {
+ return format_tip(C_("Path handle tip",
+ "<b>Shift+Ctrl+Alt</b>: preserve length and snap rotation angle to %f° "
+ "increments while rotating both handles"),
+ snap_increment_degrees());
+ } else {
+ return format_tip(C_("Path handle tip",
+ "<b>Ctrl+Alt</b>: preserve length and snap rotation angle to %f° increments"),
+ snap_increment_degrees());
+ }
} else {
- return C_("Path handle tip",
- "<b>Alt:</b> preserve handle length while dragging");
+ if (state_held_shift(state) && can_shift_rotate) {
+ return C_("Path handle tip",
+ "<b>Shift+Alt:</b> preserve handle length and rotate both handles");
+ } else {
+ return C_("Path handle tip",
+ "<b>Alt:</b> preserve handle length while dragging");
+ }
}
} else {
if (state_held_control(state)) {
- return format_tip(C_("Path handle tip",
- "<b>Ctrl:</b> snap rotation angle to %f° increments, click to retract"),
- snap_increment_degrees());
+ if (state_held_shift(state) && can_shift_rotate) {
+ return format_tip(C_("Path handle tip",
+ "<b>Ctrl:</b> snap rotation angle to %f° increments, click to retract"),
+ snap_increment_degrees());
+ } else {
+ return format_tip(C_("Path handle tip",
+ "<b>Shift+Ctrl:</b> snap rotation angle to %f° increments and rotate both handles"),
+ snap_increment_degrees());
+ }
+ } else if (state_held_shift(state) && can_shift_rotate) {
+ return C_("Path hande tip",
+ "<b>Shift</b>: rotate both handles by the same angle");
}
}
+
switch (_parent->type()) {
case NODE_AUTO:
- return C_("Path handle tip",
- "<b>Auto node handle:</b> drag to convert to smooth node");
+ return format_tip(C_("Path handle tip",
+ "<b>Auto node handle:</b> drag to convert to smooth node (%s)"), more);
default:
- return format_tip(C_("Path handle tip", "<b>%s:</b> drag to shape the curve"),
- handle_type_to_localized_string(_parent->type()));
+ return format_tip(C_("Path handle tip",
+ "<b>%s:</b> drag to shape the segment (%s)"),
+ handle_type_to_localized_string(_parent->type()), more);
}
}
@@ -497,7 +563,16 @@ void Node::setType(NodeType type, bool update_handles)
// for degenerate nodes set positions like auto handles
bool prev_line = _is_line_segment(_prev(), this);
bool next_line = _is_line_segment(this, _next());
- if (isDegenerate()) {
+ if (_type == NODE_SMOOTH) {
+ // for a node that is already smooth and at the end of a linear segment,
+ // drag out the second handle to 1/3 the length of the linear segment
+ if (next_line) {
+ _front.setRelativePos((_prev()->position() - position()) / 3);
+ }
+ if (prev_line) {
+ _back.setRelativePos((_next()->position() - position()) / 3);
+ }
+ } else if (isDegenerate()) {
_updateAutoHandles();
} else if (_front.isDegenerate()) {
// if the front handle is degenerate and this...next is a line segment,
@@ -507,14 +582,14 @@ void Node::setType(NodeType type, bool update_handles)
_back.setDirection(*_next(), *this);
} else if (_prev()) {
Geom::Point dir = direction(_back, *this);
- _front.setRelativePos((_prev()->position() - position()).length() / 3 * dir);
+ _front.setRelativePos(Geom::distance(_prev()->position(), position()) / 3 * dir);
}
} else if (_back.isDegenerate()) {
if (prev_line) {
_front.setDirection(*_prev(), *this);
} else if (_next()) {
Geom::Point dir = direction(_front, *this);
- _back.setRelativePos((_next()->position() - position()).length() / 3 * dir);
+ _back.setRelativePos(Geom::distance(_next()->position(), position()) / 3 * dir);
}
} else {
// both handles are extended. make colinear while keeping length
@@ -875,13 +950,35 @@ void Node::dragged(Geom::Point &new_pos, GdkEventMotion *event)
if (held_alt(*event)) {
// with Ctrl+Alt, constrain to handle lines
// project the new position onto a handle line that is closer
- Inkscape::Snapper::ConstraintLine line_front(origin, _front.relativePos());
- Inkscape::Snapper::ConstraintLine line_back(origin, _back.relativePos());
+ boost::optional<Geom::Point> front_point, back_point;
+ boost::optional<Inkscape::Snapper::ConstraintLine> line_front, line_back;
+ if (_front.isDegenerate()) {
+ if (_is_line_segment(this, _next()))
+ front_point = _next()->position() - origin;
+ } else {
+ front_point = _front.relativePos();
+ }
+ if (_back.isDegenerate()) {
+ if (_is_line_segment(_prev(), this))
+ back_point = _prev()->position() - origin;
+ } else {
+ back_point = _back.relativePos();
+ }
+ if (front_point)
+ line_front = Inkscape::Snapper::ConstraintLine(origin, *front_point);
+ if (back_point)
+ line_back = Inkscape::Snapper::ConstraintLine(origin, *back_point);
- // TODO: combine these two branches by modifying snap.h / snap.cpp
+ // TODO: combine the snap and non-snap branches by modifying snap.h / snap.cpp
if (snap) {
- fp = sm.constrainedSnap(Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_front);
- bp = sm.constrainedSnap(Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_back);
+ if (line_front) {
+ fp = sm.constrainedSnap(Inkscape::SnapCandidatePoint(position(),
+ _snapSourceType()), *line_front);
+ }
+ if (line_back) {
+ bp = sm.constrainedSnap(Inkscape::SnapCandidatePoint(position(),
+ _snapSourceType()), *line_back);
+ }
}
if (fp.getSnapped() || bp.getSnapped()) {
if (fp.isOtherSnapBetter(bp, false)) {
@@ -890,12 +987,19 @@ void Node::dragged(Geom::Point &new_pos, GdkEventMotion *event)
fp.getPoint(new_pos);
}
} else {
- Geom::Point p_front = line_front.projection(new_pos);
- Geom::Point p_back = line_back.projection(new_pos);
- if (Geom::distance(new_pos, p_front) < Geom::distance(new_pos, p_back)) {
- new_pos = p_front;
+ boost::optional<Geom::Point> pos;
+ if (line_front) {
+ pos = line_front->projection(new_pos);
+ }
+ if (line_back) {
+ Geom::Point pos2 = line_back->projection(new_pos);
+ if (!pos || (pos && Geom::distance(new_pos, *pos) > Geom::distance(new_pos, pos2)))
+ pos = pos2;
+ }
+ if (pos) {
+ new_pos = *pos;
} else {
- new_pos = p_back;
+ new_pos = origin;
}
}
} else {
@@ -949,12 +1053,13 @@ Inkscape::SnapTargetType Node::_snapTargetType()
Glib::ustring Node::_getTip(unsigned state)
{
if (state_held_shift(state)) {
- if ((_next() && _front.isDegenerate()) || (_prev() && _back.isDegenerate())) {
- if (state_held_control(state)) {
+ bool can_drag_out = (_next() && _front.isDegenerate()) || (_prev() && _back.isDegenerate());
+ if (can_drag_out) {
+ /*if (state_held_control(state)) {
return format_tip(C_("Path node tip",
"<b>Shift+Ctrl:</b> drag out a handle and snap its angle "
"to %f° increments"), snap_increment_degrees());
- }
+ }*/
return C_("Path node tip",
"<b>Shift:</b> drag out a handle, click to toggle selection");
}
@@ -971,8 +1076,16 @@ Glib::ustring Node::_getTip(unsigned state)
// assemble tip from node name
char const *nodetype = node_type_to_localized_string(_type);
+ if (_selection.transformHandlesEnabled() && selected()) {
+ if (_selection.size() == 1) {
+ return format_tip(C_("Path node tip",
+ "<b>%s:</b> drag to shape the path (more: Shift, Ctrl, Ctrl+Alt)"), nodetype);
+ }
+ return format_tip(C_("Path node tip",
+ "<b>%s:</b> drag to shape the path, click to toggle scale/rotation handles (more: Shift, Ctrl, Ctrl+Alt)"), nodetype);
+ }
return format_tip(C_("Path node tip",
- "<b>%s:</b> drag to shape the path, click to select this node"), nodetype);
+ "<b>%s:</b> drag to shape the path, click to select only this node (more: Shift, Ctrl, Ctrl+Alt)"), nodetype);
}
Glib::ustring Node::_getDragTip(GdkEventMotion */*event*/)
diff --git a/src/ui/tool/node.h b/src/ui/tool/node.h
index 581cc9b6f..c798a1fdb 100644
--- a/src/ui/tool/node.h
+++ b/src/ui/tool/node.h
@@ -96,6 +96,7 @@ public:
void setDirection(Geom::Point const &from, Geom::Point const &to);
void setDirection(Geom::Point const &dir);
Node *parent() { return _parent; }
+ Handle &other();
static char const *handle_type_to_localized_string(NodeType type);
protected:
@@ -116,6 +117,7 @@ private:
SPCanvasItem *_handle_line;
bool _degenerate; // this is used often internally so it makes sense to cache this
+ static Geom::Point _saved_other_pos;
static double _saved_length;
static bool _drag_out;
friend class Node;
diff --git a/src/ui/tool/path-manipulator.cpp b/src/ui/tool/path-manipulator.cpp
index 3a6b15f37..fd21970ee 100644
--- a/src/ui/tool/path-manipulator.cpp
+++ b/src/ui/tool/path-manipulator.cpp
@@ -1331,7 +1331,7 @@ double PathManipulator::_getStrokeTolerance()
* drag tolerance setting. */
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
double ret = prefs->getIntLimited("/options/dragtolerance/value", 2, 0, 100);
- if (_path && !SP_OBJECT_STYLE(_path)->stroke.isNone()) {
+ if (_path && SP_OBJECT_STYLE(_path) && !SP_OBJECT_STYLE(_path)->stroke.isNone()) {
ret += SP_OBJECT_STYLE(_path)->stroke_width.computed * 0.5
* (_edit_transform * _i2d_transform).descrim() // scale to desktop coords
* _desktop->current_zoom(); // == _d2w.descrim() - scale to window coords
diff --git a/src/ui/tool/selector.cpp b/src/ui/tool/selector.cpp
index a30f96025..d766d5be3 100644
--- a/src/ui/tool/selector.cpp
+++ b/src/ui/tool/selector.cpp
@@ -102,8 +102,8 @@ Selector::~Selector()
bool Selector::event(GdkEvent *event)
{
// The hidden control point will capture all events after it obtains the grab,
- // but it relies on this function to initiate it. Here we can filter what events
- // it will receive.
+ // but it relies on this function to initiate it. If we pass only first button
+ // press events here, it won't interfere with any other event handling.
switch (event->type) {
case GDK_BUTTON_PRESS:
// Do not pass button presses other than left button to the control point.