summaryrefslogtreecommitdiffstats
path: root/src/ui/dialog/align-and-distribute.cpp
diff options
context:
space:
mode:
authorMenTaLguY <mental@rydia.net>2006-01-16 02:36:01 +0000
committermental <mental@users.sourceforge.net>2006-01-16 02:36:01 +0000
commit179fa413b047bede6e32109e2ce82437c5fb8d34 (patch)
treea5a6ac2c1708bd02288fbd8edb2ff500ff2e0916 /src/ui/dialog/align-and-distribute.cpp
downloadinkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.tar.gz
inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.zip
moving trunk for module inkscape
(bzr r1)
Diffstat (limited to 'src/ui/dialog/align-and-distribute.cpp')
-rw-r--r--src/ui/dialog/align-and-distribute.cpp1051
1 files changed, 1051 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 000000000..262aa690b
--- /dev/null
+++ b/src/ui/dialog/align-and-distribute.cpp
@@ -0,0 +1,1051 @@
+/**
+ * \brief Align and Distribute dialog
+ *
+ * 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>
+ *
+ * Copyright (C) 1999-2004, 2005 Authors
+ *
+ * Released under GNU GPL. Read the file 'COPYING' for more information.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "verbs.h"
+
+#include "dialogs/unclump.h"
+#include "removeoverlap/removeoverlap.h"
+
+#include <gtkmm/spinbutton.h>
+
+
+
+
+#include "util/glib-list-iterators.h"
+
+#include "widgets/icon.h"
+
+#include "inkscape.h"
+#include "document.h"
+#include "selection.h"
+#include "desktop-handles.h"
+#include "macros.h"
+#include "sp-item-transform.h"
+#include "prefs-utils.h"
+#include "enums.h"
+
+#include "sp-text.h"
+#include "sp-flowtext.h"
+#include "text-editing.h"
+
+#include "node-context.h" //For node align/distribute function
+
+#include "tools-switch.h"
+
+#include "align-and-distribute.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+/////////helper classes//////////////////////////////////
+
+class Action {
+public :
+ Action(const Glib::ustring &id,
+ const Glib::ustring &tiptext,
+ guint row, guint column,
+ Gtk::Table &parent,
+ Gtk::Tooltips &tooltips,
+ AlignAndDistribute &dialog):
+ _dialog(dialog),
+ _id(id),
+ _parent(parent)
+ {
+ Gtk::Widget* pIcon = Gtk::manage( sp_icon_get_icon( _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));
+ tooltips.set_tip(*pButton, tiptext);
+ parent.attach(*pButton, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
+ }
+ virtual ~Action(){}
+
+ AlignAndDistribute &_dialog;
+
+private :
+ virtual void on_button_click(){}
+
+ Glib::ustring _id;
+ Gtk::Table &_parent;
+};
+
+
+class ActionAlign : public Action {
+public :
+ struct Coeffs {
+ double mx0, mx1, my0, my1;
+ double sx0, sx1, sy0, sy1;
+ };
+ ActionAlign(const Glib::ustring &id,
+ const Glib::ustring &tiptext,
+ guint row, guint column,
+ AlignAndDistribute &dialog,
+ guint coeffIndex):
+ Action(id, tiptext, row, column,
+ dialog.align_table(), dialog.tooltips(), dialog),
+ _index(coeffIndex),
+ _dialog(dialog)
+ {}
+
+private :
+
+ virtual void on_button_click() {
+ //Retreive selected objects
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (!desktop) return;
+
+ Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
+ if (!selection) return;
+
+ using Inkscape::Util::GSListConstIterator;
+ std::list<SPItem *> selected;
+ selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
+ if (selected.empty()) return;
+
+ NR::Point mp; //Anchor point
+ AlignAndDistribute::AlignTarget target = _dialog.getAlignTarget();
+ const Coeffs &a= _allCoeffs[_index];
+ switch (target)
+ {
+ case AlignAndDistribute::LAST:
+ case AlignAndDistribute::FIRST:
+ case AlignAndDistribute::BIGGEST:
+ case AlignAndDistribute::SMALLEST:
+ {
+ //Check 2 or more selected objects
+ std::list<SPItem *>::iterator second(selected.begin());
+ ++second;
+ if (second == selected.end())
+ return;
+ //Find the master (anchor on which the other objects are aligned)
+ std::list<SPItem *>::iterator master(
+ _dialog.find_master (
+ selected,
+ (a.mx0 != 0.0) ||
+ (a.mx1 != 0.0) )
+ );
+ //remove the master from the selection
+ SPItem * thing = *master;
+ selected.erase(master);
+ //Compute the anchor point
+ NR::Rect b = sp_item_bbox_desktop (thing);
+ mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
+ a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
+ break;
+ }
+
+ case AlignAndDistribute::PAGE:
+ mp = NR::Point(a.mx1 * sp_document_width(SP_DT_DOCUMENT(desktop)),
+ a.my1 * sp_document_height(SP_DT_DOCUMENT(desktop)));
+ break;
+
+ case AlignAndDistribute::DRAWING:
+ {
+ NR::Rect b = sp_item_bbox_desktop
+ ( (SPItem *) sp_document_root (SP_DT_DOCUMENT (desktop)) );
+ mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
+ a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
+ break;
+ }
+
+ case AlignAndDistribute::SELECTION:
+ {
+ NR::Rect b = selection->bounds();
+ mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
+ a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
+ break;
+ }
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }; // end of switch
+
+ // Top hack: temporarily set clone compensation to unmoved, so that we can align/distribute
+ // clones with their original (and the move of the original does not disturb the
+ // clones). The only problem with this is that if there are outside-of-selection clones of
+ // a selected original, they will be unmoved too, possibly contrary to user's
+ // expecation. However this is a minor point compared to making align/distribute always
+ // work as expected, and "unmoved" is the default option anyway.
+ int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
+
+ bool changed = false;
+ //Move each item in the selected list
+ for (std::list<SPItem *>::iterator it(selected.begin());
+ it != selected.end();
+ it++)
+ {
+ sp_document_ensure_up_to_date(SP_DT_DOCUMENT (desktop));
+ NR::Rect b = sp_item_bbox_desktop (*it);
+ NR::Point const sp(a.sx0 * b.min()[NR::X] + a.sx1 * b.max()[NR::X],
+ a.sy0 * b.min()[NR::Y] + a.sy1 * b.max()[NR::Y]);
+ NR::Point const mp_rel( mp - sp );
+ if (LInfty(mp_rel) > 1e-9) {
+ sp_item_move_rel(*it, NR::translate(mp_rel));
+ changed = true;
+ }
+ }
+
+ // restore compensation setting
+ prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
+
+ if (changed) {
+ sp_document_done ( SP_DT_DOCUMENT (desktop) );
+ }
+
+
+ }
+ guint _index;
+ AlignAndDistribute &_dialog;
+
+ static const Coeffs _allCoeffs[10];
+
+};
+ActionAlign::Coeffs const ActionAlign::_allCoeffs[10] = {
+ {1., 0., 0., 0., 0., 1., 0., 0.},
+ {1., 0., 0., 0., 1., 0., 0., 0.},
+ {.5, .5, 0., 0., .5, .5, 0., 0.},
+ {0., 1., 0., 0., 0., 1., 0., 0.},
+ {0., 1., 0., 0., 1., 0., 0., 0.},
+ {0., 0., 0., 1., 0., 0., 1., 0.},
+ {0., 0., 0., 1., 0., 0., 0., 1.},
+ {0., 0., .5, .5, 0., 0., .5, .5},
+ {0., 0., 1., 0., 0., 0., 1., 0.},
+ {0., 0., 1., 0., 0., 0., 0., 1.}
+};
+
+struct BBoxSort
+{
+ SPItem *item;
+ float anchor;
+ NR::Rect bbox;
+ BBoxSort(SPItem *pItem, NR::Dim2 orientation, double kBegin, double kEnd) :
+ item(pItem),
+ bbox (sp_item_bbox_desktop (pItem))
+ {
+ anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
+ }
+ 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
+ item(rhs.item), anchor(rhs.anchor), bbox(rhs.bbox) {
+ }
+};
+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,
+ NR::Dim2 orientation,
+ double kBegin, double kEnd
+ ):
+ Action(id, tiptext, row, column,
+ dialog.distribute_table(), dialog.tooltips(), dialog),
+ _dialog(dialog),
+ _onInterSpace(onInterSpace),
+ _orientation(orientation),
+ _kBegin(kBegin),
+ _kEnd( kEnd)
+ {}
+
+private :
+ virtual void on_button_click() {
+ //Retreive selected objects
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (!desktop) return;
+
+ Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
+ if (!selection) return;
+
+ using Inkscape::Util::GSListConstIterator;
+ std::list<SPItem *> selected;
+ selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
+ if (selected.empty()) return;
+
+ //Check 2 or more selected objects
+ std::list<SPItem *>::iterator second(selected.begin());
+ ++second;
+ if (second == selected.end()) return;
+
+
+ std::vector< BBoxSort > sorted;
+ for (std::list<SPItem *>::iterator it(selected.begin());
+ it != selected.end();
+ ++it)
+ {
+ BBoxSort b (*it, _orientation, _kBegin, _kEnd);
+ sorted.push_back(b);
+ }
+ //sort bbox by anchors
+ std::sort(sorted.begin(), sorted.end());
+
+ // see comment in ActionAlign above
+ int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs_set_int_attribute("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.extent(_orientation);
+ }
+ //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 (!NR_DF_TEST_CLOSE (pos, it->bbox.min()[_orientation], 1e-6)) {
+ NR::Point t(0.0, 0.0);
+ t[_orientation] = pos - it->bbox.min()[_orientation];
+ sp_item_move_rel(it->item, NR::translate(t));
+ changed = true;
+ }
+ pos += it->bbox.extent(_orientation);
+ 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 (!NR_DF_TEST_CLOSE (pos, it.anchor, 1e-6)) {
+ //Compute translation
+ NR::Point t(0.0, 0.0);
+ t[_orientation] = pos - it.anchor;
+ //translate
+ sp_item_move_rel(it.item, NR::translate(t));
+ changed = true;
+ }
+ }
+ }
+
+ // restore compensation setting
+ prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
+
+ if (changed) {
+ sp_document_done ( SP_DT_DOCUMENT (desktop) );
+ }
+ }
+ guint _index;
+ AlignAndDistribute &_dialog;
+ bool _onInterSpace;
+ NR::Dim2 _orientation;
+
+ double _kBegin;
+ double _kEnd;
+
+};
+
+
+class ActionNode : public Action {
+public :
+ ActionNode(const Glib::ustring &id,
+ const Glib::ustring &tiptext,
+ guint column,
+ AlignAndDistribute &dialog,
+ NR::Dim2 orientation, bool distribute):
+ Action(id, tiptext, 0, column,
+ dialog.nodes_table(), dialog.tooltips(), dialog),
+ _orientation(orientation),
+ _distribute(distribute)
+ {}
+
+private :
+ NR::Dim2 _orientation;
+ bool _distribute;
+ virtual void on_button_click()
+ {
+
+ if (!SP_ACTIVE_DESKTOP) return;
+ SPEventContext *event_context = SP_DT_EVENTCONTEXT(SP_ACTIVE_DESKTOP);
+ if (!SP_IS_NODE_CONTEXT (event_context)) return ;
+
+ Inkscape::NodePath::Path *nodepath = SP_NODE_CONTEXT (event_context)->nodepath;
+ if (!nodepath) return;
+ if (_distribute)
+ sp_nodepath_selected_distribute(nodepath, _orientation);
+ else
+ sp_nodepath_selected_align(nodepath, _orientation);
+
+ }
+};
+
+class ActionRemoveOverlaps : public Action {
+private:
+ Gtk::Label removeOverlapXGapLabel;
+ Gtk::Label removeOverlapYGapLabel;
+ Gtk::SpinButton removeOverlapXGap;
+ Gtk::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.tooltips(), dialog)
+ {
+ dialog.removeOverlap_table().set_col_spacings(3);
+
+ removeOverlapXGap.set_digits(1);
+ removeOverlapXGap.set_size_request(60, -1);
+ removeOverlapXGap.set_increments(1.0, 5.0);
+ removeOverlapXGap.set_range(-1000.0, 1000.0);
+ removeOverlapXGap.set_value(0);
+ dialog.tooltips().set_tip(removeOverlapXGap,
+ _("Minimum horizontal gap (in px units) between bounding boxes"));
+ /* TRANSLATORS: Horizontal gap */
+ removeOverlapXGapLabel.set_label(_("H:"));
+
+ removeOverlapYGap.set_digits(1);
+ removeOverlapYGap.set_size_request(60, -1);
+ removeOverlapYGap.set_increments(1.0, 5.0);
+ removeOverlapYGap.set_range(-1000.0, 1000.0);
+ removeOverlapYGap.set_value(0);
+ dialog.tooltips().set_tip(removeOverlapYGap,
+ _("Minimum vertical gap (in px units) between bounding boxes"));
+ /* TRANSLATORS: Vertical gap */
+ removeOverlapYGapLabel.set_label(_("V:"));
+
+ dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
+ dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
+ dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, column+3, row, row+1, Gtk::FILL, Gtk::FILL);
+ dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
+
+ }
+
+private :
+ virtual void on_button_click()
+ {
+ if (!SP_ACTIVE_DESKTOP) return;
+
+ // see comment in ActionAlign above
+ int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs_set_int_attribute("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();
+ removeoverlap(SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->itemList(),
+ xGap, yGap);
+
+ // restore compensation setting
+ prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
+
+ sp_document_done(SP_DT_DOCUMENT(SP_ACTIVE_DESKTOP));
+ }
+};
+
+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.distribute_table(), dialog.tooltips(), dialog)
+ {}
+
+private :
+ virtual void on_button_click()
+ {
+ if (!SP_ACTIVE_DESKTOP) return;
+
+ // see comment in ActionAlign above
+ int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
+
+ unclump ((GSList *) SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->itemList());
+
+ // restore compensation setting
+ prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
+
+ sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP));
+ }
+};
+
+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.distribute_table(), dialog.tooltips(), dialog)
+ {}
+
+private :
+ virtual void on_button_click()
+ {
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (!desktop) return;
+
+ Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
+ if (!selection) return;
+
+ using Inkscape::Util::GSListConstIterator;
+ std::list<SPItem *> selected;
+ selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
+ if (selected.empty()) return;
+
+ //Check 2 or more selected objects
+ if (selected.size() < 2) 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_set) {
+ _dialog.randomize_bbox = selection->bounds();
+ _dialog.randomize_bbox_set = true;
+ }
+
+ // see comment in ActionAlign above
+ int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
+
+ for (std::list<SPItem *>::iterator it(selected.begin());
+ it != selected.end();
+ ++it)
+ {
+ sp_document_ensure_up_to_date(SP_DT_DOCUMENT (desktop));
+ NR::Rect item_box = sp_item_bbox_desktop (*it);
+ // find new center, staying within bbox
+ double x = _dialog.randomize_bbox.min()[NR::X] + item_box.extent(NR::X)/2 +
+ g_random_double_range (0, _dialog.randomize_bbox.extent(NR::X) - item_box.extent(NR::X));
+ double y = _dialog.randomize_bbox.min()[NR::Y] + item_box.extent(NR::Y)/2 +
+ g_random_double_range (0, _dialog.randomize_bbox.extent(NR::Y) - item_box.extent(NR::Y));
+ // displacement is the new center minus old:
+ NR::Point t = NR::Point (x, y) - 0.5*(item_box.max() + item_box.min());
+ sp_item_move_rel(*it, NR::translate(t));
+ }
+
+ // restore compensation setting
+ prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
+
+ sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP));
+ }
+};
+
+struct Baselines
+{
+ SPItem *_item;
+ NR::Point _base;
+ NR::Dim2 _orientation;
+ Baselines(SPItem *item, NR::Point base, NR::Dim2 orientation) :
+ _item (item),
+ _base (base),
+ _orientation (orientation)
+ {}
+};
+
+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::Table &table,
+ NR::Dim2 orientation, bool distribute):
+ Action(id, tiptext, row, column,
+ table, dialog.tooltips(), dialog),
+ _orientation(orientation),
+ _distribute(distribute)
+ {}
+
+private :
+ NR::Dim2 _orientation;
+ bool _distribute;
+ virtual void on_button_click()
+ {
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (!desktop) return;
+
+ Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
+ if (!selection) return;
+
+ using Inkscape::Util::GSListConstIterator;
+ std::list<SPItem *> selected;
+ selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
+ if (selected.empty()) return;
+
+ //Check 2 or more selected objects
+ if (selected.size() < 2) return;
+
+ NR::Point b_min = NR::Point (HUGE_VAL, HUGE_VAL);
+ NR::Point b_max = NR::Point (-HUGE_VAL, -HUGE_VAL);
+
+ std::vector<Baselines> sorted;
+
+ for (std::list<SPItem *>::iterator it(selected.begin());
+ it != selected.end();
+ ++it)
+ {
+ if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
+ Inkscape::Text::Layout const *layout = te_get_layout(*it);
+ NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
+ if (base[NR::X] < b_min[NR::X]) b_min[NR::X] = base[NR::X];
+ if (base[NR::Y] < b_min[NR::Y]) b_min[NR::Y] = base[NR::Y];
+ if (base[NR::X] > b_max[NR::X]) b_max[NR::X] = base[NR::X];
+ if (base[NR::Y] > b_max[NR::Y]) b_max[NR::Y] = base[NR::Y];
+
+ Baselines b (*it, base, _orientation);
+ sorted.push_back(b);
+ }
+ }
+
+ if (sorted.size() <= 1) return;
+
+ //sort baselines
+ std::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;
+ NR::Point base = sorted[i]._base;
+ NR::Point t(0.0, 0.0);
+ t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
+ sp_item_move_rel(item, NR::translate(t));
+ changed = true;
+ }
+
+ } else {
+ for (std::list<SPItem *>::iterator it(selected.begin());
+ it != selected.end();
+ ++it)
+ {
+ if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
+ Inkscape::Text::Layout const *layout = te_get_layout(*it);
+ NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
+ NR::Point t(0.0, 0.0);
+ t[_orientation] = b_min[_orientation] - base[_orientation];
+ sp_item_move_rel(*it, NR::translate(t));
+ changed = true;
+ }
+ }
+ }
+
+ if (changed) {
+ sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP));
+ }
+ }
+};
+
+
+
+void on_tool_changed(Inkscape::Application *inkscape, SPEventContext *context, AlignAndDistribute *daad)
+{
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (desktop && SP_DT_EVENTCONTEXT(desktop))
+ daad->setMode(tools_active(desktop) == TOOLS_NODES);
+}
+
+void on_selection_changed(Inkscape::Application *inkscape, Inkscape::Selection *selection, AlignAndDistribute *daad)
+{
+ daad->randomize_bbox_set = false;
+}
+
+/////////////////////////////////////////////////////////
+
+
+
+
+AlignAndDistribute::AlignAndDistribute()
+ : Dialog ("dialogs.align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
+ randomize_bbox (NR::Point (0, 0), NR::Point (0, 0)),
+ _alignFrame(_("Align")),
+ _distributeFrame(_("Distribute")),
+ _removeOverlapFrame(_("Remove overlaps")),
+ _nodesFrame(_("Nodes")),
+ _alignTable(2, 6, true),
+ _distributeTable(3, 6, true),
+ _removeOverlapTable(1, 5, false),
+ _nodesTable(1, 4, true),
+ _anchorLabel(_("Relative to: "))
+{
+
+ //Instanciate the align buttons
+ addAlignButton("al_left_out",
+ _("Align right sides of objects to left side of anchor"),
+ 0, 0);
+ addAlignButton("al_left_in",
+ _("Align left sides"),
+ 0, 1);
+ addAlignButton("al_center_hor",
+ _("Center on vertical axis"),
+ 0, 2);
+ addAlignButton("al_right_in",
+ _("Align right sides"),
+ 0, 3);
+ addAlignButton("al_right_out",
+ _("Align left sides of objects to right side of anchor"),
+ 0, 4);
+ addAlignButton("al_top_out",
+ _("Align bottoms of objects to top of anchor"),
+ 1, 0);
+ addAlignButton("al_top_in",
+ _("Align tops"),
+ 1, 1);
+ addAlignButton("al_center_ver",
+ _("Center on horizontal axis"),
+ 1, 2);
+ addAlignButton("al_bottom_in",
+ _("Align bottoms"),
+ 1, 3);
+ addAlignButton("al_bottom_out",
+ _("Align tops of objects to bottom of anchor"),
+ 1, 4);
+
+ //Baseline aligns
+ addBaselineButton("al_baselines_vert",
+ _("Align baseline anchors of texts vertically"),
+ 0, 5, this->align_table(), NR::X, false);
+ addBaselineButton("al_baselines_hor",
+ _("Align baseline anchors of texts horizontally"),
+ 1, 5, this->align_table(), NR::Y, false);
+
+ //The distribute buttons
+ addDistributeButton("distribute_hdist",
+ _("Make horizontal gaps between objects equal"),
+ 0, 4, true, NR::X, .5, .5);
+
+ addDistributeButton("distribute_left",
+ _("Distribute left sides equidistantly"),
+ 0, 1, false, NR::X, 1., 0.);
+ addDistributeButton("distribute_hcentre",
+ _("Distribute centers equidistantly horizontally"),
+ 0, 2, false, NR::X, .5, .5);
+ addDistributeButton("distribute_right",
+ _("Distribute right sides equidistantly"),
+ 0, 3, false, NR::X, 0., 1.);
+
+ addDistributeButton("distribute_vdist",
+ _("Make vertical gaps between objects equal"),
+ 1, 4, true, NR::Y, .5, .5);
+
+ addDistributeButton("distribute_top",
+ _("Distribute tops equidistantly"),
+ 1, 1, false, NR::Y, 0, 1);
+ addDistributeButton("distribute_vcentre",
+ _("Distribute centers equidistantly vertically"),
+ 1, 2, false, NR::Y, .5, .5);
+ addDistributeButton("distribute_bottom",
+ _("Distribute bottoms equidistantly"),
+ 1, 3, false, NR::Y, 1., 0.);
+
+ //Baseline distribs
+ addBaselineButton("distribute_baselines_hor",
+ _("Distribute baseline anchors of texts horizontally"),
+ 0, 5, this->distribute_table(), NR::X, true);
+ addBaselineButton("distribute_baselines_vert",
+ _("Distribute baseline anchors of texts vertically"),
+ 1, 5, this->distribute_table(), NR::Y, true);
+
+ //Randomize & Unclump
+ addRandomizeButton("distribute_randomize",
+ _("Randomize centers in both dimensions"),
+ 2, 2);
+ addUnclumpButton("unclump",
+ _("Unclump objects: try to equalize edge-to-edge distances"),
+ 2, 4);
+
+ //Remove overlaps
+ addRemoveOverlapsButton("remove_overlaps",
+ _("Move objects as little as possible so that their bounding boxes do not overlap"),
+ 0, 0);
+
+ //Node Mode buttons
+ addNodeButton("node_halign",
+ _("Align selected nodes horizontally"),
+ 0, NR::X, false);
+ addNodeButton("node_valign",
+ _("Align selected nodes vertically"),
+ 1, NR::Y, false);
+ addNodeButton("node_hdistribute",
+ _("Distribute selected nodes horizontally"),
+ 2, NR::X, true);
+ addNodeButton("node_vdistribute",
+ _("Distribute selected nodes vertically"),
+ 3, NR::Y, true);
+
+ //Rest of the widgetry
+
+ _combo.append_text(_("Last selected"));
+ _combo.append_text(_("First selected"));
+ _combo.append_text(_("Biggest item"));
+ _combo.append_text(_("Smallest item"));
+ _combo.append_text(_("Page"));
+ _combo.append_text(_("Drawing"));
+ _combo.append_text(_("Selection"));
+
+ _combo.set_active(6);
+ _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
+
+ _anchorBox.pack_start(_anchorLabel);
+ _anchorBox.pack_start(_combo);
+
+ _alignBox.pack_start(_anchorBox);
+ _alignBox.pack_start(_alignTable);
+
+ _alignFrame.add(_alignBox);
+ _distributeFrame.add(_distributeTable);
+ _removeOverlapFrame.add(_removeOverlapTable);
+ _nodesFrame.add(_nodesTable);
+
+ // Top level vbox
+ Gtk::VBox *vbox = get_vbox();
+ vbox->set_spacing(4);
+
+ // Notebook for individual transformations
+
+ vbox->pack_start(_alignFrame, true, true);
+ vbox->pack_start(_distributeFrame, true, true);
+ vbox->pack_start(_removeOverlapFrame, true, true);
+ vbox->pack_start(_nodesFrame, true, true);
+
+ //Connect to the global tool change signal
+ g_signal_connect (G_OBJECT (INKSCAPE), "set_eventcontext", G_CALLBACK (on_tool_changed), this);
+
+ // Connect to the global selection change, to invalidate cached randomize_bbox
+ g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
+ randomize_bbox = NR::Rect (NR::Point (0, 0), NR::Point (0, 0));
+ randomize_bbox_set = false;
+
+ show_all_children();
+
+ on_tool_changed (NULL, NULL, this); // set current mode
+}
+
+AlignAndDistribute::~AlignAndDistribute()
+{
+ sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
+
+ for (std::list<Action *>::iterator it = _actionList.begin();
+ it != _actionList.end();
+ it ++)
+ delete *it;
+}
+
+void AlignAndDistribute::on_ref_change(){
+//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_all;
+
+ //Act on widgets used in selection mode
+ void ( Gtk::Widget::*mSel) () = nodeEdit ?
+ &Gtk::Widget::hide_all : &Gtk::Widget::show_all;
+
+
+ ((_alignFrame).*(mSel))();
+ ((_distributeFrame).*(mSel))();
+ ((_removeOverlapFrame).*(mSel))();
+ ((_nodesFrame).*(mNode))();
+
+}
+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,
+ NR::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, NR::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::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::Table &table, NR::Dim2 orientation, bool distribute)
+{
+ _actionList.push_back(
+ new ActionBaseline(
+ id, tiptext, row, col,
+ *this, table, orientation, distribute));
+}
+
+
+
+
+std::list<SPItem *>::iterator AlignAndDistribute::find_master( std::list<SPItem *> &list, bool horizontal){
+ std::list<SPItem *>::iterator master = list.end();
+ switch (getAlignTarget()) {
+ case LAST:
+ return list.begin();
+ break;
+
+ case FIRST:
+ return --(list.end());
+ break;
+
+ case BIGGEST:
+ {
+ gdouble max = -1e18;
+ for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
+ NR::Rect b = sp_item_bbox_desktop (*it);
+ gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
+ if (dim > max) {
+ max = dim;
+ master = it;
+ }
+ }
+ return master;
+ break;
+ }
+
+ case SMALLEST:
+ {
+ gdouble max = 1e18;
+ for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
+ NR::Rect b = sp_item_bbox_desktop (*it);
+ gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
+ if (dim < max) {
+ max = dim;
+ master = it;
+ }
+ }
+ return master;
+ break;
+ }
+
+ default:
+ g_assert_not_reached ();
+ break;
+
+ } // end of switch statement
+ return NULL;
+}
+
+AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
+ return AlignTarget(_combo.get_active_row_number());
+}
+
+
+
+} // 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:encoding=utf-8:textwidth=99 :