diff options
Diffstat (limited to 'src/ui/dialog/transformation.cpp')
-rw-r--r-- | src/ui/dialog/transformation.cpp | 1216 |
1 files changed, 1216 insertions, 0 deletions
diff --git a/src/ui/dialog/transformation.cpp b/src/ui/dialog/transformation.cpp new file mode 100644 index 0000000..51ca959 --- /dev/null +++ b/src/ui/dialog/transformation.cpp @@ -0,0 +1,1216 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Transform dialog - implementation. + */ +/* Authors: + * Bryce W. Harrington <bryce@bryceharrington.org> + * buliabyak@gmail.com + * Abhishek Sharma + * + * Copyright (C) 2004, 2005 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "transformation.h" + +#include <gtkmm/dialog.h> + +#include <2geom/transforms.h> + +#include "desktop.h" +#include "document-undo.h" +#include "document.h" +#include "inkscape.h" +#include "message-stack.h" +#include "selection-chemistry.h" + +#include "object/algorithms/bboxsort.h" +#include "object/sp-item-transform.h" +#include "object/sp-namedview.h" + +#include "ui/icon-loader.h" +#include "ui/icon-names.h" + + + +namespace Inkscape { +namespace UI { +namespace Dialog { + + +/*######################################################################## +# C O N S T R U C T O R +########################################################################*/ + +Transformation::Transformation() + : DialogBase("/dialogs/transformation", "Transform"), + _page_move (4, 2), + _page_scale (4, 2), + _page_rotate (4, 2), + _page_skew (4, 2), + _page_transform (3, 3), + _scalar_move_horizontal (_("_Horizontal:"), _("Horizontal displacement (relative) or position (absolute)"), UNIT_TYPE_LINEAR, + "", "transform-move-horizontal", &_units_move), + _scalar_move_vertical (_("_Vertical:"), _("Vertical displacement (relative) or position (absolute)"), UNIT_TYPE_LINEAR, + "", "transform-move-vertical", &_units_move), + _scalar_scale_horizontal(_("_Width:"), _("Horizontal size (absolute or percentage of current)"), UNIT_TYPE_DIMENSIONLESS, + "", "transform-scale-horizontal", &_units_scale), + _scalar_scale_vertical (_("_Height:"), _("Vertical size (absolute or percentage of current)"), UNIT_TYPE_DIMENSIONLESS, + "", "transform-scale-vertical", &_units_scale), + _scalar_rotate (_("A_ngle:"), _("Rotation angle (positive = counterclockwise)"), UNIT_TYPE_RADIAL, + "", "transform-rotate", &_units_rotate), + _scalar_skew_horizontal (_("_Horizontal:"), _("Horizontal skew angle (positive = counterclockwise), or absolute displacement, or percentage displacement"), UNIT_TYPE_LINEAR, + "", "transform-skew-horizontal", &_units_skew), + _scalar_skew_vertical (_("_Vertical:"), _("Vertical skew angle (positive = clockwise), or absolute displacement, or percentage displacement"), UNIT_TYPE_LINEAR, + "", "transform-skew-vertical", &_units_skew), + + _scalar_transform_a ("", _("Transformation matrix element A")), + _scalar_transform_b ("", _("Transformation matrix element B")), + _scalar_transform_c ("", _("Transformation matrix element C")), + _scalar_transform_d ("", _("Transformation matrix element D")), + _scalar_transform_e ("", _("Transformation matrix element E"), UNIT_TYPE_LINEAR, "", "", &_units_transform), + _scalar_transform_f ("", _("Transformation matrix element F"), UNIT_TYPE_LINEAR, "", "", &_units_transform), + + _counterclockwise_rotate (), + _clockwise_rotate (), + + _check_move_relative (_("Rela_tive move")), + _check_scale_proportional (_("_Scale proportionally")), + _check_apply_separately (_("Apply to each _object separately")), + _check_replace_matrix (_("Edit c_urrent matrix")) + +{ + _check_move_relative.set_use_underline(); + _check_move_relative.set_tooltip_text(_("Add the specified relative displacement to the current position; otherwise, edit the current absolute position directly")); + _check_scale_proportional.set_use_underline(); + _check_scale_proportional.set_tooltip_text(_("Preserve the width/height ratio of the scaled objects")); + _check_apply_separately.set_use_underline(); + _check_apply_separately.set_tooltip_text(_("Apply the scale/rotate/skew to each selected object separately; otherwise, transform the selection as a whole")); + _check_replace_matrix.set_use_underline(); + _check_replace_matrix.set_tooltip_text(_("Edit the current transform= matrix; otherwise, post-multiply transform= by this matrix")); + + set_spacing(0); + + // Notebook for individual transformations + pack_start(_notebook, false, false); + + _page_move.set_halign(Gtk::ALIGN_START); + _notebook.append_page(_page_move, _("_Move"), true); + layoutPageMove(); + + _page_scale.set_halign(Gtk::ALIGN_START); + _notebook.append_page(_page_scale, _("_Scale"), true); + layoutPageScale(); + + _page_rotate.set_halign(Gtk::ALIGN_START); + _notebook.append_page(_page_rotate, _("_Rotate"), true); + layoutPageRotate(); + + _page_skew.set_halign(Gtk::ALIGN_START); + _notebook.append_page(_page_skew, _("Ske_w"), true); + layoutPageSkew(); + + _page_transform.set_halign(Gtk::ALIGN_START); + _notebook.append_page(_page_transform, _("Matri_x"), true); + layoutPageTransform(); + + _tabSwitchConn = _notebook.signal_switch_page().connect(sigc::mem_fun(*this, &Transformation::onSwitchPage)); + + // Apply separately + pack_start(_check_apply_separately, false, false); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + _check_apply_separately.set_active(prefs->getBool("/dialogs/transformation/applyseparately")); + _check_apply_separately.signal_toggled().connect(sigc::mem_fun(*this, &Transformation::onApplySeparatelyToggled)); + + // make sure all spinbuttons activate Apply on pressing Enter + ((Gtk::Entry *) (_scalar_move_horizontal.getWidget()))->signal_activate().connect(sigc::mem_fun(*this, &Transformation::_apply)); + ((Gtk::Entry *) (_scalar_move_vertical.getWidget()))->signal_activate().connect(sigc::mem_fun(*this, &Transformation::_apply)); + ((Gtk::Entry *) (_scalar_scale_horizontal.getWidget()))->signal_activate().connect(sigc::mem_fun(*this, &Transformation::_apply)); + ((Gtk::Entry *) (_scalar_scale_vertical.getWidget()))->signal_activate().connect(sigc::mem_fun(*this, &Transformation::_apply)); + ((Gtk::Entry *) (_scalar_rotate.getWidget()))->signal_activate().connect(sigc::mem_fun(*this, &Transformation::_apply)); + ((Gtk::Entry *) (_scalar_skew_horizontal.getWidget()))->signal_activate().connect(sigc::mem_fun(*this, &Transformation::_apply)); + ((Gtk::Entry *) (_scalar_skew_vertical.getWidget()))->signal_activate().connect(sigc::mem_fun(*this, &Transformation::_apply)); + + resetButton = Gtk::manage(new Gtk::Button()); + resetButton->set_image_from_icon_name("reset-settings-symbolic"); + resetButton->set_size_request(30, -1); + resetButton->set_halign(Gtk::ALIGN_CENTER); + resetButton->set_use_underline(); + resetButton->set_tooltip_text(_("Reset the values on the current tab to defaults")); + resetButton->set_sensitive(true); + resetButton->signal_clicked().connect(sigc::mem_fun(*this, &Transformation::onClear)); + + applyButton = Gtk::manage(new Gtk::Button(_("_Apply"))); + applyButton->set_use_underline(); + applyButton->set_halign(Gtk::ALIGN_CENTER); + applyButton->set_tooltip_text(_("Apply transformation to selection")); + applyButton->set_sensitive(false); + applyButton->signal_clicked().connect(sigc::mem_fun(*this, &Transformation::_apply)); + applyButton->get_style_context()->add_class("wide-apply-button"); + + auto button_box = Gtk::manage(new Gtk::Box()); + button_box->set_margin_top(4); + button_box->set_spacing(8); + button_box->set_halign(Gtk::ALIGN_CENTER); + button_box->pack_start(*applyButton); + button_box->pack_start(*resetButton); + pack_start(*button_box, Gtk::PACK_SHRINK, 0); + + show_all_children(); +} + +Transformation::~Transformation() +{ + _tabSwitchConn.disconnect(); +} + +void Transformation::selectionChanged(Inkscape::Selection *selection) +{ + updateSelection((Inkscape::UI::Dialog::Transformation::PageType)getCurrentPage(), selection); +} +void Transformation::selectionModified(Inkscape::Selection *selection, guint flags) +{ + selectionChanged(selection); +} + +/*######################################################################## +# U T I L I T Y +########################################################################*/ + +void Transformation::presentPage(Transformation::PageType page) +{ + _notebook.set_current_page(page); + show(); +} + + + + +/*######################################################################## +# S E T U P L A Y O U T +########################################################################*/ + + +void Transformation::layoutPageMove() +{ + _units_move.setUnitType(UNIT_TYPE_LINEAR); + + _scalar_move_horizontal.initScalar(-1e6, 1e6); + _scalar_move_horizontal.setDigits(3); + _scalar_move_horizontal.setIncrements(0.1, 1.0); + _scalar_move_horizontal.set_hexpand(); + _scalar_move_horizontal.setWidthChars(7); + + _scalar_move_vertical.initScalar(-1e6, 1e6); + _scalar_move_vertical.setDigits(3); + _scalar_move_vertical.setIncrements(0.1, 1.0); + _scalar_move_vertical.set_hexpand(); + _scalar_move_vertical.setWidthChars(7); + + //_scalar_move_vertical.set_label_image( INKSCAPE_STOCK_ARROWS_HOR ); + + _page_move.table().attach(_scalar_move_horizontal, 0, 0, 2, 1); + _page_move.table().attach(_units_move, 2, 0, 1, 1); + + _scalar_move_horizontal.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onMoveValueChanged)); + + //_scalar_move_vertical.set_label_image( INKSCAPE_STOCK_ARROWS_VER ); + _page_move.table().attach(_scalar_move_vertical, 0, 1, 2, 1); + + _scalar_move_vertical.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onMoveValueChanged)); + + // Relative moves + _page_move.table().attach(_check_move_relative, 0, 2, 2, 1); + + _check_move_relative.set_active(true); + _check_move_relative.signal_toggled() + .connect(sigc::mem_fun(*this, &Transformation::onMoveRelativeToggled)); +} + +void Transformation::layoutPageScale() +{ + _units_scale.setUnitType(UNIT_TYPE_DIMENSIONLESS); + _units_scale.setUnitType(UNIT_TYPE_LINEAR); + + _scalar_scale_horizontal.initScalar(-1e6, 1e6); + _scalar_scale_horizontal.setValue(100.0, "%"); + _scalar_scale_horizontal.setDigits(3); + _scalar_scale_horizontal.setIncrements(0.1, 1.0); + _scalar_scale_horizontal.setAbsoluteIsIncrement(true); + _scalar_scale_horizontal.setPercentageIsIncrement(true); + _scalar_scale_horizontal.set_hexpand(); + _scalar_scale_horizontal.setWidthChars(7); + + _scalar_scale_vertical.initScalar(-1e6, 1e6); + _scalar_scale_vertical.setValue(100.0, "%"); + _scalar_scale_vertical.setDigits(3); + _scalar_scale_vertical.setIncrements(0.1, 1.0); + _scalar_scale_vertical.setAbsoluteIsIncrement(true); + _scalar_scale_vertical.setPercentageIsIncrement(true); + _scalar_scale_vertical.set_hexpand(); + _scalar_scale_vertical.setWidthChars(7); + + _page_scale.table().attach(_scalar_scale_horizontal, 0, 0, 2, 1); + + _scalar_scale_horizontal.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onScaleXValueChanged)); + + _page_scale.table().attach(_units_scale, 2, 0, 1, 1); + _page_scale.table().attach(_scalar_scale_vertical, 0, 1, 2, 1); + + _scalar_scale_vertical.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onScaleYValueChanged)); + + _page_scale.table().attach(_check_scale_proportional, 0, 2, 2, 1); + + _check_scale_proportional.set_active(false); + _check_scale_proportional.signal_toggled() + .connect(sigc::mem_fun(*this, &Transformation::onScaleProportionalToggled)); + + //TODO: add a widget for selecting the fixed point in scaling, or honour rotation center? +} + +void Transformation::layoutPageRotate() +{ + _units_rotate.setUnitType(UNIT_TYPE_RADIAL); + + _scalar_rotate.initScalar(-360.0, 360.0); + _scalar_rotate.setDigits(3); + _scalar_rotate.setIncrements(0.1, 1.0); + _scalar_rotate.set_hexpand(); + + auto object_rotate_left_icon = Gtk::manage(sp_get_icon_image("object-rotate-left", Gtk::ICON_SIZE_SMALL_TOOLBAR)); + + _counterclockwise_rotate.add(*object_rotate_left_icon); + _counterclockwise_rotate.set_mode(false); + _counterclockwise_rotate.set_relief(Gtk::RELIEF_NONE); + _counterclockwise_rotate.set_tooltip_text(_("Rotate in a counterclockwise direction")); + + auto object_rotate_right_icon = Gtk::manage(sp_get_icon_image("object-rotate-right", Gtk::ICON_SIZE_SMALL_TOOLBAR)); + + _clockwise_rotate.add(*object_rotate_right_icon); + _clockwise_rotate.set_mode(false); + _clockwise_rotate.set_relief(Gtk::RELIEF_NONE); + _clockwise_rotate.set_tooltip_text(_("Rotate in a clockwise direction")); + + Gtk::RadioButton::Group group = _counterclockwise_rotate.get_group(); + _clockwise_rotate.set_group(group); + + auto box = Gtk::make_managed<Gtk::Box>(); + _counterclockwise_rotate.set_halign(Gtk::ALIGN_START); + _clockwise_rotate.set_halign(Gtk::ALIGN_START); + box->pack_start(_counterclockwise_rotate); + box->pack_start(_clockwise_rotate); + + _page_rotate.table().attach(_scalar_rotate, 0, 0, 1, 1); + _page_rotate.table().attach(_units_rotate, 1, 0, 1, 1); + _page_rotate.table().attach(*box, 1, 1, 1, 1); + + _scalar_rotate.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onRotateValueChanged)); + + _counterclockwise_rotate.signal_clicked().connect(sigc::mem_fun(*this, &Transformation::onRotateCounterclockwiseClicked)); + _clockwise_rotate.signal_clicked().connect(sigc::mem_fun(*this, &Transformation::onRotateClockwiseClicked)); + + //TODO: honour rotation center? +} + +void Transformation::layoutPageSkew() +{ + _units_skew.setUnitType(UNIT_TYPE_LINEAR); + _units_skew.setUnitType(UNIT_TYPE_DIMENSIONLESS); + _units_skew.setUnitType(UNIT_TYPE_RADIAL); + + _scalar_skew_horizontal.initScalar(-1e6, 1e6); + _scalar_skew_horizontal.setDigits(3); + _scalar_skew_horizontal.setIncrements(0.1, 1.0); + _scalar_skew_horizontal.set_hexpand(); + _scalar_skew_horizontal.setWidthChars(7); + + _scalar_skew_vertical.initScalar(-1e6, 1e6); + _scalar_skew_vertical.setDigits(3); + _scalar_skew_vertical.setIncrements(0.1, 1.0); + _scalar_skew_vertical.set_hexpand(); + _scalar_skew_vertical.setWidthChars(7); + + _page_skew.table().attach(_scalar_skew_horizontal, 0, 0, 2, 1); + _page_skew.table().attach(_units_skew, 2, 0, 1, 1); + _page_skew.table().attach(_scalar_skew_vertical, 0, 1, 2, 1); + + _scalar_skew_horizontal.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onSkewValueChanged)); + _scalar_skew_vertical.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onSkewValueChanged)); + + //TODO: honour rotation center? +} + + +void Transformation::layoutPageTransform() +{ + _units_transform.setUnitType(UNIT_TYPE_LINEAR); + _units_transform.set_tooltip_text(_("E and F units")); + _units_transform.set_halign(Gtk::ALIGN_END); + _units_transform.set_margin_top(3); + _units_transform.set_margin_bottom(3); + + UI::Widget::Scalar* labels[] = {&_scalar_transform_a, &_scalar_transform_b, &_scalar_transform_c, &_scalar_transform_d, &_scalar_transform_e, &_scalar_transform_f}; + for (auto label : labels) { + label->hide_label(); + label->set_margin_start(2); + label->set_margin_end(2); + } + _page_transform.table().set_column_spacing(0); + _page_transform.table().set_row_spacing(1); + _page_transform.table().set_column_homogeneous(true); + + _scalar_transform_a.setWidgetSizeRequest(65, -1); + _scalar_transform_a.setRange(-1e10, 1e10); + _scalar_transform_a.setDigits(3); + _scalar_transform_a.setIncrements(0.1, 1.0); + _scalar_transform_a.setValue(1.0); + _scalar_transform_a.setWidthChars(6); + _scalar_transform_a.set_hexpand(); + + _page_transform.table().attach(*Gtk::make_managed<Gtk::Label>("A:"), 0, 0, 1, 1); + _page_transform.table().attach(_scalar_transform_a, 0, 1, 1, 1); + + _scalar_transform_a.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged)); + + _scalar_transform_b.setWidgetSizeRequest(65, -1); + _scalar_transform_b.setRange(-1e10, 1e10); + _scalar_transform_b.setDigits(3); + _scalar_transform_b.setIncrements(0.1, 1.0); + _scalar_transform_b.setValue(0.0); + _scalar_transform_b.setWidthChars(6); + _scalar_transform_b.set_hexpand(); + + _page_transform.table().attach(*Gtk::make_managed<Gtk::Label>("B:"), 0, 2, 1, 1); + _page_transform.table().attach(_scalar_transform_b, 0, 3, 1, 1); + + _scalar_transform_b.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged)); + + _scalar_transform_c.setWidgetSizeRequest(65, -1); + _scalar_transform_c.setRange(-1e10, 1e10); + _scalar_transform_c.setDigits(3); + _scalar_transform_c.setIncrements(0.1, 1.0); + _scalar_transform_c.setValue(0.0); + _scalar_transform_c.setWidthChars(6); + _scalar_transform_c.set_hexpand(); + + _page_transform.table().attach(*Gtk::make_managed<Gtk::Label>("C:"), 1, 0, 1, 1); + _page_transform.table().attach(_scalar_transform_c, 1, 1, 1, 1); + + _scalar_transform_c.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged)); + + + _scalar_transform_d.setWidgetSizeRequest(65, -1); + _scalar_transform_d.setRange(-1e10, 1e10); + _scalar_transform_d.setDigits(3); + _scalar_transform_d.setIncrements(0.1, 1.0); + _scalar_transform_d.setValue(1.0); + _scalar_transform_d.setWidthChars(6); + _scalar_transform_d.set_hexpand(); + + _page_transform.table().attach(*Gtk::make_managed<Gtk::Label>("D:"), 1, 2, 1, 1); + _page_transform.table().attach(_scalar_transform_d, 1, 3, 1, 1); + + _scalar_transform_d.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged)); + + + _scalar_transform_e.setWidgetSizeRequest(65, -1); + _scalar_transform_e.setRange(-1e10, 1e10); + _scalar_transform_e.setDigits(3); + _scalar_transform_e.setIncrements(0.1, 1.0); + _scalar_transform_e.setValue(0.0); + _scalar_transform_e.setWidthChars(6); + _scalar_transform_e.set_hexpand(); + + _page_transform.table().attach(*Gtk::make_managed<Gtk::Label>("E:"), 2, 0, 1, 1); + _page_transform.table().attach(_scalar_transform_e, 2, 1, 1, 1); + + _scalar_transform_e.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged)); + + + _scalar_transform_f.setWidgetSizeRequest(65, -1); + _scalar_transform_f.setRange(-1e10, 1e10); + _scalar_transform_f.setDigits(3); + _scalar_transform_f.setIncrements(0.1, 1.0); + _scalar_transform_f.setValue(0.0); + _scalar_transform_f.setWidthChars(6); + _scalar_transform_f.set_hexpand(); + + _page_transform.table().attach(*Gtk::make_managed<Gtk::Label>("F:"), 2, 2, 1, 1); + _page_transform.table().attach(_scalar_transform_f, 2, 3, 1, 1); + + auto img = Gtk::make_managed<Gtk::Image>(); + img->set_from_icon_name("matrix-2d", Gtk::ICON_SIZE_BUTTON); + img->set_pixel_size(52); + img->set_margin_top(4); + img->set_margin_bottom(4); + _page_transform.table().attach(*img, 0, 5, 1, 1); + + auto descr = Gtk::make_managed<Gtk::Label>(); + descr->set_line_wrap(); + descr->set_line_wrap_mode(Pango::WRAP_WORD); + descr->set_text( + "<small>" + "<a href=\"https://www.w3.org/TR/SVG11/coords.html#TransformMatrixDefined\">" + "2D transformation matrix</a> that combines translation (E,F), scaling (A,D)," + " rotation (A-D) and shearing (B,C)." + "</small>" + ); + descr->set_use_markup(); + _page_transform.table().attach(*descr, 1, 5, 2, 1); + + _page_transform.table().attach(_units_transform, 2, 4, 1, 1); + + _scalar_transform_f.signal_value_changed() + .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged)); + + // Edit existing matrix + _page_transform.table().attach(_check_replace_matrix, 0, 4, 2, 1); + + _check_replace_matrix.set_active(false); + _check_replace_matrix.signal_toggled() + .connect(sigc::mem_fun(*this, &Transformation::onReplaceMatrixToggled)); +} + + +/*######################################################################## +# U P D A T E +########################################################################*/ + +void Transformation::updateSelection(PageType page, Inkscape::Selection *selection) +{ + applyButton->set_sensitive(selection && !selection->isEmpty()); + + if (!selection || selection->isEmpty()) + return; + + switch (page) { + case PAGE_MOVE: { + updatePageMove(selection); + break; + } + case PAGE_SCALE: { + updatePageScale(selection); + break; + } + case PAGE_ROTATE: { + updatePageRotate(selection); + break; + } + case PAGE_SKEW: { + updatePageSkew(selection); + break; + } + case PAGE_TRANSFORM: { + updatePageTransform(selection); + break; + } + case PAGE_QTY: { + break; + } + } +} + +void Transformation::onSwitchPage(Gtk::Widget * /*page*/, guint pagenum) +{ + if (!getDesktop()) { + return; + } + + updateSelection((PageType)pagenum, getDesktop()->getSelection()); +} + + +void Transformation::updatePageMove(Inkscape::Selection *selection) +{ + if (selection && !selection->isEmpty()) { + if (!_check_move_relative.get_active()) { + Geom::OptRect bbox = selection->preferredBounds(); + if (bbox) { + double x = bbox->min()[Geom::X]; + double y = bbox->min()[Geom::Y]; + + double conversion = _units_move.getConversion("px"); + _scalar_move_horizontal.setValue(x / conversion); + _scalar_move_vertical.setValue(y / conversion); + } + } else { + // do nothing, so you can apply the same relative move to many objects in turn + } + _page_move.set_sensitive(true); + } else { + _page_move.set_sensitive(false); + } +} + +void Transformation::updatePageScale(Inkscape::Selection *selection) +{ + if (selection && !selection->isEmpty()) { + Geom::OptRect bbox = selection->preferredBounds(); + if (bbox) { + double w = bbox->dimensions()[Geom::X]; + double h = bbox->dimensions()[Geom::Y]; + _scalar_scale_horizontal.setHundredPercent(w); + _scalar_scale_vertical.setHundredPercent(h); + onScaleXValueChanged(); // to update x/y proportionality if switch is on + _page_scale.set_sensitive(true); + } else { + _page_scale.set_sensitive(false); + } + } else { + _page_scale.set_sensitive(false); + } +} + +void Transformation::updatePageRotate(Inkscape::Selection *selection) +{ + if (selection && !selection->isEmpty()) { + _page_rotate.set_sensitive(true); + } else { + _page_rotate.set_sensitive(false); + } +} + +void Transformation::updatePageSkew(Inkscape::Selection *selection) +{ + if (selection && !selection->isEmpty()) { + Geom::OptRect bbox = selection->preferredBounds(); + if (bbox) { + double w = bbox->dimensions()[Geom::X]; + double h = bbox->dimensions()[Geom::Y]; + _scalar_skew_vertical.setHundredPercent(w); + _scalar_skew_horizontal.setHundredPercent(h); + _page_skew.set_sensitive(true); + } else { + _page_skew.set_sensitive(false); + } + } else { + _page_skew.set_sensitive(false); + } +} + +void Transformation::updatePageTransform(Inkscape::Selection *selection) +{ + if (selection && !selection->isEmpty()) { + if (_check_replace_matrix.get_active()) { + Geom::Affine current (selection->items().front()->transform); // take from the first item in selection + + Geom::Affine new_displayed = current; + + _scalar_transform_a.setValue(new_displayed[0]); + _scalar_transform_b.setValue(new_displayed[1]); + _scalar_transform_c.setValue(new_displayed[2]); + _scalar_transform_d.setValue(new_displayed[3]); + _scalar_transform_e.setValue(new_displayed[4], "px"); + _scalar_transform_f.setValue(new_displayed[5], "px"); + } else { + // do nothing, so you can apply the same matrix to many objects in turn + } + _page_transform.set_sensitive(true); + } else { + _page_transform.set_sensitive(false); + } +} + + + + + +/*######################################################################## +# A P P L Y +########################################################################*/ + + + +void Transformation::_apply() +{ + auto selection = getSelection(); + if (!selection || selection->isEmpty()) + return; + + int const page = _notebook.get_current_page(); + + switch (page) { + case PAGE_MOVE: { + applyPageMove(selection); + break; + } + case PAGE_ROTATE: { + applyPageRotate(selection); + break; + } + case PAGE_SCALE: { + applyPageScale(selection); + break; + } + case PAGE_SKEW: { + applyPageSkew(selection); + break; + } + case PAGE_TRANSFORM: { + applyPageTransform(selection); + break; + } + } + + // Let's play with never turning this off + applyButton->set_sensitive(false); +} + +void Transformation::applyPageMove(Inkscape::Selection *selection) +{ + double x = _scalar_move_horizontal.getValue("px"); + double y = _scalar_move_vertical.getValue("px"); + if (_check_move_relative.get_active()) { + y *= getDesktop()->yaxisdir(); + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (!prefs->getBool("/dialogs/transformation/applyseparately")) { + // move selection as a whole + if (_check_move_relative.get_active()) { + selection->moveRelative(x, y); + } else { + Geom::OptRect bbox = selection->preferredBounds(); + if (bbox) { + selection->moveRelative(x - bbox->min()[Geom::X], y - bbox->min()[Geom::Y]); + } + } + } else { + + if (_check_move_relative.get_active()) { + // shift each object relatively to the previous one + std::vector<SPItem*> selected(selection->items().begin(), selection->items().end()); + if (selected.empty()) return; + + if (fabs(x) > 1e-6) { + std::vector< BBoxSort > sorted; + for (auto item : selected) + { + Geom::OptRect bbox = item->desktopPreferredBounds(); + if (bbox) { + sorted.emplace_back(item, *bbox, Geom::X, x > 0? 1. : 0., x > 0? 0. : 1.); + } + } + //sort bbox by anchors + std::stable_sort(sorted.begin(), sorted.end()); + + double move = x; + for ( std::vector<BBoxSort> ::iterator it (sorted.begin()); + it < sorted.end(); + ++it ) + { + it->item->move_rel(Geom::Translate(move, 0)); + // move each next object by x relative to previous + move += x; + } + } + if (fabs(y) > 1e-6) { + std::vector< BBoxSort > sorted; + for (auto item : selected) + { + Geom::OptRect bbox = item->desktopPreferredBounds(); + if (bbox) { + sorted.emplace_back(item, *bbox, Geom::Y, y > 0? 1. : 0., y > 0? 0. : 1.); + } + } + //sort bbox by anchors + std::stable_sort(sorted.begin(), sorted.end()); + + double move = y; + for ( std::vector<BBoxSort> ::iterator it (sorted.begin()); + it < sorted.end(); + ++it ) + { + it->item->move_rel(Geom::Translate(0, move)); + // move each next object by x relative to previous + move += y; + } + } + } else { + Geom::OptRect bbox = selection->preferredBounds(); + if (bbox) { + selection->moveRelative(x - bbox->min()[Geom::X], y - bbox->min()[Geom::Y]); + } + } + } + + DocumentUndo::done( selection->desktop()->getDocument(), _("Move"), INKSCAPE_ICON("dialog-transform")); +} + +void Transformation::applyPageScale(Inkscape::Selection *selection) +{ + double scaleX = _scalar_scale_horizontal.getValue("px"); + double scaleY = _scalar_scale_vertical.getValue("px"); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool transform_stroke = prefs->getBool("/options/transform/stroke", true); + bool preserve = prefs->getBool("/options/preservetransform/value", false); + if (prefs->getBool("/dialogs/transformation/applyseparately")) { + auto tmp= selection->items(); + for(auto i=tmp.begin();i!=tmp.end();++i){ + SPItem *item = *i; + Geom::OptRect bbox_pref = item->desktopPreferredBounds(); + Geom::OptRect bbox_geom = item->desktopGeometricBounds(); + if (bbox_pref && bbox_geom) { + double new_width = scaleX; + double new_height = scaleY; + // the values are increments! + if (!_units_scale.isAbsolute()) { // Relative scaling, i.e in percent + new_width = scaleX/100 * bbox_pref->width(); + new_height = scaleY/100 * bbox_pref->height(); + } + if (fabs(new_width) < 1e-6) new_width = 1e-6; // not 0, as this would result in a nasty no-bbox object + if (fabs(new_height) < 1e-6) new_height = 1e-6; + + double x0 = bbox_pref->midpoint()[Geom::X] - new_width/2; + double y0 = bbox_pref->midpoint()[Geom::Y] - new_height/2; + double x1 = bbox_pref->midpoint()[Geom::X] + new_width/2; + double y1 = bbox_pref->midpoint()[Geom::Y] + new_height/2; + + Geom::Affine scaler = get_scale_transform_for_variable_stroke (*bbox_pref, *bbox_geom, transform_stroke, preserve, x0, y0, x1, y1); + item->set_i2d_affine(item->i2dt_affine() * scaler); + item->doWriteTransform(item->transform); + } + } + } else { + Geom::OptRect bbox_pref = selection->preferredBounds(); + Geom::OptRect bbox_geom = selection->geometricBounds(); + if (bbox_pref && bbox_geom) { + // the values are increments! + double new_width = scaleX; + double new_height = scaleY; + if (!_units_scale.isAbsolute()) { // Relative scaling, i.e in percent + new_width = scaleX/100 * bbox_pref->width(); + new_height = scaleY/100 * bbox_pref->height(); + } + if (fabs(new_width) < 1e-6) new_width = 1e-6; + if (fabs(new_height) < 1e-6) new_height = 1e-6; + + double x0 = bbox_pref->midpoint()[Geom::X] - new_width/2; + double y0 = bbox_pref->midpoint()[Geom::Y] - new_height/2; + double x1 = bbox_pref->midpoint()[Geom::X] + new_width/2; + double y1 = bbox_pref->midpoint()[Geom::Y] + new_height/2; + Geom::Affine scaler = get_scale_transform_for_variable_stroke (*bbox_pref, *bbox_geom, transform_stroke, preserve, x0, y0, x1, y1); + + selection->applyAffine(scaler); + } + } + + DocumentUndo::done(selection->desktop()->getDocument(), _("Scale"), INKSCAPE_ICON("dialog-transform")); +} + +void Transformation::applyPageRotate(Inkscape::Selection *selection) +{ + double angle = _scalar_rotate.getValue(DEG); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (!prefs->getBool("/dialogs/transformation/rotateCounterClockwise", TRUE)) { + angle *= -1; + } + + if (prefs->getBool("/dialogs/transformation/applyseparately")) { + auto tmp= selection->items(); + for(auto i=tmp.begin();i!=tmp.end();++i){ + SPItem *item = *i; + item->rotate_rel(Geom::Rotate (angle*M_PI/180.0)); + } + } else { + std::optional<Geom::Point> center = selection->center(); + if (center) { + selection->rotateRelative(*center, angle); + } + } + + DocumentUndo::done(selection->desktop()->getDocument(), _("Rotate"), INKSCAPE_ICON("dialog-transform")); +} + +void Transformation::applyPageSkew(Inkscape::Selection *selection) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/dialogs/transformation/applyseparately")) { + auto items = selection->items(); + for(auto i = items.begin();i!=items.end();++i){ + SPItem *item = *i; + + if (!_units_skew.isAbsolute()) { // percentage + double skewX = _scalar_skew_horizontal.getValue("%"); + double skewY = _scalar_skew_vertical.getValue("%"); + skewY *= getDesktop()->yaxisdir(); + if (fabs(0.01*skewX*0.01*skewY - 1.0) < Geom::EPSILON) { + getDesktop()->getMessageStack()->flash(Inkscape::WARNING_MESSAGE, _("Transform matrix is singular, <b>not used</b>.")); + return; + } + item->skew_rel(0.01*skewX, 0.01*skewY); + } else if (_units_skew.isRadial()) { //deg or rad + double angleX = _scalar_skew_horizontal.getValue("rad"); + double angleY = _scalar_skew_vertical.getValue("rad"); + if ((fabs(angleX - angleY + M_PI/2) < Geom::EPSILON) + || (fabs(angleX - angleY - M_PI/2) < Geom::EPSILON) + || (fabs((angleX - angleY)/3 + M_PI/2) < Geom::EPSILON) + || (fabs((angleX - angleY)/3 - M_PI/2) < Geom::EPSILON)) { + getDesktop()->getMessageStack()->flash(Inkscape::WARNING_MESSAGE, _("Transform matrix is singular, <b>not used</b>.")); + return; + } + double skewX = tan(angleX); + double skewY = tan(angleY); + skewX *= getDesktop()->yaxisdir(); + skewY *= getDesktop()->yaxisdir(); + item->skew_rel(skewX, skewY); + } else { // absolute displacement + double skewX = _scalar_skew_horizontal.getValue("px"); + double skewY = _scalar_skew_vertical.getValue("px"); + skewY *= getDesktop()->yaxisdir(); + Geom::OptRect bbox = item->desktopPreferredBounds(); + if (bbox) { + double width = bbox->dimensions()[Geom::X]; + double height = bbox->dimensions()[Geom::Y]; + if (fabs(skewX*skewY - width*height) < Geom::EPSILON) { + getDesktop()->getMessageStack()->flash(Inkscape::WARNING_MESSAGE, _("Transform matrix is singular, <b>not used</b>.")); + return; + } + item->skew_rel(skewX/height, skewY/width); + } + } + } + } else { // transform whole selection + Geom::OptRect bbox = selection->preferredBounds(); + std::optional<Geom::Point> center = selection->center(); + + if ( bbox && center ) { + double width = bbox->dimensions()[Geom::X]; + double height = bbox->dimensions()[Geom::Y]; + + if (!_units_skew.isAbsolute()) { // percentage + double skewX = _scalar_skew_horizontal.getValue("%"); + double skewY = _scalar_skew_vertical.getValue("%"); + skewY *= getDesktop()->yaxisdir(); + if (fabs(0.01*skewX*0.01*skewY - 1.0) < Geom::EPSILON) { + getDesktop()->getMessageStack()->flash(Inkscape::WARNING_MESSAGE, _("Transform matrix is singular, <b>not used</b>.")); + return; + } + selection->skewRelative(*center, 0.01 * skewX, 0.01 * skewY); + } else if (_units_skew.isRadial()) { //deg or rad + double angleX = _scalar_skew_horizontal.getValue("rad"); + double angleY = _scalar_skew_vertical.getValue("rad"); + if ((fabs(angleX - angleY + M_PI/2) < Geom::EPSILON) + || (fabs(angleX - angleY - M_PI/2) < Geom::EPSILON) + || (fabs((angleX - angleY)/3 + M_PI/2) < Geom::EPSILON) + || (fabs((angleX - angleY)/3 - M_PI/2) < Geom::EPSILON)) { + getDesktop()->getMessageStack()->flash(Inkscape::WARNING_MESSAGE, _("Transform matrix is singular, <b>not used</b>.")); + return; + } + double skewX = tan(angleX); + double skewY = tan(angleY); + skewX *= getDesktop()->yaxisdir(); + skewY *= getDesktop()->yaxisdir(); + selection->skewRelative(*center, skewX, skewY); + } else { // absolute displacement + double skewX = _scalar_skew_horizontal.getValue("px"); + double skewY = _scalar_skew_vertical.getValue("px"); + skewY *= getDesktop()->yaxisdir(); + if (fabs(skewX*skewY - width*height) < Geom::EPSILON) { + getDesktop()->getMessageStack()->flash(Inkscape::WARNING_MESSAGE, _("Transform matrix is singular, <b>not used</b>.")); + return; + } + selection->skewRelative(*center, skewX / height, skewY / width); + } + } + } + + DocumentUndo::done(selection->desktop()->getDocument(), _("Skew"), INKSCAPE_ICON("dialog-transform")); +} + + +void Transformation::applyPageTransform(Inkscape::Selection *selection) +{ + double a = _scalar_transform_a.getValue(); + double b = _scalar_transform_b.getValue(); + double c = _scalar_transform_c.getValue(); + double d = _scalar_transform_d.getValue(); + double e = _scalar_transform_e.getValue("px"); + double f = _scalar_transform_f.getValue("px"); + + Geom::Affine displayed(a, b, c, d, e, f); + if (displayed.isSingular()) { + getDesktop()->getMessageStack()->flash(Inkscape::WARNING_MESSAGE, _("Transform matrix is singular, <b>not used</b>.")); + return; + } + + if (_check_replace_matrix.get_active()) { + auto tmp = selection->items(); + for(auto i=tmp.begin();i!=tmp.end();++i){ + SPItem *item = *i; + item->set_item_transform(displayed); + item->updateRepr(); + } + } else { + selection->applyAffine(displayed); // post-multiply each object's transform + } + + DocumentUndo::done(selection->desktop()->getDocument(), _("Edit transformation matrix"), INKSCAPE_ICON("dialog-transform")); +} + + + + + +/*######################################################################## +# V A L U E - C H A N G E D C A L L B A C K S +########################################################################*/ + +void Transformation::onMoveValueChanged() +{ + applyButton->set_sensitive(true); +} + +void Transformation::onMoveRelativeToggled() +{ + auto selection = getSelection(); + if (!selection || selection->isEmpty()) + return; + + double x = _scalar_move_horizontal.getValue("px"); + double y = _scalar_move_vertical.getValue("px"); + + double conversion = _units_move.getConversion("px"); + + //g_message("onMoveRelativeToggled: %f, %f px\n", x, y); + + Geom::OptRect bbox = selection->preferredBounds(); + + if (bbox) { + if (_check_move_relative.get_active()) { + // From absolute to relative + _scalar_move_horizontal.setValue((x - bbox->min()[Geom::X]) / conversion); + _scalar_move_vertical.setValue(( y - bbox->min()[Geom::Y]) / conversion); + } else { + // From relative to absolute + _scalar_move_horizontal.setValue((bbox->min()[Geom::X] + x) / conversion); + _scalar_move_vertical.setValue(( bbox->min()[Geom::Y] + y) / conversion); + } + } + + applyButton->set_sensitive(true); +} + +void Transformation::onScaleXValueChanged() +{ + if (_scalar_scale_horizontal.setProgrammatically) { + _scalar_scale_horizontal.setProgrammatically = false; + return; + } + + applyButton->set_sensitive(true); + + if (_check_scale_proportional.get_active()) { + if (!_units_scale.isAbsolute()) { // percentage, just copy over + _scalar_scale_vertical.setValue(_scalar_scale_horizontal.getValue("%")); + } else { + double scaleXPercentage = _scalar_scale_horizontal.getAsPercentage(); + _scalar_scale_vertical.setFromPercentage (scaleXPercentage); + } + } +} + +void Transformation::onScaleYValueChanged() +{ + if (_scalar_scale_vertical.setProgrammatically) { + _scalar_scale_vertical.setProgrammatically = false; + return; + } + + applyButton->set_sensitive(true); + + if (_check_scale_proportional.get_active()) { + if (!_units_scale.isAbsolute()) { // percentage, just copy over + _scalar_scale_horizontal.setValue(_scalar_scale_vertical.getValue("%")); + } else { + double scaleYPercentage = _scalar_scale_vertical.getAsPercentage(); + _scalar_scale_horizontal.setFromPercentage (scaleYPercentage); + } + } +} + +void Transformation::onRotateValueChanged() +{ + applyButton->set_sensitive(true); +} + +void Transformation::onRotateCounterclockwiseClicked() +{ + _scalar_rotate.setTooltipText(_("Rotation angle (positive = counterclockwise)")); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/dialogs/transformation/rotateCounterClockwise", !getDesktop()->is_yaxisdown()); +} + +void Transformation::onRotateClockwiseClicked() +{ + _scalar_rotate.setTooltipText(_("Rotation angle (positive = clockwise)")); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/dialogs/transformation/rotateCounterClockwise", getDesktop()->is_yaxisdown()); +} + +void Transformation::onSkewValueChanged() +{ + applyButton->set_sensitive(true); +} + +void Transformation::onTransformValueChanged() +{ + + /* + double a = _scalar_transform_a.getValue(); + double b = _scalar_transform_b.getValue(); + double c = _scalar_transform_c.getValue(); + double d = _scalar_transform_d.getValue(); + double e = _scalar_transform_e.getValue(); + double f = _scalar_transform_f.getValue(); + + //g_message("onTransformValueChanged: (%f, %f, %f, %f, %f, %f)\n", + // a, b, c, d, e ,f); + */ + + applyButton->set_sensitive(true); +} + +void Transformation::onReplaceMatrixToggled() +{ + auto selection = getSelection(); + if (!selection || selection->isEmpty()) + return; + + double a = _scalar_transform_a.getValue(); + double b = _scalar_transform_b.getValue(); + double c = _scalar_transform_c.getValue(); + double d = _scalar_transform_d.getValue(); + double e = _scalar_transform_e.getValue("px"); + double f = _scalar_transform_f.getValue("px"); + + Geom::Affine displayed (a, b, c, d, e, f); + Geom::Affine current = selection->items().front()->transform; // take from the first item in selection + + Geom::Affine new_displayed; + if (_check_replace_matrix.get_active()) { + new_displayed = current; + } else { + new_displayed = current.inverse() * displayed; + } + + _scalar_transform_a.setValue(new_displayed[0]); + _scalar_transform_b.setValue(new_displayed[1]); + _scalar_transform_c.setValue(new_displayed[2]); + _scalar_transform_d.setValue(new_displayed[3]); + _scalar_transform_e.setValue(new_displayed[4], "px"); + _scalar_transform_f.setValue(new_displayed[5], "px"); +} + +void Transformation::onScaleProportionalToggled() +{ + onScaleXValueChanged(); + if (_scalar_scale_vertical.setProgrammatically) { + _scalar_scale_vertical.setProgrammatically = false; + } +} + + +void Transformation::onClear() +{ + int const page = _notebook.get_current_page(); + + switch (page) { + case PAGE_MOVE: { + auto selection = getSelection(); + if (!selection || selection->isEmpty() || _check_move_relative.get_active()) { + _scalar_move_horizontal.setValue(0); + _scalar_move_vertical.setValue(0); + } else { + Geom::OptRect bbox = selection->preferredBounds(); + if (bbox) { + _scalar_move_horizontal.setValue(bbox->min()[Geom::X], "px"); + _scalar_move_vertical.setValue(bbox->min()[Geom::Y], "px"); + } + } + break; + } + case PAGE_ROTATE: { + _scalar_rotate.setValue(0); + break; + } + case PAGE_SCALE: { + _scalar_scale_horizontal.setValue(100, "%"); + _scalar_scale_vertical.setValue(100, "%"); + break; + } + case PAGE_SKEW: { + _scalar_skew_horizontal.setValue(0); + _scalar_skew_vertical.setValue(0); + break; + } + case PAGE_TRANSFORM: { + _scalar_transform_a.setValue(1); + _scalar_transform_b.setValue(0); + _scalar_transform_c.setValue(0); + _scalar_transform_d.setValue(1); + _scalar_transform_e.setValue(0, "px"); + _scalar_transform_f.setValue(0, "px"); + break; + } + } +} + +void Transformation::onApplySeparatelyToggled() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/dialogs/transformation/applyseparately", _check_apply_separately.get_active()); +} + +void Transformation::desktopReplaced() +{ + // Setting default unit to document unit + if (auto desktop = getDesktop()) { + SPNamedView *nv = desktop->getNamedView(); + if (nv->display_units) { + _units_move.setUnit(nv->display_units->abbr); + _units_transform.setUnit(nv->display_units->abbr); + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/dialogs/transformation/rotateCounterClockwise", true) != desktop->is_yaxisdown()) { + _counterclockwise_rotate.set_active(); + onRotateCounterclockwiseClicked(); + } else { + _clockwise_rotate.set_active(); + onRotateClockwiseClicked(); + } + + updateSelection(PAGE_MOVE, getSelection()); + } +} + +} // namespace Dialog +} // 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 : |