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 *
11 * Copyright (C) 1999-2004, 2005 Authors
12 *
13 * Released under GNU GPL. Read the file 'COPYING' for more information.
14 */
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
21 #include <gtkmm/spinbutton.h>
23 #include "desktop-handles.h"
24 #include "unclump.h"
25 #include "document.h"
26 #include "enums.h"
27 #include "graphlayout.h"
28 #include "inkscape.h"
29 #include "macros.h"
30 #include "preferences.h"
31 #include "removeoverlap.h"
32 #include "selection.h"
33 #include "sp-flowtext.h"
34 #include "sp-item-transform.h"
35 #include "sp-text.h"
36 #include "text-editing.h"
37 #include "tools-switch.h"
38 #include "ui/icon-names.h"
39 #include "ui/tool/node-tool.h"
40 #include "ui/tool/multi-path-manipulator.h"
41 #include "util/glib-list-iterators.h"
42 #include "verbs.h"
43 #include "widgets/icon.h"
45 #include "align-and-distribute.h"
47 namespace Inkscape {
48 namespace UI {
49 namespace Dialog {
51 /////////helper classes//////////////////////////////////
53 class Action {
54 public :
55 Action(const Glib::ustring &id,
56 const Glib::ustring &tiptext,
57 guint row, guint column,
58 Gtk::Table &parent,
59 Gtk::Tooltips &tooltips,
60 AlignAndDistribute &dialog):
61 _dialog(dialog),
62 _id(id),
63 _parent(parent)
64 {
65 Gtk::Widget* pIcon = Gtk::manage( sp_icon_get_icon( _id, Inkscape::ICON_SIZE_LARGE_TOOLBAR) );
66 Gtk::Button * pButton = Gtk::manage(new Gtk::Button());
67 pButton->set_relief(Gtk::RELIEF_NONE);
68 pIcon->show();
69 pButton->add(*pIcon);
70 pButton->show();
72 pButton->signal_clicked()
73 .connect(sigc::mem_fun(*this, &Action::on_button_click));
74 tooltips.set_tip(*pButton, tiptext);
75 parent.attach(*pButton, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
76 }
77 virtual ~Action(){}
79 AlignAndDistribute &_dialog;
81 private :
82 virtual void on_button_click(){}
84 Glib::ustring _id;
85 Gtk::Table &_parent;
86 };
89 class ActionAlign : public Action {
90 public :
91 struct Coeffs {
92 double mx0, mx1, my0, my1;
93 double sx0, sx1, sy0, sy1;
94 };
95 ActionAlign(const Glib::ustring &id,
96 const Glib::ustring &tiptext,
97 guint row, guint column,
98 AlignAndDistribute &dialog,
99 guint coeffIndex):
100 Action(id, tiptext, row, column,
101 dialog.align_table(), dialog.tooltips(), dialog),
102 _index(coeffIndex),
103 _dialog(dialog)
104 {}
106 private :
108 virtual void on_button_click() {
109 //Retreive selected objects
110 SPDesktop *desktop = _dialog.getDesktop();
111 if (!desktop) return;
113 Inkscape::Selection *selection = sp_desktop_selection(desktop);
114 if (!selection) return;
116 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
117 bool sel_as_group = prefs->getBool("/dialogs/align/sel-as-groups");
119 using Inkscape::Util::GSListConstIterator;
120 std::list<SPItem *> selected;
121 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
122 if (selected.empty()) return;
124 Geom::Point mp; //Anchor point
125 AlignAndDistribute::AlignTarget target = _dialog.getAlignTarget();
126 const Coeffs &a= _allCoeffs[_index];
127 switch (target)
128 {
129 case AlignAndDistribute::LAST:
130 case AlignAndDistribute::FIRST:
131 case AlignAndDistribute::BIGGEST:
132 case AlignAndDistribute::SMALLEST:
133 {
134 //Check 2 or more selected objects
135 std::list<SPItem *>::iterator second(selected.begin());
136 ++second;
137 if (second == selected.end())
138 return;
139 //Find the master (anchor on which the other objects are aligned)
140 std::list<SPItem *>::iterator master(
141 _dialog.find_master (
142 selected,
143 (a.mx0 != 0.0) ||
144 (a.mx1 != 0.0) )
145 );
146 //remove the master from the selection
147 SPItem * thing = *master;
148 // TODO: either uncomment or remove the following commented lines, depending on which
149 // behaviour of moving objects makes most sense; also cf. discussion at
150 // https://bugs.launchpad.net/inkscape/+bug/255933
151 /*if (!sel_as_group) { */
152 selected.erase(master);
153 /*}*/
154 //Compute the anchor point
155 Geom::OptRect b = sp_item_bbox_desktop (thing);
156 if (b) {
157 mp = Geom::Point(a.mx0 * b->min()[Geom::X] + a.mx1 * b->max()[Geom::X],
158 a.my0 * b->min()[Geom::Y] + a.my1 * b->max()[Geom::Y]);
159 } else {
160 return;
161 }
162 break;
163 }
165 case AlignAndDistribute::PAGE:
166 mp = Geom::Point(a.mx1 * sp_document_width(sp_desktop_document(desktop)),
167 a.my1 * sp_document_height(sp_desktop_document(desktop)));
168 break;
170 case AlignAndDistribute::DRAWING:
171 {
172 Geom::OptRect b = sp_item_bbox_desktop
173 ( (SPItem *) sp_document_root (sp_desktop_document (desktop)) );
174 if (b) {
175 mp = Geom::Point(a.mx0 * b->min()[Geom::X] + a.mx1 * b->max()[Geom::X],
176 a.my0 * b->min()[Geom::Y] + a.my1 * b->max()[Geom::Y]);
177 } else {
178 return;
179 }
180 break;
181 }
183 case AlignAndDistribute::SELECTION:
184 {
185 Geom::OptRect b = selection->bounds();
186 if (b) {
187 mp = Geom::Point(a.mx0 * b->min()[Geom::X] + a.mx1 * b->max()[Geom::X],
188 a.my0 * b->min()[Geom::Y] + a.my1 * b->max()[Geom::Y]);
189 } else {
190 return;
191 }
192 break;
193 }
195 default:
196 g_assert_not_reached ();
197 break;
198 }; // end of switch
200 // Top hack: temporarily set clone compensation to unmoved, so that we can align/distribute
201 // clones with their original (and the move of the original does not disturb the
202 // clones). The only problem with this is that if there are outside-of-selection clones of
203 // a selected original, they will be unmoved too, possibly contrary to user's
204 // expecation. However this is a minor point compared to making align/distribute always
205 // work as expected, and "unmoved" is the default option anyway.
206 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
207 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
209 bool changed = false;
210 Geom::OptRect b;
211 if (sel_as_group)
212 b = selection->bounds();
214 //Move each item in the selected list separately
215 for (std::list<SPItem *>::iterator it(selected.begin());
216 it != selected.end();
217 it++)
218 {
219 sp_document_ensure_up_to_date(sp_desktop_document (desktop));
220 if (!sel_as_group)
221 b = sp_item_bbox_desktop (*it);
222 if (b) {
223 Geom::Point const sp(a.sx0 * b->min()[Geom::X] + a.sx1 * b->max()[Geom::X],
224 a.sy0 * b->min()[Geom::Y] + a.sy1 * b->max()[Geom::Y]);
225 Geom::Point const mp_rel( mp - sp );
226 if (LInfty(mp_rel) > 1e-9) {
227 sp_item_move_rel(*it, Geom::Translate(mp_rel));
228 changed = true;
229 }
230 }
231 }
233 // restore compensation setting
234 prefs->setInt("/options/clonecompensation/value", saved_compensation);
236 if (changed) {
237 sp_document_done ( sp_desktop_document (desktop) , SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
238 _("Align"));
239 }
242 }
243 guint _index;
244 AlignAndDistribute &_dialog;
246 static const Coeffs _allCoeffs[10];
248 };
249 ActionAlign::Coeffs const ActionAlign::_allCoeffs[10] = {
250 {1., 0., 0., 0., 0., 1., 0., 0.},
251 {1., 0., 0., 0., 1., 0., 0., 0.},
252 {.5, .5, 0., 0., .5, .5, 0., 0.},
253 {0., 1., 0., 0., 0., 1., 0., 0.},
254 {0., 1., 0., 0., 1., 0., 0., 0.},
255 {0., 0., 0., 1., 0., 0., 1., 0.},
256 {0., 0., 0., 1., 0., 0., 0., 1.},
257 {0., 0., .5, .5, 0., 0., .5, .5},
258 {0., 0., 1., 0., 0., 0., 1., 0.},
259 {0., 0., 1., 0., 0., 0., 0., 1.}
260 };
262 BBoxSort::BBoxSort(SPItem *pItem, Geom::Rect bounds, Geom::Dim2 orientation, double kBegin, double kEnd) :
263 item(pItem),
264 bbox (bounds)
265 {
266 anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
267 }
268 BBoxSort::BBoxSort(const BBoxSort &rhs) :
269 //NOTE : this copy ctor is called O(sort) when sorting the vector
270 //this is bad. The vector should be a vector of pointers.
271 //But I'll wait the bohem GC before doing that
272 item(rhs.item), anchor(rhs.anchor), bbox(rhs.bbox)
273 {
274 }
276 bool operator< (const BBoxSort &a, const BBoxSort &b)
277 {
278 return (a.anchor < b.anchor);
279 }
281 class ActionDistribute : public Action {
282 public :
283 ActionDistribute(const Glib::ustring &id,
284 const Glib::ustring &tiptext,
285 guint row, guint column,
286 AlignAndDistribute &dialog,
287 bool onInterSpace,
288 Geom::Dim2 orientation,
289 double kBegin, double kEnd
290 ):
291 Action(id, tiptext, row, column,
292 dialog.distribute_table(), dialog.tooltips(), dialog),
293 _dialog(dialog),
294 _onInterSpace(onInterSpace),
295 _orientation(orientation),
296 _kBegin(kBegin),
297 _kEnd( kEnd)
298 {}
300 private :
301 virtual void on_button_click() {
302 //Retreive selected objects
303 SPDesktop *desktop = _dialog.getDesktop();
304 if (!desktop) return;
306 Inkscape::Selection *selection = sp_desktop_selection(desktop);
307 if (!selection) return;
309 using Inkscape::Util::GSListConstIterator;
310 std::list<SPItem *> selected;
311 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
312 if (selected.empty()) return;
314 //Check 2 or more selected objects
315 std::list<SPItem *>::iterator second(selected.begin());
316 ++second;
317 if (second == selected.end()) return;
320 std::vector< BBoxSort > sorted;
321 for (std::list<SPItem *>::iterator it(selected.begin());
322 it != selected.end();
323 ++it)
324 {
325 Geom::OptRect bbox = sp_item_bbox_desktop(*it);
326 if (bbox) {
327 sorted.push_back(BBoxSort(*it, *bbox, _orientation, _kBegin, _kEnd));
328 }
329 }
330 //sort bbox by anchors
331 std::sort(sorted.begin(), sorted.end());
333 // see comment in ActionAlign above
334 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
335 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
336 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
338 unsigned int len = sorted.size();
339 bool changed = false;
340 if (_onInterSpace)
341 {
342 //overall bboxes span
343 float dist = (sorted.back().bbox.max()[_orientation] -
344 sorted.front().bbox.min()[_orientation]);
345 //space eaten by bboxes
346 float span = 0;
347 for (unsigned int i = 0; i < len; i++)
348 {
349 span += sorted[i].bbox[_orientation].extent();
350 }
351 //new distance between each bbox
352 float step = (dist - span) / (len - 1);
353 float pos = sorted.front().bbox.min()[_orientation];
354 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
355 it < sorted.end();
356 it ++ )
357 {
358 if (!NR_DF_TEST_CLOSE (pos, it->bbox.min()[_orientation], 1e-6)) {
359 Geom::Point t(0.0, 0.0);
360 t[_orientation] = pos - it->bbox.min()[_orientation];
361 sp_item_move_rel(it->item, Geom::Translate(t));
362 changed = true;
363 }
364 pos += it->bbox[_orientation].extent();
365 pos += step;
366 }
367 }
368 else
369 {
370 //overall anchor span
371 float dist = sorted.back().anchor - sorted.front().anchor;
372 //distance between anchors
373 float step = dist / (len - 1);
375 for ( unsigned int i = 0; i < len ; i ++ )
376 {
377 BBoxSort & it(sorted[i]);
378 //new anchor position
379 float pos = sorted.front().anchor + i * step;
380 //Don't move if we are really close
381 if (!NR_DF_TEST_CLOSE (pos, it.anchor, 1e-6)) {
382 //Compute translation
383 Geom::Point t(0.0, 0.0);
384 t[_orientation] = pos - it.anchor;
385 //translate
386 sp_item_move_rel(it.item, Geom::Translate(t));
387 changed = true;
388 }
389 }
390 }
392 // restore compensation setting
393 prefs->setInt("/options/clonecompensation/value", saved_compensation);
395 if (changed) {
396 sp_document_done ( sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
397 _("Distribute"));
398 }
399 }
400 guint _index;
401 AlignAndDistribute &_dialog;
402 bool _onInterSpace;
403 Geom::Dim2 _orientation;
405 double _kBegin;
406 double _kEnd;
408 };
411 class ActionNode : public Action {
412 public :
413 ActionNode(const Glib::ustring &id,
414 const Glib::ustring &tiptext,
415 guint column,
416 AlignAndDistribute &dialog,
417 Geom::Dim2 orientation, bool distribute):
418 Action(id, tiptext, 0, column,
419 dialog.nodes_table(), dialog.tooltips(), dialog),
420 _orientation(orientation),
421 _distribute(distribute)
422 {}
424 private :
425 Geom::Dim2 _orientation;
426 bool _distribute;
427 virtual void on_button_click()
428 {
430 if (!_dialog.getDesktop()) return;
431 SPEventContext *event_context = sp_desktop_event_context(_dialog.getDesktop());
432 if (!INK_IS_NODE_TOOL (event_context)) return;
433 InkNodeTool *nt = INK_NODE_TOOL(event_context);
435 if (_distribute)
436 nt->_multipath->distributeNodes(_orientation);
437 else
438 nt->_multipath->alignNodes(_orientation);
440 }
441 };
443 class ActionRemoveOverlaps : public Action {
444 private:
445 Gtk::Label removeOverlapXGapLabel;
446 Gtk::Label removeOverlapYGapLabel;
447 Gtk::SpinButton removeOverlapXGap;
448 Gtk::SpinButton removeOverlapYGap;
450 public:
451 ActionRemoveOverlaps(Glib::ustring const &id,
452 Glib::ustring const &tiptext,
453 guint row,
454 guint column,
455 AlignAndDistribute &dialog) :
456 Action(id, tiptext, row, column + 4,
457 dialog.removeOverlap_table(), dialog.tooltips(), dialog)
458 {
459 dialog.removeOverlap_table().set_col_spacings(3);
461 removeOverlapXGap.set_digits(1);
462 removeOverlapXGap.set_size_request(60, -1);
463 removeOverlapXGap.set_increments(1.0, 0);
464 removeOverlapXGap.set_range(-1000.0, 1000.0);
465 removeOverlapXGap.set_value(0);
466 dialog.tooltips().set_tip(removeOverlapXGap,
467 _("Minimum horizontal gap (in px units) between bounding boxes"));
468 //TRANSLATORS: "H:" stands for horizontal gap
469 removeOverlapXGapLabel.set_label(C_("Gap", "H:"));
471 removeOverlapYGap.set_digits(1);
472 removeOverlapYGap.set_size_request(60, -1);
473 removeOverlapYGap.set_increments(1.0, 0);
474 removeOverlapYGap.set_range(-1000.0, 1000.0);
475 removeOverlapYGap.set_value(0);
476 dialog.tooltips().set_tip(removeOverlapYGap,
477 _("Minimum vertical gap (in px units) between bounding boxes"));
478 /* TRANSLATORS: Vertical gap */
479 removeOverlapYGapLabel.set_label(C_("Gap", "V:"));
481 dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
482 dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
483 dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, column+3, row, row+1, Gtk::FILL, Gtk::FILL);
484 dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
486 }
488 private :
489 virtual void on_button_click()
490 {
491 if (!_dialog.getDesktop()) return;
493 // see comment in ActionAlign above
494 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
495 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
496 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
498 // xGap and yGap are the minimum space required between bounding rectangles.
499 double const xGap = removeOverlapXGap.get_value();
500 double const yGap = removeOverlapYGap.get_value();
501 removeoverlap(sp_desktop_selection(_dialog.getDesktop())->itemList(),
502 xGap, yGap);
504 // restore compensation setting
505 prefs->setInt("/options/clonecompensation/value", saved_compensation);
507 sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
508 _("Remove overlaps"));
509 }
510 };
512 class ActionGraphLayout : public Action {
513 public:
514 ActionGraphLayout(Glib::ustring const &id,
515 Glib::ustring const &tiptext,
516 guint row,
517 guint column,
518 AlignAndDistribute &dialog) :
519 Action(id, tiptext, row, column,
520 dialog.rearrange_table(), dialog.tooltips(), dialog)
521 {}
523 private :
524 virtual void on_button_click()
525 {
526 if (!_dialog.getDesktop()) return;
528 // see comment in ActionAlign above
529 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
530 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
531 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
533 graphlayout(sp_desktop_selection(_dialog.getDesktop())->itemList());
535 // restore compensation setting
536 prefs->setInt("/options/clonecompensation/value", saved_compensation);
538 sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
539 _("Arrange connector network"));
540 }
541 };
543 class ActionExchangePositions : public Action {
544 public:
545 enum SortOrder {
546 None,
547 ZOrder,
548 Clockwise
549 };
551 ActionExchangePositions(Glib::ustring const &id,
552 Glib::ustring const &tiptext,
553 guint row,
554 guint column,
555 AlignAndDistribute &dialog, SortOrder order = None) :
556 Action(id, tiptext, row, column,
557 dialog.rearrange_table(), dialog.tooltips(), dialog),
558 sortOrder(order)
559 {};
562 private :
563 const SortOrder sortOrder;
564 static boost::optional<Geom::Point> center;
566 static bool sort_compare(const SPItem * a,const SPItem * b) {
567 if (a == NULL) return false;
568 if (b == NULL) return true;
569 if (center) {
570 Geom::Point point_a = a->getCenter() - (*center);
571 Geom::Point point_b = b->getCenter() - (*center);
572 // First criteria: Sort according to the angle to the center point
573 double angle_a = atan2(double(point_a[Geom::Y]), double(point_a[Geom::X]));
574 double angle_b = atan2(double(point_b[Geom::Y]), double(point_b[Geom::X]));
575 if (angle_a != angle_b) return (angle_a < angle_b);
576 // Second criteria: Sort according to the distance the center point
577 Geom::Coord length_a = point_a.length();
578 Geom::Coord length_b = point_b.length();
579 if (length_a != length_b) return (length_a > length_b);
580 }
581 // Last criteria: Sort according to the z-coordinate
582 return (a->isSiblingOf(b));
583 }
585 virtual void on_button_click()
586 {
587 SPDesktop *desktop = _dialog.getDesktop();
588 if (!desktop) return;
590 Inkscape::Selection *selection = sp_desktop_selection(desktop);
591 if (!selection) return;
593 using Inkscape::Util::GSListConstIterator;
594 std::list<SPItem *> selected;
595 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
596 if (selected.empty()) return;
598 //Check 2 or more selected objects
599 if (selected.size() < 2) return;
601 // see comment in ActionAlign above
602 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
603 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
604 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
606 // sort the list
607 if (sortOrder != None) {
608 if (sortOrder == Clockwise) {
609 center = selection->center();
610 } else { // sorting by ZOrder is outomatically done by not setting the center
611 center.reset();
612 }
613 selected.sort(ActionExchangePositions::sort_compare);
614 }
615 std::list<SPItem *>::iterator it(selected.begin());
616 Geom::Point p1 = (*it)->getCenter();
617 for (++it ;it != selected.end(); ++it)
618 {
619 Geom::Point p2 = (*it)->getCenter();
620 Geom::Point delta = p1 - p2;
621 sp_item_move_rel((*it),Geom::Translate(delta[Geom::X],delta[Geom::Y] ));
622 p1 = p2;
623 }
624 Geom::Point p2 = selected.front()->getCenter();
625 Geom::Point delta = p1 - p2;
626 sp_item_move_rel(selected.front(),Geom::Translate(delta[Geom::X],delta[Geom::Y] ));
628 // restore compensation setting
629 prefs->setInt("/options/clonecompensation/value", saved_compensation);
631 sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
632 _("Exchange Positions"));
633 }
634 };
635 // instantiae the private static member
636 boost::optional<Geom::Point> ActionExchangePositions::center;
638 class ActionUnclump : public Action {
639 public :
640 ActionUnclump(const Glib::ustring &id,
641 const Glib::ustring &tiptext,
642 guint row,
643 guint column,
644 AlignAndDistribute &dialog):
645 Action(id, tiptext, row, column,
646 dialog.rearrange_table(), dialog.tooltips(), dialog)
647 {}
649 private :
650 virtual void on_button_click()
651 {
652 if (!_dialog.getDesktop()) return;
654 // see comment in ActionAlign above
655 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
656 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
657 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
659 unclump ((GSList *) sp_desktop_selection(_dialog.getDesktop())->itemList());
661 // restore compensation setting
662 prefs->setInt("/options/clonecompensation/value", saved_compensation);
664 sp_document_done (sp_desktop_document (_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
665 _("Unclump"));
666 }
667 };
669 class ActionRandomize : public Action {
670 public :
671 ActionRandomize(const Glib::ustring &id,
672 const Glib::ustring &tiptext,
673 guint row,
674 guint column,
675 AlignAndDistribute &dialog):
676 Action(id, tiptext, row, column,
677 dialog.rearrange_table(), dialog.tooltips(), dialog)
678 {}
680 private :
681 virtual void on_button_click()
682 {
683 SPDesktop *desktop = _dialog.getDesktop();
684 if (!desktop) return;
686 Inkscape::Selection *selection = sp_desktop_selection(desktop);
687 if (!selection) return;
689 using Inkscape::Util::GSListConstIterator;
690 std::list<SPItem *> selected;
691 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
692 if (selected.empty()) return;
694 //Check 2 or more selected objects
695 if (selected.size() < 2) return;
697 Geom::OptRect sel_bbox = selection->bounds();
698 if (!sel_bbox) {
699 return;
700 }
702 // This bbox is cached between calls to randomize, so that there's no growth nor shrink
703 // nor drift on sequential randomizations. Discard cache on global (or better active
704 // desktop's) selection_change signal.
705 if (!_dialog.randomize_bbox) {
706 _dialog.randomize_bbox = *sel_bbox;
707 }
709 // see comment in ActionAlign above
710 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
711 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
712 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
714 for (std::list<SPItem *>::iterator it(selected.begin());
715 it != selected.end();
716 ++it)
717 {
718 sp_document_ensure_up_to_date(sp_desktop_document (desktop));
719 Geom::OptRect item_box = sp_item_bbox_desktop (*it);
720 if (item_box) {
721 // find new center, staying within bbox
722 double x = _dialog.randomize_bbox->min()[Geom::X] + (*item_box)[Geom::X].extent() /2 +
723 g_random_double_range (0, (*_dialog.randomize_bbox)[Geom::X].extent() - (*item_box)[Geom::X].extent());
724 double y = _dialog.randomize_bbox->min()[Geom::Y] + (*item_box)[Geom::Y].extent()/2 +
725 g_random_double_range (0, (*_dialog.randomize_bbox)[Geom::Y].extent() - (*item_box)[Geom::Y].extent());
726 // displacement is the new center minus old:
727 Geom::Point t = Geom::Point (x, y) - 0.5*(item_box->max() + item_box->min());
728 sp_item_move_rel(*it, Geom::Translate(t));
729 }
730 }
732 // restore compensation setting
733 prefs->setInt("/options/clonecompensation/value", saved_compensation);
735 sp_document_done (sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
736 _("Randomize positions"));
737 }
738 };
740 struct Baselines
741 {
742 SPItem *_item;
743 Geom::Point _base;
744 Geom::Dim2 _orientation;
745 Baselines(SPItem *item, Geom::Point base, Geom::Dim2 orientation) :
746 _item (item),
747 _base (base),
748 _orientation (orientation)
749 {}
750 };
752 bool operator< (const Baselines &a, const Baselines &b)
753 {
754 return (a._base[a._orientation] < b._base[b._orientation]);
755 }
757 class ActionBaseline : public Action {
758 public :
759 ActionBaseline(const Glib::ustring &id,
760 const Glib::ustring &tiptext,
761 guint row,
762 guint column,
763 AlignAndDistribute &dialog,
764 Gtk::Table &table,
765 Geom::Dim2 orientation, bool distribute):
766 Action(id, tiptext, row, column,
767 table, dialog.tooltips(), dialog),
768 _orientation(orientation),
769 _distribute(distribute)
770 {}
772 private :
773 Geom::Dim2 _orientation;
774 bool _distribute;
775 virtual void on_button_click()
776 {
777 SPDesktop *desktop = _dialog.getDesktop();
778 if (!desktop) return;
780 Inkscape::Selection *selection = sp_desktop_selection(desktop);
781 if (!selection) return;
783 using Inkscape::Util::GSListConstIterator;
784 std::list<SPItem *> selected;
785 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
786 if (selected.empty()) return;
788 //Check 2 or more selected objects
789 if (selected.size() < 2) return;
791 Geom::Point b_min = Geom::Point (HUGE_VAL, HUGE_VAL);
792 Geom::Point b_max = Geom::Point (-HUGE_VAL, -HUGE_VAL);
794 std::vector<Baselines> sorted;
796 for (std::list<SPItem *>::iterator it(selected.begin());
797 it != selected.end();
798 ++it)
799 {
800 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
801 Inkscape::Text::Layout const *layout = te_get_layout(*it);
802 boost::optional<Geom::Point> pt = layout->baselineAnchorPoint();
803 if (pt) {
804 Geom::Point base = *pt * sp_item_i2d_affine(*it);
805 if (base[Geom::X] < b_min[Geom::X]) b_min[Geom::X] = base[Geom::X];
806 if (base[Geom::Y] < b_min[Geom::Y]) b_min[Geom::Y] = base[Geom::Y];
807 if (base[Geom::X] > b_max[Geom::X]) b_max[Geom::X] = base[Geom::X];
808 if (base[Geom::Y] > b_max[Geom::Y]) b_max[Geom::Y] = base[Geom::Y];
809 Baselines b (*it, base, _orientation);
810 sorted.push_back(b);
811 }
812 }
813 }
815 if (sorted.size() <= 1) return;
817 //sort baselines
818 std::sort(sorted.begin(), sorted.end());
820 bool changed = false;
822 if (_distribute) {
823 double step = (b_max[_orientation] - b_min[_orientation])/(sorted.size() - 1);
824 for (unsigned int i = 0; i < sorted.size(); i++) {
825 SPItem *item = sorted[i]._item;
826 Geom::Point base = sorted[i]._base;
827 Geom::Point t(0.0, 0.0);
828 t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
829 sp_item_move_rel(item, Geom::Translate(t));
830 changed = true;
831 }
833 if (changed) {
834 sp_document_done (sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
835 _("Distribute text baselines"));
836 }
838 } else {
839 for (std::list<SPItem *>::iterator it(selected.begin());
840 it != selected.end();
841 ++it)
842 {
843 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
844 Inkscape::Text::Layout const *layout = te_get_layout(*it);
845 boost::optional<Geom::Point> pt = layout->baselineAnchorPoint();
846 if (pt) {
847 Geom::Point base = *pt * sp_item_i2d_affine(*it);
848 Geom::Point t(0.0, 0.0);
849 t[_orientation] = b_min[_orientation] - base[_orientation];
850 sp_item_move_rel(*it, Geom::Translate(t));
851 changed = true;
852 }
853 }
854 }
856 if (changed) {
857 sp_document_done (sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
858 _("Align text baselines"));
859 }
860 }
861 }
862 };
866 void on_tool_changed(Inkscape::Application */*inkscape*/, SPEventContext */*context*/, AlignAndDistribute *daad)
867 {
868 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
869 if (desktop && sp_desktop_event_context(desktop))
870 daad->setMode(tools_active(desktop) == TOOLS_NODES);
871 }
873 void on_selection_changed(Inkscape::Application */*inkscape*/, Inkscape::Selection */*selection*/, AlignAndDistribute *daad)
874 {
875 daad->randomize_bbox = Geom::OptRect();
876 }
878 /////////////////////////////////////////////////////////
883 AlignAndDistribute::AlignAndDistribute()
884 : UI::Widget::Panel ("", "/dialogs/align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
885 randomize_bbox(),
886 _alignFrame(_("Align")),
887 _distributeFrame(_("Distribute")),
888 _rearrangeFrame(_("Rearrange")),
889 _removeOverlapFrame(_("Remove overlaps")),
890 _nodesFrame(_("Nodes")),
891 _alignTable(2, 6, true),
892 _distributeTable(2, 6, true),
893 _rearrangeTable(1, 5, false),
894 _removeOverlapTable(1, 5, false),
895 _nodesTable(1, 4, true),
896 _anchorLabel(_("Relative to: ")),
897 _selgrpLabel(_("Treat selection as group: "))
898 {
899 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
901 //Instanciate the align buttons
902 addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_RIGHT_TO_ANCHOR,
903 _("Align right edges of objects to the left edge of the anchor"),
904 0, 0);
905 addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_LEFT,
906 _("Align left edges"),
907 0, 1);
908 addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_CENTER,
909 _("Center on vertical axis"),
910 0, 2);
911 addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_RIGHT,
912 _("Align right sides"),
913 0, 3);
914 addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_LEFT_TO_ANCHOR,
915 _("Align left edges of objects to the right edge of the anchor"),
916 0, 4);
917 addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_BOTTOM_TO_ANCHOR,
918 _("Align bottom edges of objects to the top edge of the anchor"),
919 1, 0);
920 addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_TOP,
921 _("Align top edges"),
922 1, 1);
923 addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_CENTER,
924 _("Center on horizontal axis"),
925 1, 2);
926 addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_BOTTOM,
927 _("Align bottom edges"),
928 1, 3);
929 addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_TOP_TO_ANCHOR,
930 _("Align top edges of objects to the bottom edge of the anchor"),
931 1, 4);
933 //Baseline aligns
934 addBaselineButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_BASELINE,
935 _("Align baseline anchors of texts horizontally"),
936 0, 5, this->align_table(), Geom::X, false);
937 addBaselineButton(INKSCAPE_ICON_ALIGN_VERTICAL_BASELINE,
938 _("Align baselines of texts"),
939 1, 5, this->align_table(), Geom::Y, false);
941 //The distribute buttons
942 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_GAPS,
943 _("Make horizontal gaps between objects equal"),
944 0, 4, true, Geom::X, .5, .5);
946 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_LEFT,
947 _("Distribute left edges equidistantly"),
948 0, 1, false, Geom::X, 1., 0.);
949 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_CENTER,
950 _("Distribute centers equidistantly horizontally"),
951 0, 2, false, Geom::X, .5, .5);
952 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_RIGHT,
953 _("Distribute right edges equidistantly"),
954 0, 3, false, Geom::X, 0., 1.);
956 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_GAPS,
957 _("Make vertical gaps between objects equal"),
958 1, 4, true, Geom::Y, .5, .5);
960 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_TOP,
961 _("Distribute top edges equidistantly"),
962 1, 1, false, Geom::Y, 0, 1);
963 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_CENTER,
964 _("Distribute centers equidistantly vertically"),
965 1, 2, false, Geom::Y, .5, .5);
966 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_BOTTOM,
967 _("Distribute bottom edges equidistantly"),
968 1, 3, false, Geom::Y, 1., 0.);
970 //Baseline distribs
971 addBaselineButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_BASELINE,
972 _("Distribute baseline anchors of texts horizontally"),
973 0, 5, this->distribute_table(), Geom::X, true);
974 addBaselineButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_BASELINE,
975 _("Distribute baselines of texts vertically"),
976 1, 5, this->distribute_table(), Geom::Y, true);
978 // Rearrange
979 //Graph Layout
980 addGraphLayoutButton(INKSCAPE_ICON_DISTRIBUTE_GRAPH,
981 _("Nicely arrange selected connector network"),
982 0, 0);
983 addExchangePositionsButton(INKSCAPE_ICON_EXCHANGE_POSITIONS,
984 _("Exchange positions of selected objects - selection order"),
985 0, 1);
986 addExchangePositionsByZOrderButton(INKSCAPE_ICON_EXCHANGE_POSITIONS_ZORDER,
987 _("Exchange positions of selected objects - stacking order"),
988 0, 2);
989 addExchangePositionsClockwiseButton(INKSCAPE_ICON_EXCHANGE_POSITIONS_CLOCKWISE,
990 _("Exchange positions of selected objects - clockwise rotate"),
991 0, 3);
993 //Randomize & Unclump
994 addRandomizeButton(INKSCAPE_ICON_DISTRIBUTE_RANDOMIZE,
995 _("Randomize centers in both dimensions"),
996 0, 4);
997 addUnclumpButton(INKSCAPE_ICON_DISTRIBUTE_UNCLUMP,
998 _("Unclump objects: try to equalize edge-to-edge distances"),
999 0, 5);
1001 //Remove overlaps
1002 addRemoveOverlapsButton(INKSCAPE_ICON_DISTRIBUTE_REMOVE_OVERLAPS,
1003 _("Move objects as little as possible so that their bounding boxes do not overlap"),
1004 0, 0);
1006 //Node Mode buttons
1007 // NOTE: "align nodes vertically" means "move nodes vertically until they align on a common
1008 // _horizontal_ line". This is analogous to what the "align-vertical-center" icon means.
1009 // There is no doubt some ambiguity. For this reason the descriptions are different.
1010 addNodeButton(INKSCAPE_ICON_ALIGN_VERTICAL_NODES,
1011 _("Align selected nodes to a common horizontal line"),
1012 0, Geom::X, false);
1013 addNodeButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_NODES,
1014 _("Align selected nodes to a common vertical line"),
1015 1, Geom::Y, false);
1016 addNodeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_NODE,
1017 _("Distribute selected nodes horizontally"),
1018 2, Geom::X, true);
1019 addNodeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_NODE,
1020 _("Distribute selected nodes vertically"),
1021 3, Geom::Y, true);
1023 //Rest of the widgetry
1025 _combo.append_text(_("Last selected"));
1026 _combo.append_text(_("First selected"));
1027 _combo.append_text(_("Biggest object"));
1028 _combo.append_text(_("Smallest object"));
1029 _combo.append_text(_("Page"));
1030 _combo.append_text(_("Drawing"));
1031 _combo.append_text(_("Selection"));
1033 _combo.set_active(prefs->getInt("/dialogs/align/align-to", 6));
1034 _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
1036 _anchorBox.pack_start(_anchorLabel);
1037 _anchorBox.pack_start(_combo);
1039 _selgrpBox.pack_start(_selgrpLabel);
1040 _selgrpBox.pack_start(_selgrp);
1041 _selgrp.set_active(prefs->getBool("/dialogs/align/sel-as-groups"));
1042 _selgrp.signal_toggled().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_selgrp_toggled));
1044 _alignBox.pack_start(_anchorBox);
1045 _alignBox.pack_start(_selgrpBox);
1046 _alignBox.pack_start(_alignTable);
1048 _alignFrame.add(_alignBox);
1049 _distributeFrame.add(_distributeTable);
1050 _rearrangeFrame.add(_rearrangeTable);
1051 _removeOverlapFrame.add(_removeOverlapTable);
1052 _nodesFrame.add(_nodesTable);
1054 Gtk::Box *contents = _getContents();
1055 contents->set_spacing(4);
1057 // Notebook for individual transformations
1059 contents->pack_start(_alignFrame, true, true);
1060 contents->pack_start(_distributeFrame, true, true);
1061 contents->pack_start(_rearrangeFrame, true, true);
1062 contents->pack_start(_removeOverlapFrame, true, true);
1063 contents->pack_start(_nodesFrame, true, true);
1065 //Connect to the global tool change signal
1066 g_signal_connect (G_OBJECT (INKSCAPE), "set_eventcontext", G_CALLBACK (on_tool_changed), this);
1068 // Connect to the global selection change, to invalidate cached randomize_bbox
1069 g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
1070 randomize_bbox = Geom::OptRect();
1072 show_all_children();
1074 on_tool_changed (NULL, NULL, this); // set current mode
1075 }
1077 AlignAndDistribute::~AlignAndDistribute()
1078 {
1079 sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
1081 for (std::list<Action *>::iterator it = _actionList.begin();
1082 it != _actionList.end();
1083 it ++)
1084 delete *it;
1085 }
1087 void AlignAndDistribute::on_ref_change(){
1088 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1089 prefs->setInt("/dialogs/align/align-to", _combo.get_active_row_number());
1091 //Make blink the master
1092 }
1094 void AlignAndDistribute::on_selgrp_toggled(){
1095 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1096 prefs->setInt("/dialogs/align/sel-as-groups", _selgrp.get_active());
1098 //Make blink the master
1099 }
1104 void AlignAndDistribute::setMode(bool nodeEdit)
1105 {
1106 //Act on widgets used in node mode
1107 void ( Gtk::Widget::*mNode) () = nodeEdit ?
1108 &Gtk::Widget::show_all : &Gtk::Widget::hide_all;
1110 //Act on widgets used in selection mode
1111 void ( Gtk::Widget::*mSel) () = nodeEdit ?
1112 &Gtk::Widget::hide_all : &Gtk::Widget::show_all;
1115 ((_alignFrame).*(mSel))();
1116 ((_distributeFrame).*(mSel))();
1117 ((_rearrangeFrame).*(mSel))();
1118 ((_removeOverlapFrame).*(mSel))();
1119 ((_nodesFrame).*(mNode))();
1121 }
1122 void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
1123 guint row, guint col)
1124 {
1125 _actionList.push_back(
1126 new ActionAlign(
1127 id, tiptext, row, col,
1128 *this , col + row * 5));
1129 }
1130 void AlignAndDistribute::addDistributeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1131 guint row, guint col, bool onInterSpace,
1132 Geom::Dim2 orientation, float kBegin, float kEnd)
1133 {
1134 _actionList.push_back(
1135 new ActionDistribute(
1136 id, tiptext, row, col, *this ,
1137 onInterSpace, orientation,
1138 kBegin, kEnd
1139 )
1140 );
1141 }
1143 void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1144 guint col, Geom::Dim2 orientation, bool distribute)
1145 {
1146 _actionList.push_back(
1147 new ActionNode(
1148 id, tiptext, col,
1149 *this, orientation, distribute));
1150 }
1152 void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
1153 guint row, guint col)
1154 {
1155 _actionList.push_back(
1156 new ActionRemoveOverlaps(
1157 id, tiptext, row, col, *this)
1158 );
1159 }
1161 void AlignAndDistribute::addGraphLayoutButton(const Glib::ustring &id, const Glib::ustring tiptext,
1162 guint row, guint col)
1163 {
1164 _actionList.push_back(
1165 new ActionGraphLayout(
1166 id, tiptext, row, col, *this)
1167 );
1168 }
1170 void AlignAndDistribute::addExchangePositionsButton(const Glib::ustring &id, const Glib::ustring tiptext,
1171 guint row, guint col)
1172 {
1173 _actionList.push_back(
1174 new ActionExchangePositions(
1175 id, tiptext, row, col, *this)
1176 );
1177 }
1179 void AlignAndDistribute::addExchangePositionsByZOrderButton(const Glib::ustring &id, const Glib::ustring tiptext,
1180 guint row, guint col)
1181 {
1182 _actionList.push_back(
1183 new ActionExchangePositions(
1184 id, tiptext, row, col, *this, ActionExchangePositions::ZOrder)
1185 );
1186 }
1188 void AlignAndDistribute::addExchangePositionsClockwiseButton(const Glib::ustring &id, const Glib::ustring tiptext,
1189 guint row, guint col)
1190 {
1191 _actionList.push_back(
1192 new ActionExchangePositions(
1193 id, tiptext, row, col, *this, ActionExchangePositions::Clockwise)
1194 );
1195 }
1197 void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
1198 guint row, guint col)
1199 {
1200 _actionList.push_back(
1201 new ActionUnclump(
1202 id, tiptext, row, col, *this)
1203 );
1204 }
1206 void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1207 guint row, guint col)
1208 {
1209 _actionList.push_back(
1210 new ActionRandomize(
1211 id, tiptext, row, col, *this)
1212 );
1213 }
1215 void AlignAndDistribute::addBaselineButton(const Glib::ustring &id, const Glib::ustring tiptext,
1216 guint row, guint col, Gtk::Table &table, Geom::Dim2 orientation, bool distribute)
1217 {
1218 _actionList.push_back(
1219 new ActionBaseline(
1220 id, tiptext, row, col,
1221 *this, table, orientation, distribute));
1222 }
1227 std::list<SPItem *>::iterator AlignAndDistribute::find_master( std::list<SPItem *> &list, bool horizontal){
1228 std::list<SPItem *>::iterator master = list.end();
1229 switch (getAlignTarget()) {
1230 case LAST:
1231 return list.begin();
1232 break;
1234 case FIRST:
1235 return --(list.end());
1236 break;
1238 case BIGGEST:
1239 {
1240 gdouble max = -1e18;
1241 for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1242 Geom::OptRect b = sp_item_bbox_desktop (*it);
1243 if (b) {
1244 gdouble dim = (*b)[horizontal ? Geom::X : Geom::Y].extent();
1245 if (dim > max) {
1246 max = dim;
1247 master = it;
1248 }
1249 }
1250 }
1251 return master;
1252 break;
1253 }
1255 case SMALLEST:
1256 {
1257 gdouble max = 1e18;
1258 for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1259 Geom::OptRect b = sp_item_bbox_desktop (*it);
1260 if (b) {
1261 gdouble dim = (*b)[horizontal ? Geom::X : Geom::Y].extent();
1262 if (dim < max) {
1263 max = dim;
1264 master = it;
1265 }
1266 }
1267 }
1268 return master;
1269 break;
1270 }
1272 default:
1273 g_assert_not_reached ();
1274 break;
1276 } // end of switch statement
1277 return master;
1278 }
1280 AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
1281 return AlignTarget(_combo.get_active_row_number());
1282 }
1286 } // namespace Dialog
1287 } // namespace UI
1288 } // namespace Inkscape
1290 /*
1291 Local Variables:
1292 mode:c++
1293 c-file-style:"stroustrup"
1294 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1295 indent-tabs-mode:nil
1296 fill-column:99
1297 End:
1298 */
1299 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :