From 9a8c99c1f18c1d69fee4fe12c455121abf6de6f3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Krzysztof=20Kosi=C5=84ski?= Date: Tue, 9 Feb 2010 03:20:18 +0100 Subject: [PATCH] Fix multiple minor problems in the node tool --- src/selection-chemistry.cpp | 8 +- src/ui/tool/control-point-selection.cpp | 132 +++++++++++------ src/ui/tool/control-point-selection.h | 11 +- src/ui/tool/curve-drag-point.cpp | 15 +- src/ui/tool/curve-drag-point.h | 3 +- src/ui/tool/node-tool.cpp | 82 +++++++---- src/ui/tool/node-tool.h | 1 + src/ui/tool/node.cpp | 183 +++++++++++++++++++----- src/ui/tool/node.h | 2 + src/ui/tool/path-manipulator.cpp | 2 +- src/ui/tool/selector.cpp | 4 +- 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(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 #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::insert(c found = _points.insert(x).first; x->updateState(); - _rot_radius.reset(); - signal_point_changed.emit(x, true); + _pointChanged(x, true); return std::pair(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(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(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 #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 _rot_radius; + boost::optional _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", "Linear segment: 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", "Bezier segment: 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", "Shift: 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", "Shift: 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", "Shift: 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 %d nodes. 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", + "%u of %u nodes 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", + "%u of %u nodes 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", - "Ctrl+Alt: 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", + "Shift+Ctrl+Alt: preserve length and snap rotation angle to %f° " + "increments while rotating both handles"), + snap_increment_degrees()); + } else { + return format_tip(C_("Path handle tip", + "Ctrl+Alt: preserve length and snap rotation angle to %f° increments"), + snap_increment_degrees()); + } } else { - return C_("Path handle tip", - "Alt: preserve handle length while dragging"); + if (state_held_shift(state) && can_shift_rotate) { + return C_("Path handle tip", + "Shift+Alt: preserve handle length and rotate both handles"); + } else { + return C_("Path handle tip", + "Alt: preserve handle length while dragging"); + } } } else { if (state_held_control(state)) { - return format_tip(C_("Path handle tip", - "Ctrl: 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", + "Ctrl: snap rotation angle to %f° increments, click to retract"), + snap_increment_degrees()); + } else { + return format_tip(C_("Path handle tip", + "Shift+Ctrl: 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", + "Shift: rotate both handles by the same angle"); } } + switch (_parent->type()) { case NODE_AUTO: - return C_("Path handle tip", - "Auto node handle: drag to convert to smooth node"); + return format_tip(C_("Path handle tip", + "Auto node handle: drag to convert to smooth node (%s)"), more); default: - return format_tip(C_("Path handle tip", "%s: drag to shape the curve"), - handle_type_to_localized_string(_parent->type())); + return format_tip(C_("Path handle tip", + "%s: 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 front_point, back_point; + boost::optional 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 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", "Shift+Ctrl: drag out a handle and snap its angle " "to %f° increments"), snap_increment_degrees()); - } + }*/ return C_("Path node tip", "Shift: 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", + "%s: drag to shape the path (more: Shift, Ctrl, Ctrl+Alt)"), nodetype); + } + return format_tip(C_("Path node tip", + "%s: drag to shape the path, click to toggle scale/rotation handles (more: Shift, Ctrl, Ctrl+Alt)"), nodetype); + } return format_tip(C_("Path node tip", - "%s: drag to shape the path, click to select this node"), nodetype); + "%s: 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. -- 2.30.2