summaryrefslogtreecommitdiffstats
path: root/src/ui/dialog/align-and-distribute.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/dialog/align-and-distribute.cpp')
-rw-r--r--src/ui/dialog/align-and-distribute.cpp1339
1 files changed, 1339 insertions, 0 deletions
diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp
new file mode 100644
index 0000000..31605a0
--- /dev/null
+++ b/src/ui/dialog/align-and-distribute.cpp
@@ -0,0 +1,1339 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Align and Distribute dialog - implementation.
+ */
+/* Authors:
+ * Bryce W. Harrington <bryce@bryceharrington.org>
+ * Aubanel MONNIER <aubi@libertysurf.fr>
+ * Frank Felfe <innerspace@iname.com>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Tim Dwyer <tgdwyer@gmail.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 1999-2004, 2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+
+#include <2geom/transforms.h>
+
+#include <utility>
+
+#include "align-and-distribute.h"
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "document.h"
+#include "graphlayout.h"
+#include "inkscape.h"
+#include "preferences.h"
+#include "removeoverlap.h"
+#include "text-editing.h"
+#include "unclump.h"
+#include "verbs.h"
+
+#include "object/sp-flowtext.h"
+#include "object/sp-item-transform.h"
+#include "object/sp-root.h"
+#include "object/sp-text.h"
+
+#include "ui/icon-loader.h"
+#include "ui/icon-names.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tools-switch.h"
+#include "ui/tools/node-tool.h"
+#include "ui/widget/spinbutton.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+/////////helper classes//////////////////////////////////
+
+Action::Action(Glib::ustring id,
+ const Glib::ustring &tiptext,
+ guint row, guint column,
+ Gtk::Grid &parent,
+ AlignAndDistribute &dialog):
+ _dialog(dialog),
+ _id(std::move(id)),
+ _parent(parent)
+{
+ Gtk::Image* pIcon = Gtk::manage(new Gtk::Image());
+ pIcon = sp_get_icon_image(_id, Gtk::ICON_SIZE_LARGE_TOOLBAR);
+ Gtk::Button * pButton = Gtk::manage(new Gtk::Button());
+ pButton->set_relief(Gtk::RELIEF_NONE);
+ pIcon->show();
+ pButton->add(*pIcon);
+ pButton->show();
+
+ pButton->signal_clicked()
+ .connect(sigc::mem_fun(*this, &Action::on_button_click));
+ pButton->set_tooltip_text(tiptext);
+ parent.attach(*pButton, column, row, 1, 1);
+}
+
+
+void ActionAlign::do_node_action(Inkscape::UI::Tools::NodeTool *nt, int verb)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int prev_pref = prefs->getInt("/dialogs/align/align-nodes-to");
+ switch(verb){
+ case SP_VERB_ALIGN_HORIZONTAL_LEFT:
+ prefs->setInt("/dialogs/align/align-nodes-to", MIN_NODE );
+ nt->_multipath->alignNodes(Geom::Y);
+ break;
+ case SP_VERB_ALIGN_HORIZONTAL_CENTER:
+ nt->_multipath->alignNodes(Geom::Y);
+ break;
+ case SP_VERB_ALIGN_HORIZONTAL_RIGHT:
+ prefs->setInt("/dialogs/align/align-nodes-to", MAX_NODE );
+ nt->_multipath->alignNodes(Geom::Y);
+ break;
+ case SP_VERB_ALIGN_VERTICAL_TOP:
+ prefs->setInt("/dialogs/align/align-nodes-to", MAX_NODE );
+ nt->_multipath->alignNodes(Geom::X);
+ break;
+ case SP_VERB_ALIGN_VERTICAL_CENTER:
+ nt->_multipath->alignNodes(Geom::X);
+ break;
+ case SP_VERB_ALIGN_VERTICAL_BOTTOM:
+ prefs->setInt("/dialogs/align/align-nodes-to", MIN_NODE );
+ nt->_multipath->alignNodes(Geom::X);
+ break;
+ case SP_VERB_ALIGN_BOTH_CENTER:
+ nt->_multipath->alignNodes(Geom::X);
+ nt->_multipath->alignNodes(Geom::Y);
+ break;
+ default:return;
+ }
+ prefs->setInt("/dialogs/align/align-nodes-to", prev_pref );
+}
+
+void ActionAlign::do_action(SPDesktop *desktop, int index)
+{
+ Inkscape::Selection *selection = desktop->getSelection();
+ if (!selection) return;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool sel_as_group = prefs->getBool("/dialogs/align/sel-as-groups");
+
+ std::vector<SPItem*> selected(selection->items().begin(), selection->items().end());
+ if (selected.empty()) return;
+
+ Coeffs a = _allCoeffs[index]; // copy
+ SPItem *focus = nullptr;
+ Geom::OptRect b = Geom::OptRect();
+ Selection::CompareSize horiz = (a.mx0 != 0.0) || (a.mx1 != 0.0)
+ ? Selection::VERTICAL : Selection::HORIZONTAL;
+
+ switch (AlignTarget(prefs->getInt("/dialogs/align/align-to", 6)))
+ {
+ case LAST:
+ focus = SP_ITEM(selected.back());
+ break;
+ case FIRST:
+ focus = SP_ITEM(selected.front());
+ break;
+ case BIGGEST:
+ focus = selection->largestItem(horiz);
+ break;
+ case SMALLEST:
+ focus = selection->smallestItem(horiz);
+ break;
+ case PAGE:
+ b = desktop->getDocument()->preferredBounds();
+ break;
+ case DRAWING:
+ b = desktop->getDocument()->getRoot()->desktopPreferredBounds();
+ break;
+ case SELECTION:
+ b = selection->preferredBounds();
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ };
+
+ if(focus)
+ b = focus->desktopPreferredBounds();
+
+ g_return_if_fail(b);
+
+ if (desktop->is_yaxisdown()) {
+ std::swap(a.my0, a.my1);
+ std::swap(a.sy0, a.sy1);
+ }
+
+ // Generate the move point from the selected bounding box
+ Geom::Point mp = Geom::Point(a.mx0 * b->min()[Geom::X] + a.mx1 * b->max()[Geom::X],
+ a.my0 * b->min()[Geom::Y] + a.my1 * b->max()[Geom::Y]);
+
+ if (sel_as_group) {
+ if (focus) {
+ // use bounding box of all selected elements except the "focused" element
+ Inkscape::ObjectSet copy;
+ copy.add(selection->objects().begin(), selection->objects().end());
+ copy.remove(focus);
+ b = copy.preferredBounds();
+ } else {
+ // use bounding box of all selected elements
+ b = selection->preferredBounds();
+ }
+ }
+
+ //Move each item in the selected list separately
+ bool changed = false;
+ for (auto item : selected)
+ {
+ desktop->getDocument()->ensureUpToDate();
+ if (!sel_as_group)
+ b = (item)->desktopPreferredBounds();
+ if (b && (!focus || (item) != focus)) {
+ Geom::Point const sp(a.sx0 * b->min()[Geom::X] + a.sx1 * b->max()[Geom::X],
+ a.sy0 * b->min()[Geom::Y] + a.sy1 * b->max()[Geom::Y]);
+ Geom::Point const mp_rel( mp - sp );
+ if (LInfty(mp_rel) > 1e-9) {
+ item->move_rel(Geom::Translate(mp_rel));
+ changed = true;
+ }
+ }
+ }
+
+ if (changed) {
+ DocumentUndo::done( desktop->getDocument() , SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
+ _("Align"));
+ }
+}
+
+
+ActionAlign::Coeffs const ActionAlign::_allCoeffs[19] = {
+ {1., 0., 0., 0., 0., 1., 0., 0., SP_VERB_ALIGN_HORIZONTAL_RIGHT_TO_ANCHOR},
+ {1., 0., 0., 0., 1., 0., 0., 0., SP_VERB_ALIGN_HORIZONTAL_LEFT},
+ {.5, .5, 0., 0., .5, .5, 0., 0., SP_VERB_ALIGN_HORIZONTAL_CENTER},
+ {0., 1., 0., 0., 0., 1., 0., 0., SP_VERB_ALIGN_HORIZONTAL_RIGHT},
+ {0., 1., 0., 0., 1., 0., 0., 0., SP_VERB_ALIGN_HORIZONTAL_LEFT_TO_ANCHOR},
+ {0., 0., 0., 1., 0., 0., 1., 0., SP_VERB_ALIGN_VERTICAL_BOTTOM_TO_ANCHOR},
+ {0., 0., 0., 1., 0., 0., 0., 1., SP_VERB_ALIGN_VERTICAL_TOP},
+ {0., 0., .5, .5, 0., 0., .5, .5, SP_VERB_ALIGN_VERTICAL_CENTER},
+ {0., 0., 1., 0., 0., 0., 1., 0., SP_VERB_ALIGN_VERTICAL_BOTTOM},
+ {0., 0., 1., 0., 0., 0., 0., 1., SP_VERB_ALIGN_VERTICAL_TOP_TO_ANCHOR},
+ {1., 0., 0., 1., 1., 0., 0., 1., SP_VERB_ALIGN_BOTH_TOP_LEFT},
+ {0., 1., 0., 1., 0., 1., 0., 1., SP_VERB_ALIGN_BOTH_TOP_RIGHT},
+ {0., 1., 1., 0., 0., 1., 1., 0., SP_VERB_ALIGN_BOTH_BOTTOM_RIGHT},
+ {1., 0., 1., 0., 1., 0., 1., 0., SP_VERB_ALIGN_BOTH_BOTTOM_LEFT},
+ {0., 1., 1., 0., 1., 0., 0., 1., SP_VERB_ALIGN_BOTH_TOP_LEFT_TO_ANCHOR},
+ {1., 0., 1., 0., 0., 1., 0., 1., SP_VERB_ALIGN_BOTH_TOP_RIGHT_TO_ANCHOR},
+ {1., 0., 0., 1., 0., 1., 1., 0., SP_VERB_ALIGN_BOTH_BOTTOM_RIGHT_TO_ANCHOR},
+ {0., 1., 0., 1., 1., 0., 1., 0., SP_VERB_ALIGN_BOTH_BOTTOM_LEFT_TO_ANCHOR},
+ {.5, .5, .5, .5, .5, .5, .5, .5, SP_VERB_ALIGN_BOTH_CENTER}
+};
+
+void ActionAlign::do_verb_action(SPDesktop *desktop, int verb)
+{
+ Inkscape::UI::Tools::ToolBase *event_context = desktop->getEventContext();
+ if (INK_IS_NODE_TOOL(event_context)) {
+ Inkscape::UI::Tools::NodeTool *nt = INK_NODE_TOOL(event_context);
+ if(!nt->_selected_nodes->empty()){
+ do_node_action(nt, verb);
+ return;
+ }
+ }
+ do_action(desktop, verb_to_coeff(verb));
+}
+
+int ActionAlign::verb_to_coeff(int verb) {
+
+ for(guint i = 0; i < G_N_ELEMENTS(_allCoeffs); i++) {
+ if (_allCoeffs[i].verb_id == verb) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+BBoxSort::BBoxSort(SPItem *pItem, Geom::Rect const &bounds, Geom::Dim2 orientation, double kBegin, double kEnd) :
+ item(pItem),
+ bbox (bounds)
+{
+ anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
+}
+BBoxSort::BBoxSort(const BBoxSort &rhs)
+ //NOTE : this copy ctor is called O(sort) when sorting the vector
+ //this is bad. The vector should be a vector of pointers.
+ //But I'll wait the bohem GC before doing that
+= default;
+
+bool operator< (const BBoxSort &a, const BBoxSort &b)
+{
+ return (a.anchor < b.anchor);
+}
+
+class ActionDistribute : public Action {
+public :
+ ActionDistribute(const Glib::ustring &id,
+ const Glib::ustring &tiptext,
+ guint row, guint column,
+ AlignAndDistribute &dialog,
+ bool onInterSpace,
+ Geom::Dim2 orientation,
+ double kBegin, double kEnd
+ ):
+ Action(id, tiptext, row, column,
+ dialog.distribute_table(), dialog),
+ _dialog(dialog),
+ _onInterSpace(onInterSpace),
+ _orientation(orientation),
+ _kBegin(kBegin),
+ _kEnd( kEnd)
+ {}
+
+private :
+ void on_button_click() override {
+ //Retrieve selected objects
+ SPDesktop *desktop = _dialog.getDesktop();
+ if (!desktop) return;
+
+ Inkscape::Selection *selection = desktop->getSelection();
+ if (!selection) return;
+
+ std::vector<SPItem*> selected(selection->items().begin(), selection->items().end());
+ if (selected.empty()) return;
+
+ //Check 2 or more selected objects
+ std::vector<SPItem*>::iterator second(selected.begin());
+ ++second;
+ if (second == selected.end()) return;
+
+ double kBegin = _kBegin;
+ double kEnd = _kEnd;
+ if (_orientation == Geom::Y && desktop->is_yaxisdown()) {
+ kBegin = 1. - kBegin;
+ kEnd = 1. - kEnd;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int prefs_bbox = prefs->getBool("/tools/bounding_box");
+ std::vector< BBoxSort > sorted;
+ for (auto item : selected){
+ Geom::OptRect bbox = !prefs_bbox ? (item)->desktopVisualBounds() : (item)->desktopGeometricBounds();
+ if (bbox) {
+ sorted.emplace_back(item, *bbox, _orientation, kBegin, kEnd);
+ }
+ }
+ //sort bbox by anchors
+ std::stable_sort(sorted.begin(), sorted.end());
+
+ // see comment in ActionAlign above
+ int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+
+ unsigned int len = sorted.size();
+ bool changed = false;
+ if (_onInterSpace)
+ {
+ //overall bboxes span
+ float dist = (sorted.back().bbox.max()[_orientation] -
+ sorted.front().bbox.min()[_orientation]);
+ //space eaten by bboxes
+ float span = 0;
+ for (unsigned int i = 0; i < len; i++)
+ {
+ span += sorted[i].bbox[_orientation].extent();
+ }
+ //new distance between each bbox
+ float step = (dist - span) / (len - 1);
+ float pos = sorted.front().bbox.min()[_orientation];
+ for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
+ it < sorted.end();
+ ++it )
+ {
+ if (!Geom::are_near(pos, it->bbox.min()[_orientation], 1e-6)) {
+ Geom::Point t(0.0, 0.0);
+ t[_orientation] = pos - it->bbox.min()[_orientation];
+ it->item->move_rel(Geom::Translate(t));
+ changed = true;
+ }
+ pos += it->bbox[_orientation].extent();
+ pos += step;
+ }
+ }
+ else
+ {
+ //overall anchor span
+ float dist = sorted.back().anchor - sorted.front().anchor;
+ //distance between anchors
+ float step = dist / (len - 1);
+
+ for ( unsigned int i = 0; i < len ; i ++ )
+ {
+ BBoxSort & it(sorted[i]);
+ //new anchor position
+ float pos = sorted.front().anchor + i * step;
+ //Don't move if we are really close
+ if (!Geom::are_near(pos, it.anchor, 1e-6)) {
+ //Compute translation
+ Geom::Point t(0.0, 0.0);
+ t[_orientation] = pos - it.anchor;
+ //translate
+ it.item->move_rel(Geom::Translate(t));
+ changed = true;
+ }
+ }
+ }
+
+ // restore compensation setting
+ prefs->setInt("/options/clonecompensation/value", saved_compensation);
+
+ if (changed) {
+ DocumentUndo::done( desktop->getDocument(), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
+ _("Distribute"));
+ }
+ }
+ guint _index;
+ AlignAndDistribute &_dialog;
+ bool _onInterSpace;
+ Geom::Dim2 _orientation;
+
+ double _kBegin;
+ double _kEnd;
+
+};
+
+
+class ActionNode : public Action {
+public :
+ ActionNode(const Glib::ustring &id,
+ const Glib::ustring &tiptext,
+ guint column,
+ AlignAndDistribute &dialog,
+ Geom::Dim2 orientation, bool distribute):
+ Action(id, tiptext, 0, column,
+ dialog.nodes_table(), dialog),
+ _orientation(orientation),
+ _distribute(distribute)
+ {}
+
+private :
+ Geom::Dim2 _orientation;
+ bool _distribute;
+
+ void on_button_click() override {
+ if (!_dialog.getDesktop()) {
+ return;
+ }
+
+ Inkscape::UI::Tools::ToolBase *event_context = _dialog.getDesktop()->getEventContext();
+
+ if (!INK_IS_NODE_TOOL(event_context)) {
+ return;
+ }
+
+ Inkscape::UI::Tools::NodeTool *nt = INK_NODE_TOOL(event_context);
+
+ if (_distribute) {
+ nt->_multipath->distributeNodes(_orientation);
+ } else {
+ nt->_multipath->alignNodes(_orientation);
+ }
+ }
+};
+
+class ActionRemoveOverlaps : public Action {
+private:
+ Gtk::Label removeOverlapXGapLabel;
+ Gtk::Label removeOverlapYGapLabel;
+ Inkscape::UI::Widget::SpinButton removeOverlapXGap;
+ Inkscape::UI::Widget::SpinButton removeOverlapYGap;
+
+public:
+ ActionRemoveOverlaps(Glib::ustring const &id,
+ Glib::ustring const &tiptext,
+ guint row,
+ guint column,
+ AlignAndDistribute &dialog) :
+ Action(id, tiptext, row, column + 4,
+ dialog.removeOverlap_table(), dialog)
+ {
+ dialog.removeOverlap_table().set_column_spacing(3);
+
+ removeOverlapXGap.set_digits(1);
+ removeOverlapXGap.set_size_request(60, -1);
+ removeOverlapXGap.set_increments(1.0, 0);
+ removeOverlapXGap.set_range(-1000.0, 1000.0);
+ removeOverlapXGap.set_value(0);
+ removeOverlapXGap.set_tooltip_text(_("Minimum horizontal gap (in px units) between bounding boxes"));
+ //TRANSLATORS: "H:" stands for horizontal gap
+ removeOverlapXGapLabel.set_text_with_mnemonic(C_("Gap", "_H:"));
+ removeOverlapXGapLabel.set_mnemonic_widget(removeOverlapXGap);
+
+ removeOverlapYGap.set_digits(1);
+ removeOverlapYGap.set_size_request(60, -1);
+ removeOverlapYGap.set_increments(1.0, 0);
+ removeOverlapYGap.set_range(-1000.0, 1000.0);
+ removeOverlapYGap.set_value(0);
+ removeOverlapYGap.set_tooltip_text(_("Minimum vertical gap (in px units) between bounding boxes"));
+ /* TRANSLATORS: Vertical gap */
+ removeOverlapYGapLabel.set_text_with_mnemonic(C_("Gap", "_V:"));
+ removeOverlapYGapLabel.set_mnemonic_widget(removeOverlapYGap);
+
+ dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, row, 1, 1);
+ dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, row, 1, 1);
+ dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, row, 1, 1);
+ dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, row, 1, 1);
+ }
+
+private :
+ void on_button_click() override
+ {
+ if (!_dialog.getDesktop()) return;
+
+ // see comment in ActionAlign above
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+
+ // xGap and yGap are the minimum space required between bounding rectangles.
+ double const xGap = removeOverlapXGap.get_value();
+ double const yGap = removeOverlapYGap.get_value();
+ auto tmp = _dialog.getDesktop()->getSelection()->items();
+ std::vector<SPItem *> vec(tmp.begin(), tmp.end());
+ removeoverlap(vec, xGap, yGap);
+
+ // restore compensation setting
+ prefs->setInt("/options/clonecompensation/value", saved_compensation);
+
+ DocumentUndo::done(_dialog.getDesktop()->getDocument(), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
+ _("Remove overlaps"));
+ }
+};
+
+class ActionGraphLayout : public Action {
+public:
+ ActionGraphLayout(Glib::ustring const &id,
+ Glib::ustring const &tiptext,
+ guint row,
+ guint column,
+ AlignAndDistribute &dialog) :
+ Action(id, tiptext, row, column,
+ dialog.rearrange_table(), dialog)
+ {}
+
+private :
+ void on_button_click() override
+ {
+ if (!_dialog.getDesktop()) return;
+
+ // see comment in ActionAlign above
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+
+ auto tmp = _dialog.getDesktop()->getSelection()->items();
+ std::vector<SPItem *> vec(tmp.begin(), tmp.end());
+ graphlayout(vec);
+ // restore compensation setting
+ prefs->setInt("/options/clonecompensation/value", saved_compensation);
+
+ DocumentUndo::done(_dialog.getDesktop()->getDocument(), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
+ _("Arrange connector network"));
+ }
+};
+
+class ActionExchangePositions : public Action {
+public:
+ enum SortOrder {
+ None,
+ ZOrder,
+ Clockwise
+ };
+
+ ActionExchangePositions(Glib::ustring const &id,
+ Glib::ustring const &tiptext,
+ guint row,
+ guint column,
+ AlignAndDistribute &dialog, SortOrder order = None) :
+ Action(id, tiptext, row, column,
+ dialog.rearrange_table(), dialog),
+ sortOrder(order)
+ {};
+
+
+private :
+ const SortOrder sortOrder;
+ static boost::optional<Geom::Point> center;
+
+ static bool sort_compare(const SPItem * a,const SPItem * b) {
+ if (a == nullptr) return false;
+ if (b == nullptr) return true;
+ if (center) {
+ Geom::Point point_a = a->getCenter() - (*center);
+ Geom::Point point_b = b->getCenter() - (*center);
+ // First criteria: Sort according to the angle to the center point
+ double angle_a = atan2(double(point_a[Geom::Y]), double(point_a[Geom::X]));
+ double angle_b = atan2(double(point_b[Geom::Y]), double(point_b[Geom::X]));
+ double dt_yaxisdir = SP_ACTIVE_DESKTOP ? SP_ACTIVE_DESKTOP->yaxisdir() : 1;
+ angle_a *= -dt_yaxisdir;
+ angle_b *= -dt_yaxisdir;
+ if (angle_a != angle_b) return (angle_a < angle_b);
+ // Second criteria: Sort according to the distance the center point
+ Geom::Coord length_a = point_a.length();
+ Geom::Coord length_b = point_b.length();
+ if (length_a != length_b) return (length_a > length_b);
+ }
+ // Last criteria: Sort according to the z-coordinate
+ return sp_item_repr_compare_position(a,b)<0;
+ }
+
+ void on_button_click() override
+ {
+ SPDesktop *desktop = _dialog.getDesktop();
+ if (!desktop) return;
+
+ Inkscape::Selection *selection = desktop->getSelection();
+ if (!selection) return;
+
+ std::vector<SPItem*> selected(selection->items().begin(), selection->items().end());
+
+ //Check 2 or more selected objects
+ if (selected.size() < 2) return;
+
+ // see comment in ActionAlign above
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+
+ // sort the list
+ if (sortOrder != None) {
+ if (sortOrder == Clockwise) {
+ center = selection->center();
+ } else { // sorting by ZOrder is outomatically done by not setting the center
+ center.reset();
+ }
+ sort(selected.begin(),selected.end(),sort_compare);
+ }
+
+ Geom::Point p1 = selected.back()->getCenter();
+ for (SPItem *item : selected)
+ {
+ Geom::Point p2 = item->getCenter();
+ Geom::Point delta = p1 - p2;
+ item->move_rel(Geom::Translate(delta[Geom::X],delta[Geom::Y] ));
+ p1 = p2;
+ }
+
+ // restore compensation setting
+ prefs->setInt("/options/clonecompensation/value", saved_compensation);
+
+ DocumentUndo::done(_dialog.getDesktop()->getDocument(), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
+ _("Exchange Positions"));
+ }
+};
+
+// instantiate the private static member
+boost::optional<Geom::Point> ActionExchangePositions::center;
+
+class ActionUnclump : public Action {
+public :
+ ActionUnclump(const Glib::ustring &id,
+ const Glib::ustring &tiptext,
+ guint row,
+ guint column,
+ AlignAndDistribute &dialog):
+ Action(id, tiptext, row, column,
+ dialog.rearrange_table(), dialog)
+ {}
+
+private :
+ void on_button_click() override
+ {
+ if (!_dialog.getDesktop()) return;
+
+ // see comment in ActionAlign above
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+ auto tmp = _dialog.getDesktop()->getSelection()->items();
+ std::vector<SPItem*> x(tmp.begin(), tmp.end());
+ unclump (x);
+
+ // restore compensation setting
+ prefs->setInt("/options/clonecompensation/value", saved_compensation);
+
+ DocumentUndo::done(_dialog.getDesktop()->getDocument(), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
+ _("Unclump"));
+ }
+};
+
+class ActionRandomize : public Action {
+public :
+ ActionRandomize(const Glib::ustring &id,
+ const Glib::ustring &tiptext,
+ guint row,
+ guint column,
+ AlignAndDistribute &dialog):
+ Action(id, tiptext, row, column,
+ dialog.rearrange_table(), dialog)
+ {}
+
+private :
+ void on_button_click() override
+ {
+ SPDesktop *desktop = _dialog.getDesktop();
+ if (!desktop) return;
+
+ Inkscape::Selection *selection = desktop->getSelection();
+ if (!selection) return;
+
+ std::vector<SPItem*> selected(selection->items().begin(), selection->items().end());
+ if (selected.empty()) return;
+
+ //Check 2 or more selected objects
+ if (selected.size() < 2) return;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int prefs_bbox = prefs->getBool("/tools/bounding_box");
+ Geom::OptRect sel_bbox = !prefs_bbox ? selection->visualBounds() : selection->geometricBounds();
+ if (!sel_bbox) {
+ return;
+ }
+
+ // This bbox is cached between calls to randomize, so that there's no growth nor shrink
+ // nor drift on sequential randomizations. Discard cache on global (or better active
+ // desktop's) selection_change signal.
+ if (!_dialog.randomize_bbox) {
+ _dialog.randomize_bbox = *sel_bbox;
+ }
+
+ // see comment in ActionAlign above
+ int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+
+ for (auto item : selected)
+ {
+ desktop->getDocument()->ensureUpToDate();
+ Geom::OptRect item_box = !prefs_bbox ? (item)->desktopVisualBounds() : (item)->desktopGeometricBounds();
+ if (item_box) {
+ // find new center, staying within bbox
+ double x = _dialog.randomize_bbox->min()[Geom::X] + (*item_box)[Geom::X].extent() /2 +
+ g_random_double_range (0, (*_dialog.randomize_bbox)[Geom::X].extent() - (*item_box)[Geom::X].extent());
+ double y = _dialog.randomize_bbox->min()[Geom::Y] + (*item_box)[Geom::Y].extent()/2 +
+ g_random_double_range (0, (*_dialog.randomize_bbox)[Geom::Y].extent() - (*item_box)[Geom::Y].extent());
+ // displacement is the new center minus old:
+ Geom::Point t = Geom::Point (x, y) - 0.5*(item_box->max() + item_box->min());
+ item->move_rel(Geom::Translate(t));
+ }
+ }
+
+ // restore compensation setting
+ prefs->setInt("/options/clonecompensation/value", saved_compensation);
+
+ DocumentUndo::done(desktop->getDocument(), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
+ _("Randomize positions"));
+ }
+};
+
+struct Baselines
+{
+ SPItem *_item;
+ Geom::Point _base;
+ Geom::Dim2 _orientation;
+ Baselines(SPItem *item, Geom::Point base, Geom::Dim2 orientation) :
+ _item (item),
+ _base (base),
+ _orientation (orientation)
+ {}
+};
+
+static bool operator< (const Baselines &a, const Baselines &b)
+{
+ return (a._base[a._orientation] < b._base[b._orientation]);
+}
+
+class ActionBaseline : public Action {
+public :
+ ActionBaseline(const Glib::ustring &id,
+ const Glib::ustring &tiptext,
+ guint row,
+ guint column,
+ AlignAndDistribute &dialog,
+ Gtk::Grid &table,
+ Geom::Dim2 orientation, bool distribute):
+ Action(id, tiptext, row, column,
+ table, dialog),
+ _orientation(orientation),
+ _distribute(distribute)
+ {}
+
+private :
+ Geom::Dim2 _orientation;
+ bool _distribute;
+ void on_button_click() override
+ {
+ SPDesktop *desktop = _dialog.getDesktop();
+ if (!desktop) return;
+
+ Inkscape::Selection *selection = desktop->getSelection();
+ if (!selection) return;
+
+ std::vector<SPItem*> selected(selection->items().begin(), selection->items().end());
+
+ //Check 2 or more selected objects
+ if (selected.size() < 2) return;
+
+ Geom::Point b_min = Geom::Point (HUGE_VAL, HUGE_VAL);
+ Geom::Point b_max = Geom::Point (-HUGE_VAL, -HUGE_VAL);
+
+ std::vector<Baselines> sorted;
+
+ for (auto item : selected)
+ {
+ if (SP_IS_TEXT (item) || SP_IS_FLOWTEXT (item)) {
+ Inkscape::Text::Layout const *layout = te_get_layout(item);
+ boost::optional<Geom::Point> pt = layout->baselineAnchorPoint();
+ if (pt) {
+ Geom::Point base = *pt * (item)->i2dt_affine();
+ if (base[Geom::X] < b_min[Geom::X]) b_min[Geom::X] = base[Geom::X];
+ if (base[Geom::Y] < b_min[Geom::Y]) b_min[Geom::Y] = base[Geom::Y];
+ if (base[Geom::X] > b_max[Geom::X]) b_max[Geom::X] = base[Geom::X];
+ if (base[Geom::Y] > b_max[Geom::Y]) b_max[Geom::Y] = base[Geom::Y];
+ Baselines b (item, base, _orientation);
+ sorted.push_back(b);
+ }
+ }
+ }
+
+ if (sorted.size() <= 1) return;
+
+ //sort baselines
+ std::stable_sort(sorted.begin(), sorted.end());
+
+ bool changed = false;
+
+ if (_distribute) {
+ double step = (b_max[_orientation] - b_min[_orientation])/(sorted.size() - 1);
+ for (unsigned int i = 0; i < sorted.size(); i++) {
+ SPItem *item = sorted[i]._item;
+ Geom::Point base = sorted[i]._base;
+ Geom::Point t(0.0, 0.0);
+ t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
+ item->move_rel(Geom::Translate(t));
+ changed = true;
+ }
+
+ if (changed) {
+ DocumentUndo::done(desktop->getDocument(), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
+ _("Distribute text baselines"));
+ }
+
+ } else { //align
+ Geom::Point ref_point;
+ SPItem *focus = nullptr;
+ Geom::OptRect b = Geom::OptRect();
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ switch (AlignTarget(prefs->getInt("/dialogs/align/align-to", 6)))
+ {
+ case LAST:
+ focus = SP_ITEM(selected.back());
+ break;
+ case FIRST:
+ focus = SP_ITEM(selected.front());
+ break;
+ case BIGGEST:
+ focus = selection->largestItem(Selection::AREA);
+ break;
+ case SMALLEST:
+ focus = selection->smallestItem(Selection::AREA);
+ break;
+ case PAGE:
+ b = desktop->getDocument()->preferredBounds();
+ break;
+ case DRAWING:
+ b = desktop->getDocument()->getRoot()->desktopPreferredBounds();
+ break;
+ case SELECTION:
+ b = selection->preferredBounds();
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ };
+
+ if(focus) {
+ if (SP_IS_TEXT (focus) || SP_IS_FLOWTEXT (focus)) {
+ ref_point = *(te_get_layout(focus)->baselineAnchorPoint())*(focus->i2dt_affine());
+ } else {
+ ref_point = focus->desktopPreferredBounds()->min();
+ }
+ } else {
+ ref_point = b->min();
+ }
+
+ for (auto item : selected)
+ {
+ if (SP_IS_TEXT (item) || SP_IS_FLOWTEXT (item)) {
+ Inkscape::Text::Layout const *layout = te_get_layout(item);
+ boost::optional<Geom::Point> pt = layout->baselineAnchorPoint();
+ if (pt) {
+ Geom::Point base = *pt * (item)->i2dt_affine();
+ Geom::Point t(0.0, 0.0);
+ t[_orientation] = ref_point[_orientation] - base[_orientation];
+ item->move_rel(Geom::Translate(t));
+ changed = true;
+ }
+ }
+ }
+
+ if (changed) {
+ DocumentUndo::done(desktop->getDocument(), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
+ _("Align text baselines"));
+ }
+ }
+ }
+};
+
+
+
+static void on_tool_changed(AlignAndDistribute *daad)
+{
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (desktop && desktop->getEventContext())
+ daad->setMode(tools_active(desktop) == TOOLS_NODES);
+ else
+ daad->setMode(false);
+
+}
+
+static void on_selection_changed(AlignAndDistribute *daad)
+{
+ daad->randomize_bbox = Geom::OptRect();
+}
+
+/////////////////////////////////////////////////////////
+
+
+
+
+AlignAndDistribute::AlignAndDistribute()
+ : UI::Widget::Panel("/dialogs/align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
+ randomize_bbox(),
+ _alignFrame(_("Align")),
+ _distributeFrame(_("Distribute")),
+ _rearrangeFrame(_("Rearrange")),
+ _removeOverlapFrame(_("Remove overlaps")),
+ _nodesFrame(_("Nodes")),
+ _alignTable(),
+ _distributeTable(),
+ _rearrangeTable(),
+ _removeOverlapTable(),
+ _nodesTable(),
+ _anchorLabel(_("Relative to: ")),
+ _anchorLabelNode(_("Relative to: "))
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ //Instantiate the align buttons
+ addAlignButton(INKSCAPE_ICON("align-horizontal-right-to-anchor"),
+ _("Align right edges of objects to the left edge of the anchor"),
+ 0, 0);
+ addAlignButton(INKSCAPE_ICON("align-horizontal-left"),
+ _("Align left edges"),
+ 0, 1);
+ addAlignButton(INKSCAPE_ICON("align-horizontal-center"),
+ _("Center on vertical axis"),
+ 0, 2);
+ addAlignButton(INKSCAPE_ICON("align-horizontal-right"),
+ _("Align right sides"),
+ 0, 3);
+ addAlignButton(INKSCAPE_ICON("align-horizontal-left-to-anchor"),
+ _("Align left edges of objects to the right edge of the anchor"),
+ 0, 4);
+ addAlignButton(INKSCAPE_ICON("align-vertical-bottom-to-anchor"),
+ _("Align bottom edges of objects to the top edge of the anchor"),
+ 1, 0);
+ addAlignButton(INKSCAPE_ICON("align-vertical-top"),
+ _("Align top edges"),
+ 1, 1);
+ addAlignButton(INKSCAPE_ICON("align-vertical-center"),
+ _("Center on horizontal axis"),
+ 1, 2);
+ addAlignButton(INKSCAPE_ICON("align-vertical-bottom"),
+ _("Align bottom edges"),
+ 1, 3);
+ addAlignButton(INKSCAPE_ICON("align-vertical-top-to-anchor"),
+ _("Align top edges of objects to the bottom edge of the anchor"),
+ 1, 4);
+
+ //Baseline aligns
+ addBaselineButton(INKSCAPE_ICON("align-horizontal-baseline"),
+ _("Align baseline anchors of texts horizontally"),
+ 0, 5, this->align_table(), Geom::X, false);
+ addBaselineButton(INKSCAPE_ICON("align-vertical-baseline"),
+ _("Align baselines of texts"),
+ 1, 5, this->align_table(), Geom::Y, false);
+
+ //The distribute buttons
+ addDistributeButton(INKSCAPE_ICON("distribute-horizontal-gaps"),
+ _("Make horizontal gaps between objects equal"),
+ 0, 4, true, Geom::X, .5, .5);
+
+ addDistributeButton(INKSCAPE_ICON("distribute-horizontal-left"),
+ _("Distribute left edges equidistantly"),
+ 0, 1, false, Geom::X, 1., 0.);
+ addDistributeButton(INKSCAPE_ICON("distribute-horizontal-center"),
+ _("Distribute centers equidistantly horizontally"),
+ 0, 2, false, Geom::X, .5, .5);
+ addDistributeButton(INKSCAPE_ICON("distribute-horizontal-right"),
+ _("Distribute right edges equidistantly"),
+ 0, 3, false, Geom::X, 0., 1.);
+
+ addDistributeButton(INKSCAPE_ICON("distribute-vertical-gaps"),
+ _("Make vertical gaps between objects equal"),
+ 1, 4, true, Geom::Y, .5, .5);
+
+ addDistributeButton(INKSCAPE_ICON("distribute-vertical-top"),
+ _("Distribute top edges equidistantly"),
+ 1, 1, false, Geom::Y, 0, 1);
+ addDistributeButton(INKSCAPE_ICON("distribute-vertical-center"),
+ _("Distribute centers equidistantly vertically"),
+ 1, 2, false, Geom::Y, .5, .5);
+ addDistributeButton(INKSCAPE_ICON("distribute-vertical-bottom"),
+ _("Distribute bottom edges equidistantly"),
+ 1, 3, false, Geom::Y, 1., 0.);
+
+ //Baseline distribs
+ addBaselineButton(INKSCAPE_ICON("distribute-horizontal-baseline"),
+ _("Distribute baseline anchors of texts horizontally"),
+ 0, 5, this->distribute_table(), Geom::X, true);
+ addBaselineButton(INKSCAPE_ICON("distribute-vertical-baseline"),
+ _("Distribute baselines of texts vertically"),
+ 1, 5, this->distribute_table(), Geom::Y, true);
+
+ // Rearrange
+ //Graph Layout
+ addGraphLayoutButton(INKSCAPE_ICON("distribute-graph"),
+ _("Nicely arrange selected connector network"),
+ 0, 0);
+ addExchangePositionsButton(INKSCAPE_ICON("exchange-positions"),
+ _("Exchange positions of selected objects - selection order"),
+ 0, 1);
+ addExchangePositionsByZOrderButton(INKSCAPE_ICON("exchange-positions-zorder"),
+ _("Exchange positions of selected objects - stacking order"),
+ 0, 2);
+ addExchangePositionsClockwiseButton(INKSCAPE_ICON("exchange-positions-clockwise"),
+ _("Exchange positions of selected objects - clockwise rotate"),
+ 0, 3);
+
+ //Randomize & Unclump
+ addRandomizeButton(INKSCAPE_ICON("distribute-randomize"),
+ _("Randomize centers in both dimensions"),
+ 0, 4);
+ addUnclumpButton(INKSCAPE_ICON("distribute-unclump"),
+ _("Unclump objects: try to equalize edge-to-edge distances"),
+ 0, 5);
+
+ //Remove overlaps
+ addRemoveOverlapsButton(INKSCAPE_ICON("distribute-remove-overlaps"),
+ _("Move objects as little as possible so that their bounding boxes do not overlap"),
+ 0, 0);
+
+ //Node Mode buttons
+ // NOTE: "align nodes vertically" means "move nodes vertically until they align on a common
+ // _horizontal_ line". This is analogous to what the "align-vertical-center" icon means.
+ // There is no doubt some ambiguity. For this reason the descriptions are different.
+ addNodeButton(INKSCAPE_ICON("align-vertical-node"),
+ _("Align selected nodes to a common horizontal line"),
+ 0, Geom::X, false);
+ addNodeButton(INKSCAPE_ICON("align-horizontal-node"),
+ _("Align selected nodes to a common vertical line"),
+ 1, Geom::Y, false);
+ addNodeButton(INKSCAPE_ICON("distribute-horizontal-node"),
+ _("Distribute selected nodes horizontally"),
+ 2, Geom::X, true);
+ addNodeButton(INKSCAPE_ICON("distribute-vertical-node"),
+ _("Distribute selected nodes vertically"),
+ 3, Geom::Y, true);
+
+ //Rest of the widgetry
+
+ _combo.append(_("Last selected"));
+ _combo.append(_("First selected"));
+ _combo.append(_("Biggest object"));
+ _combo.append(_("Smallest object"));
+ _combo.append(_("Page"));
+ _combo.append(_("Drawing"));
+ _combo.append(_("Selection Area"));
+ _combo.set_active(prefs->getInt("/dialogs/align/align-to", 6));
+ _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
+
+ _comboNode.append(_("Last selected"));
+ _comboNode.append(_("First selected"));
+ _comboNode.append(_("Middle of selection"));
+ _comboNode.append(_("Min value"));
+ _comboNode.append(_("Max value"));
+ _comboNode.set_active(prefs->getInt("/dialogs/align/align-nodes-to", 2));
+ _comboNode.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_node_ref_change));
+
+ Gtk::Image* selgrp_icon = Gtk::manage(new Gtk::Image());
+ selgrp_icon = sp_get_icon_image("align-sel-as-group", Gtk::ICON_SIZE_LARGE_TOOLBAR);
+ _selgrp.add(*selgrp_icon);
+
+ _selgrp.set_active(prefs->getBool("/dialogs/align/sel-as-groups"));
+ _selgrp.set_relief(Gtk::RELIEF_NONE);
+ _selgrp.set_tooltip_text(_("Treat selection as group"));
+ _selgrp.signal_toggled().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_selgrp_toggled));
+ _anchorBox.pack_end(_selgrp, false, false);
+ _anchorBox.pack_end(_combo, false, false);
+ _anchorBox.pack_end(_anchorLabel, false, false);
+
+ _anchorBoxNode.pack_end(_comboNode, false, false);
+ _anchorBoxNode.pack_end(_anchorLabelNode, false, false);
+
+ Gtk::Image* oncanvas_icon = Gtk::manage(new Gtk::Image());
+ oncanvas_icon = sp_get_icon_image("align-on-canvas", Gtk::ICON_SIZE_LARGE_TOOLBAR);
+ _oncanvas.add(*oncanvas_icon);
+
+ _oncanvas.set_relief(Gtk::RELIEF_NONE);
+ _oncanvas.set_tooltip_text(_("Enable on-canvas alignment handles."));
+ _anchorBox.pack_start(_oncanvas, false, false);
+ _oncanvas.set_active(prefs->getBool("/dialogs/align/oncanvas"));
+ _oncanvas.signal_toggled().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_oncanvas_toggled));
+
+ // Right align the buttons
+ _alignTableBox.pack_end(_alignTable, false, false);
+ _distributeTableBox.pack_end(_distributeTable, false, false);
+ _rearrangeTableBox.pack_end(_rearrangeTable, false, false);
+ _removeOverlapTableBox.pack_end(_removeOverlapTable, false, false);
+ _nodesTableBox.pack_end(_nodesTable, false, false);
+
+ _alignBox.pack_start(_anchorBox);
+ _alignBox.pack_start(_selgrpBox);
+ _alignBox.pack_start(_alignTableBox);
+
+ _alignBoxNode.pack_start(_anchorBoxNode, false, false);
+ _alignBoxNode.pack_start(_nodesTableBox);
+
+
+ _alignFrame.add(_alignBox);
+ _distributeFrame.add(_distributeTableBox);
+ _rearrangeFrame.add(_rearrangeTableBox);
+ _removeOverlapFrame.add(_removeOverlapTableBox);
+ _nodesFrame.add(_alignBoxNode);
+
+ Gtk::Box *contents = _getContents();
+ contents->set_spacing(4);
+
+ // Notebook for individual transformations
+
+ contents->pack_start(_alignFrame, true, true);
+ contents->pack_start(_distributeFrame, true, true);
+ contents->pack_start(_rearrangeFrame, true, true);
+ contents->pack_start(_removeOverlapFrame, true, true);
+ contents->pack_start(_nodesFrame, true, false);
+
+ //Connect to the global tool change signal
+ _toolChangeConn = INKSCAPE.signal_eventcontext_set.connect(sigc::hide<0>(sigc::bind(sigc::ptr_fun(&on_tool_changed), this)));
+
+ // Connect to the global selection change, to invalidate cached randomize_bbox
+ _selChangeConn = INKSCAPE.signal_selection_changed.connect(sigc::hide<0>(sigc::bind(sigc::ptr_fun(&on_selection_changed), this)));
+ randomize_bbox = Geom::OptRect();
+
+ _desktopChangeConn = _deskTrack.connectDesktopChanged( sigc::mem_fun(*this, &AlignAndDistribute::setDesktop) );
+ _deskTrack.connect(GTK_WIDGET(gobj()));
+
+ show_all_children();
+
+ on_tool_changed (this); // set current mode
+}
+
+AlignAndDistribute::~AlignAndDistribute()
+{
+ for (auto & it : _actionList) {
+ delete it;
+ }
+
+ _toolChangeConn.disconnect();
+ _selChangeConn.disconnect();
+ _desktopChangeConn.disconnect();
+ _deskTrack.disconnect();
+}
+
+void AlignAndDistribute::setTargetDesktop(SPDesktop *desktop)
+{
+ if (_desktop != desktop) {
+ _desktop = desktop;
+ on_tool_changed (this);
+ }
+}
+
+
+void AlignAndDistribute::on_ref_change(){
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/dialogs/align/align-to", _combo.get_active_row_number());
+
+ //Make blink the master
+}
+
+void AlignAndDistribute::on_node_ref_change(){
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/dialogs/align/align-nodes-to", _comboNode.get_active_row_number());
+
+ //Make blink the master
+}
+
+void AlignAndDistribute::on_selgrp_toggled(){
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/dialogs/align/sel-as-groups", _selgrp.get_active());
+
+ //Make blink the master
+}
+
+void AlignAndDistribute::on_oncanvas_toggled(){
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/dialogs/align/oncanvas", _oncanvas.get_active());
+
+ //Make blink the master
+}
+
+void AlignAndDistribute::setMode(bool nodeEdit)
+{
+ //Act on widgets used in node mode
+ void ( Gtk::Widget::*mNode) () = nodeEdit ?
+ &Gtk::Widget::show_all : &Gtk::Widget::hide;
+
+ //Act on widgets used in selection mode
+ void ( Gtk::Widget::*mSel) () = nodeEdit ?
+ &Gtk::Widget::hide : &Gtk::Widget::show_all;
+
+ ((_alignFrame).*(mSel))();
+ ((_distributeFrame).*(mSel))();
+ ((_rearrangeFrame).*(mSel))();
+ ((_removeOverlapFrame).*(mSel))();
+ ((_nodesFrame).*(mNode))();
+ _getContents()->queue_resize();
+
+}
+void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
+ guint row, guint col)
+{
+ _actionList.push_back(
+ new ActionAlign(
+ id, tiptext, row, col,
+ *this , col + row * 5));
+}
+void AlignAndDistribute::addDistributeButton(const Glib::ustring &id, const Glib::ustring tiptext,
+ guint row, guint col, bool onInterSpace,
+ Geom::Dim2 orientation, float kBegin, float kEnd)
+{
+ _actionList.push_back(
+ new ActionDistribute(
+ id, tiptext, row, col, *this ,
+ onInterSpace, orientation,
+ kBegin, kEnd
+ )
+ );
+}
+
+void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
+ guint col, Geom::Dim2 orientation, bool distribute)
+{
+ _actionList.push_back(
+ new ActionNode(
+ id, tiptext, col,
+ *this, orientation, distribute));
+}
+
+void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
+ guint row, guint col)
+{
+ _actionList.push_back(
+ new ActionRemoveOverlaps(
+ id, tiptext, row, col, *this)
+ );
+}
+
+void AlignAndDistribute::addGraphLayoutButton(const Glib::ustring &id, const Glib::ustring tiptext,
+ guint row, guint col)
+{
+ _actionList.push_back(
+ new ActionGraphLayout(
+ id, tiptext, row, col, *this)
+ );
+}
+
+void AlignAndDistribute::addExchangePositionsButton(const Glib::ustring &id, const Glib::ustring tiptext,
+ guint row, guint col)
+{
+ _actionList.push_back(
+ new ActionExchangePositions(
+ id, tiptext, row, col, *this)
+ );
+}
+
+void AlignAndDistribute::addExchangePositionsByZOrderButton(const Glib::ustring &id, const Glib::ustring tiptext,
+ guint row, guint col)
+{
+ _actionList.push_back(
+ new ActionExchangePositions(
+ id, tiptext, row, col, *this, ActionExchangePositions::ZOrder)
+ );
+}
+
+void AlignAndDistribute::addExchangePositionsClockwiseButton(const Glib::ustring &id, const Glib::ustring tiptext,
+ guint row, guint col)
+{
+ _actionList.push_back(
+ new ActionExchangePositions(
+ id, tiptext, row, col, *this, ActionExchangePositions::Clockwise)
+ );
+}
+
+void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
+ guint row, guint col)
+{
+ _actionList.push_back(
+ new ActionUnclump(
+ id, tiptext, row, col, *this)
+ );
+}
+
+void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
+ guint row, guint col)
+{
+ _actionList.push_back(
+ new ActionRandomize(
+ id, tiptext, row, col, *this)
+ );
+}
+
+void AlignAndDistribute::addBaselineButton(const Glib::ustring &id, const Glib::ustring tiptext,
+ guint row, guint col, Gtk::Grid &table, Geom::Dim2 orientation, bool distribute)
+{
+ _actionList.push_back(
+ new ActionBaseline(
+ id, tiptext, row, col,
+ *this, table, orientation, distribute));
+}
+
+} // 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 :