diff options
Diffstat (limited to 'src/ui/tool/transform-handle-set.cpp')
| -rw-r--r-- | src/ui/tool/transform-handle-set.cpp | 305 |
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 |
