summaryrefslogtreecommitdiffstats
path: root/src/ui/tool
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/tool')
-rw-r--r--src/ui/tool/control-point-selection.cpp41
-rw-r--r--src/ui/tool/control-point-selection.h3
-rw-r--r--src/ui/tool/control-point.cpp36
-rw-r--r--src/ui/tool/control-point.h3
-rw-r--r--src/ui/tool/curve-drag-point.cpp2
-rw-r--r--src/ui/tool/manipulator.h1
-rw-r--r--src/ui/tool/multi-path-manipulator.cpp41
-rw-r--r--src/ui/tool/multi-path-manipulator.h5
-rw-r--r--src/ui/tool/node-tool.cpp12
-rw-r--r--src/ui/tool/node-tool.h9
-rw-r--r--src/ui/tool/node.cpp58
-rw-r--r--src/ui/tool/node.h1
-rw-r--r--src/ui/tool/path-manipulator.cpp53
-rw-r--r--src/ui/tool/path-manipulator.h6
-rw-r--r--src/ui/tool/transform-handle-set.cpp305
-rw-r--r--src/ui/tool/transform-handle-set.h26
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