seltrans.cpp revision 39604a5993c9e99970592313a75a01be735a877a
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * Helper object for transforming selected items.
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm/* Authors:
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * Lauris Kaplinski <lauris@kaplinski.com>
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * bulia byak <buliabyak@users.sf.net>
1b3a8414f17dc95fc921d999ea715c99d10dd4aaAlex Valavanis * Carl Hetherington <inkscape@carlh.net>
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * Diederik van Lierop <mail@diedenrezi.nl>
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * Abhishek Sharma
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * Copyright (C) 1999-2002 Lauris Kaplinski
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * Copyright (C) 1999-2008 Authors
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * Released under GNU GPL, read the file 'COPYING' for more information
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrmstatic void sp_remove_handles(SPKnot *knot[], gint num);
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrmstatic void sp_sel_trans_handle_grab(SPKnot *knot, guint state, gpointer data);
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrmstatic void sp_sel_trans_handle_ungrab(SPKnot *knot, guint state, gpointer data);
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrmstatic void sp_sel_trans_handle_click(SPKnot *knot, guint state, gpointer data);
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrmstatic void sp_sel_trans_handle_new_event(SPKnot *knot, Geom::Point *position, guint32 state, gpointer data);
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrmstatic gboolean sp_sel_trans_handle_request(SPKnot *knot, Geom::Point *p, guint state, gboolean *data);
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrmstatic gboolean sp_seltrans_handle_event(SPKnot *knot, GdkEvent *event, gpointer)
6d4d113f18776c07a193beeab77046b475858945johanengelen /* stamping mode: both mode(show content and outline) operation with knot */
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm Inkscape::SelTrans *seltrans = SP_SELECT_CONTEXT(desktop->event_context)->_seltrans;
d37634d73670180f99a3e0ea583621373d90ec4fJohan EngelenInkscape::SelTrans::SelTrans(SPDesktop *desktop) :
0903335a0099bd7ee779925f43a15a2216a0e863johanengelen Inkscape::Preferences *prefs = Inkscape::Preferences::get();
0903335a0099bd7ee779925f43a15a2216a0e863johanengelen int prefs_bbox = prefs->getBool("/tools/bounding_box");
0903335a0099bd7ee779925f43a15a2216a0e863johanengelen for (int i = 0; i < 8; i++) {
0903335a0099bd7ee779925f43a15a2216a0e863johanengelen _center_is_set = false; // reread _center from items, or set to bbox midpoint
fefed98e624e5b375661d137181340caa08440e3johanengelen _norm = sp_canvas_item_new(sp_desktop_controls(desktop),
797bee69297bbdd86c5cff2e0771a71d1e2ac69dcilix _grip = sp_canvas_item_new(sp_desktop_controls(desktop),
797bee69297bbdd86c5cff2e0771a71d1e2ac69dcilix for (int i = 0; i < 4; i++) {
797bee69297bbdd86c5cff2e0771a71d1e2ac69dcilix _l[i] = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
1e944d29efb206f5d0b5d1069cb098e22169d548cilix sigc::mem_fun(*this, &Inkscape::SelTrans::_selChanged)
797bee69297bbdd86c5cff2e0771a71d1e2ac69dcilix _sel_modified_connection = _selection->connectModified(
1e944d29efb206f5d0b5d1069cb098e22169d548cilix sigc::mem_fun(*this, &Inkscape::SelTrans::_selModified)
7655c8b8ffe3674dd7e7c74f450fb7194943c0deJon A. Cruz _all_snap_sources_iter = _all_snap_sources_sorted.end();
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm for (unsigned int i = 0; i < 8; i++) {
if (_norm) {
if (_grip) {
if (_l[i]) {
_center = p;
_center_is_set = true;
void Inkscape::SelTrans::grab(Geom::Point const &p, gdouble x, gdouble y, bool show_handles, bool translating)
// While dragging a handle, we will either scale, skew, or rotate and the "translating" parameter will be false
// When dragging the selected item itself however, we will translate the selection and that parameter will be true
_grabbed = true;
_changed = false;
if (_empty) {
_items_centers.push_back(it->getCenter()); // for content-dragging, we need to remember original centers
_handle_x = x;
_handle_y = y;
_point = p;
if (_geometric_bbox) {
_point_geom = p;
std::vector<Inkscape::SnapCandidatePoint> snap_points_hull = selection->getSnapPointsConvexHull(&m.snapprefs);
// 1) Preferably we'd use the bbox of each selected item, instead of the bbox of the selection as a whole; for translations
// this is easy to do, but when snapping the visual bbox while scaling we will have to compensate for the scaling of the
// stroke width. (see get_scale_transform_for_stroke()). This however is currently only implemented for a single bbox.
// (see the comment a few lines above). In that case we will use the bbox of the selection as a whole
if (_bbox) {
// There are two separate "opposites" (i.e. opposite w.r.t. the handle being dragged):
// The "opposite" in case of a geometric boundingbox always coincides with the "opposite" for the special points
// These distinct "opposites" are needed in the snapmanager to avoid bugs such as #sf1540195 (in which
_opposite_for_specpoints = snap_points_bbox.min() + snap_points_bbox.dimensions() * Geom::Scale(1-x, 1-y);
// When snapping the node closest to the mouse pointer is absolutely preferred over the closest snap
// (i.e. when weight == 1), then we will not even try to snap to other points and disregard those other points
if (_bbox) {
_changed = true;
_grabbed = false;
_show_handles = true;
if (_stamp_cache) {
sp_selection_apply_affine(selection, _current_relative_affine, (_show == SHOW_OUTLINE)? true : false);
if (_center) {
_center_is_set = true;
if (_center_is_set) {
if (!_empty) {
GSList *l;
if (_stamp_cache) {
l = _stamp_cache;
_stamp_cache = l;
l = l->next;
if (!_center_is_set) {
_center_is_set = true;
_("<b>Squeeze or stretch</b> selection; with <b>Ctrl</b> to scale uniformly; with <b>Shift</b> to scale around rotation center"),
_("<b>Scale</b> selection; with <b>Ctrl</b> to scale uniformly; with <b>Shift</b> to scale around rotation center"));
_("<b>Skew</b> selection; with <b>Ctrl</b> to snap angle; with <b>Shift</b> to skew around the opposite side"),
_("<b>Rotate</b> selection; with <b>Ctrl</b> to snap angle; with <b>Shift</b> to rotate around the opposite corner"));
* Multiple handles will be shown, for rotating, skewing and the center handle. For straight lines, the bounding box of the center handle will be
* fully overlapped by bounding boxes of two of the skew handles. Due to the internals of sp_canvas_group_point, the center handle must be the
* last handle in the SPCanvasGroup if it is to be selectable in such a case. So we have made sure here that the center handle is added to the
* Now when the center handle is in still in the center, the skew handles can be selected because because the bounding box of the
* center handle does not fully overlap the bounding box of either of the skew handles. However, if the center handle has been moved such that it
* covers one of the other eight handles, then either the opposite handle has to be used (in case of rotating), or the center handle has to be moved.
* Although this is annoying, this is still better than not being able to select the center handle at all
_chandle = sp_knot_new(_desktop, _("<b>Center</b> of rotation and skewing: drag to reposition; scaling with Shift also uses this center"));
if ( _center ) {
if (_empty) {
if (!_bbox) {
_empty = true;
for (int i = 0; i < num; i++) {
for (int i = 0; i < num; i++) {
g_signal_connect(G_OBJECT(knot[i]), "event", G_CALLBACK(sp_seltrans_handle_event), (gpointer) &handle[i]);
static void sp_sel_trans_handle_new_event(SPKnot *knot, Geom::Point *position, guint state, gpointer data)
static gboolean sp_sel_trans_handle_request(SPKnot *knot, Geom::Point *position, guint state, gboolean *data)
case GTK_ANCHOR_CENTER:
case GTK_ANCHOR_CENTER:
NULL);
NULL);
void Inkscape::SelTrans::handleNewEvent(SPKnot *knot, Geom::Point *position, guint state, SPSelTransHandle const &handle)
gboolean Inkscape::SelTrans::handleRequest(SPKnot *knot, Geom::Point *position, guint state, SPSelTransHandle const &handle)
return TRUE;
} else if (_center) {
return TRUE;
return TRUE;
if (!_grabbed) {
if (!_grabbed) {
_changed = false;
static double sign(double const x)
} else if (default_scale[i] != 0) {
m.unSetup();
return TRUE;
gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, Geom::Point &pt, guint state)
case GDK_TOP_SIDE:
case GDK_BOTTOM_SIDE:
case GDK_LEFT_SIDE:
case GDK_RIGHT_SIDE:
return TRUE;
bb = m.constrainedSnapStretch(_bbox_points, _point, Geom::Coord(default_scale[axis]), _origin_for_bboxpoints, Geom::Dim2(axis), symmetrical);
sn = m.constrainedSnapStretch(_snap_points, _point, Geom::Coord(geom_scale[axis]), _origin_for_specpoints, Geom::Dim2(axis), symmetrical);
if (symmetrical) {
m.unSetup();
return TRUE;
gboolean Inkscape::SelTrans::skewRequest(SPSelTransHandle const &handle, Geom::Point &pt, guint state)
case GDK_SB_H_DOUBLE_ARROW:
case GDK_SB_V_DOUBLE_ARROW:
abort();
if (snaps) {
// When skewing, we cannot snap the corners of the bounding box, see the comment in "constrainedSnapSkew" for details
Inkscape::SnappedPoint sn = m.constrainedSnapSkew(_snap_points, _point, constraint, s, _origin, Geom::Dim2(dim_b));
m.unSetup();
degrees);
return TRUE;
if (snaps) {
// When rotating, we cannot snap the corners of the bounding box, see the comment in "constrainedSnapRotate" for details
m.unSetup();
return TRUE;
// centers of any of the selected objects. Therefore we will have to pass the list of selected items
Inkscape::SnappedPoint sp = m.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_ROTATION_CENTER), constraints, state & GDK_SHIFT_MASK);
m.unSetup();
_message_context.setF(Inkscape::NORMAL_MESSAGE, _("Move <b>center</b> to %s, %s"), xs->str, ys->str);
return TRUE;
void sp_sel_trans_stretch(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, Geom::Point &pt, guint state)
void sp_sel_trans_scale(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, Geom::Point &pt, guint state)
void sp_sel_trans_skew(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, Geom::Point &pt, guint state)
void sp_sel_trans_rotate(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, Geom::Point &pt, guint state)
void Inkscape::SelTrans::stretch(SPSelTransHandle const &/*handle*/, Geom::Point &/*pt*/, guint /*state*/)
transform(_absolute_affine, Geom::Point(0, 0)); // we have already accounted for origin, so pass 0,0
transform(_absolute_affine, Geom::Point(0, 0)); // we have already accounted for origin, so pass 0,0
void Inkscape::SelTrans::skew(SPSelTransHandle const &/*handle*/, Geom::Point &/*pt*/, guint /*state*/)
void sp_sel_trans_center(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, Geom::Point &pt, guint /*state*/)
if (alt) {
m.unSetup();
} else if (shift) {
dxy));
dxy));
// Let's leave this timer code here for a while. I'll probably need it in the near future (Diederik van Lierop)
double elapsed = ((((double)endtime.tv_sec - starttime.tv_sec) * G_USEC_PER_SEC + (endtime.tv_usec - starttime.tv_usec))) / 1000.0;
m.unSetup();
if (i->getSnapped()) {
best_snapped_point = *i;
if (control) {
_message_context.setF(Inkscape::NORMAL_MESSAGE, _("<b>Move</b> by %s, %s; with <b>Ctrl</b> to restrict to horizontal/vertical; with <b>Shift</b> to disable snapping"), xs->str, ys->str);
return visual_handle_pos;
if (!_geometric_bbox) {
return visual_handle_pos;
// because this will also hold for _bbox, and which is required for get_scale_transform_for_stroke()
Geom::Rect new_bbox = Geom::Rect(_origin_for_bboxpoints, visual_handle_pos); // new visual bounding box
// Please note that the new_bbox might in fact be just a single line, for example when stretching (in
Geom::Point normalized_handle_pos = (visual_handle_pos - new_bbox.min()) * Geom::Scale(new_bbox.dimensions()).inverse();
Geom::Affine abs_affine = get_scale_transform_for_uniform_stroke (*_bbox, _strokewidth, transform_stroke,
new_bbox.min()[Geom::X], new_bbox.min()[Geom::Y], new_bbox.max()[Geom::X], new_bbox.max()[Geom::Y]);
Geom::Rect new_geom_bbox = Geom::Rect(_geometric_bbox->min() * abs_affine, _geometric_bbox->max() * abs_affine);
return normalized_handle_pos * Geom::Scale(new_geom_bbox.dimensions()) + new_geom_bbox.min(); //new position of the geometric handle
Geom::Scale Inkscape::calcScaleFactors(Geom::Point const &initial_point, Geom::Point const &new_point, Geom::Point const &origin, bool const skew)
if (skew) {
return scale;
// Only for scaling/stretching
Geom::Affine abs_affine = Geom::Translate(-_origin) * Geom::Affine(default_scale) * Geom::Translate(_origin);
bool transform_stroke = false;
_absolute_affine = get_scale_transform_for_uniform_stroke (*_visual_bbox, strokewidth, transform_stroke,
// Only for scaling/stretching
_absolute_affine = Geom::Translate(-_origin_for_specpoints) * _relative_affine * Geom::Translate(_origin_for_specpoints);
if (_geometric_bbox) {
Geom::Rect visual_bbox = get_visual_bbox(_geometric_bbox, _absolute_affine, _strokewidth, transform_stroke);
// If we're not going to snap nodes, then we might just as well get rid of their snappoints right away
if (!(m.snapprefs.isTargetSnappable(SNAPTARGET_NODE_CATEGORY, SNAPTARGET_OTHERS_CATEGORY) || m.snapprefs.isAnyDatumSnappable())) {
// If we're not going to snap bounding boxes, then we might just as well get rid of their snappoints right away
_all_snap_sources_sorted.insert(_all_snap_sources_sorted.end(), _bbox_points.begin(), _bbox_points.end());
for(std::vector<Inkscape::SnapCandidatePoint>::iterator i = _all_snap_sources_sorted.begin(); i != _all_snap_sources_sorted.end(); ++i) {