From 285b96e08e3f8ac1c5969281940b334182b9431b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Krzysztof=20Kosi=C5=84ski?= Date: Sun, 27 Dec 2009 00:59:01 +0100 Subject: [PATCH] Implement selection linear grow --- src/ui/tool/node-tool.cpp | 44 ++++++++ src/ui/tool/node.cpp | 142 +++++++++++++++++++++++-- src/ui/tool/node.h | 1 + src/ui/tool/path-manipulator.cpp | 5 - src/ui/tool/path-manipulator.h | 1 - src/ui/tool/selectable-control-point.h | 4 +- 6 files changed, 178 insertions(+), 19 deletions(-) diff --git a/src/ui/tool/node-tool.cpp b/src/ui/tool/node-tool.cpp index e310731bb..735ddf87e 100644 --- a/src/ui/tool/node-tool.cpp +++ b/src/ui/tool/node-tool.cpp @@ -38,6 +38,50 @@ #include "pixmaps/cursor-node.xpm" #include "pixmaps/cursor-node-d.xpm" +/** @struct InkNodeTool + * + * Node tool event context. + * + * @par Architectural overview of the tool + * @par + * Here's a breakdown of what each object does. + * - Handle: shows a handle and keeps the node type constraint (smooth / symmetric) by updating + * the other handle's position when dragged. Its move() method cannot violate the constraints. + * - Node: keeps node type constraints for auto nodes and smooth nodes at ends of linear segments. + * Its move() method cannot violate constraints. Handles linear grow and dispatches spatial grow + * to MultiPathManipulator. Keeps a reference to its NodeList. + * - NodeList: exposes an iterator-based interface to nodes. It is possible to obtain an iterator + * to a node from the node. Keeps a reference to its SubpathList. + * - SubpathList: list of NodeLists that represents an editable pathvector. Keeps a reference + * to its PathManipulator. + * - PathManipulator: performs most of the single-path actions like reverse subpaths, + * delete segment, shift selection, etc. Keeps a reference to MultiPathManipulator. + * - MultiPathManipulator: performs additional operations for actions that are not per-path, + * for example node joins and segment joins. Tracks the control transforms for PMs that edit + * clipping paths and masks. It is more or less equivalent to ShapeEditor and in the future + * it might handle all shapes. Handles XML commit of actions that affect all paths or + * the node selection and removes PathManipulators that have no nodes left after e.g. node + * deletes. + * - ControlPointSelection: keeps track of node selection. Performs actions that require no + * knowledge about the path, only about the nodes, like dragging and transforms. It is not + * specific to nodes and can accomodate any control point derived from SelectableControlPoint. + * Transforms nodes in response to transform handle events. + * - TransformHandleSet: displays nodeset transform handles and emits transform events. The aim + * is to eventually use a common class for object and control point transforms. + * + * @par Plans for the future + * @par + * - MultiPathManipulator should become a generic shape editor that manages all active manipulator, + * more or less like the old ShapeEditor. + * - Knotholder should be rewritten into one manipulator class per shape, using the control point + * classes. Interesting features like dragging rectangle sides could be added along the way. + * - Better handling of clip and mask editing, particularly in response to undo. + * - High level refactoring of the event context hierarchy. All aspects of tools, like toolbox + * controls, icons, event handling should be collected in one class, though each aspect + * of a tool might be in an separate class for better modularity. The long term goal is to allow + * tools to be defined in extensions or shared library plugins. + */ + namespace { SPCanvasGroup *create_control_group(SPDesktop *d); void ink_node_tool_class_init(InkNodeToolClass *klass); diff --git a/src/ui/tool/node.cpp b/src/ui/tool/node.cpp index 4f6d0d5d7..22d4ddc47 100644 --- a/src/ui/tool/node.cpp +++ b/src/ui/tool/node.cpp @@ -13,11 +13,9 @@ #include #include #include +#include <2geom/bezier-utils.h> #include <2geom/transforms.h> -#include "ui/tool/event-utils.h" -#include "ui/tool/multi-path-manipulator.h" -#include "ui/tool/node.h" -#include "ui/tool/path-manipulator.h" + #include "display/sp-ctrlline.h" #include "display/sp-canvas.h" #include "display/sp-canvas-util.h" @@ -26,6 +24,11 @@ #include "preferences.h" #include "sp-metrics.h" #include "sp-namedview.h" +#include "ui/tool/control-point-selection.h" +#include "ui/tool/event-utils.h" +#include "ui/tool/multi-path-manipulator.h" +#include "ui/tool/node.h" +#include "ui/tool/path-manipulator.h" namespace Inkscape { namespace UI { @@ -621,6 +624,7 @@ NodeType Node::parse_nodetype(char x) } } +/** Customized event handler to catch scroll events needed for selection grow/shrink. */ bool Node::_eventHandler(GdkEvent *event) { static NodeList::iterator origin; @@ -639,7 +643,7 @@ bool Node::_eventHandler(GdkEvent *event) if (held_control(event->scroll)) { list()->_list._path_manipulator._multi_path_manipulator.spatialGrow(origin, dir); } else { - list()->_list._path_manipulator.linearGrow(origin, dir); + _linearGrow(dir); } return true; default: @@ -648,6 +652,124 @@ bool Node::_eventHandler(GdkEvent *event) return ControlPoint::_eventHandler(event); } +// TODO Move this to 2Geom +static double bezier_length (Geom::Point a0, Geom::Point a1, Geom::Point a2, Geom::Point a3) +{ + double lower = Geom::distance(a0, a3); + double upper = Geom::distance(a0, a1) + Geom::distance(a1, a2) + Geom::distance(a2, a3); + + // TODO maybe EPSILON is this is too big in this case? + if (upper - lower < Geom::EPSILON) return (lower + upper)/2; + + Geom::Point // Casteljau subdivision + b0 = a0, + c0 = a3, + b1 = 0.5*(a0 + a1), + t0 = 0.5*(a1 + a2), + c1 = 0.5*(a2 + a3), + b2 = 0.5*(b1 + t0), + c2 = 0.5*(t0 + c1), + b3 = 0.5*(b2 + c2); // == c3 + return bezier_length(b0, b1, b2, b3) + bezier_length(b3, c2, c1, c0); +} + +/** Select or deselect a node in this node's subpath based on its path distance from this node. + * @param dir If negative, shrink selection by one node; if positive, grow by one node */ +void Node::_linearGrow(int dir) +{ + // Interestingly, we do not need any help from PathManipulator when doing linear grow. + // First handle the trivial case of growing over an unselected node. + if (!selected() && dir > 0) { + _selection.insert(this); + return; + } + + NodeList::iterator this_iter = NodeList::get_iterator(this); + NodeList::iterator fwd = this_iter, rev = this_iter; + double distance_back = 0, distance_front = 0; + + // Linear grow is simple. We find the first unselected nodes in each direction + // and compare the linear distances to them. + if (dir > 0) { + if (!selected()) { + _selection.insert(this); + return; + } + + // find first unselected nodes on both sides + while (fwd && fwd->selected()) { + NodeList::iterator n = fwd.next(); + distance_front += bezier_length(*fwd, fwd->_front, n->_back, *n); + fwd = n; + if (fwd == this_iter) + // there is no unselected node in this cyclic subpath + return; + } + // do the same for the second direction. Do not check for equality with + // this node, because there is at least one unselected node in the subpath, + // so we are guaranteed to stop. + while (rev && rev->selected()) { + NodeList::iterator p = rev.prev(); + distance_back += bezier_length(*rev, rev->_back, p->_front, *p); + rev = p; + } + + NodeList::iterator t; // node to select + if (fwd && rev) { + if (distance_front <= distance_back) t = fwd; + else t = rev; + } else { + if (fwd) t = fwd; + if (rev) t = rev; + } + if (t) _selection.insert(t.ptr()); + + // Linear shrink is more complicated. We need to find the farthest selected node. + // This means we have to check the entire subpath. We go in the direction in which + // the distance we traveled is lower. We do this until we run out of nodes (ends of path) + // or the two iterators meet. On the way, we store the last selected node and its distance + // in each direction (if any). At the end, we choose the one that is farther and deselect it. + } else { + // both iterators that store last selected nodes are initially empty + NodeList::iterator last_fwd, last_rev; + double last_distance_back, last_distance_front; + + while (rev || fwd) { + if (fwd && (!rev || distance_front <= distance_back)) { + if (fwd->selected()) { + last_fwd = fwd; + last_distance_front = distance_front; + } + NodeList::iterator n = fwd.next(); + distance_front += bezier_length(*fwd, fwd->_front, n->_back, *n); + fwd = n; + } else if (rev && (!fwd || distance_front > distance_back)) { + if (rev->selected()) { + last_rev = rev; + last_distance_back = distance_back; + } + NodeList::iterator p = rev.prev(); + distance_back += bezier_length(*rev, rev->_back, p->_front, *p); + rev = p; + } + // Check whether we walked the entire cyclic subpath. + // This is initially true because both iterators start from this node, + // so this check cannot go in the while condition. + if (fwd == rev) break; + } + + NodeList::iterator t; + if (last_fwd && last_rev) { + if (last_distance_front >= last_distance_back) t = last_fwd; + else t = last_rev; + } else { + if (last_fwd) t = last_fwd; + if (last_rev) t = last_rev; + } + if (t) _selection.erase(t.ptr()); + } +} + void Node::_setState(State state) { // change node size to match type and selection state @@ -667,14 +789,14 @@ void Node::_setState(State state) bool Node::_grabbedHandler(GdkEventMotion *event) { - // dragging out handles + // Dragging out handles with Shift + drag on a node. if (!held_shift(*event)) return false; Handle *h; Geom::Point evp = event_point(*event); Geom::Point rel_evp = evp - _last_click_event_point(); - // this should work even if dragtolerance is zero and evp coincides with node position + // This should work even if dragtolerance is zero and evp coincides with node position. double angle_next = HUGE_VAL; double angle_prev = HUGE_VAL; bool has_degenerate = false; @@ -727,7 +849,7 @@ void Node::_draggedHandler(Geom::Point &new_pos, GdkEventMotion *event) new_pos[d] = origin[d]; } } else { - // snapping? + // TODO snapping? } } @@ -813,8 +935,6 @@ SPCtrlShapeType Node::_node_type_to_shape(NodeType type) * It can optionally be cyclic to represent a closed path. * The list has iterators that act like plain node iterators, but can also be used * to obtain shared pointers to nodes. - * - * @todo Manage geometric representation to improve speed */ NodeList::NodeList(SubpathList &splist) @@ -959,7 +1079,7 @@ NodeList::iterator NodeList::erase(iterator i) return i; } -// TODO this method is nasty and ugly! +// TODO this method is very ugly! // converting SubpathList to an intrusive list might allow us to get rid of it void NodeList::kill() { diff --git a/src/ui/tool/node.h b/src/ui/tool/node.h index 68ad63ba9..167cf90b8 100644 --- a/src/ui/tool/node.h +++ b/src/ui/tool/node.h @@ -148,6 +148,7 @@ private: void _draggedHandler(Geom::Point &, GdkEventMotion *); void _fixNeighbors(Geom::Point const &old_pos, Geom::Point const &new_pos); void _updateAutoHandles(); + void _linearGrow(int dir); Node *_next(); Node *_prev(); static SPCtrlShapeType _node_type_to_shape(NodeType type); diff --git a/src/ui/tool/path-manipulator.cpp b/src/ui/tool/path-manipulator.cpp index 42db45321..2755d6fb3 100644 --- a/src/ui/tool/path-manipulator.cpp +++ b/src/ui/tool/path-manipulator.cpp @@ -298,11 +298,6 @@ void PathManipulator::shiftSelection(int dir) } } -void PathManipulator::linearGrow(NodeList::iterator center, int dir) -{ - g_message("linearGrow unimplemented"); -} - /** Invert selection in the entire path. */ void PathManipulator::invertSelection() { diff --git a/src/ui/tool/path-manipulator.h b/src/ui/tool/path-manipulator.h index 82ce7fd5d..38f66dee0 100644 --- a/src/ui/tool/path-manipulator.h +++ b/src/ui/tool/path-manipulator.h @@ -67,7 +67,6 @@ public: void selectAll(); void selectArea(Geom::Rect const &); void shiftSelection(int dir); - void linearGrow(NodeList::iterator center, int dir); void invertSelection(); void invertSelectionInSubpaths(); diff --git a/src/ui/tool/selectable-control-point.h b/src/ui/tool/selectable-control-point.h index a432b68db..87e415258 100644 --- a/src/ui/tool/selectable-control-point.h +++ b/src/ui/tool/selectable-control-point.h @@ -44,14 +44,14 @@ protected: ControlPointSelection &sel, ColorSet *cset = 0, SPCanvasGroup *group = 0); virtual void _setState(State state); + + ControlPointSelection &_selection; private: void _connectHandlers(); void _takeSelection(); bool _clickedHandler(GdkEventButton *); void _grabbedHandler(GdkEventMotion *); - - ControlPointSelection &_selection; }; } // namespace UI -- 2.30.2