17#include <sigc++/functors/mem_fun.h>
19#include <gtkmm/button.h>
20#include <gtkmm/entry.h>
21#include <gtkmm/grid.h>
22#include <gtkmm/label.h>
23#include <gtkmm/version.h>
48 :
DialogBase(
"/dialogs/transformation",
"Transform"),
54 _page_transform (3, 3),
56 _scalar_move_horizontal (_(
"_Horizontal:"), _(
"Horizontal displacement (relative) or position (absolute)"), UNIT_TYPE_LINEAR,
57 "transform-move-horizontal", &_units_move),
58 _scalar_move_vertical (_(
"_Vertical:"), _(
"Vertical displacement (relative) or position (absolute)"), UNIT_TYPE_LINEAR,
59 "transform-move-vertical", &_units_move),
60 _scalar_scale_horizontal(_(
"_Width:"), _(
"Horizontal size (absolute or percentage of current)"), UNIT_TYPE_DIMENSIONLESS,
61 "transform-scale-horizontal", &_units_scale),
62 _scalar_scale_vertical (_(
"_Height:"), _(
"Vertical size (absolute or percentage of current)"), UNIT_TYPE_DIMENSIONLESS,
63 "transform-scale-vertical", &_units_scale),
64 _scalar_rotate (_(
"A_ngle:"), _(
"Rotation angle (positive = counterclockwise)"), UNIT_TYPE_RADIAL,
65 "transform-rotate", &_units_rotate),
66 _scalar_skew_horizontal (_(
"_Horizontal:"), _(
"Horizontal skew angle (positive = counterclockwise), or absolute displacement, or percentage displacement"), UNIT_TYPE_LINEAR,
67 "transform-skew-horizontal", &_units_skew),
68 _scalar_skew_vertical (_(
"_Vertical:"), _(
"Vertical skew angle (positive = clockwise), or absolute displacement, or percentage displacement"), UNIT_TYPE_LINEAR,
69 "transform-skew-vertical", &_units_skew),
71 _scalar_transform_a ({}, _(
"Transformation matrix element A")),
72 _scalar_transform_b ({}, _(
"Transformation matrix element B")),
73 _scalar_transform_c ({}, _(
"Transformation matrix element C")),
74 _scalar_transform_d ({}, _(
"Transformation matrix element D")),
75 _scalar_transform_e ({}, _(
"Transformation matrix element E"),
77 _scalar_transform_f ({}, _(
"Transformation matrix element F"),
80 _check_move_relative (_(
"Rela_tive move")),
81 _check_scale_proportional(_(
"_Scale proportionally")),
82 _check_apply_separately (_(
"Apply to each _object separately")),
83 _check_replace_matrix (_(
"Edit c_urrent matrix")),
85 resetButton{Gtk::make_managed<Gtk::Button>()},
86 applyButton{Gtk::make_managed<Gtk::Button>(_(
"_Apply"))}
88 _scalar_move_horizontal.getLabel()->set_hexpand();
89 _scalar_move_vertical.getLabel()->set_hexpand();
90 _scalar_scale_horizontal.getLabel()->set_hexpand();
91 _scalar_scale_vertical.getLabel()->set_hexpand();
92 _scalar_skew_horizontal.getLabel()->set_hexpand();
93 _scalar_skew_vertical.getLabel()->set_hexpand();
95 _check_move_relative.set_use_underline();
96 _check_move_relative.set_tooltip_text(_(
"Add the specified relative displacement to the current position; otherwise, edit the current absolute position directly"));
98 _check_scale_proportional.set_use_underline();
99 _check_scale_proportional.set_tooltip_text(_(
"Preserve the width/height ratio of the scaled objects"));
101 _check_apply_separately.set_use_underline();
102 _check_apply_separately.set_tooltip_text(_(
"Apply the scale/rotate/skew to each selected object separately; otherwise, transform the selection as a whole"));
104 _check_replace_matrix.set_use_underline();
105 _check_replace_matrix.set_tooltip_text(_(
"Edit the current transform= matrix; otherwise, post-multiply transform= by this matrix"));
110 _page_move.set_halign(Gtk::Align::START);
111 _notebook.append_page(_page_move, _(
"_Move"),
true);
114 _page_scale.set_halign(Gtk::Align::START);
115 _notebook.append_page(_page_scale, _(
"_Scale"),
true);
118 _page_rotate.set_halign(Gtk::Align::START);
119 _notebook.append_page(_page_rotate, _(
"_Rotate"),
true);
122 _page_skew.set_halign(Gtk::Align::START);
123 _notebook.append_page(_page_skew, _(
"Ske_w"),
true);
126 _page_transform.set_halign(Gtk::Align::START);
127 _notebook.append_page(_page_transform, _(
"Matri_x"),
true);
128 layoutPageTransform();
135 _check_apply_separately.set_active(prefs->
getBool(
"/dialogs/transformation/applyseparately"));
138#if GTKMM_CHECK_VERSION(4, 14, 0)
140 auto const apply_on_activate = [
this](UI::Widget::ScalarUnit &scalar) {
141 scalar.getSpinButton().signal_activate().connect([
this] { _apply(); });
143 apply_on_activate(_scalar_move_horizontal );
144 apply_on_activate(_scalar_move_vertical );
145 apply_on_activate(_scalar_scale_horizontal);
146 apply_on_activate(_scalar_scale_vertical );
147 apply_on_activate(_scalar_rotate );
148 apply_on_activate(_scalar_skew_horizontal );
149 apply_on_activate(_scalar_skew_vertical );
152 resetButton->set_image_from_icon_name(
"reset-settings-symbolic");
153 resetButton->set_size_request(30, -1);
154 resetButton->set_halign(Gtk::Align::CENTER);
155 resetButton->set_use_underline();
156 resetButton->set_tooltip_text(_(
"Reset the values on the current tab to defaults"));
157 resetButton->set_sensitive(
true);
160 applyButton->set_use_underline();
161 applyButton->set_halign(Gtk::Align::CENTER);
162 applyButton->set_tooltip_text(_(
"Apply transformation to selection"));
163 applyButton->set_sensitive(
false);
165 applyButton->add_css_class(
"wide-apply-button");
167 auto const button_box = Gtk::make_managed<Gtk::Box>();
168 button_box->set_margin_top(4);
169 button_box->set_spacing(8);
170 button_box->set_halign(Gtk::Align::CENTER);
298 auto const box = Gtk::make_managed<Gtk::Box>();
356 for (
auto label : labels) {
358 label->set_margin_start(2);
359 label->set_margin_end(2);
446 auto const img = Gtk::make_managed<Gtk::Image>();
447 img->set_from_icon_name(
"matrix-2d");
448 img->set_pixel_size(52);
449 img->set_margin_top(4);
450 img->set_margin_bottom(4);
453 auto const descr = Gtk::make_managed<Gtk::Label>();
455 descr->set_wrap_mode(Pango::WrapMode::WORD);
458 "<a href=\"https://www.w3.org/TR/SVG11/coords.html#TransformMatrixDefined\">"
459 "2D transformation matrix</a> that combines translation (E,F), scaling (A,D),"
460 " rotation (A-D) and shearing (B,C)."
463 descr->set_use_markup();
532 double x = bbox->min()[
Geom::X];
533 double y = bbox->min()[
Geom::Y];
553 double w = bbox->dimensions()[
Geom::X];
554 double h = bbox->dimensions()[
Geom::Y];
581 double w = bbox->dimensions()[
Geom::X];
582 double h = bbox->dimensions()[
Geom::Y];
665 if (!prefs->
getBool(
"/dialogs/transformation/applyseparately")) {
680 if (selected.empty())
return;
682 if (fabs(x) > 1e-6) {
683 std::vector< BBoxSort > sorted;
684 for (
auto item : selected)
688 sorted.emplace_back(
item, *bbox,
Geom::X, x > 0? 1. : 0., x > 0? 0. : 1.);
692 std::stable_sort(sorted.begin(), sorted.end());
695 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
704 if (fabs(y) > 1e-6) {
705 std::vector< BBoxSort > sorted;
706 for (
auto item : selected)
710 sorted.emplace_back(
item, *bbox,
Geom::Y, y > 0? 1. : 0., y > 0? 0. : 1.);
714 std::stable_sort(sorted.begin(), sorted.end());
717 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
743 bool transform_stroke = prefs->
getBool(
"/options/transform/stroke",
true);
744 bool preserve = prefs->
getBool(
"/options/preservetransform/value",
false);
745 if (prefs->
getBool(
"/dialogs/transformation/applyseparately")) {
747 for(
auto i=tmp.begin();i!=tmp.end();++i){
751 if (bbox_pref && bbox_geom) {
752 double new_width = scaleX;
753 double new_height = scaleY;
756 new_width = scaleX/100 * bbox_pref->width();
757 new_height = scaleY/100 * bbox_pref->height();
759 if (fabs(new_width) < 1e-6) new_width = 1e-6;
760 if (fabs(new_height) < 1e-6) new_height = 1e-6;
762 double x0 = bbox_pref->midpoint()[
Geom::X] - new_width/2;
763 double y0 = bbox_pref->midpoint()[
Geom::Y] - new_height/2;
764 double x1 = bbox_pref->midpoint()[
Geom::X] + new_width/2;
765 double y1 = bbox_pref->midpoint()[
Geom::Y] + new_height/2;
775 if (bbox_pref && bbox_geom) {
777 double new_width = scaleX;
778 double new_height = scaleY;
780 new_width = scaleX/100 * bbox_pref->width();
781 new_height = scaleY/100 * bbox_pref->height();
783 if (fabs(new_width) < 1e-6) new_width = 1e-6;
784 if (fabs(new_height) < 1e-6) new_height = 1e-6;
786 double x0 = bbox_pref->midpoint()[
Geom::X] - new_width/2;
787 double y0 = bbox_pref->midpoint()[
Geom::Y] - new_height/2;
788 double x1 = bbox_pref->midpoint()[
Geom::X] + new_width/2;
789 double y1 = bbox_pref->midpoint()[
Geom::Y] + new_height/2;
804 if (!prefs->
getBool(
"/dialogs/transformation/rotateCounterClockwise", TRUE)) {
808 if (prefs->
getBool(
"/dialogs/transformation/applyseparately")) {
810 for(
auto i=tmp.begin();i!=tmp.end();++i){
827 if (prefs->
getBool(
"/dialogs/transformation/applyseparately")) {
829 for(
auto i =
items.begin();i!=
items.end();++i){
851 double skewX = tan(angleX);
852 double skewY = tan(angleY);
876 if ( bbox && center ) {
899 double skewX = tan(angleX);
900 double skewY = tan(angleY);
937 for(
auto i=tmp.begin();i!=tmp.end();++i){
1033 _scalar_rotate.set_tooltip_text(_(
"Rotation angle (positive = counterclockwise)"));
1035 prefs->
setBool(
"/dialogs/transformation/rotateCounterClockwise", !
getDesktop()->is_yaxisdown());
1040 _scalar_rotate.set_tooltip_text(_(
"Rotation angle (positive = clockwise)"));
1042 prefs->
setBool(
"/dialogs/transformation/rotateCounterClockwise",
getDesktop()->is_yaxisdown());
Simple helper class for sorting objects based on their bounding boxes.
3x3 matrix representing an affine transformation.
bool isSingular(Coord eps=EPSILON) const
Check whether this matrix is singular.
Affine inverse() const
Compute the inverse matrix.
Axis-aligned rectangle that can be empty.
Rotation around the origin.
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
SPDesktop * desktop()
Returns the desktop the selection is bound to.
SPItemRange items()
Returns a range of selected SPItems.
Geom::OptRect preferredBounds() const
Returns either the visual or geometric bounding rectangle of the selection, based on the preferences ...
void rotateRelative(const Geom::Point &, double)
void applyAffine(Geom::Affine const &affine, bool set_i2d=true, bool compensate=true, bool adjust_transf_center=true)
Apply matrix to the selection.
void moveRelative(const Geom::Point &move, bool compensate=true)
std::optional< Geom::Point > center() const
Returns the rotation/skew center of the selection.
Geom::OptRect geometricBounds() const
void skewRelative(const Geom::Point &, double, double)
bool isEmpty()
Returns true if no items are selected.
Preference storage class.
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
The set of selected SPObjects for a given document and layer model.
DialogBase is the base class for the dialog system.
Selection * getSelection() const
SPDesktop * getDesktop() const
SPDocument * getDocument() const
Inkscape::MessageStack * messageStack() const
SPNamedView * getNamedView() const
bool is_yaxisdown() const
Base class for visual SVG elements.
Geom::OptRect desktopPreferredBounds() const
void set_i2d_affine(Geom::Affine const &transform)
void set_item_transform(Geom::Affine const &transform_matrix)
Sets item private transform (not propagated to repr), without compensating stroke widths,...
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
void skew_rel(double skewX, double skewY)
void rotate_rel(Geom::Rotate const &rotation)
Geom::OptRect desktopGeometricBounds() const
Get item's geometric bbox in desktop coordinate system.
void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv=nullptr, bool compensate=true)
Set a new transform on an object.
Inkscape::Util::Unit const * display_units
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
Editable view implementation.
static char const *const current
TODO: insert short description here.
constexpr Coord EPSILON
Default "acceptably small" value.
Macro for icon names used in Inkscape.
static const unsigned int DEG
Raw stack of active status messages.
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
Singleton class to access the preferences file in a convenient way.