Code

Mnemonics in "Fill and stroke", "Align and distribute", and "Transform" dialogs ...
[inkscape.git] / src / ui / dialog / align-and-distribute.cpp
1 /** @file
2  * @brief Align and Distribute dialog - implementation
3  */
4 /* Authors:
5  *   Bryce W. Harrington <bryce@bryceharrington.org>
6  *   Aubanel MONNIER <aubi@libertysurf.fr>
7  *   Frank Felfe <innerspace@iname.com>
8  *   Lauris Kaplinski <lauris@kaplinski.com>
9  *   Tim Dwyer <tgdwyer@gmail.com>
10  *   Jon A. Cruz <jon@joncruz.org>
11  *   Abhishek Sharma
12  *
13  * Copyright (C) 1999-2004, 2005 Authors
14  *
15  * Released under GNU GPL.  Read the file 'COPYING' for more information.
16  */
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
23 #include <gtkmm/spinbutton.h>
25 #include "desktop-handles.h"
26 #include "unclump.h"
27 #include "document.h"
28 #include "enums.h"
29 #include "graphlayout.h"
30 #include "inkscape.h"
31 #include "macros.h"
32 #include "preferences.h"
33 #include "removeoverlap.h"
34 #include "selection.h"
35 #include "sp-flowtext.h"
36 #include "sp-item-transform.h"
37 #include "sp-text.h"
38 #include "text-editing.h"
39 #include "tools-switch.h"
40 #include "ui/icon-names.h"
41 #include "ui/tool/node-tool.h"
42 #include "ui/tool/multi-path-manipulator.h"
43 #include "util/glib-list-iterators.h"
44 #include "verbs.h"
45 #include "widgets/icon.h"
47 #include "align-and-distribute.h"
49 namespace Inkscape {
50 namespace UI {
51 namespace Dialog {
53 /////////helper classes//////////////////////////////////
55 class Action {
56 public :
57     Action(const Glib::ustring &id,
58            const Glib::ustring &tiptext,
59            guint row, guint column,
60            Gtk::Table &parent,
61            Gtk::Tooltips &tooltips,
62            AlignAndDistribute &dialog):
63         _dialog(dialog),
64         _id(id),
65         _parent(parent)
66     {
67         Gtk::Widget*  pIcon = Gtk::manage( sp_icon_get_icon( _id, Inkscape::ICON_SIZE_LARGE_TOOLBAR) );
68         Gtk::Button * pButton = Gtk::manage(new Gtk::Button());
69         pButton->set_relief(Gtk::RELIEF_NONE);
70         pIcon->show();
71         pButton->add(*pIcon);
72         pButton->show();
74         pButton->signal_clicked()
75             .connect(sigc::mem_fun(*this, &Action::on_button_click));
76         tooltips.set_tip(*pButton, tiptext);
77         parent.attach(*pButton, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
78     }
79     virtual ~Action(){}
81     AlignAndDistribute &_dialog;
83 private :
84     virtual void on_button_click(){}
86     Glib::ustring _id;
87     Gtk::Table &_parent;
88 };
91 class ActionAlign : public Action {
92 public :
93     struct Coeffs {
94        double mx0, mx1, my0, my1;
95        double sx0, sx1, sy0, sy1;
96     };
97     ActionAlign(const Glib::ustring &id,
98                 const Glib::ustring &tiptext,
99                 guint row, guint column,
100                 AlignAndDistribute &dialog,
101                 guint coeffIndex):
102         Action(id, tiptext, row, column,
103                dialog.align_table(), dialog.tooltips(), dialog),
104         _index(coeffIndex),
105         _dialog(dialog)
106     {}
108 private :
110     virtual void on_button_click() {
111         //Retreive selected objects
112         SPDesktop *desktop = _dialog.getDesktop();
113         if (!desktop) return;
115         Inkscape::Selection *selection = sp_desktop_selection(desktop);
116         if (!selection) return;
118         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
119         bool sel_as_group = prefs->getBool("/dialogs/align/sel-as-groups");
121         using Inkscape::Util::GSListConstIterator;
122         std::list<SPItem *> selected;
123         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
124         if (selected.empty()) return;
126         Geom::Point mp; //Anchor point
127         AlignAndDistribute::AlignTarget target = _dialog.getAlignTarget();
128         const Coeffs &a= _allCoeffs[_index];
129         switch (target)
130         {
131         case AlignAndDistribute::LAST:
132         case AlignAndDistribute::FIRST:
133         case AlignAndDistribute::BIGGEST:
134         case AlignAndDistribute::SMALLEST:
135         {
136             //Check 2 or more selected objects
137             std::list<SPItem *>::iterator second(selected.begin());
138             ++second;
139             if (second == selected.end())
140                 return;
141             //Find the master (anchor on which the other objects are aligned)
142             std::list<SPItem *>::iterator master(
143                 _dialog.find_master (
144                     selected,
145                     (a.mx0 != 0.0) ||
146                     (a.mx1 != 0.0) )
147                 );
148             //remove the master from the selection
149             SPItem * thing = *master;
150             // TODO: either uncomment or remove the following commented lines, depending on which
151             //       behaviour of moving objects makes most sense; also cf. discussion at
152             //       https://bugs.launchpad.net/inkscape/+bug/255933
153             /*if (!sel_as_group) { */
154                 selected.erase(master);
155             /*}*/
156             //Compute the anchor point
157             Geom::OptRect b = thing->getBboxDesktop ();
158             if (b) {
159                 mp = Geom::Point(a.mx0 * b->min()[Geom::X] + a.mx1 * b->max()[Geom::X],
160                                a.my0 * b->min()[Geom::Y] + a.my1 * b->max()[Geom::Y]);
161             } else {
162                 return;
163             }
164             break;
165         }
167         case AlignAndDistribute::PAGE:
168             mp = Geom::Point(a.mx1 * sp_desktop_document(desktop)->getWidth(),
169                            a.my1 * sp_desktop_document(desktop)->getHeight());
170             break;
172         case AlignAndDistribute::DRAWING:
173         {
174             Geom::OptRect b = static_cast<SPItem *>( sp_desktop_document(desktop)->getRoot() )->getBboxDesktop();
175             if (b) {
176                 mp = Geom::Point(a.mx0 * b->min()[Geom::X] + a.mx1 * b->max()[Geom::X],
177                                a.my0 * b->min()[Geom::Y] + a.my1 * b->max()[Geom::Y]);
178             } else {
179                 return;
180             }
181             break;
182         }
184         case AlignAndDistribute::SELECTION:
185         {
186             Geom::OptRect b =  selection->bounds();
187             if (b) {
188                 mp = Geom::Point(a.mx0 * b->min()[Geom::X] + a.mx1 * b->max()[Geom::X],
189                                a.my0 * b->min()[Geom::Y] + a.my1 * b->max()[Geom::Y]);
190             } else {
191                 return;
192             }
193             break;
194         }
196         default:
197             g_assert_not_reached ();
198             break;
199         };  // end of switch
201         // Top hack: temporarily set clone compensation to unmoved, so that we can align/distribute
202         // clones with their original (and the move of the original does not disturb the
203         // clones). The only problem with this is that if there are outside-of-selection clones of
204         // a selected original, they will be unmoved too, possibly contrary to user's
205         // expecation. However this is a minor point compared to making align/distribute always
206         // work as expected, and "unmoved" is the default option anyway.
207         int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
208         prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
210         bool changed = false;
211         Geom::OptRect b;
212         if (sel_as_group)
213             b = selection->bounds();
215         //Move each item in the selected list separately
216         for (std::list<SPItem *>::iterator it(selected.begin());
217              it != selected.end();
218              it++)
219         {
220             sp_desktop_document (desktop)->ensureUpToDate();
221             if (!sel_as_group)
222                 b = (*it)->getBboxDesktop();
223             if (b) {
224                 Geom::Point const sp(a.sx0 * b->min()[Geom::X] + a.sx1 * b->max()[Geom::X],
225                                      a.sy0 * b->min()[Geom::Y] + a.sy1 * b->max()[Geom::Y]);
226                 Geom::Point const mp_rel( mp - sp );
227                 if (LInfty(mp_rel) > 1e-9) {
228                     sp_item_move_rel(*it, Geom::Translate(mp_rel));
229                     changed = true;
230                 }
231             }
232         }
234         // restore compensation setting
235         prefs->setInt("/options/clonecompensation/value", saved_compensation);
237         if (changed) {
238             DocumentUndo::done( sp_desktop_document(desktop) , SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
239                                 _("Align"));
240         }
243     }
244     guint _index;
245     AlignAndDistribute &_dialog;
247     static const Coeffs _allCoeffs[10];
249 };
250 ActionAlign::Coeffs const ActionAlign::_allCoeffs[10] = {
251     {1., 0., 0., 0., 0., 1., 0., 0.},
252     {1., 0., 0., 0., 1., 0., 0., 0.},
253     {.5, .5, 0., 0., .5, .5, 0., 0.},
254     {0., 1., 0., 0., 0., 1., 0., 0.},
255     {0., 1., 0., 0., 1., 0., 0., 0.},
256     {0., 0., 0., 1., 0., 0., 1., 0.},
257     {0., 0., 0., 1., 0., 0., 0., 1.},
258     {0., 0., .5, .5, 0., 0., .5, .5},
259     {0., 0., 1., 0., 0., 0., 1., 0.},
260     {0., 0., 1., 0., 0., 0., 0., 1.}
261 };
263 BBoxSort::BBoxSort(SPItem *pItem, Geom::Rect bounds, Geom::Dim2 orientation, double kBegin, double kEnd) :
264         item(pItem),
265         bbox (bounds)
267         anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
269 BBoxSort::BBoxSort(const BBoxSort &rhs) :
270         //NOTE :  this copy ctor is called O(sort) when sorting the vector
271         //this is bad. The vector should be a vector of pointers.
272         //But I'll wait the bohem GC before doing that
273         item(rhs.item), anchor(rhs.anchor), bbox(rhs.bbox) 
277 bool operator< (const BBoxSort &a, const BBoxSort &b)
279     return (a.anchor < b.anchor);
282 class ActionDistribute : public Action {
283 public :
284     ActionDistribute(const Glib::ustring &id,
285                      const Glib::ustring &tiptext,
286                      guint row, guint column,
287                      AlignAndDistribute &dialog,
288                      bool onInterSpace,
289                      Geom::Dim2 orientation,
290                      double kBegin, double kEnd
291         ):
292         Action(id, tiptext, row, column,
293                dialog.distribute_table(), dialog.tooltips(), dialog),
294         _dialog(dialog),
295         _onInterSpace(onInterSpace),
296         _orientation(orientation),
297         _kBegin(kBegin),
298         _kEnd( kEnd)
299     {}
301 private :
302     virtual void on_button_click() {
303         //Retreive selected objects
304         SPDesktop *desktop = _dialog.getDesktop();
305         if (!desktop) return;
307         Inkscape::Selection *selection = sp_desktop_selection(desktop);
308         if (!selection) return;
310         using Inkscape::Util::GSListConstIterator;
311         std::list<SPItem *> selected;
312         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
313         if (selected.empty()) return;
315         //Check 2 or more selected objects
316         std::list<SPItem *>::iterator second(selected.begin());
317         ++second;
318         if (second == selected.end()) return;
321         std::vector< BBoxSort  > sorted;
322         for (std::list<SPItem *>::iterator it(selected.begin());
323             it != selected.end();
324             ++it)
325         {
326             Geom::OptRect bbox = (*it)->getBboxDesktop();
327             if (bbox) {
328                 sorted.push_back(BBoxSort(*it, *bbox, _orientation, _kBegin, _kEnd));
329             }
330         }
331         //sort bbox by anchors
332         std::sort(sorted.begin(), sorted.end());
334         // see comment in ActionAlign above
335         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
336         int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
337         prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
339         unsigned int len = sorted.size();
340         bool changed = false;
341         if (_onInterSpace)
342         {
343             //overall bboxes span
344             float dist = (sorted.back().bbox.max()[_orientation] -
345                           sorted.front().bbox.min()[_orientation]);
346             //space eaten by bboxes
347             float span = 0;
348             for (unsigned int i = 0; i < len; i++)
349             {
350                 span += sorted[i].bbox[_orientation].extent();
351             }
352             //new distance between each bbox
353             float step = (dist - span) / (len - 1);
354             float pos = sorted.front().bbox.min()[_orientation];
355             for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
356                   it < sorted.end();
357                   it ++ )
358             {
359                 if (!NR_DF_TEST_CLOSE (pos, it->bbox.min()[_orientation], 1e-6)) {
360                     Geom::Point t(0.0, 0.0);
361                     t[_orientation] = pos - it->bbox.min()[_orientation];
362                     sp_item_move_rel(it->item, Geom::Translate(t));
363                     changed = true;
364                 }
365                 pos += it->bbox[_orientation].extent();
366                 pos += step;
367             }
368         }
369         else
370         {
371             //overall anchor span
372             float dist = sorted.back().anchor - sorted.front().anchor;
373             //distance between anchors
374             float step = dist / (len - 1);
376             for ( unsigned int i = 0; i < len ; i ++ )
377             {
378                 BBoxSort & it(sorted[i]);
379                 //new anchor position
380                 float pos = sorted.front().anchor + i * step;
381                 //Don't move if we are really close
382                 if (!NR_DF_TEST_CLOSE (pos, it.anchor, 1e-6)) {
383                     //Compute translation
384                     Geom::Point t(0.0, 0.0);
385                     t[_orientation] = pos - it.anchor;
386                     //translate
387                     sp_item_move_rel(it.item, Geom::Translate(t));
388                     changed = true;
389                 }
390             }
391         }
393         // restore compensation setting
394         prefs->setInt("/options/clonecompensation/value", saved_compensation);
396         if (changed) {
397             DocumentUndo::done( sp_desktop_document(desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
398                                 _("Distribute"));
399         }
400     }
401     guint _index;
402     AlignAndDistribute &_dialog;
403     bool _onInterSpace;
404     Geom::Dim2 _orientation;
406     double _kBegin;
407     double _kEnd;
409 };
412 class ActionNode : public Action {
413 public :
414     ActionNode(const Glib::ustring &id,
415                const Glib::ustring &tiptext,
416                guint column,
417                AlignAndDistribute &dialog,
418                Geom::Dim2 orientation, bool distribute):
419         Action(id, tiptext, 0, column,
420                dialog.nodes_table(), dialog.tooltips(), dialog),
421         _orientation(orientation),
422         _distribute(distribute)
423     {}
425 private :
426     Geom::Dim2 _orientation;
427     bool _distribute;
428     virtual void on_button_click()
429     {
431         if (!_dialog.getDesktop()) return;
432         SPEventContext *event_context = sp_desktop_event_context(_dialog.getDesktop());
433         if (!INK_IS_NODE_TOOL (event_context)) return;
434         InkNodeTool *nt = INK_NODE_TOOL(event_context);
436         if (_distribute)
437             nt->_multipath->distributeNodes(_orientation);
438         else
439             nt->_multipath->alignNodes(_orientation);
441     }
442 };
444 class ActionRemoveOverlaps : public Action {
445 private:
446     Gtk::Label removeOverlapXGapLabel;
447     Gtk::Label removeOverlapYGapLabel;
448     Gtk::SpinButton removeOverlapXGap;
449     Gtk::SpinButton removeOverlapYGap;
451 public:
452     ActionRemoveOverlaps(Glib::ustring const &id,
453                          Glib::ustring const &tiptext,
454                          guint row,
455                          guint column,
456                          AlignAndDistribute &dialog) :
457         Action(id, tiptext, row, column + 4,
458                dialog.removeOverlap_table(), dialog.tooltips(), dialog)
459     {
460         dialog.removeOverlap_table().set_col_spacings(3);
462         removeOverlapXGap.set_digits(1);
463         removeOverlapXGap.set_size_request(60, -1);
464         removeOverlapXGap.set_increments(1.0, 0);
465         removeOverlapXGap.set_range(-1000.0, 1000.0);
466         removeOverlapXGap.set_value(0);
467         dialog.tooltips().set_tip(removeOverlapXGap,
468                                   _("Minimum horizontal gap (in px units) between bounding boxes"));
469         //TRANSLATORS: "H:" stands for horizontal gap
470         removeOverlapXGapLabel.set_text_with_mnemonic(C_("Gap", "_H:"));
471         removeOverlapXGapLabel.set_mnemonic_widget(removeOverlapXGap);
473         removeOverlapYGap.set_digits(1);
474         removeOverlapYGap.set_size_request(60, -1);
475         removeOverlapYGap.set_increments(1.0, 0);
476         removeOverlapYGap.set_range(-1000.0, 1000.0);
477         removeOverlapYGap.set_value(0);
478         dialog.tooltips().set_tip(removeOverlapYGap,
479                                   _("Minimum vertical gap (in px units) between bounding boxes"));
480         /* TRANSLATORS: Vertical gap */
481         removeOverlapYGapLabel.set_text_with_mnemonic(C_("Gap", "_V:"));
482         removeOverlapYGapLabel.set_mnemonic_widget(removeOverlapYGap);
484         dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
485         dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
486         dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, column+3, row, row+1, Gtk::FILL, Gtk::FILL);
487         dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
489     }
491 private :
492     virtual void on_button_click()
493     {
494         if (!_dialog.getDesktop()) return;
496         // see comment in ActionAlign above
497         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
498         int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
499         prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
501         // xGap and yGap are the minimum space required between bounding rectangles.
502         double const xGap = removeOverlapXGap.get_value();
503         double const yGap = removeOverlapYGap.get_value();
504         removeoverlap(sp_desktop_selection(_dialog.getDesktop())->itemList(),
505                       xGap, yGap);
507         // restore compensation setting
508         prefs->setInt("/options/clonecompensation/value", saved_compensation);
510         DocumentUndo::done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
511                            _("Remove overlaps"));
512     }
513 };
515 class ActionGraphLayout : public Action {
516 public:
517     ActionGraphLayout(Glib::ustring const &id,
518                          Glib::ustring const &tiptext,
519                          guint row,
520                          guint column,
521                          AlignAndDistribute &dialog) :
522         Action(id, tiptext, row, column,
523                dialog.rearrange_table(), dialog.tooltips(), dialog)
524     {}
526 private :
527     virtual void on_button_click()
528     {
529         if (!_dialog.getDesktop()) return;
531         // see comment in ActionAlign above
532         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
533         int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
534         prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
536         graphlayout(sp_desktop_selection(_dialog.getDesktop())->itemList());
538         // restore compensation setting
539         prefs->setInt("/options/clonecompensation/value", saved_compensation);
541         DocumentUndo::done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
542                            _("Arrange connector network"));
543     }
544 };
546 class ActionExchangePositions : public Action {
547 public:
548     enum SortOrder {
549         None,
550         ZOrder,
551         Clockwise
552     };      
554     ActionExchangePositions(Glib::ustring const &id,
555                          Glib::ustring const &tiptext,
556                          guint row,
557                          guint column,
558                          AlignAndDistribute &dialog, SortOrder order = None) :
559         Action(id, tiptext, row, column,
560                dialog.rearrange_table(), dialog.tooltips(), dialog),
561         sortOrder(order)
562     {};
565 private :
566     const SortOrder sortOrder;
567     static boost::optional<Geom::Point> center;
569     static bool sort_compare(const SPItem * a,const SPItem * b) {
570         if (a == NULL) return false;
571         if (b == NULL) return true;
572         if (center) {
573             Geom::Point point_a = a->getCenter() - (*center);
574             Geom::Point point_b = b->getCenter() - (*center);
575             // First criteria: Sort according to the angle to the center point
576             double angle_a = atan2(double(point_a[Geom::Y]), double(point_a[Geom::X]));
577             double angle_b = atan2(double(point_b[Geom::Y]), double(point_b[Geom::X]));
578             if (angle_a != angle_b) return (angle_a < angle_b);
579             // Second criteria: Sort according to the distance the center point
580             Geom::Coord length_a = point_a.length();
581             Geom::Coord length_b = point_b.length();
582             if (length_a != length_b) return (length_a > length_b);
583         }
584         // Last criteria: Sort according to the z-coordinate
585         return (a->isSiblingOf(b));
586     }
588     virtual void on_button_click()
589     {
590         SPDesktop *desktop = _dialog.getDesktop();
591         if (!desktop) return;
593         Inkscape::Selection *selection = sp_desktop_selection(desktop);
594         if (!selection) return;
596         using Inkscape::Util::GSListConstIterator;
597         std::list<SPItem *> selected;
598         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
599         if (selected.empty()) return;
601         //Check 2 or more selected objects
602         if (selected.size() < 2) return;
604         // see comment in ActionAlign above
605         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
606         int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
607         prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
609         // sort the list
610         if (sortOrder != None) {
611                 if (sortOrder == Clockwise) {
612                         center = selection->center();
613                 } else { // sorting by ZOrder is outomatically done by not setting the center
614                         center.reset();
615                 }
616                 selected.sort(ActionExchangePositions::sort_compare);
617         }
618         std::list<SPItem *>::iterator it(selected.begin());
619         Geom::Point p1 =  (*it)->getCenter();
620         for (++it ;it != selected.end(); ++it)
621         {
622                 Geom::Point p2 = (*it)->getCenter();
623                 Geom::Point delta = p1 - p2;
624                 sp_item_move_rel((*it),Geom::Translate(delta[Geom::X],delta[Geom::Y] ));
625                 p1 = p2;
626         }
627         Geom::Point p2 = selected.front()->getCenter();
628         Geom::Point delta = p1 - p2;
629         sp_item_move_rel(selected.front(),Geom::Translate(delta[Geom::X],delta[Geom::Y] ));
631         // restore compensation setting
632         prefs->setInt("/options/clonecompensation/value", saved_compensation);
634         DocumentUndo::done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
635                            _("Exchange Positions"));
636     }
637 };
639 // instantiae the private static member
640 boost::optional<Geom::Point> ActionExchangePositions::center;
642 class ActionUnclump : public Action {
643 public :
644     ActionUnclump(const Glib::ustring &id,
645                const Glib::ustring &tiptext,
646                guint row,
647                guint column,
648                AlignAndDistribute &dialog):
649         Action(id, tiptext, row, column,
650                dialog.rearrange_table(), dialog.tooltips(), dialog)
651     {}
653 private :
654     virtual void on_button_click()
655     {
656         if (!_dialog.getDesktop()) return;
658         // see comment in ActionAlign above
659         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
660         int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
661         prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
663         unclump ((GSList *) sp_desktop_selection(_dialog.getDesktop())->itemList());
665         // restore compensation setting
666         prefs->setInt("/options/clonecompensation/value", saved_compensation);
668         DocumentUndo::done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
669                            _("Unclump"));
670     }
671 };
673 class ActionRandomize : public Action {
674 public :
675     ActionRandomize(const Glib::ustring &id,
676                const Glib::ustring &tiptext,
677                guint row,
678                guint column,
679                AlignAndDistribute &dialog):
680         Action(id, tiptext, row, column,
681                dialog.rearrange_table(), dialog.tooltips(), dialog)
682     {}
684 private :
685     virtual void on_button_click()
686     {
687         SPDesktop *desktop = _dialog.getDesktop();
688         if (!desktop) return;
690         Inkscape::Selection *selection = sp_desktop_selection(desktop);
691         if (!selection) return;
693         using Inkscape::Util::GSListConstIterator;
694         std::list<SPItem *> selected;
695         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
696         if (selected.empty()) return;
698         //Check 2 or more selected objects
699         if (selected.size() < 2) return;
701         Geom::OptRect sel_bbox = selection->bounds();
702         if (!sel_bbox) {
703             return;
704         }
706         // This bbox is cached between calls to randomize, so that there's no growth nor shrink
707         // nor drift on sequential randomizations. Discard cache on global (or better active
708         // desktop's) selection_change signal.
709         if (!_dialog.randomize_bbox) {
710             _dialog.randomize_bbox = *sel_bbox;
711         }
713         // see comment in ActionAlign above
714         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
715         int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
716         prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
718         for (std::list<SPItem *>::iterator it(selected.begin());
719             it != selected.end();
720             ++it)
721         {
722             sp_desktop_document (desktop)->ensureUpToDate();
723             Geom::OptRect item_box = (*it)->getBboxDesktop ();
724             if (item_box) {
725                 // find new center, staying within bbox
726                 double x = _dialog.randomize_bbox->min()[Geom::X] + (*item_box)[Geom::X].extent() /2 +
727                     g_random_double_range (0, (*_dialog.randomize_bbox)[Geom::X].extent() - (*item_box)[Geom::X].extent());
728                 double y = _dialog.randomize_bbox->min()[Geom::Y] + (*item_box)[Geom::Y].extent()/2 +
729                     g_random_double_range (0, (*_dialog.randomize_bbox)[Geom::Y].extent() - (*item_box)[Geom::Y].extent());
730                 // displacement is the new center minus old:
731                 Geom::Point t = Geom::Point (x, y) - 0.5*(item_box->max() + item_box->min());
732                 sp_item_move_rel(*it, Geom::Translate(t));
733             }
734         }
736         // restore compensation setting
737         prefs->setInt("/options/clonecompensation/value", saved_compensation);
739         DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
740                            _("Randomize positions"));
741     }
742 };
744 struct Baselines
746     SPItem *_item;
747     Geom::Point _base;
748     Geom::Dim2 _orientation;
749     Baselines(SPItem *item, Geom::Point base, Geom::Dim2 orientation) :
750         _item (item),
751         _base (base),
752         _orientation (orientation)
753     {}
754 };
756 bool operator< (const Baselines &a, const Baselines &b)
758     return (a._base[a._orientation] < b._base[b._orientation]);
761 class ActionBaseline : public Action {
762 public :
763     ActionBaseline(const Glib::ustring &id,
764                const Glib::ustring &tiptext,
765                guint row,
766                guint column,
767                AlignAndDistribute &dialog,
768                Gtk::Table &table,
769                Geom::Dim2 orientation, bool distribute):
770         Action(id, tiptext, row, column,
771                table, dialog.tooltips(), dialog),
772         _orientation(orientation),
773         _distribute(distribute)
774     {}
776 private :
777     Geom::Dim2 _orientation;
778     bool _distribute;
779     virtual void on_button_click()
780     {
781         SPDesktop *desktop = _dialog.getDesktop();
782         if (!desktop) return;
784         Inkscape::Selection *selection = sp_desktop_selection(desktop);
785         if (!selection) return;
787         using Inkscape::Util::GSListConstIterator;
788         std::list<SPItem *> selected;
789         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
790         if (selected.empty()) return;
792         //Check 2 or more selected objects
793         if (selected.size() < 2) return;
795         Geom::Point b_min = Geom::Point (HUGE_VAL, HUGE_VAL);
796         Geom::Point b_max = Geom::Point (-HUGE_VAL, -HUGE_VAL);
798         std::vector<Baselines> sorted;
800         for (std::list<SPItem *>::iterator it(selected.begin());
801             it != selected.end();
802             ++it)
803         {
804             if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
805                 Inkscape::Text::Layout const *layout = te_get_layout(*it);
806                 boost::optional<Geom::Point> pt = layout->baselineAnchorPoint();
807                 if (pt) {
808                     Geom::Point base = *pt * (*it)->i2d_affine();
809                     if (base[Geom::X] < b_min[Geom::X]) b_min[Geom::X] = base[Geom::X];
810                     if (base[Geom::Y] < b_min[Geom::Y]) b_min[Geom::Y] = base[Geom::Y];
811                     if (base[Geom::X] > b_max[Geom::X]) b_max[Geom::X] = base[Geom::X];
812                     if (base[Geom::Y] > b_max[Geom::Y]) b_max[Geom::Y] = base[Geom::Y];
813                     Baselines b (*it, base, _orientation);
814                     sorted.push_back(b);
815                 }
816             }
817         }
819         if (sorted.size() <= 1) return;
821         //sort baselines
822         std::sort(sorted.begin(), sorted.end());
824         bool changed = false;
826         if (_distribute) {
827             double step = (b_max[_orientation] - b_min[_orientation])/(sorted.size() - 1);
828             for (unsigned int i = 0; i < sorted.size(); i++) {
829                 SPItem *item = sorted[i]._item;
830                 Geom::Point base = sorted[i]._base;
831                 Geom::Point t(0.0, 0.0);
832                 t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
833                 sp_item_move_rel(item, Geom::Translate(t));
834                 changed = true;
835             }
837             if (changed) {
838                 DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
839                                     _("Distribute text baselines"));
840             }
842         } else {
843             for (std::list<SPItem *>::iterator it(selected.begin());
844                  it != selected.end();
845                  ++it)
846             {
847                 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
848                     Inkscape::Text::Layout const *layout = te_get_layout(*it);
849                     boost::optional<Geom::Point> pt = layout->baselineAnchorPoint();
850                     if (pt) {
851                         Geom::Point base = *pt * (*it)->i2d_affine();
852                         Geom::Point t(0.0, 0.0);
853                         t[_orientation] = b_min[_orientation] - base[_orientation];
854                         sp_item_move_rel(*it, Geom::Translate(t));
855                         changed = true;
856                     }
857                 }
858             }
860             if (changed) {
861                 DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
862                                    _("Align text baselines"));
863             }
864         }
865     }
866 };
870 void on_tool_changed(Inkscape::Application */*inkscape*/, SPEventContext */*context*/, AlignAndDistribute *daad)
872     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
873     if (desktop && sp_desktop_event_context(desktop))
874         daad->setMode(tools_active(desktop) == TOOLS_NODES);
877 void on_selection_changed(Inkscape::Application */*inkscape*/, Inkscape::Selection */*selection*/, AlignAndDistribute *daad)
879     daad->randomize_bbox = Geom::OptRect();
882 /////////////////////////////////////////////////////////
887 AlignAndDistribute::AlignAndDistribute()
888     : UI::Widget::Panel ("", "/dialogs/align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
889       randomize_bbox(),
890       _alignFrame(_("Align")),
891       _distributeFrame(_("Distribute")),
892       _rearrangeFrame(_("Rearrange")),
893       _removeOverlapFrame(_("Remove overlaps")),
894       _nodesFrame(_("Nodes")),
895       _alignTable(2, 6, true),
896       _distributeTable(2, 6, true),
897       _rearrangeTable(1, 5, false),
898       _removeOverlapTable(1, 5, false),
899       _nodesTable(1, 4, true),
900       _anchorLabel(_("Relative to: ")),
901       _selgrpLabel(_("_Treat selection as group: "), 1)
903     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
905     //Instanciate the align buttons
906     addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_RIGHT_TO_ANCHOR,
907                    _("Align right edges of objects to the left edge of the anchor"),
908                    0, 0);
909     addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_LEFT,
910                    _("Align left edges"),
911                    0, 1);
912     addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_CENTER,
913                    _("Center on vertical axis"),
914                    0, 2);
915     addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_RIGHT,
916                    _("Align right sides"),
917                    0, 3);
918     addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_LEFT_TO_ANCHOR,
919                    _("Align left edges of objects to the right edge of the anchor"),
920                    0, 4);
921     addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_BOTTOM_TO_ANCHOR,
922                    _("Align bottom edges of objects to the top edge of the anchor"),
923                    1, 0);
924     addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_TOP,
925                    _("Align top edges"),
926                    1, 1);
927     addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_CENTER,
928                    _("Center on horizontal axis"),
929                    1, 2);
930     addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_BOTTOM,
931                    _("Align bottom edges"),
932                    1, 3);
933     addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_TOP_TO_ANCHOR,
934                    _("Align top edges of objects to the bottom edge of the anchor"),
935                    1, 4);
937     //Baseline aligns
938     addBaselineButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_BASELINE,
939                    _("Align baseline anchors of texts horizontally"),
940                       0, 5, this->align_table(), Geom::X, false);
941     addBaselineButton(INKSCAPE_ICON_ALIGN_VERTICAL_BASELINE,
942                    _("Align baselines of texts"),
943                      1, 5, this->align_table(), Geom::Y, false);
945     //The distribute buttons
946     addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_GAPS,
947                         _("Make horizontal gaps between objects equal"),
948                         0, 4, true, Geom::X, .5, .5);
950     addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_LEFT,
951                         _("Distribute left edges equidistantly"),
952                         0, 1, false, Geom::X, 1., 0.);
953     addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_CENTER,
954                         _("Distribute centers equidistantly horizontally"),
955                         0, 2, false, Geom::X, .5, .5);
956     addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_RIGHT,
957                         _("Distribute right edges equidistantly"),
958                         0, 3, false, Geom::X, 0., 1.);
960     addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_GAPS,
961                         _("Make vertical gaps between objects equal"),
962                         1, 4, true, Geom::Y, .5, .5);
964     addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_TOP,
965                         _("Distribute top edges equidistantly"),
966                         1, 1, false, Geom::Y, 0, 1);
967     addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_CENTER,
968                         _("Distribute centers equidistantly vertically"),
969                         1, 2, false, Geom::Y, .5, .5);
970     addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_BOTTOM,
971                         _("Distribute bottom edges equidistantly"),
972                         1, 3, false, Geom::Y, 1., 0.);
974     //Baseline distribs
975     addBaselineButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_BASELINE,
976                    _("Distribute baseline anchors of texts horizontally"),
977                       0, 5, this->distribute_table(), Geom::X, true);
978     addBaselineButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_BASELINE,
979                    _("Distribute baselines of texts vertically"),
980                      1, 5, this->distribute_table(), Geom::Y, true);
982     // Rearrange
983     //Graph Layout
984     addGraphLayoutButton(INKSCAPE_ICON_DISTRIBUTE_GRAPH,
985                             _("Nicely arrange selected connector network"),
986                             0, 0);
987     addExchangePositionsButton(INKSCAPE_ICON_EXCHANGE_POSITIONS,
988                             _("Exchange positions of selected objects - selection order"),
989                             0, 1);
990     addExchangePositionsByZOrderButton(INKSCAPE_ICON_EXCHANGE_POSITIONS_ZORDER,
991                             _("Exchange positions of selected objects - stacking order"),
992                             0, 2);
993     addExchangePositionsClockwiseButton(INKSCAPE_ICON_EXCHANGE_POSITIONS_CLOCKWISE,
994                             _("Exchange positions of selected objects - clockwise rotate"),
995                             0, 3);
996                             
997     //Randomize & Unclump
998     addRandomizeButton(INKSCAPE_ICON_DISTRIBUTE_RANDOMIZE,
999                         _("Randomize centers in both dimensions"),
1000                         0, 4);
1001     addUnclumpButton(INKSCAPE_ICON_DISTRIBUTE_UNCLUMP,
1002                         _("Unclump objects: try to equalize edge-to-edge distances"),
1003                         0, 5);
1005     //Remove overlaps
1006     addRemoveOverlapsButton(INKSCAPE_ICON_DISTRIBUTE_REMOVE_OVERLAPS,
1007                             _("Move objects as little as possible so that their bounding boxes do not overlap"),
1008                             0, 0);
1010     //Node Mode buttons
1011     // NOTE: "align nodes vertically" means "move nodes vertically until they align on a common
1012     // _horizontal_ line". This is analogous to what the "align-vertical-center" icon means.
1013     // There is no doubt some ambiguity. For this reason the descriptions are different.
1014     addNodeButton(INKSCAPE_ICON_ALIGN_VERTICAL_NODES,
1015                   _("Align selected nodes to a common horizontal line"),
1016                   0, Geom::X, false);
1017     addNodeButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_NODES,
1018                   _("Align selected nodes to a common vertical line"),
1019                   1, Geom::Y, false);
1020     addNodeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_NODE,
1021                   _("Distribute selected nodes horizontally"),
1022                   2, Geom::X, true);
1023     addNodeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_NODE,
1024                   _("Distribute selected nodes vertically"),
1025                   3, Geom::Y, true);
1027     //Rest of the widgetry
1029     _combo.append_text(_("Last selected"));
1030     _combo.append_text(_("First selected"));
1031     _combo.append_text(_("Biggest object"));
1032     _combo.append_text(_("Smallest object"));
1033     _combo.append_text(_("Page"));
1034     _combo.append_text(_("Drawing"));
1035     _combo.append_text(_("Selection"));
1037     _combo.set_active(prefs->getInt("/dialogs/align/align-to", 6));
1038     _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
1040     _anchorBox.pack_start(_anchorLabel);
1041     _anchorBox.pack_start(_combo);
1043     _selgrpLabel.set_mnemonic_widget(_selgrp);
1044     _selgrpBox.pack_start(_selgrpLabel);
1045     _selgrpBox.pack_start(_selgrp);
1046     _selgrp.set_active(prefs->getBool("/dialogs/align/sel-as-groups"));
1047     _selgrp.signal_toggled().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_selgrp_toggled));
1049     _alignBox.pack_start(_anchorBox);
1050     _alignBox.pack_start(_selgrpBox);
1051     _alignBox.pack_start(_alignTable);
1053     _alignFrame.add(_alignBox);
1054     _distributeFrame.add(_distributeTable);
1055     _rearrangeFrame.add(_rearrangeTable);
1056     _removeOverlapFrame.add(_removeOverlapTable);
1057     _nodesFrame.add(_nodesTable);
1059     Gtk::Box *contents = _getContents();
1060     contents->set_spacing(4);
1062     // Notebook for individual transformations
1064     contents->pack_start(_alignFrame, true, true);
1065     contents->pack_start(_distributeFrame, true, true);
1066     contents->pack_start(_rearrangeFrame, true, true);
1067     contents->pack_start(_removeOverlapFrame, true, true);
1068     contents->pack_start(_nodesFrame, true, true);
1070     //Connect to the global tool change signal
1071     g_signal_connect (G_OBJECT (INKSCAPE), "set_eventcontext", G_CALLBACK (on_tool_changed), this);
1073     // Connect to the global selection change, to invalidate cached randomize_bbox
1074     g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
1075     randomize_bbox = Geom::OptRect();
1077     show_all_children();
1079     on_tool_changed (NULL, NULL, this); // set current mode
1082 AlignAndDistribute::~AlignAndDistribute()
1084     sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
1086     for (std::list<Action *>::iterator it = _actionList.begin();
1087          it != _actionList.end();
1088          it ++)
1089         delete *it;
1092 void AlignAndDistribute::on_ref_change(){
1093     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1094     prefs->setInt("/dialogs/align/align-to", _combo.get_active_row_number());
1096     //Make blink the master
1099 void AlignAndDistribute::on_selgrp_toggled(){
1100     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1101     prefs->setInt("/dialogs/align/sel-as-groups", _selgrp.get_active());
1103     //Make blink the master
1109 void AlignAndDistribute::setMode(bool nodeEdit)
1111     //Act on widgets used in node mode
1112     void ( Gtk::Widget::*mNode) ()  = nodeEdit ?
1113         &Gtk::Widget::show_all : &Gtk::Widget::hide_all;
1115     //Act on widgets used in selection mode
1116   void ( Gtk::Widget::*mSel) ()  = nodeEdit ?
1117       &Gtk::Widget::hide_all : &Gtk::Widget::show_all;
1120     ((_alignFrame).*(mSel))();
1121     ((_distributeFrame).*(mSel))();
1122     ((_rearrangeFrame).*(mSel))();
1123     ((_removeOverlapFrame).*(mSel))();
1124     ((_nodesFrame).*(mNode))();
1127 void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
1128                                  guint row, guint col)
1130     _actionList.push_back(
1131         new ActionAlign(
1132             id, tiptext, row, col,
1133             *this , col + row * 5));
1135 void AlignAndDistribute::addDistributeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1136                                       guint row, guint col, bool onInterSpace,
1137                                       Geom::Dim2 orientation, float kBegin, float kEnd)
1139     _actionList.push_back(
1140         new ActionDistribute(
1141             id, tiptext, row, col, *this ,
1142             onInterSpace, orientation,
1143             kBegin, kEnd
1144             )
1145         );
1148 void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1149                    guint col, Geom::Dim2 orientation, bool distribute)
1151     _actionList.push_back(
1152         new ActionNode(
1153             id, tiptext, col,
1154             *this, orientation, distribute));
1157 void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
1158                                       guint row, guint col)
1160     _actionList.push_back(
1161         new ActionRemoveOverlaps(
1162             id, tiptext, row, col, *this)
1163         );
1166 void AlignAndDistribute::addGraphLayoutButton(const Glib::ustring &id, const Glib::ustring tiptext,
1167                                       guint row, guint col)
1169     _actionList.push_back(
1170         new ActionGraphLayout(
1171             id, tiptext, row, col, *this)
1172         );
1175 void AlignAndDistribute::addExchangePositionsButton(const Glib::ustring &id, const Glib::ustring tiptext,
1176                                       guint row, guint col)
1178     _actionList.push_back(
1179         new ActionExchangePositions(
1180             id, tiptext, row, col, *this)
1181         );
1184 void AlignAndDistribute::addExchangePositionsByZOrderButton(const Glib::ustring &id, const Glib::ustring tiptext,
1185                                       guint row, guint col)
1187     _actionList.push_back(
1188         new ActionExchangePositions(
1189             id, tiptext, row, col, *this, ActionExchangePositions::ZOrder)
1190         );
1193 void AlignAndDistribute::addExchangePositionsClockwiseButton(const Glib::ustring &id, const Glib::ustring tiptext,
1194                                       guint row, guint col)
1196     _actionList.push_back(
1197         new ActionExchangePositions(
1198             id, tiptext, row, col, *this, ActionExchangePositions::Clockwise)
1199         );
1202 void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
1203                                       guint row, guint col)
1205     _actionList.push_back(
1206         new ActionUnclump(
1207             id, tiptext, row, col, *this)
1208         );
1211 void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1212                                       guint row, guint col)
1214     _actionList.push_back(
1215         new ActionRandomize(
1216             id, tiptext, row, col, *this)
1217         );
1220 void AlignAndDistribute::addBaselineButton(const Glib::ustring &id, const Glib::ustring tiptext,
1221                                     guint row, guint col, Gtk::Table &table, Geom::Dim2 orientation, bool distribute)
1223     _actionList.push_back(
1224         new ActionBaseline(
1225             id, tiptext, row, col,
1226             *this, table, orientation, distribute));
1232 std::list<SPItem *>::iterator AlignAndDistribute::find_master( std::list<SPItem *> &list, bool horizontal){
1233     std::list<SPItem *>::iterator master = list.end();
1234     switch (getAlignTarget()) {
1235     case LAST:
1236         return list.begin();
1237         break;
1239     case FIRST:
1240         return --(list.end());
1241         break;
1243     case BIGGEST:
1244     {
1245         gdouble max = -1e18;
1246         for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1247             Geom::OptRect b = (*it)->getBboxDesktop ();
1248             if (b) {
1249                 gdouble dim = (*b)[horizontal ? Geom::X : Geom::Y].extent();
1250                 if (dim > max) {
1251                     max = dim;
1252                     master = it;
1253                 }
1254             }
1255         }
1256         return master;
1257         break;
1258     }
1260     case SMALLEST:
1261     {
1262         gdouble max = 1e18;
1263         for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1264             Geom::OptRect b = (*it)->getBboxDesktop ();
1265             if (b) {
1266                 gdouble dim = (*b)[horizontal ? Geom::X : Geom::Y].extent();
1267                 if (dim < max) {
1268                     max = dim;
1269                     master = it;
1270                 }
1271             }
1272         }
1273         return master;
1274         break;
1275     }
1277     default:
1278         g_assert_not_reached ();
1279         break;
1281     } // end of switch statement
1282     return master;
1285 AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
1286     return AlignTarget(_combo.get_active_row_number());
1291 } // namespace Dialog
1292 } // namespace UI
1293 } // namespace Inkscape
1295 /*
1296   Local Variables:
1297   mode:c++
1298   c-file-style:"stroustrup"
1299   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1300   indent-tabs-mode:nil
1301   fill-column:99
1302   End:
1303 */
1304 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :