diff options
Diffstat (limited to 'src/ui/tool')
| -rw-r--r-- | src/ui/tool/control-point-selection.cpp | 41 | ||||
| -rw-r--r-- | src/ui/tool/control-point-selection.h | 3 | ||||
| -rw-r--r-- | src/ui/tool/control-point.cpp | 36 | ||||
| -rw-r--r-- | src/ui/tool/control-point.h | 3 | ||||
| -rw-r--r-- | src/ui/tool/curve-drag-point.cpp | 2 | ||||
| -rw-r--r-- | src/ui/tool/manipulator.h | 1 | ||||
| -rw-r--r-- | src/ui/tool/multi-path-manipulator.cpp | 41 | ||||
| -rw-r--r-- | src/ui/tool/multi-path-manipulator.h | 5 | ||||
| -rw-r--r-- | src/ui/tool/node-tool.cpp | 12 | ||||
| -rw-r--r-- | src/ui/tool/node-tool.h | 9 | ||||
| -rw-r--r-- | src/ui/tool/node.cpp | 58 | ||||
| -rw-r--r-- | src/ui/tool/node.h | 1 | ||||
| -rw-r--r-- | src/ui/tool/path-manipulator.cpp | 53 | ||||
| -rw-r--r-- | src/ui/tool/path-manipulator.h | 6 | ||||
| -rw-r--r-- | src/ui/tool/transform-handle-set.cpp | 305 | ||||
| -rw-r--r-- | src/ui/tool/transform-handle-set.h | 26 |
16 files changed, 392 insertions, 210 deletions
diff --git a/src/ui/tool/control-point-selection.cpp b/src/ui/tool/control-point-selection.cpp index 1fb98d78f..308359c33 100644 --- a/src/ui/tool/control-point-selection.cpp +++ b/src/ui/tool/control-point-selection.cpp @@ -1,5 +1,6 @@ -/** @file - * Node selection - implementation +/** + * @file + * Node selection - implementation. */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> @@ -16,13 +17,14 @@ #include "ui/tool/event-utils.h" #include "ui/tool/selectable-control-point.h" #include "ui/tool/transform-handle-set.h" +#include "ui/tool/node.h" namespace Inkscape { namespace UI { /** * @class ControlPointSelection - * @brief Group of selected control points. + * Group of selected control points. * * Some operations can be performed on all selected points regardless of their type, therefore * this class is also a Manipulator. It handles the transformations of points using @@ -432,7 +434,7 @@ bool ControlPointSelection::_keyboardMove(GdkEventKey const &event, Geom::Point delta /= _desktop->current_zoom(); } else { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - double nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000); + double nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); delta *= nudge; } @@ -445,8 +447,10 @@ 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". */ +/** + * 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 @@ -459,7 +463,8 @@ double ControlPointSelection::_rotationRadius(Geom::Point const &rc) return maxlen; } -/** Rotates the selected points in the given direction according to the modifier state +/** + * 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 * @param dir Direction of rotation (math convention: 1 = counterclockwise, -1 = clockwise) @@ -533,7 +538,7 @@ bool ControlPointSelection::_keyboardScale(GdkEventKey const &event, int dir) length_change = 1.0 / _desktop->current_zoom() * dir; } else { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000); + length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000, "px"); length_change *= dir; } double scale = (maxext + length_change) / maxext; @@ -642,13 +647,24 @@ bool ControlPointSelection::event(GdkEvent *event) return false; } -std::vector<Inkscape::SnapCandidatePoint> ControlPointSelection::getOriginalPoints() +void ControlPointSelection::getOriginalPoints(std::vector<Inkscape::SnapCandidatePoint> &pts) { - std::vector<Inkscape::SnapCandidatePoint> points; + pts.clear(); for (iterator i = _points.begin(); i != _points.end(); ++i) { - points.push_back(Inkscape::SnapCandidatePoint(_original_positions[*i], SNAPSOURCE_NODE_HANDLE)); + pts.push_back(Inkscape::SnapCandidatePoint(_original_positions[*i], SNAPSOURCE_NODE_HANDLE)); + } +} + +void ControlPointSelection::getUnselectedPoints(std::vector<Inkscape::SnapCandidatePoint> &pts) +{ + pts.clear(); + ControlPointSelection::Set &nodes = this->allPoints(); + for (ControlPointSelection::Set::iterator i = nodes.begin(); i != nodes.end(); ++i) { + if (!(*i)->selected()) { + Node *n = static_cast<Node*>(*i); + pts.push_back(n->snapCandidatePoint()); + } } - return points; } void ControlPointSelection::setOriginalPoints() @@ -659,7 +675,6 @@ void ControlPointSelection::setOriginalPoints() } } - } // namespace UI } // namespace Inkscape diff --git a/src/ui/tool/control-point-selection.h b/src/ui/tool/control-point-selection.h index 7e09d50f5..67bd07644 100644 --- a/src/ui/tool/control-point-selection.h +++ b/src/ui/tool/control-point-selection.h @@ -111,7 +111,8 @@ public: sigc::signal<void, SelectableControlPoint *, bool> signal_point_changed; sigc::signal<void, CommitEvent> signal_commit; - std::vector<Inkscape::SnapCandidatePoint> getOriginalPoints(); + void getOriginalPoints(std::vector<Inkscape::SnapCandidatePoint> &pts); + void getUnselectedPoints(std::vector<Inkscape::SnapCandidatePoint> &pts); void setOriginalPoints(); private: diff --git a/src/ui/tool/control-point.cpp b/src/ui/tool/control-point.cpp index bece1324b..79d70d453 100644 --- a/src/ui/tool/control-point.cpp +++ b/src/ui/tool/control-point.cpp @@ -1,5 +1,6 @@ -/** @file - * Desktop-bound visual control object - implementation +/** + * @file + * Desktop-bound visual control object - implementation. */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> @@ -21,6 +22,7 @@ #include "preferences.h" #include "ui/tool/control-point.h" #include "ui/tool/event-utils.h" +#include "ui/tool/transform-handle-set.h" namespace Inkscape { namespace UI { @@ -29,7 +31,7 @@ namespace UI { /** * @class ControlPoint - * @brief Draggable point, the workhorse of on-canvas editing. + * Draggable point, the workhorse of on-canvas editing. * * Control points (formerly known as knots) are graphical representations of some significant * point in the drawing. The drawing can be changed by dragging the point and the things that are @@ -436,6 +438,32 @@ bool ControlPoint::_eventHandler(GdkEvent *event) // update tips on modifier state change // TODO add ESC keybinding as drag cancel case GDK_KEY_PRESS: + switch (get_group0_keyval(&event->key)) + { + case GDK_Tab: + {// Downcast from ControlPoint to TransformHandle, if possible + // This is an ugly hack; we should have the transform handle intercept the keystrokes itself + TransformHandle *th = dynamic_cast<TransformHandle*>(this); + if (th) { + th->getNextClosestPoint(false); + return true; + } + break; + } + case GDK_ISO_Left_Tab: + {// Downcast from ControlPoint to TransformHandle, if possible + // This is an ugly hack; we should have the transform handle intercept the keystrokes itself + TransformHandle *th = dynamic_cast<TransformHandle*>(this); + if (th) { + th->getNextClosestPoint(true); + return true; + } + break; + } + default: + break; + } + // Do not break here, to allow for updating tooltips and such case GDK_KEY_RELEASE: if (mouseovered_point != this) return false; if (_drag_initiated) { @@ -537,7 +565,7 @@ void ControlPoint::transferGrab(ControlPoint *prev_point, GdkEventMotion *event) } /** - * @brief Change the state of the knot + * Change the state of the knot. * Alters the appearance of the knot to match one of the states: normal, mouseover * or clicked. */ diff --git a/src/ui/tool/control-point.h b/src/ui/tool/control-point.h index 9f62fca42..72106403e 100644 --- a/src/ui/tool/control-point.h +++ b/src/ui/tool/control-point.h @@ -18,10 +18,11 @@ #include <gtkmm.h> #include <2geom/point.h> -#include "forward.h" #include "util/accumulators.h" #include "display/sodipodi-ctrl.h" +class SPDesktop; + namespace Inkscape { namespace UI { diff --git a/src/ui/tool/curve-drag-point.cpp b/src/ui/tool/curve-drag-point.cpp index a3fb5aa6e..8dafb55d7 100644 --- a/src/ui/tool/curve-drag-point.cpp +++ b/src/ui/tool/curve-drag-point.cpp @@ -153,7 +153,7 @@ void CurveDragPoint::_insertNode(bool take_selection) } _pm._selection.insert(inserted.ptr()); - _pm.update(); + _pm.update(true); _pm._commit(_("Add node")); } diff --git a/src/ui/tool/manipulator.h b/src/ui/tool/manipulator.h index 6866ec9dd..474ccd8f3 100644 --- a/src/ui/tool/manipulator.h +++ b/src/ui/tool/manipulator.h @@ -40,7 +40,6 @@ public: /// Handle input event. Returns true if handled. virtual bool event(GdkEvent *)=0; -protected: SPDesktop *const _desktop; }; diff --git a/src/ui/tool/multi-path-manipulator.cpp b/src/ui/tool/multi-path-manipulator.cpp index 082ac194b..2316058ed 100644 --- a/src/ui/tool/multi-path-manipulator.cpp +++ b/src/ui/tool/multi-path-manipulator.cpp @@ -1,5 +1,6 @@ -/** @file - * Multi path manipulator - implementation +/** + * @file + * Multi path manipulator - implementation. */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> @@ -149,9 +150,11 @@ void MultiPathManipulator::cleanup() } } -/** @brief Change the set of items to edit. +/** + * Change the set of items to edit. * - * This method attempts to preserve as much of the state as possible. */ + * This method attempts to preserve as much of the state as possible. + */ void MultiPathManipulator::setItems(std::set<ShapeRecord> const &s) { std::set<ShapeRecord> shapes(s); @@ -399,21 +402,21 @@ void MultiPathManipulator::joinNodes() invokeForAll(&PathManipulator::weldNodes, preserve_pos); } - _doneWithCleanup(_("Join nodes")); + _doneWithCleanup(_("Join nodes"), true); } void MultiPathManipulator::breakNodes() { if (_selection.empty()) return; invokeForAll(&PathManipulator::breakNodes); - _done(_("Break nodes")); + _done(_("Break nodes"), true); } void MultiPathManipulator::deleteNodes(bool keep_shape) { if (_selection.empty()) return; invokeForAll(&PathManipulator::deleteNodes, keep_shape); - _doneWithCleanup(_("Delete nodes")); + _doneWithCleanup(_("Delete nodes"), true); } /** Join selected endpoints to create segments. */ @@ -439,14 +442,14 @@ void MultiPathManipulator::joinSegments() if (joins.empty()) { invokeForAll(&PathManipulator::weldSegments); } - _doneWithCleanup("Join segments"); + _doneWithCleanup("Join segments", true); } void MultiPathManipulator::deleteSegments() { if (_selection.empty()) return; invokeForAll(&PathManipulator::deleteSegments); - _doneWithCleanup("Delete segments"); + _doneWithCleanup("Delete segments", true); } void MultiPathManipulator::alignNodes(Geom::Dim2 d) @@ -507,20 +510,24 @@ void MultiPathManipulator::showPathDirection(bool show) _show_path_direction = show; } -/** @brief Set live outline update status +/** + * Set live outline update status. * When set to true, outline will be updated continuously when dragging * or transforming nodes. Otherwise it will only update when changes are committed - * to XML. */ + * to XML. + */ void MultiPathManipulator::setLiveOutline(bool set) { invokeForAll(&PathManipulator::setLiveOutline, set); _live_outline = set; } -/** @brief Set live object update status +/** + * Set live object update status. * When set to true, objects will be updated continuously when dragging * or transforming nodes. Otherwise they will only update when changes are committed - * to XML. */ + * to XML. + */ void MultiPathManipulator::setLiveObjects(bool set) { invokeForAll(&PathManipulator::setLiveObjects, set); @@ -794,17 +801,17 @@ void MultiPathManipulator::_commit(CommitEvent cps) } /** Commits changes to XML and adds undo stack entry. */ -void MultiPathManipulator::_done(gchar const *reason) { - invokeForAll(&PathManipulator::update); +void MultiPathManipulator::_done(gchar const *reason, bool alert_LPE) { + invokeForAll(&PathManipulator::update, alert_LPE); invokeForAll(&PathManipulator::writeXML); DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason); signal_coords_changed.emit(); } /** Commits changes to XML, adds undo stack entry and removes empty manipulators. */ -void MultiPathManipulator::_doneWithCleanup(gchar const *reason) { +void MultiPathManipulator::_doneWithCleanup(gchar const *reason, bool alert_LPE) { _changed.block(); - _done(reason); + _done(reason, alert_LPE); cleanup(); _changed.unblock(); } diff --git a/src/ui/tool/multi-path-manipulator.h b/src/ui/tool/multi-path-manipulator.h index c25719790..6b5686139 100644 --- a/src/ui/tool/multi-path-manipulator.h +++ b/src/ui/tool/multi-path-manipulator.h @@ -13,7 +13,6 @@ #include <stddef.h> #include <sigc++/connection.h> -#include "forward.h" #include "ui/tool/commit-events.h" #include "ui/tool/manipulator.h" #include "ui/tool/modifier-tracker.h" @@ -103,8 +102,8 @@ private: } void _commit(CommitEvent cps); - void _done(gchar const *); - void _doneWithCleanup(gchar const *); + void _done(gchar const *reason, bool alert_LPE = false); + void _doneWithCleanup(gchar const *reason, bool alert_LPE = false); guint32 _getOutlineColor(ShapeRole role); MapType _mmap; diff --git a/src/ui/tool/node-tool.cpp b/src/ui/tool/node-tool.cpp index e75f31370..33020982e 100644 --- a/src/ui/tool/node-tool.cpp +++ b/src/ui/tool/node-tool.cpp @@ -1,5 +1,6 @@ -/** @file - * @brief New node tool - implementation +/** + * @file + * New node tool - implementation. */ /* Authors: * Krzysztof Kosiński <tweenk@gmail.com> @@ -480,11 +481,12 @@ gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event) nt->flash_tempitem = NULL; nt->flashed_item = NULL; } - if (!SP_IS_PATH(over_item)) break; // for now, handle only paths + if (!SP_IS_SHAPE(over_item)) break; // for now, handle only shapes nt->flashed_item = over_item; - SPCurve *c = sp_path_get_curve_for_edit(SP_PATH(over_item)); - c->transform(over_item->i2d_affine()); + SPCurve *c = SP_SHAPE(over_item)->getCurveBeforeLPE(); + if (!c) break; // break out when curve doesn't exist + c->transform(over_item->i2dt_affine()); SPCanvasItem *flash = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), c); sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(flash), prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff), 1.0, diff --git a/src/ui/tool/node-tool.h b/src/ui/tool/node-tool.h index d005a0bdf..6f7ab01d4 100644 --- a/src/ui/tool/node-tool.h +++ b/src/ui/tool/node-tool.h @@ -17,14 +17,13 @@ #include <stddef.h> #include <sigc++/sigc++.h> #include "event-context.h" -#include "forward.h" #include "ui/tool/node-types.h" #define INK_TYPE_NODE_TOOL (ink_node_tool_get_type ()) -#define INK_NODE_TOOL(obj) (GTK_CHECK_CAST ((obj), INK_TYPE_NODE_TOOL, InkNodeTool)) -#define INK_NODE_TOOL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), INK_TYPE_NODE_TOOL, InkNodeToolClass)) -#define INK_IS_NODE_TOOL(obj) (GTK_CHECK_TYPE ((obj), INK_TYPE_NODE_TOOL)) -#define INK_IS_NODE_TOOL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), INK_TYPE_NODE_TOOL)) +#define INK_NODE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INK_TYPE_NODE_TOOL, InkNodeTool)) +#define INK_NODE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INK_TYPE_NODE_TOOL, InkNodeToolClass)) +#define INK_IS_NODE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INK_TYPE_NODE_TOOL)) +#define INK_IS_NODE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INK_TYPE_NODE_TOOL)) class InkNodeTool; class InkNodeToolClass; diff --git a/src/ui/tool/node.cpp b/src/ui/tool/node.cpp index 8e3da266b..d268a9f14 100644 --- a/src/ui/tool/node.cpp +++ b/src/ui/tool/node.cpp @@ -1,5 +1,6 @@ -/** @file - * Editable node - implementation +/** + * @file + * Editable node - implementation. */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> @@ -70,13 +71,11 @@ static Geom::Point direction(Geom::Point const &first, Geom::Point const &second } /** - * @class Handle - * @brief Control point of a cubic Bezier curve in a path. + * Control point of a cubic Bezier curve in a path. * * Handle keeps the node type invariant only for the opposite handle of the same node. * 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; @@ -307,12 +306,10 @@ void Handle::dragged(Geom::Point &new_pos, GdkEventMotion *event) std::vector<Inkscape::SnapCandidatePoint> unselected; if (snap) { - typedef ControlPointSelection::Set Set; - Set &nodes = _parent->_selection.allPoints(); - for (Set::iterator i = nodes.begin(); i != nodes.end(); ++i) { + ControlPointSelection::Set &nodes = _parent->_selection.allPoints(); + for (ControlPointSelection::Set::iterator i = nodes.begin(); i != nodes.end(); ++i) { Node *n = static_cast<Node*>(*i); - Inkscape::SnapCandidatePoint p(n->position(), n->_snapSourceType(), n->_snapTargetType()); - unselected.push_back(p); + unselected.push_back(n->snapCandidatePoint()); } sm.setupIgnoreSelection(_desktop, true, &unselected); @@ -326,7 +323,7 @@ void Handle::dragged(Geom::Point &new_pos, GdkEventMotion *event) } else if (ctrl_constraint) { // NOTE: this is subtly wrong. // We should get all possible constraints and snap along them using - // multipleConstrainedSnaps, instead of first snapping to angle and the to objects + // multipleConstrainedSnaps, instead of first snapping to angle and then to objects Inkscape::SnappedPoint p; p = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos, SNAPSOURCE_NODE_HANDLE), *ctrl_constraint); new_pos = p.getPoint(); @@ -469,12 +466,10 @@ Glib::ustring Handle::_getDragTip(GdkEventMotion */*event*/) } /** - * @class Node - * @brief Curve endpoint in an editable path. + * Curve endpoint in an editable path. * * The method move() keeps node type invariants during translations. */ - Node::Node(NodeSharedData const &data, Geom::Point const &initial_pos) : SelectableControlPoint(data.desktop, initial_pos, Gtk::ANCHOR_CENTER, SP_CTRL_SHAPE_DIAMOND, 9.0, *data.selection, &node_colors, data.node_group) @@ -1118,8 +1113,15 @@ Inkscape::SnapTargetType Node::_snapTargetType() return SNAPTARGET_NODE_CUSP; } -/** @brief Gets the handle that faces the given adjacent node. - * Will abort with error if the given node is not adjacent. */ +Inkscape::SnapCandidatePoint Node::snapCandidatePoint() +{ + return SnapCandidatePoint(position(), _snapSourceType(), _snapTargetType()); +} + +/** + * Gets the handle that faces the given adjacent node. + * Will abort with error if the given node is not adjacent. + */ Handle *Node::handleToward(Node *to) { if (_next() == to) { @@ -1131,8 +1133,10 @@ Handle *Node::handleToward(Node *to) g_error("Node::handleToward(): second node is not adjacent!"); } -/** @brief Gets the node in the direction of the given handle. - * Will abort with error if the handle doesn't belong to this node. */ +/** + * Gets the node in the direction of the given handle. + * Will abort with error if the handle doesn't belong to this node. + */ Node *Node::nodeToward(Handle *dir) { if (front() == dir) { @@ -1144,8 +1148,10 @@ Node *Node::nodeToward(Handle *dir) g_error("Node::nodeToward(): handle is not a child of this node!"); } -/** @brief Gets the handle that goes in the direction opposite to the given adjacent node. - * Will abort with error if the given node is not adjacent. */ +/** + * Gets the handle that goes in the direction opposite to the given adjacent node. + * Will abort with error if the given node is not adjacent. + */ Handle *Node::handleAwayFrom(Node *to) { if (_next() == to) { @@ -1157,8 +1163,10 @@ Handle *Node::handleAwayFrom(Node *to) g_error("Node::handleAwayFrom(): second node is not adjacent!"); } -/** @brief Gets the node in the direction opposite to the given handle. - * Will abort with error if the handle doesn't belong to this node. */ +/** + * Gets the node in the direction opposite to the given handle. + * Will abort with error if the handle doesn't belong to this node. + */ Node *Node::nodeAwayFrom(Handle *h) { if (front() == h) { @@ -1259,14 +1267,12 @@ SPCtrlShapeType Node::_node_type_to_shape(NodeType type) /** - * @class NodeList - * @brief An editable list of nodes representing a subpath. + * An editable list of nodes representing a subpath. * * 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. */ - NodeList::NodeList(SubpathList &splist) : _list(splist) , _closed(false) @@ -1427,7 +1433,7 @@ NodeList &NodeList::get(iterator const &i) { /** * @class SubpathList - * @brief Editable path composed of one or more subpaths + * Editable path composed of one or more subpaths. */ } // namespace UI diff --git a/src/ui/tool/node.h b/src/ui/tool/node.h index b7145790b..f3416ed1c 100644 --- a/src/ui/tool/node.h +++ b/src/ui/tool/node.h @@ -151,6 +151,7 @@ public: static char const *node_type_to_localized_string(NodeType type); // temporarily public virtual bool _eventHandler(GdkEvent *event); + Inkscape::SnapCandidatePoint snapCandidatePoint(); protected: virtual void dragged(Geom::Point &, GdkEventMotion *); virtual bool grabbed(GdkEventMotion *); diff --git a/src/ui/tool/path-manipulator.cpp b/src/ui/tool/path-manipulator.cpp index 7c2013872..96b3a1bb1 100644 --- a/src/ui/tool/path-manipulator.cpp +++ b/src/ui/tool/path-manipulator.cpp @@ -1,5 +1,6 @@ -/** @file - * Path manipulator - implementation +/** + * @file + * Path manipulator - implementation. */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> @@ -29,7 +30,9 @@ #include "document.h" #include "live_effects/effect.h" #include "live_effects/lpeobject.h" +#include "live_effects/lpeobject-reference.h" #include "live_effects/parameter/path.h" +#include "live_effects/lpe-powerstroke.h" #include "sp-path.h" #include "helper/geom.h" #include "preferences.h" @@ -120,7 +123,7 @@ PathManipulator::PathManipulator(MultiPathManipulator &mpm, SPPath *path, , _lpe_key(lpe_key) { if (_lpe_key.empty()) { - _i2d_transform = SP_ITEM(path)->i2d_affine(); + _i2d_transform = path->i2dt_affine(); } else { _i2d_transform = Geom::identity(); } @@ -136,7 +139,7 @@ PathManipulator::PathManipulator(MultiPathManipulator &mpm, SPPath *path, sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(_outline), 0, SP_WIND_RULE_NONZERO); _selection.signal_update.connect( - sigc::mem_fun(*this, &PathManipulator::update)); + sigc::bind(sigc::mem_fun(*this, &PathManipulator::update), false)); _selection.signal_point_changed.connect( sigc::mem_fun(*this, &PathManipulator::_selectionChanged)); _desktop->signal_zoom_changed.connect( @@ -174,10 +177,12 @@ bool PathManipulator::empty() { return !_path || _subpaths.empty(); } -/** Update the display and the outline of the path. */ -void PathManipulator::update() +/** Update the display and the outline of the path. + * \param alert_LPE if true, alerts an applied LPE to what the path is going to be changed to, so it can adjust its parameters for nicer user interfacing + */ +void PathManipulator::update(bool alert_LPE) { - _createGeometryFromControlPoints(); + _createGeometryFromControlPoints(alert_LPE); } /** Store the changes to the path in XML. */ @@ -534,13 +539,15 @@ void PathManipulator::deleteNodes(bool keep_shape) } } -/** @brief Delete nodes between the two iterators. +/** + * Delete nodes between the two iterators. * The given range can cross the beginning of the subpath in closed subpaths. * @param start Beginning of the range to delete * @param end End of the range * @param keep_shape Whether to fit the handles at surrounding nodes to approximate * the shape before deletion - * @return Number of deleted nodes */ + * @return Number of deleted nodes + */ unsigned PathManipulator::_deleteStretch(NodeList::iterator start, NodeList::iterator end, bool keep_shape) { unsigned const samples_per_segment = 10; @@ -727,7 +734,7 @@ void PathManipulator::scaleHandle(Node *n, int which, int dir, bool pixel) length_change = 1.0 / _desktop->current_zoom() * dir; } else { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000); + length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000, "px"); length_change *= dir; } @@ -976,7 +983,7 @@ void PathManipulator::_externalChange(unsigned type) } break; case PATH_CHANGE_TRANSFORM: { Geom::Affine i2d_change = _d2i_transform; - _i2d_transform = SP_ITEM(_path)->i2d_affine(); + _i2d_transform = _path->i2dt_affine(); _d2i_transform = _i2d_transform.inverse(); i2d_change *= _i2d_transform; for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) { @@ -1003,7 +1010,7 @@ void PathManipulator::_createControlPointsFromGeometry() // When we erase an element, the next one slides into position, // so we do not increment the iterator even though it is theoretically invalidated. if (i->empty()) { - pathv.erase(i); + i = pathv.erase(i); } else { ++i; } @@ -1091,8 +1098,10 @@ void PathManipulator::_createControlPointsFromGeometry() } /** Construct the geometric representation of nodes and handles, update the outline - * and display */ -void PathManipulator::_createGeometryFromControlPoints() + * and display + * \param alert_LPE if true, first the LPE is warned what the new path is going to be before updating it + */ +void PathManipulator::_createGeometryFromControlPoints(bool alert_LPE) { Geom::PathBuilder builder; for (std::list<SubpathPtr>::iterator spi = _subpaths.begin(); spi != _subpaths.end(); ) { @@ -1120,7 +1129,18 @@ void PathManipulator::_createGeometryFromControlPoints() ++spi; } builder.finish(); - _spcurve->set_pathvector(builder.peek() * (_edit_transform * _i2d_transform).inverse()); + Geom::PathVector pathv = builder.peek() * (_edit_transform * _i2d_transform).inverse(); + _spcurve->set_pathvector(pathv); + if (alert_LPE) { + if (SP_IS_LPE_ITEM(_path) && sp_lpe_item_has_path_effect(SP_LPE_ITEM(_path))) { + PathEffectList effect_list = sp_lpe_item_get_effect_list(SP_LPE_ITEM(_path)); + LivePathEffect::LPEPowerStroke *lpe_pwr = dynamic_cast<LivePathEffect::LPEPowerStroke*>( effect_list.front()->lpeobject->get_lpe() ); + if (lpe_pwr) { + lpe_pwr->adjustForNewPath(pathv); + } + } + } + if (_live_outline) _updateOutline(); if (_live_objects) @@ -1278,8 +1298,9 @@ bool PathManipulator::_nodeClicked(Node *n, GdkEventButton *event) } if (!empty()) { - update(); + update(true); } + // We need to call MPM's method because it could have been our last node _multi_path_manipulator._doneWithCleanup(_("Delete node")); diff --git a/src/ui/tool/path-manipulator.h b/src/ui/tool/path-manipulator.h index 27a83f06b..e3b724e37 100644 --- a/src/ui/tool/path-manipulator.h +++ b/src/ui/tool/path-manipulator.h @@ -17,12 +17,12 @@ #include <2geom/affine.h> #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> -#include "forward.h" #include "ui/tool/node.h" #include "ui/tool/manipulator.h" struct SPCanvasItem; struct SPCurve; +struct SPPath; namespace Inkscape { namespace XML { class Node; } @@ -60,7 +60,7 @@ public: bool empty(); void writeXML(); - void update(); // update display, but don't commit + void update(bool alert_LPE = false); // update display, but don't commit void clear(); // remove all nodes from manipulator SPPath *item() { return _path; } @@ -102,7 +102,7 @@ private: typedef boost::shared_ptr<NodeList> SubpathPtr; void _createControlPointsFromGeometry(); - void _createGeometryFromControlPoints(); + void _createGeometryFromControlPoints(bool alert_LPE = false); unsigned _deleteStretch(NodeList::iterator first, NodeList::iterator last, bool keep_shape); std::string _createTypeString(); void _updateOutline(); diff --git a/src/ui/tool/transform-handle-set.cpp b/src/ui/tool/transform-handle-set.cpp index 26263c26b..7a12f4fbd 100644 --- a/src/ui/tool/transform-handle-set.cpp +++ b/src/ui/tool/transform-handle-set.cpp @@ -28,6 +28,8 @@ #include "ui/tool/event-utils.h" #include "ui/tool/transform-handle-set.h" #include "ui/tool/node-tool.h" +#include "ui/tool/node.h" +#include "seltrans.h" // FIXME BRAIN DAMAGE WARNING: this is a global variable in select-context.cpp // It should be moved to a header @@ -82,70 +84,101 @@ ControlPoint::ColorSet center_cset = { } // anonymous namespace /** Base class for node transform handles to simplify implementation */ -class TransformHandle : public ControlPoint { -public: - TransformHandle(TransformHandleSet &th, Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb) - : ControlPoint(th._desktop, Geom::Point(), anchor, pb, &thandle_cset, - th._transform_handle_group) - , _th(th) - { - setVisible(false); +TransformHandle::TransformHandle(TransformHandleSet &th, Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb) + : ControlPoint(th._desktop, Geom::Point(), anchor, pb, &thandle_cset, + th._transform_handle_group) + , _th(th) +{ + setVisible(false); +} + +void TransformHandle::getNextClosestPoint(bool reverse) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/options/snapclosestonly/value", false)) { + if (!_all_snap_sources_sorted.empty()) { + if (reverse) { // Shift-tab will find a closer point + if (_all_snap_sources_iter == _all_snap_sources_sorted.begin()) { + _all_snap_sources_iter = _all_snap_sources_sorted.end(); + } + --_all_snap_sources_iter; + } else { // Tab will find a point further away + ++_all_snap_sources_iter; + if (_all_snap_sources_iter == _all_snap_sources_sorted.end()) { + _all_snap_sources_iter = _all_snap_sources_sorted.begin(); + } + } + + _snap_points.clear(); + _snap_points.push_back(*_all_snap_sources_iter); + + } } -protected: - virtual void startTransform() {} - virtual void endTransform() {} - virtual Geom::Affine computeTransform(Geom::Point const &pos, GdkEventMotion *event) = 0; - virtual CommitEvent getCommitEvent() = 0; +} - Geom::Affine _last_transform; - Geom::Point _origin; - TransformHandleSet &_th; - std::vector<Inkscape::SnapCandidatePoint> _snap_points; +bool TransformHandle::grabbed(GdkEventMotion *) +{ + _origin = position(); + _last_transform.setIdentity(); + startTransform(); -private: - virtual bool grabbed(GdkEventMotion *) { - _origin = position(); - _last_transform.setIdentity(); - startTransform(); - - _th._setActiveHandle(this); - _cset = &invisible_cset; - _setState(_state); - - // Collect the snap-candidates, one for each selected node. These will be stored in the _snap_points vector. - SPDesktop *desktop = SP_ACTIVE_DESKTOP; - SnapManager &m = desktop->namedview->snap_manager; - InkNodeTool *nt = INK_NODE_TOOL(_desktop->event_context); - ControlPointSelection *selection = nt->_selected_nodes.get(); - - _snap_points = selection->getOriginalPoints(); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/options/snapclosestonly/value", false)) { - m.keepClosestPointOnly(_snap_points, _origin); + _th._setActiveHandle(this); + _cset = &invisible_cset; + _setState(_state); + + // Collect the snap-candidates, one for each selected node. These will be stored in the _snap_points vector. + InkNodeTool *nt = INK_NODE_TOOL(_th._desktop->event_context); + ControlPointSelection *selection = nt->_selected_nodes.get(); + + selection->setOriginalPoints(); + selection->getOriginalPoints(_snap_points); + selection->getUnselectedPoints(_unselected_points); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/options/snapclosestonly/value", false)) { + // Find the closest snap source candidate + _all_snap_sources_sorted = _snap_points; + + // Calculate and store the distance to the reference point for each snap candidate point + for(std::vector<Inkscape::SnapCandidatePoint>::iterator i = _all_snap_sources_sorted.begin(); i != _all_snap_sources_sorted.end(); ++i) { + (*i).setDistance(Geom::L2((*i).getPoint() - _origin)); } - return false; - } - virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event) - { - Geom::Affine t = computeTransform(new_pos, event); - // protect against degeneracies - if (t.isSingular()) return; - Geom::Affine incr = _last_transform.inverse() * t; - if (incr.isSingular()) return; - _th.signal_transform.emit(incr); - _last_transform = t; - } - virtual void ungrabbed(GdkEventButton *) { + // Sort them ascending, using the distance calculated above as the single criteria + std::sort(_all_snap_sources_sorted.begin(), _all_snap_sources_sorted.end()); + + // Now get the closest snap source _snap_points.clear(); - _th._clearActiveHandle(); - _cset = &thandle_cset; - _setState(_state); - endTransform(); - _th.signal_commit.emit(getCommitEvent()); + if (!_all_snap_sources_sorted.empty()) { + _all_snap_sources_iter = _all_snap_sources_sorted.begin(); + _snap_points.push_back(_all_snap_sources_sorted.front()); + } } -}; + + return false; +} + +void TransformHandle::dragged(Geom::Point &new_pos, GdkEventMotion *event) +{ + Geom::Affine t = computeTransform(new_pos, event); + // protect against degeneracies + if (t.isSingular()) return; + Geom::Affine incr = _last_transform.inverse() * t; + if (incr.isSingular()) return; + _th.signal_transform.emit(incr); + _last_transform = t; +} + +void TransformHandle::ungrabbed(GdkEventButton *) +{ + _snap_points.clear(); + _th._clearActiveHandle(); + _cset = &thandle_cset; + _setState(_state); + endTransform(); + _th.signal_commit.emit(getCommitEvent()); +} + class ScaleHandle : public TransformHandle { public: @@ -198,9 +231,6 @@ protected: _sc_center = _th.rotationCenter(); _sc_opposite = _th.bounds().corner(_corner + 2); _last_scale_x = _last_scale_y = 1.0; - InkNodeTool *nt = INK_NODE_TOOL(_desktop->event_context); - ControlPointSelection *selection = nt->_selected_nodes.get(); - selection->setOriginalPoints(); } virtual Geom::Affine computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) { Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite; @@ -214,30 +244,15 @@ protected: if (held_alt(*event)) { for (unsigned i = 0; i < 2; ++i) { - if (scale[i] >= 1.0) scale[i] = round(scale[i]); - else scale[i] = 1.0 / round(1.0 / scale[i]); + if (fabs(scale[i]) >= 1.0) { + scale[i] = round(scale[i]); + } else { + scale[i] = 1.0 / round(1.0 / MIN(scale[i],10)); + } } } else { - //SPDesktop *desktop = _th._desktop; // Won't work as _desktop is protected - SPDesktop *desktop = SP_ACTIVE_DESKTOP; - SnapManager &m = desktop->namedview->snap_manager; - - // The lines below have been copied from Handle::dragged() in node.cpp, and need to be - // activated if we want to snap to unselected (i.e. stationary) nodes and stationary pieces of paths of the - // path that's currently being edited - /* - std::vector<Inkscape::SnapCandidatePoint> unselected; - typedef ControlPointSelection::Set Set; - Set &nodes = _parent->_selection.allPoints(); - for (Set::iterator i = nodes.begin(); i != nodes.end(); ++i) { - Node *n = static_cast<Node*>(*i); - Inkscape::SnapCandidatePoint p(n->position(), n->_snapSourceType(), n->_snapTargetType()); - unselected.push_back(p); - } - m.setupIgnoreSelection(_desktop, true, &unselected); - */ - - m.setupIgnoreSelection(_desktop); + SnapManager &m = _th._desktop->namedview->snap_manager; + m.setupIgnoreSelection(_th._desktop, true, &_unselected_points); Inkscape::SnappedPoint sp; if (held_control(*event)) { @@ -306,10 +321,30 @@ protected: vs[d1] = (new_pos - scc)[d1] / (_origin - scc)[d1]; if (held_alt(*event)) { - if (vs[d1] >= 1.0) vs[d1] = round(vs[d1]); - else vs[d1] = 1.0 / round(1.0 / vs[d1]); + if (fabs(vs[d1]) >= 1.0) { + vs[d1] = round(vs[d1]); + } else { + vs[d1] = 1.0 / round(1.0 / MIN(vs[d1],10)); + } + vs[d2] = 1.0; + } else { + SnapManager &m = _th._desktop->namedview->snap_manager; + m.setupIgnoreSelection(_th._desktop, true, &_unselected_points); + + bool uniform = held_control(*event); + Inkscape::SnappedPoint sp = m.constrainedSnapStretch(_snap_points, _origin, vs[d1], scc, d1, uniform); + m.unSetup(); + + if (sp.getSnapped()) { + Geom::Point result = sp.getTransformation(); + vs[d1] = result[d1]; + vs[d2] = result[d2]; + } else { + // on ctrl, apply uniform scaling instead of stretching + // Preserve aspect ratio, but never flip in the dimension not being edited (by using fabs()) + vs[d2] = uniform ? fabs(vs[d1]) : 1.0; + } } - vs[d2] = held_control(*event) ? vs[d1] : 1.0; _last_scale_x = vs[Geom::X]; _last_scale_y = vs[Geom::Y]; @@ -357,7 +392,17 @@ protected: double angle = Geom::angle_between(_origin - rotc, new_pos - rotc); if (held_control(*event)) { angle = snap_angle(angle); + } else { + SnapManager &m = _th._desktop->namedview->snap_manager; + m.setupIgnoreSelection(_th._desktop, true, &_unselected_points); + Inkscape::SnappedPoint sp = m.constrainedSnapRotate(_snap_points, _origin, angle, rotc); + m.unSetup(); + + if (sp.getSnapped()) { + angle = sp.getTransformation()[0]; + } } + _last_angle = angle; Geom::Affine t = Geom::Translate(-rotc) * Geom::Rotate(angle) @@ -428,44 +473,76 @@ protected: virtual Geom::Affine computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) { Geom::Point scc = held_shift(*event) ? _skew_center : _skew_opposite; - // d1 and d2 are reversed with respect to ScaleSideHandle - Geom::Dim2 d1 = static_cast<Geom::Dim2>(_side % 2); - Geom::Dim2 d2 = static_cast<Geom::Dim2>((_side + 1) % 2); - Geom::Point proj, scale(1.0, 1.0); + Geom::Dim2 d1 = static_cast<Geom::Dim2>((_side + 1) % 2); + Geom::Dim2 d2 = static_cast<Geom::Dim2>(_side % 2); + + Geom::Point const initial_delta = _origin - scc; + + if (fabs(initial_delta[d1]) < 1e-15) { + return Geom::Affine(); + } + + // Calculate the scale factors, which can be either visual or geometric + // depending on which type of bbox is currently being used (see preferences -> selector tool) + Geom::Scale scale = calcScaleFactors(_origin, new_pos, scc, false); + Geom::Scale skew = calcScaleFactors(_origin, new_pos, scc, true); + scale[d2] = 1; + skew[d2] = 1; // Skew handles allow scaling up to integer multiples of the original size // in the second direction; prevent explosions - // TODO should the scaling part be only active with Alt? - if (!Geom::are_near(_origin[d2], scc[d2])) { - scale[d2] = (new_pos - scc)[d2] / (_origin - scc)[d2]; - } - if (scale[d2] < 1.0) { - scale[d2] = copysign(1.0, scale[d2]); + if (fabs(scale[d1]) < 1) { + // Prevent shrinking of the selected object, while allowing mirroring + scale[d1] = copysign(1.0, scale[d1]); } else { - scale[d2] = floor(scale[d2]); + // Allow expanding of the selected object by integer multiples + scale[d1] = floor(scale[d1] + 0.5); } - // Calculate skew angle. The angle is calculated with regards to the point obtained - // by projecting the handle position on the relevant side of the bounding box. - // This avoids degeneracies when moving the skew angle over the rotation center - proj[d1] = new_pos[d1]; - proj[d2] = scc[d2] + (_origin[d2] - scc[d2]) * scale[d2]; - double angle = 0; - if (!Geom::are_near(proj[d2], scc[d2])) - angle = Geom::angle_between(_origin - scc, proj - scc); - if (held_control(*event)) angle = snap_angle(angle); - - // skew matrix has the from [[1, k],[0, 1]] for horizontal skew - // and [[1,0],[k,1]] for vertical skew. - Geom::Affine skew = Geom::identity(); - // correct the sign of the tangent - skew[d2 + 1] = (d1 == Geom::X ? -1.0 : 1.0) * tan(angle); + double angle = atan(skew[d1] / scale[d1]); + + if (held_control(*event)) { + angle = snap_angle(angle); + skew[d1] = tan(angle) * scale[d1]; + } else { + SnapManager &m = _th._desktop->namedview->snap_manager; + m.setupIgnoreSelection(_th._desktop, true, &_unselected_points); + + Geom::Point cvec; cvec[d2] = 1.0; + Inkscape::Snapper::SnapConstraint const constraint(cvec); + Inkscape::SnappedPoint sp = m.constrainedSnapSkew(_snap_points, _origin, constraint, Geom::Point(skew[d1], scale[d1]), scc, d2); + m.unSetup(); + + if (sp.getSnapped()) { + skew[d1] = sp.getTransformation()[0]; + } + } _last_angle = angle; + + // Update the handle position + Geom::Point new_new_pos; + new_new_pos[d2] = initial_delta[d1] * skew[d1] + _origin[d2]; + new_new_pos[d1] = initial_delta[d1] * scale[d1] + scc[d1]; + + // Calculate the relative affine + Geom::Affine relative_affine = Geom::identity(); + relative_affine[2*d1 + d1] = (new_new_pos[d1] - scc[d1]) / initial_delta[d1]; + relative_affine[2*d1 + (d2)] = (new_new_pos[d2] - _origin[d2]) / initial_delta[d1]; + relative_affine[2*(d2) + (d1)] = 0; + relative_affine[2*(d2) + (d2)] = 1; + + for (int i = 0; i < 2; i++) { + if (fabs(relative_affine[3*i]) < 1e-15) { + relative_affine[3*i] = 1e-15; + } + } + Geom::Affine t = Geom::Translate(-scc) - * Geom::Scale(scale) * skew + * relative_affine * Geom::Translate(scc); + return t; } @@ -537,8 +614,8 @@ public: protected: virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event) { - SnapManager &sm = _desktop->namedview->snap_manager; - sm.setup(_desktop); + SnapManager &sm = _th._desktop->namedview->snap_manager; + sm.setup(_th._desktop); bool snap = !held_shift(*event) && sm.someSnapperMightSnap(); if (held_control(*event)) { // constrain to axes diff --git a/src/ui/tool/transform-handle-set.h b/src/ui/tool/transform-handle-set.h index 0557b1278..8ce7011c5 100644 --- a/src/ui/tool/transform-handle-set.h +++ b/src/ui/tool/transform-handle-set.h @@ -78,6 +78,32 @@ private: friend class RotationCenter; }; +/** Base class for node transform handles to simplify implementation */ +class TransformHandle : public ControlPoint { +public: + TransformHandle(TransformHandleSet &th, Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb); + void getNextClosestPoint(bool reverse); + +protected: + virtual void startTransform() {} + virtual void endTransform() {} + virtual Geom::Affine computeTransform(Geom::Point const &pos, GdkEventMotion *event) = 0; + virtual CommitEvent getCommitEvent() = 0; + + Geom::Affine _last_transform; + Geom::Point _origin; + TransformHandleSet &_th; + std::vector<Inkscape::SnapCandidatePoint> _snap_points; + std::vector<Inkscape::SnapCandidatePoint> _unselected_points; + std::vector<Inkscape::SnapCandidatePoint> _all_snap_sources_sorted; + std::vector<Inkscape::SnapCandidatePoint>::iterator _all_snap_sources_iter; + +private: + virtual bool grabbed(GdkEventMotion *); + virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event); + virtual void ungrabbed(GdkEventButton *); +}; + } // namespace UI } // namespace Inkscape |
