index 5a84592b69113dbf0a47ea055fa206de877996dc..615587eebb7aaa62bc38277c7978bae2b936ca48 100644 (file)
* Released under GNU GPL, read the file 'COPYING' for more information
*/
+#include <boost/none.hpp>
#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::iterator, bool> ControlPointSelection::insert(c
return std::pair<iterator, bool>(found, false);
}
- boost::shared_ptr<connlist_type> 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<iterator, bool>(found, true);
}
@@ -111,15 +88,10 @@ std::pair<ControlPointSelection::iterator, bool> ControlPointSelection::insert(c
/** Remove a point from the selection. */
void ControlPointSelection::erase(iterator pos)
{
- SelectableControlPoint *erased = pos->first;
- boost::shared_ptr<connlist_type> 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)
{
@@ -197,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();
}
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);
}
}
// 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]));
}
* 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)
_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_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);
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;
@@ -396,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
@@ -405,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<SelectableControlPoint*>(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;
// 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);
{
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<SelectableControlPoint*>(ControlPoint::mouseovered_point);
+ if (scp) {
+ center = scp->position();
+ } else {
+ center = _handles->rotationCenter().position();
+ }
double length_change;
if (held_alt(event)) {
return true;
}
-void ControlPointSelection::_commitTransform(CommitEvent ce)
+void ControlPointSelection::_commitHandlesTransform(CommitEvent ce)
{
+ _updateBounds();
_updateTransformHandles(true);
signal_commit.emit(ce);
}
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