seltrans.cpp revision a4030d5ca449e7e384bc699cd249ee704faaeab0
919N/A#ifdef HAVE_CONFIG_H
919N/A#include "document.h"
287N/A#include "sp-namedview.h"
287N/A#include "desktop-handles.h"
287N/A#include "desktop-style.h"
287N/A#include "selection.h"
851N/A#include "select-context.h"
911N/A#include "sp-item-transform.h"
911N/A#include "seltrans-handles.h"
911N/A#include "seltrans.h"
911N/A#include "selection-chemistry.h"
287N/A#include "sp-metrics.h"
287N/A#include "display/sp-ctrlline.h"
287N/A#include "preferences.h"
287N/A#include "display/snap-indicator.h"
969N/Astatic void sp_sel_trans_handle_new_event(SPKnot *knot, Geom::Point *position, guint32 state, gpointer data);
287N/Astatic gboolean sp_sel_trans_handle_request(SPKnot *knot, Geom::Point *p, guint state, gboolean *data);
992N/A case GDK_MOTION_NOTIFY:
287N/A case GDK_KEY_PRESS:
return FALSE;
return TRUE;
return FALSE;
_grabbed(false),
_show_handles(true),
_bbox(),
NULL);
NULL);
if (_shandle[i]) {
if (_rhandle[i]) {
if (_chandle) {
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;
_approximate_bbox = selection->bounds(SPItem::APPROXIMATE_BBOX); // Used for correctly scaling the strokewidth
_point = p;
if (_geometric_bbox) {
_point_geom = p;
std::vector<Inkscape::SnapCandidatePoint> snap_points_hull = selection->getSnapPointsConvexHull(&m.snapprefs);
// 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_with_stroke()). This however is currently only implemented for a single bbox.
if (((_items.size() > 0) && (_items.size() < 50)) || prefs->getBool("/options/snapclosestonly/value", false)) {
// (see the comment a few lines above). In that case we will use the bbox of the selection as a whole
getBBoxPoints(sp_item_bbox_desktop(_items[i], _snap_bbox_type), &_bbox_points_for_translating, false, true, emp, mp);
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 discard those other
if (_snap_points.size() > 1 || _bbox_points.size() > 1 || _bbox_points_for_translating.size() > 1) {
g_warning("Incorrect assumption encountered while finding the snap source; nothing serious, but please report to Diederik");
Geom::Coord dsp = _snap_points.size() == 1 ? Geom::L2((_snap_points.at(0)).getPoint() - p) : NR_HUGE;
Geom::Coord dbbp = _bbox_points.size() == 1 ? Geom::L2((_bbox_points.at(0)).getPoint() - p) : NR_HUGE;
Geom::Coord dbbpft = _bbox_points_for_translating.size() == 1 ? Geom::L2((_bbox_points_for_translating.at(0)).getPoint() - p) : NR_HUGE;
if (translating) {
g_warning("Checking number of snap sources failed; nothing serious, but please report to Diederik");
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;
_chandle = sp_knot_new(_desktop, _("<b>Center</b> of rotation and skewing: drag to reposition; scaling with Shift also uses this center"));
_("<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"));
if (!_center_is_set) {
_center_is_set = true;
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) {
//SPItem::APPROXIMATE_BBOX will be replaced by SPItem::VISUAL_BBOX, as soon as the latter is implemented properly
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
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_with_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();
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::Matrix abs_affine = Geom::Translate(-_origin) * Geom::Matrix(default_scale) * Geom::Translate(_origin);
bool transform_stroke = false;
_absolute_affine = get_scale_transform_with_stroke (*_approximate_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);
void Inkscape::SelTrans::_keepClosestPointOnly(std::vector<Inkscape::SnapCandidatePoint> &points, const Geom::Point &reference)
Inkscape::SnapCandidatePoint closest_point = Inkscape::SnapCandidatePoint(Geom::Point(NR_HUGE, NR_HUGE), SNAPSOURCE_UNDEFINED, SNAPTARGET_UNDEFINED);
for(std::vector<Inkscape::SnapCandidatePoint>::const_iterator i = points.begin(); i != points.end(); i++) {
closest_point = *i;