X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fui%2Ftool%2Fcontrol-point-selection.cpp;h=baa53f76ea920b8e4d208393b25184e4f11e679b;hb=bd7bd2319ce1940e8cdba8becbecf5af45800959;hp=d10045c623598dffffaf1ae545837c2aeed5fc0f;hpb=44aa4bdf094f63e3f9e5ccf1c84b835c3e06863b;p=inkscape.git diff --git a/src/ui/tool/control-point-selection.cpp b/src/ui/tool/control-point-selection.cpp index d10045c62..baa53f76e 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" @@ -49,21 +50,17 @@ ControlPointSelection::ControlPointSelection(SPDesktop *d, SPCanvasGroup *th_gro , _dragging(false) , _handles_visible(true) , _one_node_handles(false) - , _sculpt_enabled(false) - , _sculpting(false) { 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() @@ -80,30 +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))); - // hide event parameter - clist->push_back( - x->signal_ungrabbed.connect( - sigc::hide( - sigc::mem_fun(*this, &ControlPointSelection::_selectionUngrabbed)))); - - 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); } @@ -111,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) { @@ -140,15 +112,70 @@ void ControlPointSelection::clear() erase(i++); } -/** Transform all selected control points by the supplied affine transformation. */ +/** Select all points that this selection can contain. */ +void ControlPointSelection::selectAll() +{ + for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) { + insert(*i); + } +} +/** Select all points inside the given rectangle (in desktop coordinates). */ +void ControlPointSelection::selectArea(Geom::Rect const &r) +{ + for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) { + if (r.contains(**i)) + insert(*i); + } +} +/** Unselect all selected points and select all unselected points. */ +void ControlPointSelection::invertSelection() +{ + for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) { + if ((*i)->selected()) erase(*i); + else insert(*i); + } +} +void ControlPointSelection::spatialGrow(SelectableControlPoint *origin, int dir) +{ + bool grow = (dir > 0); + Geom::Point p = origin->position(); + double best_dist = grow ? HUGE_VAL : 0; + SelectableControlPoint *match = NULL; + for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) { + bool selected = (*i)->selected(); + if (grow && !selected) { + double dist = Geom::distance((*i)->position(), p); + if (dist < best_dist) { + best_dist = dist; + match = *i; + } + } + if (!grow && selected) { + double dist = Geom::distance((*i)->position(), p); + // use >= to also deselect the origin node when it's the last one selected + if (dist >= best_dist) { + best_dist = dist; + match = *i; + } + } + } + if (match) { + if (grow) insert(match); + else erase(match); + } +} + +/** Transform all selected control points by the given affine transformation. */ 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(); } @@ -160,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); } } @@ -184,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])); } @@ -205,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) @@ -245,69 +256,160 @@ void ControlPointSelection::restoreTransformHandles() _updateTransformHandles(true); } -void ControlPointSelection::_selectionGrabbed(SelectableControlPoint *p, GdkEventMotion *event) +void ControlPointSelection::toggleTransformHandlesMode() { - hideTransformHandles(); - _dragging = true; - if (held_alt(*event) && _sculpt_enabled) { - _sculpting = true; - _grabbed_point = p; + if (_handles->mode() == TransformHandleSet::MODE_SCALE) { + _handles->setMode(TransformHandleSet::MODE_ROTATE_SKEW); + if (size() == 1) _handles->rotationCenter().setVisible(false); } else { - _sculpting = false; + _handles->setMode(TransformHandleSet::MODE_SCALE); } } -void ControlPointSelection::_selectionDragged(Geom::Point const &old_pos, Geom::Point &new_pos, - GdkEventMotion *event) +void ControlPointSelection::_pointGrabbed(SelectableControlPoint *point) { - Geom::Point delta = new_pos - old_pos; - /*if (_sculpting) { - // for now we only support the default sculpting profile (bell) - // others will be added when preferences will be able to store enumerated values - double pressure, alpha; - if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &pressure)) { - pressure = CLAMP(pressure, 0.2, 0.8); - } else { - pressure = 0.5; + 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; } + } +} - alpha = 1 - 2 * fabs(pressure - 0.5); - if (pressure > 0.5) alpha = 1/alpha; - +void ControlPointSelection::_pointDragged(Geom::Point &new_pos, GdkEventMotion *event) +{ + 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->first; - double dist = Geom::distance(cur->position(), _grabbed_point->position()); - - cur->move(cur->position() + delta); + 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*/ { + } else { + Geom::Point delta = new_pos - _grabbed_point->position(); for (iterator i = _points.begin(); i != _points.end(); ++i) { - SelectableControlPoint *cur = i->first; - cur->move(cur->position() + delta); + SelectableControlPoint *cur = (*i); + cur->move(_original_positions[cur] + abs_delta); } _handles->rotationCenter().move(_handles->rotationCenter().position() + delta); } signal_update.emit(); } -void ControlPointSelection::_selectionUngrabbed() +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); } +bool ControlPointSelection::_pointClicked(SelectableControlPoint *p, GdkEventButton *event) +{ + // clicking a selected node should toggle the transform handles between rotate and scale mode, + // if they are visible + 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); @@ -322,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; @@ -343,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 @@ -352,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; @@ -368,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); @@ -388,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)) { @@ -433,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); } @@ -491,13 +624,7 @@ bool ControlPointSelection::event(GdkEvent *event) case GDK_h: case GDK_H: if (held_shift(event->key)) { - // TODO make a method for mode switching - if (_handles->mode() == TransformHandleSet::MODE_SCALE) { - _handles->setMode(TransformHandleSet::MODE_ROTATE_SKEW); - if (size() == 1) _handles->rotationCenter().setVisible(false); - } else { - _handles->setMode(TransformHandleSet::MODE_SCALE); - } + toggleTransformHandlesMode(); return true; } // any modifiers except shift should cause no action @@ -527,4 +654,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 :