summaryrefslogtreecommitdiffstats
path: root/src/ui/dialog/polar-arrange-tab.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/dialog/polar-arrange-tab.cpp')
-rw-r--r--src/ui/dialog/polar-arrange-tab.cpp409
1 files changed, 409 insertions, 0 deletions
diff --git a/src/ui/dialog/polar-arrange-tab.cpp b/src/ui/dialog/polar-arrange-tab.cpp
new file mode 100644
index 0000000..f3f645f
--- /dev/null
+++ b/src/ui/dialog/polar-arrange-tab.cpp
@@ -0,0 +1,409 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @brief Arranges Objects into a Circle/Ellipse
+ */
+/* Authors:
+ * Declara Denis
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+#include <gtkmm/messagedialog.h>
+
+#include <2geom/transforms.h>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "document.h"
+#include "inkscape.h"
+#include "preferences.h"
+
+#include "object/sp-ellipse.h"
+#include "object/sp-item-transform.h"
+
+#include "ui/dialog/polar-arrange-tab.h"
+#include "ui/dialog/tile.h"
+#include "ui/icon-names.h"
+
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+PolarArrangeTab::PolarArrangeTab(ArrangeDialog *parent_)
+ : parent(parent_),
+ parametersTable(),
+ centerY("", C_("Polar arrange tab", "Y coordinate of the center"), UNIT_TYPE_LINEAR),
+ centerX("", C_("Polar arrange tab", "X coordinate of the center"), centerY),
+ radiusY("", C_("Polar arrange tab", "Y coordinate of the radius"), UNIT_TYPE_LINEAR),
+ radiusX("", C_("Polar arrange tab", "X coordinate of the radius"), radiusY),
+ angleY("", C_("Polar arrange tab", "Ending angle"), UNIT_TYPE_RADIAL),
+ angleX("", C_("Polar arrange tab", "Starting angle"), angleY)
+{
+ anchorPointLabel.set_text(C_("Polar arrange tab", "Anchor point:"));
+ anchorPointLabel.set_halign(Gtk::ALIGN_START);
+ pack_start(anchorPointLabel, false, false);
+
+ anchorBoundingBoxRadio.set_label(C_("Polar arrange tab", "Objects' bounding boxes:"));
+ anchorRadioGroup = anchorBoundingBoxRadio.get_group();
+ anchorBoundingBoxRadio.signal_toggled().connect(sigc::mem_fun(*this, &PolarArrangeTab::on_anchor_radio_changed));
+ pack_start(anchorBoundingBoxRadio, false, false);
+
+ pack_start(anchorSelector, false, false);
+
+ anchorObjectPivotRadio.set_label(C_("Polar arrange tab", "Objects' rotational centers"));
+ anchorObjectPivotRadio.set_group(anchorRadioGroup);
+ anchorObjectPivotRadio.signal_toggled().connect(sigc::mem_fun(*this, &PolarArrangeTab::on_anchor_radio_changed));
+ pack_start(anchorObjectPivotRadio, false, false);
+
+ arrangeOnLabel.set_text(C_("Polar arrange tab", "Arrange on:"));
+ arrangeOnLabel.set_halign(Gtk::ALIGN_START);
+ pack_start(arrangeOnLabel, false, false);
+
+ arrangeOnFirstCircleRadio.set_label(C_("Polar arrange tab", "First selected circle/ellipse/arc"));
+ arrangeRadioGroup = arrangeOnFirstCircleRadio.get_group();
+ arrangeOnFirstCircleRadio.signal_toggled().connect(sigc::mem_fun(*this, &PolarArrangeTab::on_arrange_radio_changed));
+ pack_start(arrangeOnFirstCircleRadio, false, false);
+
+ arrangeOnLastCircleRadio.set_label(C_("Polar arrange tab", "Last selected circle/ellipse/arc"));
+ arrangeOnLastCircleRadio.set_group(arrangeRadioGroup);
+ arrangeOnLastCircleRadio.signal_toggled().connect(sigc::mem_fun(*this, &PolarArrangeTab::on_arrange_radio_changed));
+ pack_start(arrangeOnLastCircleRadio, false, false);
+
+ arrangeOnParametersRadio.set_label(C_("Polar arrange tab", "Parameterized:"));
+ arrangeOnParametersRadio.set_group(arrangeRadioGroup);
+ arrangeOnParametersRadio.signal_toggled().connect(sigc::mem_fun(*this, &PolarArrangeTab::on_arrange_radio_changed));
+ pack_start(arrangeOnParametersRadio, false, false);
+
+ centerLabel.set_text(C_("Polar arrange tab", "Center X/Y:"));
+ parametersTable.attach(centerLabel, 0, 0, 1, 1);
+ centerX.setDigits(2);
+ centerX.setIncrements(0.2, 0);
+ centerX.setRange(-10000, 10000);
+ centerX.setValue(0, "px");
+ centerY.setDigits(2);
+ centerY.setIncrements(0.2, 0);
+ centerY.setRange(-10000, 10000);
+ centerY.setValue(0, "px");
+ parametersTable.attach(centerX, 1, 0, 1, 1);
+ parametersTable.attach(centerY, 2, 0, 1, 1);
+
+ radiusLabel.set_text(C_("Polar arrange tab", "Radius X/Y:"));
+ parametersTable.attach(radiusLabel, 0, 1, 1, 1);
+ radiusX.setDigits(2);
+ radiusX.setIncrements(0.2, 0);
+ radiusX.setRange(0.001, 10000);
+ radiusX.setValue(100, "px");
+ radiusY.setDigits(2);
+ radiusY.setIncrements(0.2, 0);
+ radiusY.setRange(0.001, 10000);
+ radiusY.setValue(100, "px");
+ parametersTable.attach(radiusX, 1, 1, 1, 1);
+ parametersTable.attach(radiusY, 2, 1, 1, 1);
+
+ angleLabel.set_text(_("Angle X/Y:"));
+ parametersTable.attach(angleLabel, 0, 2, 1, 1);
+ angleX.setDigits(2);
+ angleX.setIncrements(0.2, 0);
+ angleX.setRange(-10000, 10000);
+ angleX.setValue(0, "°");
+ angleY.setDigits(2);
+ angleY.setIncrements(0.2, 0);
+ angleY.setRange(-10000, 10000);
+ angleY.setValue(180, "°");
+ parametersTable.attach(angleX, 1, 2, 1, 1);
+ parametersTable.attach(angleY, 2, 2, 1, 1);
+ parametersTable.set_row_spacing(4);
+ parametersTable.set_column_spacing(4);
+ pack_start(parametersTable, false, false);
+
+ rotateObjectsCheckBox.set_label(_("Rotate objects"));
+ rotateObjectsCheckBox.set_active(true);
+ pack_start(rotateObjectsCheckBox, false, false);
+
+ centerX.set_sensitive(false);
+ centerY.set_sensitive(false);
+ angleX.set_sensitive(false);
+ angleY.set_sensitive(false);
+ radiusX.set_sensitive(false);
+ radiusY.set_sensitive(false);
+
+ set_border_width(4);
+
+ parametersTable.show_all();
+ parametersTable.set_no_show_all();
+ parametersTable.hide();
+}
+
+/**
+ * This function rotates an item around a given point by a given amount
+ * @param item item to rotate
+ * @param center center of the rotation to perform
+ * @param rotation amount to rotate the object by
+ */
+static void rotateAround(SPItem *item, Geom::Point center, Geom::Rotate const &rotation)
+{
+ Geom::Translate const s(center);
+ Geom::Affine affine = Geom::Affine(s).inverse() * Geom::Affine(rotation) * Geom::Affine(s);
+
+ // Save old center
+ center = item->getCenter();
+
+ item->set_i2d_affine(item->i2dt_affine() * affine);
+ item->doWriteTransform(item->transform);
+
+ if(item->isCenterSet())
+ {
+ item->setCenter(center * affine);
+ item->updateRepr();
+ }
+}
+
+/**
+ * Calculates the angle at which to put an object given the total amount
+ * of objects, the index of the objects as well as the arc start and end
+ * points
+ * @param arcBegin angle at which the arc begins
+ * @param arcEnd angle at which the arc ends
+ * @param count number of objects in the selection
+ * @param n index of the object in the selection
+ */
+static float calcAngle(float arcBegin, float arcEnd, int count, int n)
+{
+ float arcLength = arcEnd - arcBegin;
+ float delta = std::abs(std::abs(arcLength) - 2*M_PI);
+ if(delta > 0.01) count--; // If not a complete circle, put an object also at the extremes of the arc;
+
+ float angle = n / (float)count;
+ // Normalize for arcLength:
+ angle = angle * arcLength;
+ angle += arcBegin;
+
+ return angle;
+}
+
+/**
+ * Calculates the point at which an object needs to be, given the center of the ellipse,
+ * it's radius (x and y), as well as the angle
+ */
+static Geom::Point calcPoint(float cx, float cy, float rx, float ry, float angle)
+{
+ return Geom::Point(cx + cos(angle) * rx, cy + sin(angle) * ry);
+}
+
+/**
+ * Returns the selected anchor point in desktop coordinates. If anchor
+ * is 0 to 8, then a bounding box point has been chosen. If it is 9 however
+ * the rotational center is chosen.
+ */
+static Geom::Point getAnchorPoint(int anchor, SPItem *item)
+{
+ Geom::Point source;
+
+ Geom::OptRect bbox = item->documentVisualBounds();
+
+ switch(anchor)
+ {
+ case 0: // Top - Left
+ case 3: // Middle - Left
+ case 6: // Bottom - Left
+ source[0] = bbox->min()[Geom::X];
+ break;
+ case 1: // Top - Middle
+ case 4: // Middle - Middle
+ case 7: // Bottom - Middle
+ source[0] = (bbox->min()[Geom::X] + bbox->max()[Geom::X]) / 2.0f;
+ break;
+ case 2: // Top - Right
+ case 5: // Middle - Right
+ case 8: // Bottom - Right
+ source[0] = bbox->max()[Geom::X];
+ break;
+ };
+
+ switch(anchor)
+ {
+ case 0: // Top - Left
+ case 1: // Top - Middle
+ case 2: // Top - Right
+ source[1] = bbox->min()[Geom::Y];
+ break;
+ case 3: // Middle - Left
+ case 4: // Middle - Middle
+ case 5: // Middle - Right
+ source[1] = (bbox->min()[Geom::Y] + bbox->max()[Geom::Y]) / 2.0f;
+ break;
+ case 6: // Bottom - Left
+ case 7: // Bottom - Middle
+ case 8: // Bottom - Right
+ source[1] = bbox->max()[Geom::Y];
+ break;
+ };
+
+ // If using center
+ if(anchor == 9)
+ source = item->getCenter();
+ else
+ {
+ source *= item->document->doc2dt();
+ }
+
+ return source;
+}
+
+/**
+ * Moves an SPItem to a given location, the location is based on the given anchor point.
+ * @param anchor 0 to 8 are the various bounding box points like follows:
+ * 0 1 2
+ * 3 4 5
+ * 6 7 8
+ * Anchor mode 9 is the rotational center of the object
+ * @param item Item to move
+ * @param p point at which to move the object
+ */
+static void moveToPoint(int anchor, SPItem *item, Geom::Point p)
+{
+ item->move_rel(Geom::Translate(p - getAnchorPoint(anchor, item)));
+}
+
+void PolarArrangeTab::arrange()
+{
+ Inkscape::Selection *selection = parent->getDesktop()->getSelection();
+ const std::vector<SPItem*> tmp(selection->items().begin(), selection->items().end());
+ SPGenericEllipse *referenceEllipse = nullptr; // Last ellipse in selection
+
+ bool arrangeOnEllipse = !arrangeOnParametersRadio.get_active();
+ bool arrangeOnFirstEllipse = arrangeOnEllipse && arrangeOnFirstCircleRadio.get_active();
+ float yaxisdir = parent->getDesktop()->yaxisdir();
+
+ int count = 0;
+ for(auto item : tmp)
+ {
+ if(arrangeOnEllipse)
+ {
+ if(!arrangeOnFirstEllipse)
+ {
+ if(SP_IS_GENERICELLIPSE(item))
+ referenceEllipse = SP_GENERICELLIPSE(item);
+ } else {
+ if(SP_IS_GENERICELLIPSE(item) && referenceEllipse == nullptr)
+ referenceEllipse = SP_GENERICELLIPSE(item);
+ }
+ }
+ ++count;
+ }
+
+ float cx, cy; // Center of the ellipse
+ float rx, ry; // Radiuses of the ellipse in x and y direction
+ float arcBeg, arcEnd; // begin and end angles for arcs
+ Geom::Affine transformation; // Any additional transformation to apply to the objects
+
+ if(arrangeOnEllipse)
+ {
+ if(referenceEllipse == nullptr)
+ {
+ Gtk::MessageDialog dialog(_("Couldn't find an ellipse in selection"), false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE, true);
+ dialog.run();
+ return;
+ } else {
+ cx = referenceEllipse->cx.value;
+ cy = referenceEllipse->cy.value;
+ rx = referenceEllipse->rx.value;
+ ry = referenceEllipse->ry.value;
+ arcBeg = referenceEllipse->start;
+ arcEnd = referenceEllipse->end;
+
+ transformation = referenceEllipse->i2dt_affine();
+
+ // We decrement the count by 1 as we are not going to lay
+ // out the reference ellipse
+ --count;
+ }
+
+ } else {
+ // Read options from UI
+ cx = centerX.getValue("px");
+ cy = centerY.getValue("px");
+ rx = radiusX.getValue("px");
+ ry = radiusY.getValue("px");
+ arcBeg = angleX.getValue("rad");
+ arcEnd = angleY.getValue("rad") * yaxisdir;
+ transformation.setIdentity();
+ referenceEllipse = nullptr;
+ }
+
+ int anchor = 9;
+ if(anchorBoundingBoxRadio.get_active())
+ {
+ anchor = anchorSelector.getHorizontalAlignment() +
+ anchorSelector.getVerticalAlignment() * 3;
+ }
+
+ Geom::Point realCenter = Geom::Point(cx, cy) * transformation;
+
+ int i = 0;
+ for(auto item : tmp)
+ {
+ // Ignore the reference ellipse if any
+ if(item != referenceEllipse)
+ {
+ float angle = calcAngle(arcBeg, arcEnd, count, i);
+ Geom::Point newLocation = calcPoint(cx, cy, rx, ry, angle) * transformation;
+
+ moveToPoint(anchor, item, newLocation);
+
+ if(rotateObjectsCheckBox.get_active()) {
+ // Calculate the angle by which to rotate each object
+ angle = -atan2f(-yaxisdir * (newLocation.x() - realCenter.x()), -yaxisdir * (newLocation.y() - realCenter.y()));
+ rotateAround(item, newLocation, Geom::Rotate(angle));
+ }
+
+ ++i;
+ }
+ }
+
+ DocumentUndo::done(parent->getDesktop()->getDocument(), _("Arrange on ellipse"), INKSCAPE_ICON("dialog-align-and-distribute"));
+}
+
+void PolarArrangeTab::updateSelection()
+{
+}
+
+void PolarArrangeTab::on_arrange_radio_changed()
+{
+ bool arrangeParametric = arrangeOnParametersRadio.get_active();
+
+ centerX.set_sensitive(arrangeParametric);
+ centerY.set_sensitive(arrangeParametric);
+
+ angleX.set_sensitive(arrangeParametric);
+ angleY.set_sensitive(arrangeParametric);
+
+ radiusX.set_sensitive(arrangeParametric);
+ radiusY.set_sensitive(arrangeParametric);
+
+ parametersTable.set_visible(arrangeParametric);
+}
+
+void PolarArrangeTab::on_anchor_radio_changed()
+{
+ bool anchorBoundingBox = anchorBoundingBoxRadio.get_active();
+
+ anchorSelector.set_sensitive(anchorBoundingBox);
+}
+
+} //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 :