From c4c42ebb66d55ca883ed93b2a72d26cd8a759a6d Mon Sep 17 00:00:00 2001 From: Diederik van Lierop Date: Sat, 30 May 2015 20:27:42 +0200 Subject: Snapping in node tool now also works when: - when double clicking to insert a node on a path - when dragging a part of the path to deform it Fixed bugs: - https://launchpad.net/bugs/1448859 (bzr r14189) --- src/ui/tool/control-point.cpp | 15 +++-- src/ui/tool/control-point.h | 6 +- src/ui/tool/curve-drag-point.cpp | 20 ++++--- src/ui/tool/curve-drag-point.h | 6 +- src/ui/tool/multi-path-manipulator.cpp | 8 +++ src/ui/tool/multi-path-manipulator.h | 1 + src/ui/tool/path-manipulator.cpp | 36 +++++++++-- src/ui/tool/path-manipulator.h | 6 +- src/ui/tool/selector.cpp | 4 ++ src/ui/tool/selector.h | 1 + src/ui/tools/node-tool.cpp | 106 ++++++++++++++++++++++++--------- src/ui/tools/node-tool.h | 3 + 12 files changed, 164 insertions(+), 48 deletions(-) (limited to 'src/ui') diff --git a/src/ui/tool/control-point.cpp b/src/ui/tool/control-point.cpp index bcf5c9fce..636595016 100644 --- a/src/ui/tool/control-point.cpp +++ b/src/ui/tool/control-point.cpp @@ -71,7 +71,8 @@ ControlPoint::ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, SPAncho _cset(cset), _state(STATE_NORMAL), _position(initial_pos), - _lurking(false) + _lurking(false), + _double_clicked(false) { _canvas_item = sp_canvas_item_new( group ? group : _desktop->getControls(), SP_TYPE_CTRL, @@ -80,6 +81,7 @@ ControlPoint::ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, SPAncho "filled", TRUE, "fill_color", _cset.normal.fill, "stroked", TRUE, "stroke_color", _cset.normal.stroke, "mode", SP_CTRL_MODE_XOR, NULL); + _commonInit(); } @@ -91,7 +93,8 @@ ControlPoint::ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, SPAncho _cset(cset), _state(STATE_NORMAL), _position(initial_pos), - _lurking(false) + _lurking(false), + _double_clicked(false) { _canvas_item = ControlManager::getManager().createControl(group ? group : _desktop->getControls(), type); g_object_set(_canvas_item, @@ -245,7 +248,8 @@ bool ControlPoint::_eventHandler(Inkscape::UI::Tools::ToolBase *event_context, G static Geom::Point pointer_offset; // number of last doubleclicked button static unsigned next_release_doubleclick = 0; - + _double_clicked = false; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); int drag_tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); GdkEventMotion em; @@ -278,6 +282,7 @@ bool ControlPoint::_eventHandler(Inkscape::UI::Tools::ToolBase *event_context, G Ca = _desktop->canvas; em = event->motion; combine_motion_events(Ca, em, 0); + if (_event_grab && ! event_context->space_panning) { _desktop->snapindicator->remove_snaptarget(); bool transferred = false; @@ -298,6 +303,7 @@ bool ControlPoint::_eventHandler(Inkscape::UI::Tools::ToolBase *event_context, G _drag_initiated = true; } } + if (!transferred) { // dragging in progress Geom::Point new_pos = _desktop->w2d(event_point(event->motion)) + pointer_offset; @@ -305,7 +311,7 @@ bool ControlPoint::_eventHandler(Inkscape::UI::Tools::ToolBase *event_context, G dragged(new_pos, &em); move(new_pos); _updateDragTip(&em); // update dragging tip after moving to new position - + _desktop->scroll_to_point(new_pos); _desktop->set_coordinate_status(_position); sp_event_context_snap_delay_handler(event_context, NULL, @@ -342,6 +348,7 @@ bool ControlPoint::_eventHandler(Inkscape::UI::Tools::ToolBase *event_context, G } else { // it is the end of a click if (next_release_doubleclick) { + _double_clicked = true; return doubleclicked(&event->button); } else { return clicked(&event->button); diff --git a/src/ui/tool/control-point.h b/src/ui/tool/control-point.h index b3ed9545e..4a01b9f21 100644 --- a/src/ui/tool/control-point.h +++ b/src/ui/tool/control-point.h @@ -193,6 +193,8 @@ public: virtual bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event); SPDesktop *const _desktop; ///< The desktop this control point resides on. + bool doubleClicked() {return _double_clicked;} + protected: struct ColorEntry { @@ -361,6 +363,8 @@ protected: /** Events which should be captured when a handle is being dragged. */ static int const _grab_event_mask; + static bool _drag_initiated; + private: ControlPoint(ControlPoint const &other); @@ -397,7 +401,7 @@ private: static bool _event_grab; - static bool _drag_initiated; + bool _double_clicked; }; diff --git a/src/ui/tool/curve-drag-point.cpp b/src/ui/tool/curve-drag-point.cpp index 23640456e..d1756fa2c 100644 --- a/src/ui/tool/curve-drag-point.cpp +++ b/src/ui/tool/curve-drag-point.cpp @@ -15,6 +15,8 @@ #include "ui/tool/multi-path-manipulator.h" #include "ui/tool/path-manipulator.h" #include "ui/tool/node.h" +#include "sp-namedview.h" +#include "snap.h" namespace Inkscape { namespace UI { @@ -77,6 +79,16 @@ void CurveDragPoint::dragged(Geom::Point &new_pos, GdkEventMotion *event) return; } + if (_drag_initiated && !(event->state & GDK_SHIFT_MASK)) { + SnapManager &m = _desktop->namedview->snap_manager; + SPItem *path = static_cast(_pm._path); + m.setup(_desktop, true, path); // We will not try to snap to "path" itself + Inkscape::SnapCandidatePoint scp(new_pos, Inkscape::SNAPSOURCE_OTHER_HANDLE); + Inkscape::SnappedPoint sp = m.freeSnap(scp, Geom::OptRect(), false); + new_pos = sp.getPoint(); + m.unSetup(); + } + // Magic Bezier Drag Equations follow! // "weight" describes how the influence of the drag should be distributed // among the handles; 0 = front handle only, 1 = back handle only. @@ -166,14 +178,8 @@ void CurveDragPoint::_insertNode(bool take_selection) // Otherwise clicks on the new node would only work after the user moves the mouse a bit. // PathManipulator will restore visibility when necessary. setVisible(false); - NodeList::iterator inserted = _pm.subdivideSegment(first, _t); - if (take_selection) { - _pm._selection.clear(); - } - _pm._selection.insert(inserted.ptr()); - _pm.update(true); - _pm._commit(_("Add node")); + _pm.insertNode(first, _t, take_selection); } Glib::ustring CurveDragPoint::_getTip(unsigned state) const diff --git a/src/ui/tool/curve-drag-point.h b/src/ui/tool/curve-drag-point.h index ea83978e0..c1d40575f 100644 --- a/src/ui/tool/curve-drag-point.h +++ b/src/ui/tool/curve-drag-point.h @@ -34,7 +34,9 @@ public: CurveDragPoint(PathManipulator &pm); void setSize(double sz) { _setSize(sz); } void setTimeValue(double t) { _t = t; } + double getTimeValue() { return _t; } void setIterator(NodeList::iterator i) { first = i; } + NodeList::iterator getIterator() { return first; } virtual bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event); protected: @@ -47,9 +49,6 @@ protected: virtual bool doubleclicked(GdkEventButton *); private: - - void _insertNode(bool take_selection); - double _t; PathManipulator &_pm; NodeList::iterator first; @@ -57,6 +56,7 @@ private: static bool _drags_stroke; static bool _segment_was_degenerate; static Geom::Point _stroke_drag_origin; + void _insertNode(bool take_selection); }; } // namespace UI diff --git a/src/ui/tool/multi-path-manipulator.cpp b/src/ui/tool/multi-path-manipulator.cpp index f53cef5f4..46c6246a1 100644 --- a/src/ui/tool/multi-path-manipulator.cpp +++ b/src/ui/tool/multi-path-manipulator.cpp @@ -339,6 +339,14 @@ void MultiPathManipulator::insertNodesAtExtrema(ExtremumType extremum) _done(_("Add extremum nodes")); } +void MultiPathManipulator::insertNode(Geom::Point pt) +{ + // When double clicking to insert nodes, we might not have a selection of nodes (and we don't need one) + // so don't check for "_selection.empty()" here, contrary to the other methods above and below this one + invokeForAll(&PathManipulator::insertNode, pt); + _done(_("Add nodes")); +} + void MultiPathManipulator::duplicateNodes() { if (_selection.empty()) return; diff --git a/src/ui/tool/multi-path-manipulator.h b/src/ui/tool/multi-path-manipulator.h index 1bbcdd7ec..c908cede2 100644 --- a/src/ui/tool/multi-path-manipulator.h +++ b/src/ui/tool/multi-path-manipulator.h @@ -53,6 +53,7 @@ public: void insertNodesAtExtrema(ExtremumType extremum); void insertNodes(); + void insertNode(Geom::Point pt); void alertLPE(); void duplicateNodes(); void joinNodes(); diff --git a/src/ui/tool/path-manipulator.cpp b/src/ui/tool/path-manipulator.cpp index 6b0c95f68..a772c07c2 100644 --- a/src/ui/tool/path-manipulator.cpp +++ b/src/ui/tool/path-manipulator.cpp @@ -174,7 +174,8 @@ bool PathManipulator::event(Inkscape::UI::Tools::ToolBase * /*event_context*/, G case GDK_MOTION_NOTIFY: _updateDragPoint(event_point(event->motion)); break; - default: break; + default: + break; } return false; } @@ -275,6 +276,27 @@ void PathManipulator::insertNodes() } } +void PathManipulator::insertNode(Geom::Point pt) +{ + Geom::Coord dist = _updateDragPoint(pt); + if (dist < 1e-5) { // 1e-6 is too small, as observed occasionally when inserting a node at a snapped intersection of paths + insertNode(_dragpoint->getIterator(), _dragpoint->getTimeValue(), true); + } +} + +void PathManipulator::insertNode(NodeList::iterator first, double t, bool take_selection) +{ + NodeList::iterator inserted = subdivideSegment(first, t); + if (take_selection) { + _selection.clear(); + } + _selection.insert(inserted.ptr()); + + update(true); + _commit(_("Add node")); +} + + static void add_or_replace_if_extremum(std::vector< std::pair > &vec, double & extrvalue, double testvalue, NodeList::iterator const& node, double t) @@ -1643,13 +1665,15 @@ void PathManipulator::_commit(Glib::ustring const &annotation, gchar const *key) /** Update the position of the curve drag point such that it is over the nearest * point of the path. */ -void PathManipulator::_updateDragPoint(Geom::Point const &evp) +Geom::Coord PathManipulator::_updateDragPoint(Geom::Point const &evp) { + Geom::Coord dist = 1e23; + Geom::Affine to_desktop = _edit_transform * _i2d_transform; Geom::PathVector pv = _spcurve->get_pathvector(); boost::optional pvp = Geom::nearestPoint(pv, _desktop->w2d(evp) * to_desktop.inverse()); - if (!pvp) return; + if (!pvp) return dist; Geom::Point nearest_point = _desktop->d2w(pv.at(pvp->path_nr).pointAt(pvp->t) * to_desktop); double fracpart; @@ -1657,10 +1681,12 @@ void PathManipulator::_updateDragPoint(Geom::Point const &evp) for (unsigned i = 0; i < pvp->path_nr; ++i, ++spi) {} NodeList::iterator first = (*spi)->before(pvp->t, &fracpart); + dist = Geom::distance(evp, nearest_point); + double stroke_tolerance = _getStrokeTolerance(); if (first && first.next() && fracpart != 0.0 && - Geom::distance(evp, nearest_point) < stroke_tolerance) + dist < stroke_tolerance) { _dragpoint->setVisible(true); _dragpoint->setPosition(_desktop->w2d(nearest_point)); @@ -1670,6 +1696,8 @@ void PathManipulator::_updateDragPoint(Geom::Point const &evp) } else { _dragpoint->setVisible(false); } + + return dist; } /// This is called on zoom change to update the direction arrows diff --git a/src/ui/tool/path-manipulator.h b/src/ui/tool/path-manipulator.h index 2219af849..4c6f74ba4 100644 --- a/src/ui/tool/path-manipulator.h +++ b/src/ui/tool/path-manipulator.h @@ -70,6 +70,8 @@ public: void insertNodeAtExtremum(ExtremumType extremum); void insertNodes(); + void insertNode(Geom::Point); + void insertNode(NodeList::iterator first, double t, bool take_selection); void duplicateNodes(); void weldNodes(NodeList::iterator preserve_pos = NodeList::iterator()); void weldSegments(); @@ -133,7 +135,7 @@ private: void _removeNodesFromSelection(); void _commit(Glib::ustring const &annotation); void _commit(Glib::ustring const &annotation, gchar const *key); - void _updateDragPoint(Geom::Point const &); + Geom::Coord _updateDragPoint(Geom::Point const &); void _updateOutlineOnZoomChange(); double _getStrokeTolerance(); Handle *_chooseHandle(Node *n, int which); @@ -143,7 +145,7 @@ private: SPPath *_path; ///< can be an SPPath or an Inkscape::LivePathEffect::Effect !!! SPCurve *_spcurve; // in item coordinates SPCanvasItem *_outline; - CurveDragPoint *_dragpoint; // an invisible control point hoverng over curve + CurveDragPoint *_dragpoint; // an invisible control point hovering over curve PathManipulatorObserver *_observer; Geom::Affine _d2i_transform; ///< desktop-to-item transform Geom::Affine _i2d_transform; ///< item-to-desktop transform, inverse of _d2i_transform diff --git a/src/ui/tool/selector.cpp b/src/ui/tool/selector.cpp index e4e701785..051cb41ae 100644 --- a/src/ui/tool/selector.cpp +++ b/src/ui/tool/selector.cpp @@ -129,6 +129,10 @@ bool Selector::event(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *eve return false; } +bool Selector::doubleClicked() { + return _dragger->doubleClicked(); +} + } // namespace UI } // namespace Inkscape diff --git a/src/ui/tool/selector.h b/src/ui/tool/selector.h index dbe751ede..bd8d3e1aa 100644 --- a/src/ui/tool/selector.h +++ b/src/ui/tool/selector.h @@ -29,6 +29,7 @@ public: Selector(SPDesktop *d); virtual ~Selector(); virtual bool event(Inkscape::UI::Tools::ToolBase *, GdkEvent *); + virtual bool doubleClicked(); sigc::signal signal_area; sigc::signal signal_point; diff --git a/src/ui/tools/node-tool.cpp b/src/ui/tools/node-tool.cpp index ef00eaa40..761492f41 100644 --- a/src/ui/tools/node-tool.cpp +++ b/src/ui/tools/node-tool.cpp @@ -26,6 +26,8 @@ #include "ui/shape-editor.h" // temporary! #include "live_effects/effect.h" #include "display/curve.h" +#include "snap.h" +#include "sp-namedview.h" #include "sp-clippath.h" #include "sp-item-group.h" #include "sp-mask.h" @@ -112,7 +114,7 @@ namespace UI { namespace Tools { const std::string& NodeTool::getPrefsPath() { - return NodeTool::prefsPath; + return NodeTool::prefsPath; } const std::string NodeTool::prefsPath = "/tools/nodes"; @@ -216,7 +218,7 @@ void NodeTool::setup() { Inkscape::UI::ControlPoint::signal_mouseover_change.connect(sigc::mem_fun(this, &NodeTool::mouseover_changed)); this->_sizeUpdatedConn = ControlManager::getManager().connectCtrlSizeChanged( - sigc::mem_fun(this, &NodeTool::handleControlUiStyleChange) + sigc::mem_fun(this, &NodeTool::handleControlUiStyleChange) ); this->_selected_nodes = new Inkscape::UI::ControlPointSelection(this->desktop, this->_transform_handle_group); @@ -236,14 +238,14 @@ void NodeTool::setup() { ); this->_selected_nodes->signal_selection_changed.connect( - // Hide both signal parameters and bind the function parameter to 0 - // sigc::signal - // <=> - // void update_tip(GdkEvent *event) - sigc::hide(sigc::hide(sigc::bind( - sigc::mem_fun(this, &NodeTool::update_tip), - (GdkEvent*)NULL - ))) + // Hide both signal parameters and bind the function parameter to 0 + // sigc::signal + // <=> + // void update_tip(GdkEvent *event) + sigc::hide(sigc::hide(sigc::bind( + sigc::mem_fun(this, &NodeTool::update_tip), + (GdkEvent*)NULL + ))) ); this->helperpath_tmpitem = NULL; @@ -359,7 +361,7 @@ void NodeTool::set(const Inkscape::Preferences::Entry& value) { this->edit_masks = value.getBool(); this->selection_changed(this->desktop->selection); } else { - ToolBase::set(value); + ToolBase::set(value); } } @@ -370,7 +372,7 @@ void gather_items(NodeTool *nt, SPItem *base, SPObject *obj, Inkscape::UI::Shape using namespace Inkscape::UI; if (!obj) { - return; + return; } //XML Tree being used directly here while it shouldn't be. @@ -446,6 +448,9 @@ void NodeTool::selection_changed(Inkscape::Selection *sel) { } } + _previous_selection = _current_selection; + _current_selection = sel->itemList(); + this->_multipath->setItems(shapes); this->update_tip(NULL); this->desktop->updateNow(); @@ -458,31 +463,47 @@ bool NodeTool::root_handler(GdkEvent* event) { * 3. some keybindings */ using namespace Inkscape::UI; // pull in event helpers - + Inkscape::Selection *selection = desktop->selection; static Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (this->_multipath->event(this, event)) { - return true; + return true; } if (this->_selector->event(this, event)) { - return true; + return true; } if (this->_selected_nodes->event(this, event)) { - return true; + return true; } switch (event->type) { case GDK_MOTION_NOTIFY: { - this->update_helperpath(); + this->update_helperpath(); combine_motion_events(desktop->canvas, event->motion, 0); this->update_helperpath(); SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button), FALSE, TRUE); + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point const motion_dt(this->desktop->w2d(motion_w)); + + SnapManager &m = this->desktop->namedview->snap_manager; + + // We will show a pre-snap indication for when the user adds a node through double-clicking + // Adding a node will only work when a path has been selected; if that's not the case then snapping is useless + if (not this->desktop->selection->isEmpty()) { + if (!(event->motion.state & GDK_SHIFT_MASK)) { + m.setup(this->desktop); + Inkscape::SnapCandidatePoint scp(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE); + m.preSnap(scp, true); + m.unSetup(); + } + } + if (over_item != this->_last_over) { this->_last_over = over_item; //ink_node_tool_update_tip(nt, event); @@ -491,11 +512,11 @@ bool NodeTool::root_handler(GdkEvent* event) { // create pathflash outline if (prefs->getBool("/tools/nodes/pathflash_enabled")) { if (over_item == this->flashed_item) { - break; + break; } if (!prefs->getBool("/tools/nodes/pathflash_selected") && selection->includes(over_item)) { - break; + break; } if (this->flash_tempitem) { @@ -505,14 +526,14 @@ bool NodeTool::root_handler(GdkEvent* event) { } if (!SP_IS_SHAPE(over_item)) { - break; // for now, handle only shapes + break; // for now, handle only shapes } this->flashed_item = over_item; SPCurve *c = SP_SHAPE(over_item)->getCurveBeforeLPE(); if (!c) { - break; // break out when curve doesn't exist + break; // break out when curve doesn't exist } c->transform(over_item->i2dt_affine()); @@ -575,11 +596,42 @@ bool NodeTool::root_handler(GdkEvent* event) { case GDK_KEY_RELEASE: //ink_node_tool_update_tip(nt, event); - this->update_tip(event); + this->update_tip(event); + break; + + case GDK_BUTTON_RELEASE: + if (this->_selector->doubleClicked()) { + // If the selector received the doubleclick event, then we're at some distance from + // the path; otherwise, the doubleclick event would have been received by + // CurveDragPoint; we will insert nodes into the path anyway but only if we can snap + // to the path. Otherwise the position would not be very well defined. + if (!(event->motion.state & GDK_SHIFT_MASK)) { + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point const motion_dt(this->desktop->w2d(motion_w)); + + SnapManager &m = this->desktop->namedview->snap_manager; + m.setup(this->desktop); + Inkscape::SnapCandidatePoint scp(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE); + Inkscape::SnappedPoint sp = m.freeSnap(scp, Geom::OptRect(), true); + m.unSetup(); + + if (sp.getSnapped()) { + // The first click of the double click will have cleared the path selection, because + // we clicked aside of the path. We need to undo this on double click + Inkscape::Selection *selection = desktop->getSelection(); + selection->addList(_previous_selection); + + // The selection has been restored, and the signal selection_changed has been emitted, + // which has again forced a restore of the _mmap variable of the MultiPathManipulator (this->_multipath) + // Now we can insert the new nodes as if nothing has happened! + this->_multipath->insertNode(this->desktop->d2w(sp.getPoint())); + } + } + } break; default: - break; + break; } return ToolBase::root_handler(event); @@ -592,7 +644,7 @@ void NodeTool::update_tip(GdkEvent *event) { unsigned new_state = state_after_event(event); if (new_state == event->key.state) { - return; + return; } if (state_held_shift(new_state)) { @@ -661,7 +713,7 @@ void NodeTool::select_area(Geom::Rect const &sel, GdkEventButton *event) { selection->setList(items); } else { if (!held_shift(*event)) { - this->_selected_nodes->clear(); + this->_selected_nodes->clear(); } this->_selected_nodes->selectArea(sel); @@ -672,11 +724,11 @@ void NodeTool::select_point(Geom::Point const &/*sel*/, GdkEventButton *event) { using namespace Inkscape::UI; // pull in event helpers if (!event) { - return; + return; } if (event->button != 1) { - return; + return; } Inkscape::Selection *selection = this->desktop->selection; diff --git a/src/ui/tools/node-tool.h b/src/ui/tools/node-tool.h index 20375e869..d5a21e0c6 100644 --- a/src/ui/tools/node-tool.h +++ b/src/ui/tools/node-tool.h @@ -85,6 +85,9 @@ private: bool show_transform_handles; bool single_node_transform_handles; + std::vector _current_selection; + std::vector _previous_selection; + void selection_changed(Inkscape::Selection *sel); void select_area(Geom::Rect const &sel, GdkEventButton *event); -- cgit v1.2.3