Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
polar-arrange-tab.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/* Authors:
6 * Declara Denis
7 *
8 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9 */
10
11#include "polar-arrange-tab.h"
12
13#include <glibmm/i18n.h>
14#include <gtkmm/messagedialog.h>
15
16#include <2geom/transforms.h>
17
18#include "desktop.h"
19#include "document-undo.h"
20#include "document.h"
21#include "selection.h"
22#include "object/sp-ellipse.h"
23#include "ui/dialog/tile.h"
24#include "ui/pack.h"
25#include "ui/icon-names.h"
26
27namespace Inkscape::UI::Dialog {
28
30 : parent(parent_),
31 parametersTable(),
32 centerY("", C_("Polar arrange tab", "Y coordinate of the center"), UNIT_TYPE_LINEAR),
33 centerX("", C_("Polar arrange tab", "X coordinate of the center"), centerY),
34 radiusY("", C_("Polar arrange tab", "Y coordinate of the radius"), UNIT_TYPE_LINEAR),
35 radiusX("", C_("Polar arrange tab", "X coordinate of the radius"), radiusY),
36 angleY("", C_("Polar arrange tab", "Ending angle"), UNIT_TYPE_RADIAL),
37 angleX("", C_("Polar arrange tab", "Starting angle"), angleY)
38{
39 set_spacing(4);
40
41 anchorPointLabel.set_markup(C_("Polar arrange tab", "<b>Anchor point:</b>"));
42 anchorPointLabel.set_halign(Gtk::Align::START);
43 UI::pack_start(*this, anchorPointLabel, false, false);
44
45 anchorBoundingBoxRadio.set_label(C_("Polar arrange tab", "Objects' bounding boxes:"));
46 anchorBoundingBoxRadio.signal_toggled().connect(sigc::mem_fun(*this, &PolarArrangeTab::on_anchor_radio_changed));
47 anchorBoundingBoxRadio.set_margin_start(4);
48 anchorBoundingBoxRadio.set_active(true);
49 UI::pack_start(*this, anchorBoundingBoxRadio, false, false);
50
51 anchorSelector.set_margin_start(16);
52 anchorSelector.set_halign(Gtk::Align::START);
53 UI::pack_start(*this, anchorSelector, false, false);
54
55 anchorObjectPivotRadio.set_label(C_("Polar arrange tab", "Objects' rotational centers"));
57 anchorObjectPivotRadio.signal_toggled().connect(sigc::mem_fun(*this, &PolarArrangeTab::on_anchor_radio_changed));
58 anchorObjectPivotRadio.set_margin_start(4);
59 UI::pack_start(*this, anchorObjectPivotRadio, false, false);
60
61 arrangeOnLabel.set_markup(C_("Polar arrange tab", "<b>Arrange on:</b>"));
62 arrangeOnLabel.set_margin_top(8);
63 arrangeOnLabel.set_halign(Gtk::Align::START);
64 UI::pack_start(*this, arrangeOnLabel, false, false);
65
66 arrangeOnFirstCircleRadio.set_label(C_("Polar arrange tab", "First selected circle/ellipse/arc"));
67 arrangeOnFirstCircleRadio.signal_toggled().connect(sigc::mem_fun(*this, &PolarArrangeTab::on_arrange_radio_changed));
68 arrangeOnFirstCircleRadio.set_margin_start(4);
69 arrangeOnFirstCircleRadio.set_active(true);
70 UI::pack_start(*this, arrangeOnFirstCircleRadio, false, false);
71
72 arrangeOnLastCircleRadio.set_label(C_("Polar arrange tab", "Last selected circle/ellipse/arc"));
74 arrangeOnLastCircleRadio.signal_toggled().connect(sigc::mem_fun(*this, &PolarArrangeTab::on_arrange_radio_changed));
75 arrangeOnLastCircleRadio.set_margin_start(4);
76 UI::pack_start(*this, arrangeOnLastCircleRadio, false, false);
77
78 arrangeOnParametersRadio.set_label(C_("Polar arrange tab", "Parameterized:"));
80 arrangeOnParametersRadio.signal_toggled().connect(sigc::mem_fun(*this, &PolarArrangeTab::on_arrange_radio_changed));
81 arrangeOnParametersRadio.set_margin_start(4);
82 UI::pack_start(*this, arrangeOnParametersRadio, false, false);
83
84 centerLabel.set_text(C_("Polar arrange tab", "Center X/Y:"));
85 parametersTable.attach(centerLabel, 0, 0, 1, 1);
87 centerX.setIncrements(0.2, 0);
88 centerX.setRange(-10000, 10000);
89 centerX.setValue(0, "px");
91 centerY.setIncrements(0.2, 0);
92 centerY.setRange(-10000, 10000);
93 centerY.setValue(0, "px");
94 parametersTable.attach(centerX, 1, 0, 1, 1);
95 parametersTable.attach(centerY, 2, 0, 1, 1);
96
97 radiusLabel.set_text(C_("Polar arrange tab", "Radius X/Y:"));
98 parametersTable.attach(radiusLabel, 0, 1, 1, 1);
100 radiusX.setIncrements(0.2, 0);
101 radiusX.setRange(0.001, 10000);
102 radiusX.setValue(100, "px");
104 radiusY.setIncrements(0.2, 0);
105 radiusY.setRange(0.001, 10000);
106 radiusY.setValue(100, "px");
107 parametersTable.attach(radiusX, 1, 1, 1, 1);
108 parametersTable.attach(radiusY, 2, 1, 1, 1);
109
110 angleLabel.set_text(_("Angle X/Y:"));
111 parametersTable.attach(angleLabel, 0, 2, 1, 1);
112 angleX.setDigits(2);
113 angleX.setIncrements(0.2, 0);
114 angleX.setRange(-10000, 10000);
115 angleX.setValue(0, "°");
116 angleY.setDigits(2);
117 angleY.setIncrements(0.2, 0);
118 angleY.setRange(-10000, 10000);
119 angleY.setValue(180, "°");
120 parametersTable.attach(angleX, 1, 2, 1, 1);
121 parametersTable.attach(angleY, 2, 2, 1, 1);
122 parametersTable.set_margin_start(16);
123 parametersTable.set_row_spacing(4);
124 parametersTable.set_column_spacing(4);
125 UI::pack_start(*this, parametersTable, false, false);
126
127 rotateObjectsCheckBox.set_label(_("Rotate objects"));
128 rotateObjectsCheckBox.set_active(true);
129 rotateObjectsCheckBox.set_margin_top(8);
130 UI::pack_start(*this, rotateObjectsCheckBox, false, false);
131
132 centerX.set_sensitive(false);
133 centerY.set_sensitive(false);
134 angleX.set_sensitive(false);
135 angleY.set_sensitive(false);
136 radiusX.set_sensitive(false);
137 radiusY.set_sensitive(false);
138
139 set_margin(8);
140
141 parametersTable.set_visible(false);
142}
143
150static void rotateAround(SPItem *item, Geom::Point center, Geom::Rotate const &rotation)
151{
152 Geom::Translate const s(center);
153 Geom::Affine affine = Geom::Affine(s).inverse() * Geom::Affine(rotation) * Geom::Affine(s);
154
155 // Save old center
156 center = item->getCenter();
157
158 item->set_i2d_affine(item->i2dt_affine() * affine);
160
161 if(item->isCenterSet())
162 {
163 item->setCenter(center * affine);
164 item->updateRepr();
165 }
166}
167
177static float calcAngle(float arcBegin, float arcEnd, int count, int n)
178{
179 float arcLength = arcEnd - arcBegin;
180 float delta = std::abs(std::abs(arcLength) - 2*M_PI);
181 if(delta > 0.01) count--; // If not a complete circle, put an object also at the extremes of the arc;
182
183 float angle = n / (float)count;
184 // Normalize for arcLength:
185 angle = angle * arcLength;
186 angle += arcBegin;
187
188 return angle;
189}
190
195static Geom::Point calcPoint(float cx, float cy, float rx, float ry, float angle)
196{
197 return Geom::Point(cx + cos(angle) * rx, cy + sin(angle) * ry);
198}
199
206{
207 Geom::Point source;
209
210 switch(anchor)
211 {
212 case 0: // Top - Left
213 case 3: // Middle - Left
214 case 6: // Bottom - Left
215 source[0] = bbox->min()[Geom::X];
216 break;
217 case 1: // Top - Middle
218 case 4: // Middle - Middle
219 case 7: // Bottom - Middle
220 source[0] = (bbox->min()[Geom::X] + bbox->max()[Geom::X]) / 2.0f;
221 break;
222 case 2: // Top - Right
223 case 5: // Middle - Right
224 case 8: // Bottom - Right
225 source[0] = bbox->max()[Geom::X];
226 break;
227 };
228
229 switch(anchor)
230 {
231 case 0: // Top - Left
232 case 1: // Top - Middle
233 case 2: // Top - Right
234 source[1] = bbox->min()[Geom::Y];
235 break;
236 case 3: // Middle - Left
237 case 4: // Middle - Middle
238 case 5: // Middle - Right
239 source[1] = (bbox->min()[Geom::Y] + bbox->max()[Geom::Y]) / 2.0f;
240 break;
241 case 6: // Bottom - Left
242 case 7: // Bottom - Middle
243 case 8: // Bottom - Right
244 source[1] = bbox->max()[Geom::Y];
245 break;
246 };
247
248 // If using center
249 if(anchor == 9)
250 source = item->getCenter();
251 else
252 source *= item->document->doc2dt();
253
254 return source;
255}
256
267static void moveToPoint(int anchor, SPItem *item, Geom::Point p)
268{
270}
271
273{
275 const std::vector<SPItem*> tmp(selection->items().begin(), selection->items().end());
276 SPGenericEllipse *referenceEllipse = nullptr; // Last ellipse in selection
277
278 bool arrangeOnEllipse = !arrangeOnParametersRadio.get_active();
279 bool arrangeOnFirstEllipse = arrangeOnEllipse && arrangeOnFirstCircleRadio.get_active();
280 float yaxisdir = parent->getDesktop()->yaxisdir();
281
282 int count = 0;
283 if (arrangeOnEllipse) {
284 for (auto item : tmp) {
285 if (auto ellipse = cast<SPGenericEllipse>(item)) {
286 if (!referenceEllipse || !arrangeOnFirstEllipse) {
287 referenceEllipse = ellipse;
288 }
289 }
290 ++count;
291 }
292 } else {
293 count = tmp.size();
294 }
295
296 float cx, cy; // Center of the ellipse
297 float rx, ry; // Radiuses of the ellipse in x and y direction
298 float arcBeg, arcEnd; // begin and end angles for arcs
299 Geom::Affine transformation; // Any additional transformation to apply to the objects
300 if (arrangeOnEllipse) {
301 if (!referenceEllipse) {
302 if (auto desktop = parent->getDesktop()) {
303 desktop->showNotice(_("Couldn't find an ellipse in selection"), 5000);
304 }
305 return;
306 } else {
307 cx = referenceEllipse->cx.value;
308 cy = referenceEllipse->cy.value;
309 rx = referenceEllipse->rx.value;
310 ry = referenceEllipse->ry.value;
311 arcBeg = referenceEllipse->start;
312 arcEnd = referenceEllipse->end;
313
314 transformation = referenceEllipse->i2dt_affine();
315
316 // We decrement the count by 1 as we are not going to lay
317 // out the reference ellipse
318 --count;
319 }
320 } else {
321 // Read options from UI
322 cx = centerX.getValue("px");
323 cy = centerY.getValue("px");
324 rx = radiusX.getValue("px");
325 ry = radiusY.getValue("px");
326 arcBeg = angleX.getValue("rad");
327 arcEnd = angleY.getValue("rad") * yaxisdir;
328 transformation.setIdentity();
329 referenceEllipse = nullptr;
330 }
331
332 if (count < 1) {
333 if (auto desktop = parent->getDesktop()) {
334 desktop->showNotice(_("No objects to arrange"), 5000);
335 }
336 return;
337 }
338
339 int anchor = 9;
340 if(anchorBoundingBoxRadio.get_active())
341 {
344 }
345
346 Geom::Point realCenter = Geom::Point(cx, cy) * transformation;
347
348 int i = 0;
349 for(auto item : tmp)
350 {
351 // Ignore the reference ellipse if any
352 if(item != referenceEllipse)
353 {
354 float angle = calcAngle(arcBeg, arcEnd, count, i);
355 Geom::Point newLocation = calcPoint(cx, cy, rx, ry, angle) * transformation;
356
357 moveToPoint(anchor, item, newLocation);
358
359 if(rotateObjectsCheckBox.get_active()) {
360 // Calculate the angle by which to rotate each object
361 angle = -atan2f(-yaxisdir * (newLocation.x() - realCenter.x()), -yaxisdir * (newLocation.y() - realCenter.y()));
362 rotateAround(item, newLocation, Geom::Rotate(angle));
363 }
364
365 ++i;
366 }
367 }
368
369 DocumentUndo::done(parent->getDesktop()->getDocument(), _("Arrange on ellipse"), INKSCAPE_ICON("dialog-align-and-distribute"));
370}
371
375
377{
378 bool arrangeParametric = arrangeOnParametersRadio.get_active();
379
380 centerX.set_sensitive(arrangeParametric);
381 centerY.set_sensitive(arrangeParametric);
382
383 angleX.set_sensitive(arrangeParametric);
384 angleY.set_sensitive(arrangeParametric);
385
386 radiusX.set_sensitive(arrangeParametric);
387 radiusY.set_sensitive(arrangeParametric);
388
389 parametersTable.set_visible(arrangeParametric);
390}
391
393{
394 bool anchorBoundingBox = anchorBoundingBoxRadio.get_active();
395 anchorSelector.set_sensitive(anchorBoundingBox);
396}
397
398} // namespace Inkscape::UI::Dialog
399
400/*
401 Local Variables:
402 mode:c++
403 c-file-style:"stroustrup"
404 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
405 indent-tabs-mode:nil
406 fill-column:99
407 End:
408*/
409// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
3x3 matrix representing an affine transformation.
Definition affine.h:70
void setIdentity()
Sets this matrix to be the Identity Affine.
Definition affine.cpp:96
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Coord y() const noexcept
Definition point.h:106
constexpr Coord x() const noexcept
Definition point.h:104
Rotation around the origin.
Definition transforms.h:187
Translation by a vector.
Definition transforms.h:115
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:255
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
SPDesktop * getDesktop() const
Definition dialog-base.h:79
void updateSelection()
Respond to selection change.
void arrange() override
Do the actual arrangement.
Inkscape::UI::Widget::ScalarUnit radiusX
Inkscape::UI::Widget::ScalarUnit centerY
Inkscape::UI::Widget::ScalarUnit angleX
Inkscape::UI::Widget::ScalarUnit radiusY
Inkscape::UI::Widget::AnchorSelector anchorSelector
Inkscape::UI::Widget::ScalarUnit angleY
Inkscape::UI::Widget::ScalarUnit centerX
void setValue(double number, Glib::ustring const &units)
Sets the number and unit system.
double getValue(Glib::ustring const &units) const
Returns the value in the given unit system.
void setIncrements(double step, double page)
Sets the step and page increments for the spin button.
Definition scalar.cpp:123
void setDigits(unsigned digits)
Sets the precision to be displayed by the spin button.
Definition scalar.cpp:94
void setRange(double min, double max)
Sets the minimum and maximum range allowed for the spin button.
Definition scalar.cpp:128
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::Selection * getSelection() const
Definition desktop.h:188
double yaxisdir() const
Definition desktop.h:426
void showNotice(Glib::ustring const &msg, int timeout=0)
Definition desktop.cpp:1268
const Geom::Affine & doc2dt() const
Document to desktop coordinate transformation.
Definition document.cpp:935
Base class for visual SVG elements.
Definition sp-item.h:109
void set_i2d_affine(Geom::Affine const &transform)
Definition sp-item.cpp:1827
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1821
Geom::OptRect documentVisualBounds() const
Get item's visual bbox in document coordinate system.
Definition sp-item.cpp:1018
Geom::Point getCenter(bool ensure_uptodate=true) const
Definition sp-item.cpp:377
Geom::Affine transform
Definition sp-item.h:138
void move_rel(Geom::Translate const &tr)
Definition sp-item.cpp:1951
bool isCenterSet() const
Definition sp-item.cpp:372
void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv=nullptr, bool compensate=true)
Set a new transform on an object.
Definition sp-item.cpp:1658
void setCenter(Geom::Point const &object_centre)
Sets the transform_center_x and transform_center_y properties to retain the rotation center.
Definition sp-item.cpp:331
SPDocument * document
Definition sp-object.h:188
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
float value
Definition svg-length.h:47
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
TODO: insert short description here.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Macro for icon names used in Inkscape.
SPItem * item
Dialog code.
Definition desktop.h:117
static void rotateAround(SPItem *item, Geom::Point center, Geom::Rotate const &rotation)
This function rotates an item around a given point by a given amount.
static Geom::Point calcPoint(float cx, float cy, float rx, float ry, float angle)
Calculates the point at which an object needs to be, given the center of the ellipse,...
static Geom::Point getAnchorPoint(int anchor, SPItem *item)
Returns the selected anchor point in desktop coordinates.
static void moveToPoint(int anchor, SPItem *item, Geom::Point p)
Moves an SPItem to a given location, the location is based on the given anchor point.
static float calcAngle(float arcBegin, float arcEnd, int count, int n)
Calculates the angle at which to put an object given the total amount of objects, the index of the ob...
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.
Definition pack.cpp:141
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
int delta
SPDesktop * desktop
Affine transformation classes.
Dialog for creating grid type arrangements of selected objects.