// 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 :
