summaryrefslogtreecommitdiffstats
path: root/src/ui/tool/transform-handle-set.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/tool/transform-handle-set.cpp')
-rw-r--r--src/ui/tool/transform-handle-set.cpp305
1 files changed, 191 insertions, 114 deletions
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