diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /src/ui/tool/transform-handle-set.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-upstream.tar.xz inkscape-upstream.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/ui/tool/transform-handle-set.cpp | 827 |
1 files changed, 827 insertions, 0 deletions
diff --git a/src/ui/tool/transform-handle-set.cpp b/src/ui/tool/transform-handle-set.cpp new file mode 100644 index 0000000..875429a --- /dev/null +++ b/src/ui/tool/transform-handle-set.cpp @@ -0,0 +1,827 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Affine transform handles component + */ +/* Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2009 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cmath> +#include <algorithm> + +#include <glib/gi18n.h> + +#include <2geom/transforms.h> + +#include "control-point.h" +#include "desktop.h" +#include "pure-transform.h" +#include "seltrans.h" +#include "snap.h" + +#include "display/control/canvas-item-rect.h" + +#include "object/sp-namedview.h" + +#include "ui/tool/commit-events.h" +#include "ui/tool/control-point-selection.h" +#include "ui/tool/event-utils.h" +#include "ui/tool/node.h" +#include "ui/tool/selectable-control-point.h" +#include "ui/tool/transform-handle-set.h" +#include "ui/tools/node-tool.h" + + +GType sp_select_context_get_type(); + +namespace Inkscape { +namespace UI { + +namespace { + +SPAnchorType corner_to_anchor(unsigned c) { + switch (c % 4) { + case 0: return SP_ANCHOR_NE; + case 1: return SP_ANCHOR_NW; + case 2: return SP_ANCHOR_SW; + default: return SP_ANCHOR_SE; + } +} + +SPAnchorType side_to_anchor(unsigned s) { + switch (s % 4) { + case 0: return SP_ANCHOR_N; + case 1: return SP_ANCHOR_W; + case 2: return SP_ANCHOR_S; + default: return SP_ANCHOR_E; + } +} + +// TODO move those two functions into a common place +double snap_angle(double a) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000); + double unit_angle = M_PI / snaps; + return CLAMP(unit_angle * round(a / unit_angle), -M_PI, M_PI); +} + +double snap_increment_degrees() { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000); + return 180.0 / snaps; +} + +} // anonymous namespace + +ControlPoint::ColorSet TransformHandle::thandle_cset = { + {0x000000ff, 0x000000ff}, // normal fill, stroke + {0x00ff66ff, 0x000000ff}, // mouseover fill, stroke + {0x00ff66ff, 0x000000ff}, // clicked fill, stroke + // + {0x000000ff, 0x000000ff}, // normal fill, stroke when selected + {0x00ff66ff, 0x000000ff}, // mouseover fill, stroke when selected + {0x00ff66ff, 0x000000ff} // clicked fill, stroke when selected +}; + +TransformHandle::TransformHandle(TransformHandleSet &th, SPAnchorType anchor, Inkscape::CanvasItemCtrlType type) + : ControlPoint(th._desktop, Geom::Point(), anchor, type, thandle_cset, th._transform_handle_group) + , _th(th) +{ + _canvas_item_ctrl->set_name("CanvasItemCtrl:TransformHandle"); + setVisible(false); +} + +// TODO: This code is duplicated in seltrans.cpp; fix this! +void TransformHandle::getNextClosestPoint(bool reverse) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/options/snapclosestonly/value", false)) { + if (!_all_snap_sources_sorted.empty()) { + if (reverse) { // Shift-tab will find a closer point + if (_all_snap_sources_iter == _all_snap_sources_sorted.begin()) { + _all_snap_sources_iter = _all_snap_sources_sorted.end(); + } + --_all_snap_sources_iter; + } else { // Tab will find a point further away + ++_all_snap_sources_iter; + if (_all_snap_sources_iter == _all_snap_sources_sorted.end()) { + _all_snap_sources_iter = _all_snap_sources_sorted.begin(); + } + } + + _snap_points.clear(); + _snap_points.push_back(*_all_snap_sources_iter); + + // Show the updated snap source now; otherwise it won't be shown until the selection is being moved again + SnapManager &m = _desktop->namedview->snap_manager; + m.setup(_desktop); + m.displaySnapsource(*_all_snap_sources_iter); + m.unSetup(); + } + } +} + +bool TransformHandle::grabbed(GdkEventMotion *) +{ + _origin = position(); + _last_transform.setIdentity(); + startTransform(); + + _th._setActiveHandle(this); + _setLurking(true); + _setState(_state); + + // Collect the snap-candidates, one for each selected node. These will be stored in the _snap_points vector. + Inkscape::UI::Tools::NodeTool *nt = INK_NODE_TOOL(_th._desktop->event_context); + //ControlPointSelection *selection = nt->_selected_nodes.get(); + ControlPointSelection* selection = nt->_selected_nodes; + + selection->setOriginalPoints(); + selection->getOriginalPoints(_snap_points); + selection->getUnselectedPoints(_unselected_points); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/options/snapclosestonly/value", false)) { + // Find the closest snap source candidate + _all_snap_sources_sorted = _snap_points; + + // Calculate and store the distance to the reference point for each snap candidate point + for(auto & i : _all_snap_sources_sorted) { + i.setDistance(Geom::L2(i.getPoint() - _origin)); + } + + // Sort them ascending, using the distance calculated above as the single criteria + std::sort(_all_snap_sources_sorted.begin(), _all_snap_sources_sorted.end()); + + // Now get the closest snap source + _snap_points.clear(); + if (!_all_snap_sources_sorted.empty()) { + _all_snap_sources_iter = _all_snap_sources_sorted.begin(); + _snap_points.push_back(_all_snap_sources_sorted.front()); + } + } + + return false; +} + +void TransformHandle::dragged(Geom::Point &new_pos, GdkEventMotion *event) +{ + Geom::Affine t = computeTransform(new_pos, event); + // protect against degeneracies + if (t.isSingular()) return; + Geom::Affine incr = _last_transform.inverse() * t; + if (incr.isSingular()) return; + _th.signal_transform.emit(incr); + _last_transform = t; +} + +void TransformHandle::ungrabbed(GdkEventButton *) +{ + _snap_points.clear(); + _th._clearActiveHandle(); + _setLurking(false); + _setState(_state); + endTransform(); + _th.signal_commit.emit(getCommitEvent()); + + //updates the positions of the nodes + Inkscape::UI::Tools::NodeTool *nt = INK_NODE_TOOL(_th._desktop->event_context); + ControlPointSelection* selection = nt->_selected_nodes; + selection->setOriginalPoints(); +} + + +class ScaleHandle : public TransformHandle { +public: + ScaleHandle(TransformHandleSet &th, SPAnchorType anchor, Inkscape::CanvasItemCtrlType type) + : TransformHandle(th, anchor, type) + {} +protected: + Glib::ustring _getTip(unsigned state) const override { + if (state_held_control(state)) { + if (state_held_shift(state)) { + return C_("Transform handle tip", + "<b>Shift+Ctrl</b>: scale uniformly about the rotation center"); + } + return C_("Transform handle tip", "<b>Ctrl:</b> scale uniformly"); + } + if (state_held_shift(state)) { + if (state_held_alt(state)) { + return C_("Transform handle tip", + "<b>Shift+Alt</b>: scale using an integer ratio about the rotation center"); + } + return C_("Transform handle tip", "<b>Shift</b>: scale from the rotation center"); + } + if (state_held_alt(state)) { + return C_("Transform handle tip", "<b>Alt</b>: scale using an integer ratio"); + } + return C_("Transform handle tip", "<b>Scale handle</b>: drag to scale the selection"); + } + + Glib::ustring _getDragTip(GdkEventMotion */*event*/) const override { + return format_tip(C_("Transform handle tip", + "Scale by %.2f%% x %.2f%%"), _last_scale_x * 100, _last_scale_y * 100); + } + + bool _hasDragTips() const override { return true; } + + static double _last_scale_x, _last_scale_y; +}; +double ScaleHandle::_last_scale_x = 1.0; +double ScaleHandle::_last_scale_y = 1.0; + +/** + * Corner scaling handle for node transforms. + */ +class ScaleCornerHandle : public ScaleHandle { +public: + + ScaleCornerHandle(TransformHandleSet &th, unsigned corner, unsigned d_corner) : + ScaleHandle(th, corner_to_anchor(d_corner), Inkscape::CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE), + _corner(corner) + {} + +protected: + void startTransform() override { + _sc_center = _th.rotationCenter(); + _sc_opposite = _th.bounds().corner(_corner + 2); + _last_scale_x = _last_scale_y = 1.0; + } + + Geom::Affine computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) override { + Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite; + Geom::Point vold = _origin - scc, vnew = new_pos - scc; + // avoid exploding the selection + if (Geom::are_near(vold[Geom::X], 0) || Geom::are_near(vold[Geom::Y], 0)) + return Geom::identity(); + + Geom::Scale scale = Geom::Scale(vnew[Geom::X] / vold[Geom::X], vnew[Geom::Y] / vold[Geom::Y]); + + if (held_alt(*event)) { + for (unsigned i = 0; i < 2; ++i) { + if (fabs(scale[i]) >= 1.0) { + scale[i] = round(scale[i]); + } else { + scale[i] = 1.0 / round(1.0 / MIN(scale[i],10)); + } + } + } else { + SnapManager &m = _th._desktop->namedview->snap_manager; + m.setupIgnoreSelection(_th._desktop, true, &_unselected_points); + + Inkscape::PureScale *ptr; + if (held_control(*event)) { + scale[0] = scale[1] = std::min(scale[0], scale[1]); + ptr = new Inkscape::PureScaleConstrained(Geom::Scale(scale[0], scale[1]), scc); + } else { + ptr = new Inkscape::PureScale(Geom::Scale(scale[0], scale[1]), scc, false); + } + m.snapTransformed(_snap_points, _origin, (*ptr)); + m.unSetup(); + if (ptr->best_snapped_point.getSnapped()) { + scale = ptr->getScaleSnapped(); + } + + delete ptr; + } + + _last_scale_x = scale[0]; + _last_scale_y = scale[1]; + Geom::Affine t = Geom::Translate(-scc) + * Geom::Scale(scale[0], scale[1]) + * Geom::Translate(scc); + return t; + } + + CommitEvent getCommitEvent() override { + return _last_transform.isUniformScale() + ? COMMIT_MOUSE_SCALE_UNIFORM + : COMMIT_MOUSE_SCALE; + } + +private: + + Geom::Point _sc_center; + Geom::Point _sc_opposite; + unsigned _corner; +}; + +/** + * Side scaling handle for node transforms. + */ +class ScaleSideHandle : public ScaleHandle { +public: + ScaleSideHandle(TransformHandleSet &th, unsigned side, unsigned d_side) + : ScaleHandle(th, side_to_anchor(d_side), Inkscape::CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE) + , _side(side) + {} +protected: + void startTransform() override { + _sc_center = _th.rotationCenter(); + Geom::Rect b = _th.bounds(); + _sc_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3)); + _last_scale_x = _last_scale_y = 1.0; + } + Geom::Affine computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) override { + Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite; + Geom::Point vs; + Geom::Dim2 d1 = static_cast<Geom::Dim2>((_side + 1) % 2); + Geom::Dim2 d2 = static_cast<Geom::Dim2>(_side % 2); + + // avoid exploding the selection + if (Geom::are_near(scc[d1], _origin[d1])) + return Geom::identity(); + + vs[d1] = (new_pos - scc)[d1] / (_origin - scc)[d1]; + if (held_alt(*event)) { + if (fabs(vs[d1]) >= 1.0) { + vs[d1] = round(vs[d1]); + } else { + vs[d1] = 1.0 / round(1.0 / MIN(vs[d1],10)); + } + vs[d2] = 1.0; + } else { + SnapManager &m = _th._desktop->namedview->snap_manager; + m.setupIgnoreSelection(_th._desktop, true, &_unselected_points); + + bool uniform = held_control(*event); + Inkscape::PureStretchConstrained psc = Inkscape::PureStretchConstrained(vs[d1], scc, d1, uniform); + m.snapTransformed(_snap_points, _origin, psc); + m.unSetup(); + + if (psc.best_snapped_point.getSnapped()) { + Geom::Point result = psc.getStretchSnapped().vector(); //best_snapped_point.getTransformation(); + vs[d1] = result[d1]; + vs[d2] = result[d2]; + } else { + // on ctrl, apply uniform scaling instead of stretching + // Preserve aspect ratio, but never flip in the dimension not being edited (by using fabs()) + vs[d2] = uniform ? fabs(vs[d1]) : 1.0; + } + } + + _last_scale_x = vs[Geom::X]; + _last_scale_y = vs[Geom::Y]; + Geom::Affine t = Geom::Translate(-scc) + * Geom::Scale(vs) + * Geom::Translate(scc); + return t; + } + CommitEvent getCommitEvent() override { + return _last_transform.isUniformScale() + ? COMMIT_MOUSE_SCALE_UNIFORM + : COMMIT_MOUSE_SCALE; + } + +private: + + Geom::Point _sc_center; + Geom::Point _sc_opposite; + unsigned _side; +}; + +/** + * Rotation handle for node transforms. + */ +class RotateHandle : public TransformHandle { +public: + RotateHandle(TransformHandleSet &th, unsigned corner, unsigned d_corner) + : TransformHandle(th, corner_to_anchor(d_corner), Inkscape::CANVAS_ITEM_CTRL_TYPE_ADJ_ROTATE) + , _corner(corner) + {} +protected: + + void startTransform() override { + _rot_center = _th.rotationCenter(); + _rot_opposite = _th.bounds().corner(_corner + 2); + _last_angle = 0; + } + + Geom::Affine computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) override + { + Geom::Point rotc = held_shift(*event) ? _rot_opposite : _rot_center; + double angle = Geom::angle_between(_origin - rotc, new_pos - rotc); + if (held_control(*event)) { + angle = snap_angle(angle); + } else { + SnapManager &m = _th._desktop->namedview->snap_manager; + m.setupIgnoreSelection(_th._desktop, true, &_unselected_points); + Inkscape::PureRotateConstrained prc = Inkscape::PureRotateConstrained(angle, rotc); + m.snapTransformed(_snap_points, _origin, prc); + m.unSetup(); + + if (prc.best_snapped_point.getSnapped()) { + angle = prc.getAngleSnapped(); //best_snapped_point.getTransformation()[0]; + } + } + + _last_angle = angle; + Geom::Affine t = Geom::Translate(-rotc) + * Geom::Rotate(angle) + * Geom::Translate(rotc); + return t; + } + + CommitEvent getCommitEvent() override { return COMMIT_MOUSE_ROTATE; } + + Glib::ustring _getTip(unsigned state) const override { + if (state_held_shift(state)) { + if (state_held_control(state)) { + return format_tip(C_("Transform handle tip", + "<b>Shift+Ctrl</b>: rotate around the opposite corner and snap " + "angle to %f° increments"), snap_increment_degrees()); + } + return C_("Transform handle tip", "<b>Shift</b>: rotate around the opposite corner"); + } + if (state_held_control(state)) { + return format_tip(C_("Transform handle tip", + "<b>Ctrl</b>: snap angle to %f° increments"), snap_increment_degrees()); + } + return C_("Transform handle tip", "<b>Rotation handle</b>: drag to rotate " + "the selection around the rotation center"); + } + + Glib::ustring _getDragTip(GdkEventMotion */*event*/) const override { + return format_tip(C_("Transform handle tip", "Rotate by %.2f°"), + _last_angle * 180.0 / M_PI); + } + + bool _hasDragTips() const override { return true; } + +private: + Geom::Point _rot_center; + Geom::Point _rot_opposite; + unsigned _corner; + static double _last_angle; +}; +double RotateHandle::_last_angle = 0; + +class SkewHandle : public TransformHandle { +public: + SkewHandle(TransformHandleSet &th, unsigned side, unsigned d_side) + : TransformHandle(th, side_to_anchor(d_side), Inkscape::CANVAS_ITEM_CTRL_TYPE_ADJ_SKEW) + , _side(side) + {} + +protected: + + void startTransform() override { + _skew_center = _th.rotationCenter(); + Geom::Rect b = _th.bounds(); + _skew_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3)); + _last_angle = 0; + _last_horizontal = _side % 2; + } + + Geom::Affine computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) override + { + Geom::Point scc = held_shift(*event) ? _skew_center : _skew_opposite; + Geom::Dim2 d1 = static_cast<Geom::Dim2>((_side + 1) % 2); + Geom::Dim2 d2 = static_cast<Geom::Dim2>(_side % 2); + + Geom::Point const initial_delta = _origin - scc; + + if (fabs(initial_delta[d1]) < 1e-15) { + return Geom::Affine(); + } + + // Calculate the scale factors, which can be either visual or geometric + // depending on which type of bbox is currently being used (see preferences -> selector tool) + Geom::Scale scale = calcScaleFactors(_origin, new_pos, scc, false); + Geom::Scale skew = calcScaleFactors(_origin, new_pos, scc, true); + scale[d2] = 1; + skew[d2] = 1; + + // Skew handles allow scaling up to integer multiples of the original size + // in the second direction; prevent explosions + + if (fabs(scale[d1]) < 1) { + // Prevent shrinking of the selected object, while allowing mirroring + scale[d1] = copysign(1.0, scale[d1]); + } else { + // Allow expanding of the selected object by integer multiples + scale[d1] = floor(scale[d1] + 0.5); + } + + double angle = atan(skew[d1] / scale[d1]); + + if (held_control(*event)) { + angle = snap_angle(angle); + skew[d1] = tan(angle) * scale[d1]; + } else { + SnapManager &m = _th._desktop->namedview->snap_manager; + m.setupIgnoreSelection(_th._desktop, true, &_unselected_points); + + Inkscape::PureSkewConstrained psc = Inkscape::PureSkewConstrained(skew[d1], scale[d1], scc, d2); + m.snapTransformed(_snap_points, _origin, psc); + m.unSetup(); + + if (psc.best_snapped_point.getSnapped()) { + skew[d1] = psc.getSkewSnapped(); //best_snapped_point.getTransformation()[0]; + } + } + + _last_angle = angle; + + // Update the handle position + Geom::Point new_new_pos; + new_new_pos[d2] = initial_delta[d1] * skew[d1] + _origin[d2]; + new_new_pos[d1] = initial_delta[d1] * scale[d1] + scc[d1]; + + // Calculate the relative affine + Geom::Affine relative_affine = Geom::identity(); + relative_affine[2*d1 + d1] = (new_new_pos[d1] - scc[d1]) / initial_delta[d1]; + relative_affine[2*d1 + (d2)] = (new_new_pos[d2] - _origin[d2]) / initial_delta[d1]; + relative_affine[2*(d2) + (d1)] = 0; + relative_affine[2*(d2) + (d2)] = 1; + + for (int i = 0; i < 2; i++) { + if (fabs(relative_affine[3*i]) < 1e-15) { + relative_affine[3*i] = 1e-15; + } + } + + Geom::Affine t = Geom::Translate(-scc) + * relative_affine + * Geom::Translate(scc); + + return t; + } + + CommitEvent getCommitEvent() override { + return (_side % 2) + ? COMMIT_MOUSE_SKEW_Y + : COMMIT_MOUSE_SKEW_X; + } + + Glib::ustring _getTip(unsigned state) const override { + if (state_held_shift(state)) { + if (state_held_control(state)) { + return format_tip(C_("Transform handle tip", + "<b>Shift+Ctrl</b>: skew about the rotation center with snapping " + "to %f° increments"), snap_increment_degrees()); + } + return C_("Transform handle tip", "<b>Shift</b>: skew about the rotation center"); + } + if (state_held_control(state)) { + return format_tip(C_("Transform handle tip", + "<b>Ctrl</b>: snap skew angle to %f° increments"), snap_increment_degrees()); + } + return C_("Transform handle tip", + "<b>Skew handle</b>: drag to skew (shear) selection about " + "the opposite handle"); + } + + Glib::ustring _getDragTip(GdkEventMotion */*event*/) const override { + if (_last_horizontal) { + return format_tip(C_("Transform handle tip", "Skew horizontally by %.2f°"), + _last_angle * 360.0); + } else { + return format_tip(C_("Transform handle tip", "Skew vertically by %.2f°"), + _last_angle * 360.0); + } + } + + bool _hasDragTips() const override { return true; } + +private: + + Geom::Point _skew_center; + Geom::Point _skew_opposite; + unsigned _side; + static bool _last_horizontal; + static double _last_angle; +}; +bool SkewHandle::_last_horizontal = false; +double SkewHandle::_last_angle = 0; + +class RotationCenter : public ControlPoint { + +public: + RotationCenter(TransformHandleSet &th) : + ControlPoint(th._desktop, Geom::Point(), SP_ANCHOR_CENTER, + Inkscape::CANVAS_ITEM_CTRL_TYPE_ADJ_CENTER, + _center_cset, th._transform_handle_group), + _th(th) + { + setVisible(false); + } + +protected: + void dragged(Geom::Point &new_pos, GdkEventMotion *event) override { + SnapManager &sm = _th._desktop->namedview->snap_manager; + sm.setup(_th._desktop); + bool snap = !held_shift(*event) && sm.someSnapperMightSnap(); + if (held_control(*event)) { + // constrain to axes + Geom::Point origin = _last_drag_origin(); + std::vector<Inkscape::Snapper::SnapConstraint> constraints; + constraints.emplace_back(origin, Geom::Point(1, 0)); + constraints.emplace_back(origin, Geom::Point(0, 1)); + new_pos = sm.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(new_pos, + SNAPSOURCE_ROTATION_CENTER), constraints, held_shift(*event)).getPoint(); + } else if (snap) { + sm.freeSnapReturnByRef(new_pos, SNAPSOURCE_ROTATION_CENTER); + } + sm.unSetup(); + } + Glib::ustring _getTip(unsigned /*state*/) const override { + return C_("Transform handle tip", + "<b>Rotation center</b>: drag to change the origin of transforms"); + } + +private: + + static ColorSet _center_cset; + TransformHandleSet &_th; +}; + +ControlPoint::ColorSet RotationCenter::_center_cset = { + {0x000000ff, 0x000000ff}, // normal fill, stroke + {0x00ff66ff, 0x000000ff}, // mouseover fill, stroke + {0x00ff66ff, 0x000000ff}, // clicked fill, stroke + // + {0x000000ff, 0x000000ff}, // normal fill, stroke when selected + {0x00ff66ff, 0x000000ff}, // mouseover fill, stroke when selected + {0x00ff66ff, 0x000000ff} // clicked fill, stroke when selected +}; + + +TransformHandleSet::TransformHandleSet(SPDesktop *d, Inkscape::CanvasItemGroup *th_group) + : Manipulator(d) + , _active(nullptr) + , _transform_handle_group(th_group) + , _mode(MODE_SCALE) + , _in_transform(false) + , _visible(true) +{ + _trans_outline = new Inkscape::CanvasItemRect(_desktop->getCanvasControls()); + _trans_outline->set_name("CanvasItemRect:Transform"); + _trans_outline->hide(); + _trans_outline->set_dashed(true); + + bool y_inverted = !d->is_yaxisdown(); + for (unsigned i = 0; i < 4; ++i) { + unsigned d_c = y_inverted ? i : 3 - i; + unsigned d_s = y_inverted ? i : 6 - i; + _scale_corners[i] = new ScaleCornerHandle(*this, i, d_c); + _scale_sides[i] = new ScaleSideHandle(*this, i, d_s); + _rot_corners[i] = new RotateHandle(*this, i, d_c); + _skew_sides[i] = new SkewHandle(*this, i, d_s); + } + _center = new RotationCenter(*this); + // when transforming, update rotation center position + signal_transform.connect(sigc::mem_fun(*_center, &RotationCenter::transform)); +} + +TransformHandleSet::~TransformHandleSet() +{ + for (auto & _handle : _handles) { + delete _handle; + } +} + +void TransformHandleSet::setMode(Mode m) +{ + _mode = m; + _updateVisibility(_visible); +} + +Geom::Rect TransformHandleSet::bounds() const +{ + return Geom::Rect(*_scale_corners[0], *_scale_corners[2]); +} + +ControlPoint const &TransformHandleSet::rotationCenter() const +{ + return *_center; +} + +ControlPoint &TransformHandleSet::rotationCenter() +{ + return *_center; +} + +void TransformHandleSet::setVisible(bool v) +{ + if (_visible != v) { + _visible = v; + _updateVisibility(_visible); + } +} + +void TransformHandleSet::setBounds(Geom::Rect const &r, bool preserve_center) +{ + if (_in_transform) { + _trans_outline->set_rect(r); + } else { + for (unsigned i = 0; i < 4; ++i) { + _scale_corners[i]->move(r.corner(i)); + _scale_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1))); + _rot_corners[i]->move(r.corner(i)); + _skew_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1))); + } + if (!preserve_center) _center->move(r.midpoint()); + if (_visible) _updateVisibility(true); + } +} + +bool TransformHandleSet::event(Inkscape::UI::Tools::ToolBase *, GdkEvent*) +{ + return false; +} + +void TransformHandleSet::_emitTransform(Geom::Affine const &t) +{ + signal_transform.emit(t); + _center->transform(t); +} + +void TransformHandleSet::_setActiveHandle(ControlPoint *th) +{ + _active = th; + if (_in_transform) + throw std::logic_error("Transform initiated when another transform in progress"); + _in_transform = true; + // hide all handles except the active one + _updateVisibility(false); + _trans_outline->show(); +} + +void TransformHandleSet::_clearActiveHandle() +{ + // This can only be called from handles, so they had to be visible before _setActiveHandle + _trans_outline->hide(); + _active = nullptr; + _in_transform = false; + _updateVisibility(_visible); +} + +void TransformHandleSet::_updateVisibility(bool v) +{ + if (v) { + Geom::Rect b = bounds(); + + // Roughly estimate handle size. + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int handle_index = prefs->getIntLimited("/options/grabsize/value", 3, 1, 15); + int handle_size = handle_index * 2 + 1; // Handle pixmaps are actually larger but that's to allow space when handle is rotated. + + Geom::Point bp = b.dimensions(); + + // do not scale when the bounding rectangle has zero width or height + bool show_scale = (_mode == MODE_SCALE) && !Geom::are_near(b.minExtent(), 0); + // do not rotate if the bounding rectangle is degenerate + bool show_rotate = (_mode == MODE_ROTATE_SKEW) && !Geom::are_near(b.maxExtent(), 0); + bool show_scale_side[2], show_skew[2]; + + // show sides if: + // a) there is enough space between corner handles, or + // b) corner handles are not shown, but side handles make sense + // this affects horizontal and vertical scale handles; skew handles never + // make sense if rotate handles are not shown + for (unsigned i = 0; i < 2; ++i) { + Geom::Dim2 d = static_cast<Geom::Dim2>(i); + Geom::Dim2 otherd = static_cast<Geom::Dim2>((i+1)%2); + show_scale_side[i] = (_mode == MODE_SCALE); + show_scale_side[i] &= (show_scale ? bp[d] >= handle_size + : !Geom::are_near(bp[otherd], 0)); + show_skew[i] = (show_rotate && bp[d] >= handle_size + && !Geom::are_near(bp[otherd], 0)); + } + + for (unsigned i = 0; i < 4; ++i) { + _scale_corners[i]->setVisible(show_scale); + _rot_corners[i]->setVisible(show_rotate); + _scale_sides[i]->setVisible(show_scale_side[i%2]); + _skew_sides[i]->setVisible(show_skew[i%2]); + } + + // show rotation center + _center->setVisible(show_rotate); + } else { + for (auto & _handle : _handles) { + if (_handle != _active) + _handle->setVisible(false); + } + } + +} + +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |