X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fui%2Ftool%2Fcontrol-point-selection.cpp;h=517e90da22d5e6a6e5bcaca6c6237e7011c1f227;hb=457ae1f251852ceb19549b16cf82feb4f01d9066;hp=245feae1d2bed10e132a6c0090936822b74ec8e2;hpb=639ba7867996397061ae1264267ab15b08e0f575;p=inkscape.git diff --git a/src/ui/tool/control-point-selection.cpp b/src/ui/tool/control-point-selection.cpp index 245feae1d..517e90da2 100644 --- a/src/ui/tool/control-point-selection.cpp +++ b/src/ui/tool/control-point-selection.cpp @@ -8,6 +8,7 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ +#include #include <2geom/transforms.h> #include "desktop.h" #include "preferences.h" @@ -53,15 +54,13 @@ ControlPointSelection::ControlPointSelection(SPDesktop *d, SPCanvasGroup *th_gro signal_update.connect( sigc::bind( sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles), true)); - signal_point_changed.connect( - sigc::hide( sigc::hide( - sigc::bind( - sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles), - false)))); + ControlPoint::signal_mouseover_change.connect( + sigc::hide( + sigc::mem_fun(*this, &ControlPointSelection::_mouseoverChanged))); _handles->signal_transform.connect( sigc::mem_fun(*this, &ControlPointSelection::transform)); _handles->signal_commit.connect( - sigc::mem_fun(*this, &ControlPointSelection::_commitTransform)); + sigc::mem_fun(*this, &ControlPointSelection::_commitHandlesTransform)); } ControlPointSelection::~ControlPointSelection() @@ -78,35 +77,10 @@ std::pair ControlPointSelection::insert(c return std::pair(found, false); } - boost::shared_ptr clist(new connlist_type()); - - // hide event param and always return false - /*clist->push_back( - x->signal_grabbed.connect( - sigc::bind_return( - sigc::bind<0>( - sigc::mem_fun(*this, &ControlPointSelection::_selectionGrabbed), - x), - false))); - clist->push_back( - x->signal_dragged.connect( - sigc::mem_fun(*this, &ControlPointSelection::_selectionDragged))); - clist->push_back( - x->signal_ungrabbed.connect( - sigc::hide( - sigc::mem_fun(*this, &ControlPointSelection::_selectionUngrabbed)))); - clist->push_back( - x->signal_clicked.connect( - sigc::hide( - sigc::bind<0>( - sigc::mem_fun(*this, &ControlPointSelection::_selectionClicked), - x))));*/ - - found = _points.insert(std::make_pair(x, clist)).first; + found = _points.insert(x).first; x->updateState(); - _rot_radius.reset(); - signal_point_changed.emit(x, true); + _pointChanged(x, true); return std::pair(found, true); } @@ -114,15 +88,10 @@ std::pair ControlPointSelection::insert(c /** Remove a point from the selection. */ void ControlPointSelection::erase(iterator pos) { - SelectableControlPoint *erased = pos->first; - boost::shared_ptr clist = pos->second; - for (connlist_type::iterator i = clist->begin(); i != clist->end(); ++i) { - i->disconnect(); - } + SelectableControlPoint *erased = *pos; _points.erase(pos); erased->updateState(); - _rot_radius.reset(); - signal_point_changed.emit(erased, false); + _pointChanged(erased, false); } ControlPointSelection::size_type ControlPointSelection::erase(const key_type &k) { @@ -200,11 +169,13 @@ void ControlPointSelection::spatialGrow(SelectableControlPoint *origin, int dir) void ControlPointSelection::transform(Geom::Matrix const &m) { for (iterator i = _points.begin(); i != _points.end(); ++i) { - SelectableControlPoint *cur = i->first; + SelectableControlPoint *cur = *i; cur->transform(m); } + _updateBounds(); // TODO preserving the rotation radius needs some rethinking... if (_rot_radius) (*_rot_radius) *= m.descrim(); + if (_mouseover_rot_radius) (*_mouseover_rot_radius) *= m.descrim(); signal_update.emit(); } @@ -216,14 +187,14 @@ void ControlPointSelection::align(Geom::Dim2 axis) Geom::OptInterval bound; for (iterator i = _points.begin(); i != _points.end(); ++i) { - bound.unionWith(Geom::OptInterval(i->first->position()[d])); + bound.unionWith(Geom::OptInterval((*i)->position()[d])); } double new_coord = bound->middle(); for (iterator i = _points.begin(); i != _points.end(); ++i) { - Geom::Point pos = i->first->position(); + Geom::Point pos = (*i)->position(); pos[d] = new_coord; - i->first->move(pos); + (*i)->move(pos); } } @@ -240,8 +211,8 @@ void ControlPointSelection::distribute(Geom::Dim2 d) // first we insert all points into a multimap keyed by the aligned coord to sort them // simultaneously we compute the extent of selection for (iterator i = _points.begin(); i != _points.end(); ++i) { - Geom::Point pos = i->first->position(); - sm.insert(std::make_pair(pos[d], i->first)); + Geom::Point pos = (*i)->position(); + sm.insert(std::make_pair(pos[d], (*i))); bound.unionWith(Geom::OptInterval(pos[d])); } @@ -261,28 +232,12 @@ void ControlPointSelection::distribute(Geom::Dim2 d) * or nothing if the selection is empty */ Geom::OptRect ControlPointSelection::pointwiseBounds() { - Geom::OptRect bound; - for (iterator i = _points.begin(); i != _points.end(); ++i) { - SelectableControlPoint *cur = i->first; - Geom::Point p = cur->position(); - if (!bound) { - bound = Geom::Rect(p, p); - } else { - bound->expandTo(p); - } - } - return bound; + return _bounds; } Geom::OptRect ControlPointSelection::bounds() { - Geom::OptRect bound; - for (iterator i = _points.begin(); i != _points.end(); ++i) { - SelectableControlPoint *cur = i->first; - Geom::OptRect r = cur->bounds(); - bound.unionWith(r); - } - return bound; + return size() == 1 ? (*_points.begin())->bounds() : _bounds; } void ControlPointSelection::showTransformHandles(bool v, bool one_node) @@ -311,28 +266,96 @@ void ControlPointSelection::toggleTransformHandlesMode() } } -void ControlPointSelection::_pointGrabbed() +void ControlPointSelection::_pointGrabbed(SelectableControlPoint *point) { hideTransformHandles(); _dragging = true; + _grabbed_point = point; + _farthest_point = point; + double maxdist = 0; + Geom::Matrix m; + m.setIdentity(); + for (iterator i = _points.begin(); i != _points.end(); ++i) { + _original_positions.insert(std::make_pair(*i, (*i)->position())); + _last_trans.insert(std::make_pair(*i, m)); + double dist = Geom::distance(*_grabbed_point, **i); + if (dist > maxdist) { + maxdist = dist; + _farthest_point = *i; + } + } } -void ControlPointSelection::_pointDragged(Geom::Point const &old_pos, Geom::Point &new_pos, - GdkEventMotion *event) +void ControlPointSelection::_pointDragged(Geom::Point &new_pos, GdkEventMotion *event) { - Geom::Point delta = new_pos - old_pos; - for (iterator i = _points.begin(); i != _points.end(); ++i) { - SelectableControlPoint *cur = i->first; - cur->move(cur->position() + delta); + Geom::Point abs_delta = new_pos - _original_positions[_grabbed_point]; + double fdist = Geom::distance(_original_positions[_grabbed_point], _original_positions[_farthest_point]); + if (held_only_alt(*event) && fdist > 0) { + // Sculpting + for (iterator i = _points.begin(); i != _points.end(); ++i) { + SelectableControlPoint *cur = (*i); + Geom::Matrix trans; + trans.setIdentity(); + double dist = Geom::distance(_original_positions[cur], _original_positions[_grabbed_point]); + double deltafrac = 0.5 + 0.5 * cos(M_PI * dist/fdist); + if (dist != 0.0) { + // The sculpting transformation is not affine, but it can be + // locally approximated by one. Here we compute the local + // affine approximation of the sculpting transformation near + // the currently transformed point. We then transform the point + // by this approximation. This gives us sensible behavior for node handles. + // NOTE: probably it would be better to transform the node handles, + // but ControlPointSelection is supposed to work for any + // SelectableControlPoints, not only Nodes. We could create a specialized + // NodeSelection class that inherits from this one and move sculpting there. + Geom::Point origdx(Geom::EPSILON, 0); + Geom::Point origdy(0, Geom::EPSILON); + Geom::Point origp = _original_positions[cur]; + Geom::Point origpx = _original_positions[cur] + origdx; + Geom::Point origpy = _original_positions[cur] + origdy; + double distdx = Geom::distance(origpx, _original_positions[_grabbed_point]); + double distdy = Geom::distance(origpy, _original_positions[_grabbed_point]); + double deltafracdx = 0.5 + 0.5 * cos(M_PI * distdx/fdist); + double deltafracdy = 0.5 + 0.5 * cos(M_PI * distdy/fdist); + Geom::Point newp = origp + abs_delta * deltafrac; + Geom::Point newpx = origpx + abs_delta * deltafracdx; + Geom::Point newpy = origpy + abs_delta * deltafracdy; + Geom::Point newdx = (newpx - newp) / Geom::EPSILON; + Geom::Point newdy = (newpy - newp) / Geom::EPSILON; + + Geom::Matrix itrans(newdx[Geom::X], newdx[Geom::Y], newdy[Geom::X], newdy[Geom::Y], 0, 0); + if (itrans.isSingular()) + itrans.setIdentity(); + + trans *= Geom::Translate(-cur->position()); + trans *= _last_trans[cur].inverse(); + trans *= itrans; + trans *= Geom::Translate(_original_positions[cur] + abs_delta * deltafrac); + _last_trans[cur] = itrans; + } else { + trans *= Geom::Translate(-cur->position() + _original_positions[cur] + abs_delta * deltafrac); + } + cur->transform(trans); + //cur->move(_original_positions[cur] + abs_delta * deltafrac); + } + } else { + Geom::Point delta = new_pos - _grabbed_point->position(); + for (iterator i = _points.begin(); i != _points.end(); ++i) { + SelectableControlPoint *cur = (*i); + cur->move(_original_positions[cur] + abs_delta); + } + _handles->rotationCenter().move(_handles->rotationCenter().position() + delta); } - _handles->rotationCenter().move(_handles->rotationCenter().position() + delta); signal_update.emit(); } void ControlPointSelection::_pointUngrabbed() { + _original_positions.clear(); + _last_trans.clear(); _dragging = false; - _grabbed_point = NULL; + _grabbed_point = _farthest_point = NULL; + _updateBounds(); restoreTransformHandles(); signal_commit.emit(COMMIT_MOUSE_MOVE); } @@ -341,24 +364,52 @@ bool ControlPointSelection::_pointClicked(SelectableControlPoint *p, GdkEventBut { // clicking a selected node should toggle the transform handles between rotate and scale mode, // if they are visible - if (held_shift(*event)) return false; - if (_handles_visible && p->selected()) { + if (held_no_modifiers(*event) && _handles_visible && p->selected()) { toggleTransformHandlesMode(); return true; } return false; } +void ControlPointSelection::_pointChanged(SelectableControlPoint *p, bool selected) +{ + _updateBounds(); + _updateTransformHandles(false); + if (_bounds) + _handles->rotationCenter().move(_bounds->midpoint()); + + signal_point_changed.emit(p, selected); +} + +void ControlPointSelection::_mouseoverChanged() +{ + _mouseover_rot_radius = boost::none; +} + +void ControlPointSelection::_updateBounds() +{ + _rot_radius = boost::none; + _bounds = Geom::OptRect(); + for (iterator i = _points.begin(); i != _points.end(); ++i) { + SelectableControlPoint *cur = (*i); + Geom::Point p = cur->position(); + if (!_bounds) { + _bounds = Geom::Rect(p, p); + } else { + _bounds->expandTo(p); + } + } +} + void ControlPointSelection::_updateTransformHandles(bool preserve_center) { if (_dragging) return; if (_handles_visible && size() > 1) { - Geom::OptRect b = pointwiseBounds(); - _handles->setBounds(*b, preserve_center); + _handles->setBounds(*bounds(), preserve_center); _handles->setVisible(true); } else if (_one_node_handles && size() == 1) { // only one control point in selection - SelectableControlPoint *p = begin()->first; + SelectableControlPoint *p = *begin(); _handles->setBounds(p->bounds()); _handles->rotationCenter().move(p->position()); _handles->rotationCenter().setVisible(false); @@ -373,7 +424,7 @@ void ControlPointSelection::_updateTransformHandles(bool preserve_center) bool ControlPointSelection::_keyboardMove(GdkEventKey const &event, Geom::Point const &dir) { if (held_control(event)) return false; - unsigned num = 1 + consume_same_key_events(shortcut_key(event), 0); + unsigned num = 1 + combine_key_events(shortcut_key(event), 0); Geom::Point delta = dir * num; if (held_shift(event)) delta *= 10; @@ -394,6 +445,20 @@ 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". */ +double ControlPointSelection::_rotationRadius(Geom::Point const &rc) +{ + if (empty()) return 1.0; // some safe value + Geom::Rect b = *bounds(); + double maxlen = 0; + for (unsigned i = 0; i < 4; ++i) { + double len = Geom::distance(b.corner(i), rc); + if (len > maxlen) maxlen = len; + } + return maxlen; +} + /** 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 @@ -403,15 +468,25 @@ bool ControlPointSelection::_keyboardRotate(GdkEventKey const &event, int dir) { if (empty()) return false; - Geom::Point rc = _handles->rotationCenter(); - if (!_rot_radius) { - Geom::Rect b = *(size() == 1 ? bounds() : pointwiseBounds()); - double maxlen = 0; - for (unsigned i = 0; i < 4; ++i) { - double len = (b.corner(i) - rc).length(); - if (len > maxlen) maxlen = len; + Geom::Point rc; + + // rotate around the mouseovered point, or the selection's rotation center + // if nothing is mouseovered + double radius; + SelectableControlPoint *scp = + dynamic_cast(ControlPoint::mouseovered_point); + if (scp) { + rc = scp->position(); + if (!_mouseover_rot_radius) { + _mouseover_rot_radius = _rotationRadius(rc); } - _rot_radius = maxlen; + radius = *_mouseover_rot_radius; + } else { + rc = _handles->rotationCenter(); + if (!_rot_radius) { + _rot_radius = _rotationRadius(rc); + } + radius = *_rot_radius; } double angle; @@ -419,7 +494,7 @@ bool ControlPointSelection::_keyboardRotate(GdkEventKey const &event, int dir) // Rotate by "one pixel". We interpret this as rotating by an angle that causes // the topmost point of a circle circumscribed about the selection's bounding box // to move on an arc 1 screen pixel long. - angle = atan2(1.0 / _desktop->current_zoom(), *_rot_radius) * dir; + angle = atan2(1.0 / _desktop->current_zoom(), radius) * dir; } else { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000); @@ -439,11 +514,17 @@ bool ControlPointSelection::_keyboardScale(GdkEventKey const &event, int dir) { if (empty()) return false; - // TODO should the saved rotation center or the current center be used? - Geom::Rect bound = (size() == 1 ? *bounds() : *pointwiseBounds()); - double maxext = bound.maxExtent(); + double maxext = bounds()->maxExtent(); if (Geom::are_near(maxext, 0)) return false; - Geom::Point center = _handles->rotationCenter().position(); + + Geom::Point center; + SelectableControlPoint *scp = + dynamic_cast(ControlPoint::mouseovered_point); + if (scp) { + center = scp->position(); + } else { + center = _handles->rotationCenter().position(); + } double length_change; if (held_alt(event)) { @@ -484,8 +565,9 @@ bool ControlPointSelection::_keyboardFlip(Geom::Dim2 d) return true; } -void ControlPointSelection::_commitTransform(CommitEvent ce) +void ControlPointSelection::_commitHandlesTransform(CommitEvent ce) { + _updateBounds(); _updateTransformHandles(true); signal_commit.emit(ce); } @@ -560,6 +642,24 @@ bool ControlPointSelection::event(GdkEvent *event) return false; } +std::vector ControlPointSelection::getOriginalPoints() +{ + std::vector points; + for (iterator i = _points.begin(); i != _points.end(); ++i) { + points.push_back(Inkscape::SnapCandidatePoint(_original_positions[*i], SNAPSOURCE_NODE_HANDLE)); + } + return points; +} + +void ControlPointSelection::setOriginalPoints() +{ + _original_positions.clear(); + for (iterator i = _points.begin(); i != _points.end(); ++i) { + _original_positions.insert(std::make_pair(*i, (*i)->position())); + } +} + + } // namespace UI } // namespace Inkscape @@ -572,4 +672,4 @@ bool ControlPointSelection::event(GdkEvent *event) fill-column:99 End: */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :