X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fseltrans.cpp;h=9c874c242cb666d8d2b07f18c92deb0e7b1846bd;hb=724821145d62dee9f97465c706952582da6e432d;hp=5ace790ecf8a4c46e0ec6de66b6b0de2f928dbfa;hpb=21c27920218c0cee0a6231c9fff353dfd5ad74d4;p=inkscape.git diff --git a/src/seltrans.cpp b/src/seltrans.cpp index 5ace790ec..9c874c242 100644 --- a/src/seltrans.cpp +++ b/src/seltrans.cpp @@ -88,11 +88,15 @@ Inkscape::SelTrans::SelTrans(SPDesktop *desktop) : _show(SHOW_CONTENT), _grabbed(false), _show_handles(true), - _box(NR::Nothing()), + _bbox(NR::Nothing()), + _approximate_bbox(NR::Nothing()), _chandle(NULL), _stamp_cache(NULL), _message_context(desktop->messageStack()) { + gchar const *prefs_bbox = prefs_get_string_attribute("tools.select", "bounding_box"); + _snap_bbox_type = (prefs_bbox != NULL && strcmp(prefs_bbox, "geometric")==0)? SPItem::GEOMETRIC_BBOX : SPItem::APPROXIMATE_BBOX; + g_return_if_fail(desktop != NULL); for (int i = 0; i < 8; i++) { @@ -254,56 +258,70 @@ void Inkscape::SelTrans::grab(NR::Point const &p, gdouble x, gdouble y, bool sho _current.set_identity(); _point = p; + + // The selector tool should snap the bbox and the special snappoints, but not path nodes + // (The special points are the handles, center, rotation axis, font baseline, ends of spiral, etc.) + + // First, determine the bounding box for snapping ... + _bbox = selection->bounds(_snap_bbox_type); + _approximate_bbox = selection->bounds(SPItem::APPROXIMATE_BBOX); // Used for correctly scaling the strokewidth - _snap_points = selection->getSnapPoints(); + + // Next, get all special points for snapping + _snap_points = selection->getSnapPoints(); // Excludes path nodes + std::vector snap_points_hull = selection->getSnapPointsConvexHull(); // Includes path nodes if (_snap_points.size() > 100) { /* Snapping a huge number of nodes will take way too long, so limit the number of snappable nodes An average user would rarely ever try to snap such a large number of nodes anyway, because (s)he could hardly discern which node would be snapping */ - _snap_points = selection->getSnapPointsConvexHull(); + _snap_points = snap_points_hull; // Unfortunately, by now we will have lost the font-baseline snappoints :-( } - _box = selection->bounds(); + + // Find bbox hulling all special points, which excludes stroke width. Here we need to include the + // path nodes, for example because a rectangle which has been converted to a path doesn't have + // any other special points + NR::Rect snap_points_bbox; + if ( snap_points_hull.empty() == false ) { + std::vector::iterator i = snap_points_hull.begin(); + snap_points_bbox = NR::Rect(*i, *i); + i++; + while (i != snap_points_hull.end()) { + snap_points_bbox.expandTo(*i); + i++; + } + } + _bbox_points.clear(); - if (_box) { + if (_bbox) { + // ... and add the bbox corners to _bbox_points for ( unsigned i = 0 ; i < 4 ; i++ ) { - _bbox_points.push_back(_box->corner(i)); + _bbox_points.push_back(_bbox->corner(i)); } + // There are two separate "opposites" (i.e. opposite w.r.t. the handle being dragged): + // - one for snapping the boundingbox, which can be either visual or geometric + // - one for snapping the special points + // 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 #1540195 (in which + // a box is caught between to guides) + _opposite_for_bboxpoints = _bbox->min() + _bbox->dimensions() * NR::scale(1-x, 1-y); + _opposite_for_specpoints = (snap_points_bbox.min() + (snap_points_bbox.dimensions() * NR::scale(1-x, 1-y) ) ); + // Only a single "opposite" can be used in calculating transformations. + _opposite = _opposite_for_bboxpoints; } - - gchar const *scale_origin = prefs_get_string_attribute("tools.select", "scale_origin"); - bool const origin_on_bbox = (scale_origin == NULL || !strcmp(scale_origin, "bbox")); - - /*Snapping will be to either nodes or to boundingbox-cornes; each will require its own origin, which - is only slightly different from the other. When we would use an origin at one of the nodes while - trying to snap the boundingbox, all four points of the boundingbox would be moving (e.g. during stretching), - and would therefore also be snapping (which is bad). This leads to bugs similar to #1540195, in which - a box is caught between to guides. To solve this, we need two different points: _opposite_for_snappoints and - _opposite_for_boundingbox - */ - - if (_box) { - _opposite_for_bboxpoints = _box->min() + _box->dimensions() * NR::scale(1-x, 1-y); - - NR::Rect op_box = *_box; - // FIXME: should be using ConvexHull here - if ( _snap_points.empty() == false) { - std::vector::iterator i = _snap_points.begin(); - op_box = NR::Rect(*i, *i); - i++; - while (i != _snap_points.end()) { - op_box.expandTo(*i); - i++; - } - } - _opposite_for_snappoints = ( op_box.min() + ( op_box.dimensions() * NR::scale(1-x, 1-y) ) ); - //until we can kick out the old _opposite, and use _opposite_for_bboxpoints or _opposite_for_snappoints everywhere - //keep the old behavior for _opposite: - _opposite = origin_on_bbox ? _opposite_for_bboxpoints : _opposite_for_snappoints; - //FIXME: get rid of _opposite. Requires different handling of preferences, see Bulia Byak's comment for bug #1540195 + /*std::cout << "Number of snap points: " << _snap_points.size() << std::endl; + for (std::vector::const_iterator i = _snap_points.begin(); i != _snap_points.end(); i++) + { + std::cout << " " << *i << std::endl; } + std::cout << "Number of bbox points: " << _bbox_points.size() << std::endl; + for (std::vector::const_iterator i = _bbox_points.begin(); i != _bbox_points.end(); i++) + { + std::cout << " " << *i << std::endl; + }*/ + if ((x != -1) && (y != -1)) { sp_canvas_item_show(_norm); sp_canvas_item_show(_grip); @@ -333,11 +351,11 @@ void Inkscape::SelTrans::transform(NR::Matrix const &rel_affine, NR::Point const sp_item_set_i2d_affine(&item, prev_transform * affine); } } else { - if (_box) { + if (_bbox) { NR::Point p[4]; /* update the outline */ for (unsigned i = 0 ; i < 4 ; i++) { - p[i] = _box->corner(i) * affine; + p[i] = _bbox->corner(i) * affine; } for (unsigned i = 0 ; i < 4 ; i++) { sp_ctrlline_set_coords(SP_CTRLLINE(_l[i]), p[i], p[(i+1)%4]); @@ -577,8 +595,11 @@ void Inkscape::SelTrans::_updateVolatileState() return; } - _box = selection->bounds(); - if (!_box) { + //Update the bboxes + _bbox = selection->bounds(_snap_bbox_type); + _approximate_bbox = selection->bounds(SPItem::APPROXIMATE_BBOX); + + if (!_bbox) { _empty = true; return; } @@ -627,9 +648,9 @@ void Inkscape::SelTrans::_showHandles(SPKnot *knot[], SPSelTransHandle const han NR::Point const handle_pt(handle[i].x, handle[i].y); // shouldn't have nullary bbox, but knots - g_assert(_box); - NR::Point p( _box->min() - + ( _box->dimensions() + g_assert(_bbox); + NR::Point p( _bbox->min() + + ( _bbox->dimensions() * NR::scale(handle_pt) ) ); sp_knot_moveto(knot[i], &p); @@ -749,11 +770,11 @@ gboolean Inkscape::SelTrans::handleRequest(SPKnot *knot, NR::Point *position, gu if ((!(state & GDK_SHIFT_MASK) == !(_state == STATE_ROTATE)) && (&handle != &handle_center)) { _origin = _opposite; _origin_for_bboxpoints = _opposite_for_bboxpoints; - _origin_for_snappoints = _opposite_for_snappoints; + _origin_for_specpoints = _opposite_for_specpoints; } else if (_center) { _origin = *_center; _origin_for_bboxpoints = *_center; - _origin_for_snappoints = *_center; + _origin_for_specpoints = *_center; } else { // FIXME return TRUE; @@ -771,6 +792,11 @@ gboolean Inkscape::SelTrans::handleRequest(SPKnot *knot, NR::Point *position, gu void Inkscape::SelTrans::_selChanged(Inkscape::Selection *selection) { if (!_grabbed) { + // reread in case it changed on the fly: + gchar const *prefs_bbox = prefs_get_string_attribute("tools.select", "bounding_box"); + _snap_bbox_type = (prefs_bbox != NULL && strcmp(prefs_bbox, "geometric")==0)? SPItem::GEOMETRIC_BBOX : SPItem::APPROXIMATE_BBOX; + //SPItem::APPROXIMATE_BBOX will be replaced by SPItem::VISUAL_BBOX, as soon as the latter is implemented properly + _updateVolatileState(); _current.set_identity(); _center_is_set = false; // center(s) may have changed @@ -864,12 +890,19 @@ gboolean Inkscape::SelTrans::scaleRequest(NR::Point &pt, guint state) } if ((state & GDK_CONTROL_MASK) || _desktop->isToolboxButtonActive ("lock")) { - /* Scale is locked to a 1:1 aspect ratio, so that s[X] must be made to equal s[Y]. - ** To do this, we snap along a suitable constraint vector from the origin. - */ - + // Scale is locked to a 1:1 aspect ratio, so that s[X] must be made to equal s[Y]. + // + // The aspect-ratio must be locked before snapping + if (fabs(s[NR::X]) > fabs(s[NR::Y])) { + s[NR::X] = fabs(s[NR::Y]) * sign(s[NR::X]); + } else { + s[NR::Y] = fabs(s[NR::X]) * sign(s[NR::Y]); + } + + // Snap along a suitable constraint vector from the origin. + // The inclination of the constraint vector is calculated from the aspect ratio - NR::Point bbox_dim = _box->dimensions(); + NR::Point bbox_dim = _bbox->dimensions(); double const aspect_ratio = bbox_dim[1] / bbox_dim[0]; // = height / width // Determine direction of the constraint vector @@ -888,21 +921,13 @@ gboolean Inkscape::SelTrans::scaleRequest(NR::Point &pt, guint state) std::pair sn = m.constrainedSnapScale(Snapper::SNAP_POINT, _snap_points, it, - Snapper::ConstraintLine(_origin_for_snappoints, cv), + Snapper::ConstraintLine(_origin_for_specpoints, cv), s, - _origin_for_snappoints); + _origin_for_specpoints); if (bb.second == false && sn.second == false) { - - /* We didn't snap, so just lock aspect ratio */ - if (fabs(s[NR::X]) > fabs(s[NR::Y])) { - s[NR::X] = fabs(s[NR::Y]) * sign(s[NR::X]); - } else { - s[NR::Y] = fabs(s[NR::X]) * sign(s[NR::Y]); - } - + /* We didn't snap, so just keep the locked aspect ratio */ } else { - /* Choose the smaller difference in scale. Since s[X] == s[Y] we can ** just compare difference in s[X]. */ @@ -923,7 +948,7 @@ gboolean Inkscape::SelTrans::scaleRequest(NR::Point &pt, guint state) _snap_points, it, s, - _origin_for_snappoints); + _origin_for_specpoints); /* Pick the snap that puts us closest to the original scale */ NR::Coord bd = bb.second ? @@ -993,6 +1018,7 @@ gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, NR:: SnapManager const &m = _desktop->namedview->snap_manager; if ( state & GDK_CONTROL_MASK ) { + // on ctrl, apply symmetrical scaling instead of stretching s[perp] = fabs(s[axis]); std::pair const bb = m.freeSnapStretch( @@ -1009,7 +1035,7 @@ gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, NR:: _snap_points, it, s[axis], - _origin_for_snappoints, + _origin_for_specpoints, axis, true); @@ -1035,7 +1061,7 @@ gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, NR:: _snap_points, it, s[axis], - _origin_for_snappoints, + _origin_for_specpoints, axis, false); @@ -1122,7 +1148,7 @@ gboolean Inkscape::SelTrans::skewRequest(SPSelTransHandle const &handle, NR::Poi _snap_points, std::list(), skew[dim_a], - _origin_for_snappoints, + _origin_for_specpoints, dim_b); if (bb.second || sn.second) { @@ -1214,21 +1240,21 @@ gboolean Inkscape::SelTrans::centerRequest(NR::Point &pt, guint state) } } - if ( !(state & GDK_SHIFT_MASK) && _box ) { + if ( !(state & GDK_SHIFT_MASK) && _bbox ) { // screen pixels to snap center to bbox #define SNAP_DIST 5 // FIXME: take from prefs double snap_dist = SNAP_DIST / _desktop->current_zoom(); for (int i = 0; i < 2; i++) { - if (fabs(pt[i] - _box->min()[i]) < snap_dist) { - pt[i] = _box->min()[i]; + if (fabs(pt[i] - _bbox->min()[i]) < snap_dist) { + pt[i] = _bbox->min()[i]; } - if (fabs(pt[i] - _box->midpoint()[i]) < snap_dist) { - pt[i] = _box->midpoint()[i]; + if (fabs(pt[i] - _bbox->midpoint()[i]) < snap_dist) { + pt[i] = _bbox->midpoint()[i]; } - if (fabs(pt[i] - _box->max()[i]) < snap_dist) { - pt[i] = _box->max()[i]; + if (fabs(pt[i] - _bbox->max()[i]) < snap_dist) { + pt[i] = _bbox->max()[i]; } } } @@ -1308,23 +1334,30 @@ void Inkscape::SelTrans::stretch(SPSelTransHandle const &handle, NR::Point &pt, s[!dim] = fabs(s[dim]); } - if (!_box) { + if (!_bbox) { return; } - NR::Point new_bbox_min = _box->min() * (NR::translate(-scale_origin) * NR::Matrix(s) * NR::translate(scale_origin)); - NR::Point new_bbox_max = _box->max() * (NR::translate(-scale_origin) * NR::Matrix(s) * NR::translate(scale_origin)); + NR::Point new_bbox_min = _approximate_bbox->min() * (NR::translate(-scale_origin) * NR::Matrix(s) * NR::translate(scale_origin)); + NR::Point new_bbox_max = _approximate_bbox->max() * (NR::translate(-scale_origin) * NR::Matrix(s) * NR::translate(scale_origin)); + + int transform_stroke = false; + gdouble strokewidth = 0; - int transform_stroke = prefs_get_int_attribute ("options.transform", "stroke", 1); - NR::Matrix scaler = get_scale_transform_with_stroke (*_box, _strokewidth, transform_stroke, - new_bbox_min[NR::X], new_bbox_min[NR::Y], new_bbox_max[NR::X], new_bbox_max[NR::Y]); + if ( _snap_bbox_type != SPItem::GEOMETRIC_BBOX) { + transform_stroke = prefs_get_int_attribute ("options.transform", "stroke", 1); + strokewidth = _strokewidth; + } + + NR::Matrix scaler = get_scale_transform_with_stroke (*_approximate_bbox, strokewidth, transform_stroke, + new_bbox_min[NR::X], new_bbox_min[NR::Y], new_bbox_max[NR::X], new_bbox_max[NR::Y]); transform(scaler, NR::Point(0, 0)); // we have already accounted for origin, so pass 0,0 } void Inkscape::SelTrans::scale(NR::Point &pt, guint state) { - if (!_box) { + if (!_bbox) { return; } @@ -1337,13 +1370,21 @@ void Inkscape::SelTrans::scale(NR::Point &pt, guint state) if (fabs(s[i]) < 1e-9) s[i] = 1e-9; } - NR::Point new_bbox_min = _box->min() * (NR::translate(-_origin) * NR::Matrix(s) * NR::translate(_origin)); - NR::Point new_bbox_max = _box->max() * (NR::translate(-_origin) * NR::Matrix(s) * NR::translate(_origin)); - - int transform_stroke = prefs_get_int_attribute ("options.transform", "stroke", 1); - NR::Matrix scaler = get_scale_transform_with_stroke (*_box, _strokewidth, transform_stroke, - new_bbox_min[NR::X], new_bbox_min[NR::Y], new_bbox_max[NR::X], new_bbox_max[NR::Y]); + + NR::Point new_bbox_min = _approximate_bbox->min() * (NR::translate(-_origin) * NR::Matrix(s) * NR::translate(_origin)); + NR::Point new_bbox_max = _approximate_bbox->max() * (NR::translate(-_origin) * NR::Matrix(s) * NR::translate(_origin)); + + int transform_stroke = false; + gdouble strokewidth = 0; + if ( _snap_bbox_type != SPItem::GEOMETRIC_BBOX) { + transform_stroke = prefs_get_int_attribute ("options.transform", "stroke", 1); + strokewidth = _strokewidth; + } + + NR::Matrix scaler = get_scale_transform_with_stroke (*_approximate_bbox, strokewidth, transform_stroke, + new_bbox_min[NR::X], new_bbox_min[NR::Y], new_bbox_max[NR::X], new_bbox_max[NR::Y]); + transform(scaler, NR::Point(0, 0)); // we have already accounted for origin, so pass 0,0 }