summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 57c90d5)
raw | patch | inline | side by side (parent: 57c90d5)
author | Krzysztof Kosiński <tweenk.pl@gmail.com> | |
Tue, 9 Feb 2010 02:20:18 +0000 (03:20 +0100) | ||
committer | Krzysztof Kosiński <tweenk.pl@gmail.com> | |
Tue, 9 Feb 2010 02:20:18 +0000 (03:20 +0100) |
index a5c6ae961609b839a04981a907df68585f8adfa0..4dfb4597d0f840f604f95794d22b9a09d812a4cd 100644 (file)
{
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)
index 638318dbfcacf4992edff39904bafea663679573..df27c2a72ad1929b3654d042e235493919d85e51 100644 (file)
* 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()
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);
}
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)
{
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();
}
* 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)
{
_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;
// 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);
{
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)) {
return true;
}
-void ControlPointSelection::_commitTransform(CommitEvent ce)
+void ControlPointSelection::_commitHandlesTransform(CommitEvent ce)
{
+ _updateBounds();
_updateTransformHandles(true);
signal_commit.emit(ce);
}
index 93fba56f55be5b0b24269e1f4f04a9c4be573ef2..48c25c2852ef62736e5914035734c608827bfc55 100644 (file)
#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"
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
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;
index 88cb72ed5698d8a952e0b1fac165ce71cea0054c..e761daf20c82e8cda27d10bc77248ad9abdf059f 100644 (file)
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();
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)) {
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)");
}
}
index 147a918371c480dfe8f2153fdbc8c41bb38c5153..288ae6a8e11973e5097a38d59de11a8e6185cb84 100644 (file)
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;
index 0c4599e52f5423b7d558cdc59319bd77c37598e3..663504b52491a8caed818a0c77240aa4b9891b39 100644 (file)
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) {
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;
}
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"));
+ }
}
}
index 130e1619815028253c3300ad82e2df2358556ce9..baac642ac97243aa61d47273e5aad004e349f034 100644 (file)
--- a/src/ui/tool/node-tool.h
+++ b/src/ui/tool/node-tool.h
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 9f1e25877254214e172202eabfa5174501dac323..09ca99c6e1f4b76277f9b3a8dad4af2bd307aa29 100644 (file)
--- a/src/ui/tool/node.cpp
+++ b/src/ui/tool/node.cpp
* 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;
bool Handle::grabbed(GdkEventMotion *)
{
+ _saved_other_pos = other().position();
_saved_length = _drag_out ? 0 : length();
_pm()._handleGrabbed();
return false;
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?
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();
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);
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);
}
}
// 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,
_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
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)) {
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 {
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");
}
// 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 581cc9b6fe9e41dab25fcd61d3aa2f5da6ea8b98..c798a1fdbb9cd7b5ae34a269c485f2bb1be95f3e 100644 (file)
--- a/src/ui/tool/node.h
+++ b/src/ui/tool/node.h
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:
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;
index 3a6b15f375d363bb170ec250c1fadffaed179426..fd21970ee0b7cb8e5140e55583e23c60f045e2f0 100644 (file)
* 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
index a30f960253b3ce62baa86efe1601995b57bbbc97..d766d5be3b9897121e07297896beffc77fb974de 100644 (file)
--- a/src/ui/tool/selector.cpp
+++ b/src/ui/tool/selector.cpp
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.