48f0fbf22ee3eaf0a6f7b59e47da5f8ee4463113
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)
266 {
267 anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
268 }
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)
274 {
275 }
277 bool operator< (const BBoxSort &a, const BBoxSort &b)
278 {
279 return (a.anchor < b.anchor);
280 }
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_label(C_("Gap", "H:"));
472 removeOverlapYGap.set_digits(1);
473 removeOverlapYGap.set_size_request(60, -1);
474 removeOverlapYGap.set_increments(1.0, 0);
475 removeOverlapYGap.set_range(-1000.0, 1000.0);
476 removeOverlapYGap.set_value(0);
477 dialog.tooltips().set_tip(removeOverlapYGap,
478 _("Minimum vertical gap (in px units) between bounding boxes"));
479 /* TRANSLATORS: Vertical gap */
480 removeOverlapYGapLabel.set_label(C_("Gap", "V:"));
482 dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
483 dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
484 dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, column+3, row, row+1, Gtk::FILL, Gtk::FILL);
485 dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
487 }
489 private :
490 virtual void on_button_click()
491 {
492 if (!_dialog.getDesktop()) return;
494 // see comment in ActionAlign above
495 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
496 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
497 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
499 // xGap and yGap are the minimum space required between bounding rectangles.
500 double const xGap = removeOverlapXGap.get_value();
501 double const yGap = removeOverlapYGap.get_value();
502 removeoverlap(sp_desktop_selection(_dialog.getDesktop())->itemList(),
503 xGap, yGap);
505 // restore compensation setting
506 prefs->setInt("/options/clonecompensation/value", saved_compensation);
508 DocumentUndo::done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
509 _("Remove overlaps"));
510 }
511 };
513 class ActionGraphLayout : public Action {
514 public:
515 ActionGraphLayout(Glib::ustring const &id,
516 Glib::ustring const &tiptext,
517 guint row,
518 guint column,
519 AlignAndDistribute &dialog) :
520 Action(id, tiptext, row, column,
521 dialog.rearrange_table(), dialog.tooltips(), dialog)
522 {}
524 private :
525 virtual void on_button_click()
526 {
527 if (!_dialog.getDesktop()) return;
529 // see comment in ActionAlign above
530 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
531 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
532 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
534 graphlayout(sp_desktop_selection(_dialog.getDesktop())->itemList());
536 // restore compensation setting
537 prefs->setInt("/options/clonecompensation/value", saved_compensation);
539 DocumentUndo::done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
540 _("Arrange connector network"));
541 }
542 };
544 class ActionExchangePositions : public Action {
545 public:
546 enum SortOrder {
547 None,
548 ZOrder,
549 Clockwise
550 };
552 ActionExchangePositions(Glib::ustring const &id,
553 Glib::ustring const &tiptext,
554 guint row,
555 guint column,
556 AlignAndDistribute &dialog, SortOrder order = None) :
557 Action(id, tiptext, row, column,
558 dialog.rearrange_table(), dialog.tooltips(), dialog),
559 sortOrder(order)
560 {};
563 private :
564 const SortOrder sortOrder;
565 static boost::optional<Geom::Point> center;
567 static bool sort_compare(const SPItem * a,const SPItem * b) {
568 if (a == NULL) return false;
569 if (b == NULL) return true;
570 if (center) {
571 Geom::Point point_a = a->getCenter() - (*center);
572 Geom::Point point_b = b->getCenter() - (*center);
573 // First criteria: Sort according to the angle to the center point
574 double angle_a = atan2(double(point_a[Geom::Y]), double(point_a[Geom::X]));
575 double angle_b = atan2(double(point_b[Geom::Y]), double(point_b[Geom::X]));
576 if (angle_a != angle_b) return (angle_a < angle_b);
577 // Second criteria: Sort according to the distance the center point
578 Geom::Coord length_a = point_a.length();
579 Geom::Coord length_b = point_b.length();
580 if (length_a != length_b) return (length_a > length_b);
581 }
582 // Last criteria: Sort according to the z-coordinate
583 return (a->isSiblingOf(b));
584 }
586 virtual void on_button_click()
587 {
588 SPDesktop *desktop = _dialog.getDesktop();
589 if (!desktop) return;
591 Inkscape::Selection *selection = sp_desktop_selection(desktop);
592 if (!selection) return;
594 using Inkscape::Util::GSListConstIterator;
595 std::list<SPItem *> selected;
596 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
597 if (selected.empty()) return;
599 //Check 2 or more selected objects
600 if (selected.size() < 2) return;
602 // see comment in ActionAlign above
603 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
604 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
605 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
607 // sort the list
608 if (sortOrder != None) {
609 if (sortOrder == Clockwise) {
610 center = selection->center();
611 } else { // sorting by ZOrder is outomatically done by not setting the center
612 center.reset();
613 }
614 selected.sort(ActionExchangePositions::sort_compare);
615 }
616 std::list<SPItem *>::iterator it(selected.begin());
617 Geom::Point p1 = (*it)->getCenter();
618 for (++it ;it != selected.end(); ++it)
619 {
620 Geom::Point p2 = (*it)->getCenter();
621 Geom::Point delta = p1 - p2;
622 sp_item_move_rel((*it),Geom::Translate(delta[Geom::X],delta[Geom::Y] ));
623 p1 = p2;
624 }
625 Geom::Point p2 = selected.front()->getCenter();
626 Geom::Point delta = p1 - p2;
627 sp_item_move_rel(selected.front(),Geom::Translate(delta[Geom::X],delta[Geom::Y] ));
629 // restore compensation setting
630 prefs->setInt("/options/clonecompensation/value", saved_compensation);
632 DocumentUndo::done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
633 _("Exchange Positions"));
634 }
635 };
637 // instantiae the private static member
638 boost::optional<Geom::Point> ActionExchangePositions::center;
640 class ActionUnclump : public Action {
641 public :
642 ActionUnclump(const Glib::ustring &id,
643 const Glib::ustring &tiptext,
644 guint row,
645 guint column,
646 AlignAndDistribute &dialog):
647 Action(id, tiptext, row, column,
648 dialog.rearrange_table(), dialog.tooltips(), dialog)
649 {}
651 private :
652 virtual void on_button_click()
653 {
654 if (!_dialog.getDesktop()) return;
656 // see comment in ActionAlign above
657 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
658 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
659 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
661 unclump ((GSList *) sp_desktop_selection(_dialog.getDesktop())->itemList());
663 // restore compensation setting
664 prefs->setInt("/options/clonecompensation/value", saved_compensation);
666 DocumentUndo::done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
667 _("Unclump"));
668 }
669 };
671 class ActionRandomize : public Action {
672 public :
673 ActionRandomize(const Glib::ustring &id,
674 const Glib::ustring &tiptext,
675 guint row,
676 guint column,
677 AlignAndDistribute &dialog):
678 Action(id, tiptext, row, column,
679 dialog.rearrange_table(), dialog.tooltips(), dialog)
680 {}
682 private :
683 virtual void on_button_click()
684 {
685 SPDesktop *desktop = _dialog.getDesktop();
686 if (!desktop) return;
688 Inkscape::Selection *selection = sp_desktop_selection(desktop);
689 if (!selection) return;
691 using Inkscape::Util::GSListConstIterator;
692 std::list<SPItem *> selected;
693 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
694 if (selected.empty()) return;
696 //Check 2 or more selected objects
697 if (selected.size() < 2) return;
699 Geom::OptRect sel_bbox = selection->bounds();
700 if (!sel_bbox) {
701 return;
702 }
704 // This bbox is cached between calls to randomize, so that there's no growth nor shrink
705 // nor drift on sequential randomizations. Discard cache on global (or better active
706 // desktop's) selection_change signal.
707 if (!_dialog.randomize_bbox) {
708 _dialog.randomize_bbox = *sel_bbox;
709 }
711 // see comment in ActionAlign above
712 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
713 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
714 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
716 for (std::list<SPItem *>::iterator it(selected.begin());
717 it != selected.end();
718 ++it)
719 {
720 sp_desktop_document (desktop)->ensureUpToDate();
721 Geom::OptRect item_box = (*it)->getBboxDesktop ();
722 if (item_box) {
723 // find new center, staying within bbox
724 double x = _dialog.randomize_bbox->min()[Geom::X] + (*item_box)[Geom::X].extent() /2 +
725 g_random_double_range (0, (*_dialog.randomize_bbox)[Geom::X].extent() - (*item_box)[Geom::X].extent());
726 double y = _dialog.randomize_bbox->min()[Geom::Y] + (*item_box)[Geom::Y].extent()/2 +
727 g_random_double_range (0, (*_dialog.randomize_bbox)[Geom::Y].extent() - (*item_box)[Geom::Y].extent());
728 // displacement is the new center minus old:
729 Geom::Point t = Geom::Point (x, y) - 0.5*(item_box->max() + item_box->min());
730 sp_item_move_rel(*it, Geom::Translate(t));
731 }
732 }
734 // restore compensation setting
735 prefs->setInt("/options/clonecompensation/value", saved_compensation);
737 DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
738 _("Randomize positions"));
739 }
740 };
742 struct Baselines
743 {
744 SPItem *_item;
745 Geom::Point _base;
746 Geom::Dim2 _orientation;
747 Baselines(SPItem *item, Geom::Point base, Geom::Dim2 orientation) :
748 _item (item),
749 _base (base),
750 _orientation (orientation)
751 {}
752 };
754 bool operator< (const Baselines &a, const Baselines &b)
755 {
756 return (a._base[a._orientation] < b._base[b._orientation]);
757 }
759 class ActionBaseline : public Action {
760 public :
761 ActionBaseline(const Glib::ustring &id,
762 const Glib::ustring &tiptext,
763 guint row,
764 guint column,
765 AlignAndDistribute &dialog,
766 Gtk::Table &table,
767 Geom::Dim2 orientation, bool distribute):
768 Action(id, tiptext, row, column,
769 table, dialog.tooltips(), dialog),
770 _orientation(orientation),
771 _distribute(distribute)
772 {}
774 private :
775 Geom::Dim2 _orientation;
776 bool _distribute;
777 virtual void on_button_click()
778 {
779 SPDesktop *desktop = _dialog.getDesktop();
780 if (!desktop) return;
782 Inkscape::Selection *selection = sp_desktop_selection(desktop);
783 if (!selection) return;
785 using Inkscape::Util::GSListConstIterator;
786 std::list<SPItem *> selected;
787 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
788 if (selected.empty()) return;
790 //Check 2 or more selected objects
791 if (selected.size() < 2) return;
793 Geom::Point b_min = Geom::Point (HUGE_VAL, HUGE_VAL);
794 Geom::Point b_max = Geom::Point (-HUGE_VAL, -HUGE_VAL);
796 std::vector<Baselines> sorted;
798 for (std::list<SPItem *>::iterator it(selected.begin());
799 it != selected.end();
800 ++it)
801 {
802 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
803 Inkscape::Text::Layout const *layout = te_get_layout(*it);
804 boost::optional<Geom::Point> pt = layout->baselineAnchorPoint();
805 if (pt) {
806 Geom::Point base = *pt * (*it)->i2d_affine();
807 if (base[Geom::X] < b_min[Geom::X]) b_min[Geom::X] = base[Geom::X];
808 if (base[Geom::Y] < b_min[Geom::Y]) b_min[Geom::Y] = base[Geom::Y];
809 if (base[Geom::X] > b_max[Geom::X]) b_max[Geom::X] = base[Geom::X];
810 if (base[Geom::Y] > b_max[Geom::Y]) b_max[Geom::Y] = base[Geom::Y];
811 Baselines b (*it, base, _orientation);
812 sorted.push_back(b);
813 }
814 }
815 }
817 if (sorted.size() <= 1) return;
819 //sort baselines
820 std::sort(sorted.begin(), sorted.end());
822 bool changed = false;
824 if (_distribute) {
825 double step = (b_max[_orientation] - b_min[_orientation])/(sorted.size() - 1);
826 for (unsigned int i = 0; i < sorted.size(); i++) {
827 SPItem *item = sorted[i]._item;
828 Geom::Point base = sorted[i]._base;
829 Geom::Point t(0.0, 0.0);
830 t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
831 sp_item_move_rel(item, Geom::Translate(t));
832 changed = true;
833 }
835 if (changed) {
836 DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
837 _("Distribute text baselines"));
838 }
840 } else {
841 for (std::list<SPItem *>::iterator it(selected.begin());
842 it != selected.end();
843 ++it)
844 {
845 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
846 Inkscape::Text::Layout const *layout = te_get_layout(*it);
847 boost::optional<Geom::Point> pt = layout->baselineAnchorPoint();
848 if (pt) {
849 Geom::Point base = *pt * (*it)->i2d_affine();
850 Geom::Point t(0.0, 0.0);
851 t[_orientation] = b_min[_orientation] - base[_orientation];
852 sp_item_move_rel(*it, Geom::Translate(t));
853 changed = true;
854 }
855 }
856 }
858 if (changed) {
859 DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
860 _("Align text baselines"));
861 }
862 }
863 }
864 };
868 void on_tool_changed(Inkscape::Application */*inkscape*/, SPEventContext */*context*/, AlignAndDistribute *daad)
869 {
870 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
871 if (desktop && sp_desktop_event_context(desktop))
872 daad->setMode(tools_active(desktop) == TOOLS_NODES);
873 }
875 void on_selection_changed(Inkscape::Application */*inkscape*/, Inkscape::Selection */*selection*/, AlignAndDistribute *daad)
876 {
877 daad->randomize_bbox = Geom::OptRect();
878 }
880 /////////////////////////////////////////////////////////
885 AlignAndDistribute::AlignAndDistribute()
886 : UI::Widget::Panel ("", "/dialogs/align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
887 randomize_bbox(),
888 _alignFrame(_("Align")),
889 _distributeFrame(_("Distribute")),
890 _rearrangeFrame(_("Rearrange")),
891 _removeOverlapFrame(_("Remove overlaps")),
892 _nodesFrame(_("Nodes")),
893 _alignTable(2, 6, true),
894 _distributeTable(2, 6, true),
895 _rearrangeTable(1, 5, false),
896 _removeOverlapTable(1, 5, false),
897 _nodesTable(1, 4, true),
898 _anchorLabel(_("Relative to: ")),
899 _selgrpLabel(_("Treat selection as group: "))
900 {
901 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
903 //Instanciate the align buttons
904 addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_RIGHT_TO_ANCHOR,
905 _("Align right edges of objects to the left edge of the anchor"),
906 0, 0);
907 addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_LEFT,
908 _("Align left edges"),
909 0, 1);
910 addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_CENTER,
911 _("Center on vertical axis"),
912 0, 2);
913 addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_RIGHT,
914 _("Align right sides"),
915 0, 3);
916 addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_LEFT_TO_ANCHOR,
917 _("Align left edges of objects to the right edge of the anchor"),
918 0, 4);
919 addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_BOTTOM_TO_ANCHOR,
920 _("Align bottom edges of objects to the top edge of the anchor"),
921 1, 0);
922 addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_TOP,
923 _("Align top edges"),
924 1, 1);
925 addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_CENTER,
926 _("Center on horizontal axis"),
927 1, 2);
928 addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_BOTTOM,
929 _("Align bottom edges"),
930 1, 3);
931 addAlignButton(INKSCAPE_ICON_ALIGN_VERTICAL_TOP_TO_ANCHOR,
932 _("Align top edges of objects to the bottom edge of the anchor"),
933 1, 4);
935 //Baseline aligns
936 addBaselineButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_BASELINE,
937 _("Align baseline anchors of texts horizontally"),
938 0, 5, this->align_table(), Geom::X, false);
939 addBaselineButton(INKSCAPE_ICON_ALIGN_VERTICAL_BASELINE,
940 _("Align baselines of texts"),
941 1, 5, this->align_table(), Geom::Y, false);
943 //The distribute buttons
944 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_GAPS,
945 _("Make horizontal gaps between objects equal"),
946 0, 4, true, Geom::X, .5, .5);
948 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_LEFT,
949 _("Distribute left edges equidistantly"),
950 0, 1, false, Geom::X, 1., 0.);
951 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_CENTER,
952 _("Distribute centers equidistantly horizontally"),
953 0, 2, false, Geom::X, .5, .5);
954 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_RIGHT,
955 _("Distribute right edges equidistantly"),
956 0, 3, false, Geom::X, 0., 1.);
958 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_GAPS,
959 _("Make vertical gaps between objects equal"),
960 1, 4, true, Geom::Y, .5, .5);
962 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_TOP,
963 _("Distribute top edges equidistantly"),
964 1, 1, false, Geom::Y, 0, 1);
965 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_CENTER,
966 _("Distribute centers equidistantly vertically"),
967 1, 2, false, Geom::Y, .5, .5);
968 addDistributeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_BOTTOM,
969 _("Distribute bottom edges equidistantly"),
970 1, 3, false, Geom::Y, 1., 0.);
972 //Baseline distribs
973 addBaselineButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_BASELINE,
974 _("Distribute baseline anchors of texts horizontally"),
975 0, 5, this->distribute_table(), Geom::X, true);
976 addBaselineButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_BASELINE,
977 _("Distribute baselines of texts vertically"),
978 1, 5, this->distribute_table(), Geom::Y, true);
980 // Rearrange
981 //Graph Layout
982 addGraphLayoutButton(INKSCAPE_ICON_DISTRIBUTE_GRAPH,
983 _("Nicely arrange selected connector network"),
984 0, 0);
985 addExchangePositionsButton(INKSCAPE_ICON_EXCHANGE_POSITIONS,
986 _("Exchange positions of selected objects - selection order"),
987 0, 1);
988 addExchangePositionsByZOrderButton(INKSCAPE_ICON_EXCHANGE_POSITIONS_ZORDER,
989 _("Exchange positions of selected objects - stacking order"),
990 0, 2);
991 addExchangePositionsClockwiseButton(INKSCAPE_ICON_EXCHANGE_POSITIONS_CLOCKWISE,
992 _("Exchange positions of selected objects - clockwise rotate"),
993 0, 3);
995 //Randomize & Unclump
996 addRandomizeButton(INKSCAPE_ICON_DISTRIBUTE_RANDOMIZE,
997 _("Randomize centers in both dimensions"),
998 0, 4);
999 addUnclumpButton(INKSCAPE_ICON_DISTRIBUTE_UNCLUMP,
1000 _("Unclump objects: try to equalize edge-to-edge distances"),
1001 0, 5);
1003 //Remove overlaps
1004 addRemoveOverlapsButton(INKSCAPE_ICON_DISTRIBUTE_REMOVE_OVERLAPS,
1005 _("Move objects as little as possible so that their bounding boxes do not overlap"),
1006 0, 0);
1008 //Node Mode buttons
1009 // NOTE: "align nodes vertically" means "move nodes vertically until they align on a common
1010 // _horizontal_ line". This is analogous to what the "align-vertical-center" icon means.
1011 // There is no doubt some ambiguity. For this reason the descriptions are different.
1012 addNodeButton(INKSCAPE_ICON_ALIGN_VERTICAL_NODES,
1013 _("Align selected nodes to a common horizontal line"),
1014 0, Geom::X, false);
1015 addNodeButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_NODES,
1016 _("Align selected nodes to a common vertical line"),
1017 1, Geom::Y, false);
1018 addNodeButton(INKSCAPE_ICON_DISTRIBUTE_HORIZONTAL_NODE,
1019 _("Distribute selected nodes horizontally"),
1020 2, Geom::X, true);
1021 addNodeButton(INKSCAPE_ICON_DISTRIBUTE_VERTICAL_NODE,
1022 _("Distribute selected nodes vertically"),
1023 3, Geom::Y, true);
1025 //Rest of the widgetry
1027 _combo.append_text(_("Last selected"));
1028 _combo.append_text(_("First selected"));
1029 _combo.append_text(_("Biggest object"));
1030 _combo.append_text(_("Smallest object"));
1031 _combo.append_text(_("Page"));
1032 _combo.append_text(_("Drawing"));
1033 _combo.append_text(_("Selection"));
1035 _combo.set_active(prefs->getInt("/dialogs/align/align-to", 6));
1036 _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
1038 _anchorBox.pack_start(_anchorLabel);
1039 _anchorBox.pack_start(_combo);
1041 _selgrpBox.pack_start(_selgrpLabel);
1042 _selgrpBox.pack_start(_selgrp);
1043 _selgrp.set_active(prefs->getBool("/dialogs/align/sel-as-groups"));
1044 _selgrp.signal_toggled().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_selgrp_toggled));
1046 _alignBox.pack_start(_anchorBox);
1047 _alignBox.pack_start(_selgrpBox);
1048 _alignBox.pack_start(_alignTable);
1050 _alignFrame.add(_alignBox);
1051 _distributeFrame.add(_distributeTable);
1052 _rearrangeFrame.add(_rearrangeTable);
1053 _removeOverlapFrame.add(_removeOverlapTable);
1054 _nodesFrame.add(_nodesTable);
1056 Gtk::Box *contents = _getContents();
1057 contents->set_spacing(4);
1059 // Notebook for individual transformations
1061 contents->pack_start(_alignFrame, true, true);
1062 contents->pack_start(_distributeFrame, true, true);
1063 contents->pack_start(_rearrangeFrame, true, true);
1064 contents->pack_start(_removeOverlapFrame, true, true);
1065 contents->pack_start(_nodesFrame, true, true);
1067 //Connect to the global tool change signal
1068 g_signal_connect (G_OBJECT (INKSCAPE), "set_eventcontext", G_CALLBACK (on_tool_changed), this);
1070 // Connect to the global selection change, to invalidate cached randomize_bbox
1071 g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
1072 randomize_bbox = Geom::OptRect();
1074 show_all_children();
1076 on_tool_changed (NULL, NULL, this); // set current mode
1077 }
1079 AlignAndDistribute::~AlignAndDistribute()
1080 {
1081 sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
1083 for (std::list<Action *>::iterator it = _actionList.begin();
1084 it != _actionList.end();
1085 it ++)
1086 delete *it;
1087 }
1089 void AlignAndDistribute::on_ref_change(){
1090 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1091 prefs->setInt("/dialogs/align/align-to", _combo.get_active_row_number());
1093 //Make blink the master
1094 }
1096 void AlignAndDistribute::on_selgrp_toggled(){
1097 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1098 prefs->setInt("/dialogs/align/sel-as-groups", _selgrp.get_active());
1100 //Make blink the master
1101 }
1106 void AlignAndDistribute::setMode(bool nodeEdit)
1107 {
1108 //Act on widgets used in node mode
1109 void ( Gtk::Widget::*mNode) () = nodeEdit ?
1110 &Gtk::Widget::show_all : &Gtk::Widget::hide_all;
1112 //Act on widgets used in selection mode
1113 void ( Gtk::Widget::*mSel) () = nodeEdit ?
1114 &Gtk::Widget::hide_all : &Gtk::Widget::show_all;
1117 ((_alignFrame).*(mSel))();
1118 ((_distributeFrame).*(mSel))();
1119 ((_rearrangeFrame).*(mSel))();
1120 ((_removeOverlapFrame).*(mSel))();
1121 ((_nodesFrame).*(mNode))();
1123 }
1124 void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
1125 guint row, guint col)
1126 {
1127 _actionList.push_back(
1128 new ActionAlign(
1129 id, tiptext, row, col,
1130 *this , col + row * 5));
1131 }
1132 void AlignAndDistribute::addDistributeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1133 guint row, guint col, bool onInterSpace,
1134 Geom::Dim2 orientation, float kBegin, float kEnd)
1135 {
1136 _actionList.push_back(
1137 new ActionDistribute(
1138 id, tiptext, row, col, *this ,
1139 onInterSpace, orientation,
1140 kBegin, kEnd
1141 )
1142 );
1143 }
1145 void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1146 guint col, Geom::Dim2 orientation, bool distribute)
1147 {
1148 _actionList.push_back(
1149 new ActionNode(
1150 id, tiptext, col,
1151 *this, orientation, distribute));
1152 }
1154 void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
1155 guint row, guint col)
1156 {
1157 _actionList.push_back(
1158 new ActionRemoveOverlaps(
1159 id, tiptext, row, col, *this)
1160 );
1161 }
1163 void AlignAndDistribute::addGraphLayoutButton(const Glib::ustring &id, const Glib::ustring tiptext,
1164 guint row, guint col)
1165 {
1166 _actionList.push_back(
1167 new ActionGraphLayout(
1168 id, tiptext, row, col, *this)
1169 );
1170 }
1172 void AlignAndDistribute::addExchangePositionsButton(const Glib::ustring &id, const Glib::ustring tiptext,
1173 guint row, guint col)
1174 {
1175 _actionList.push_back(
1176 new ActionExchangePositions(
1177 id, tiptext, row, col, *this)
1178 );
1179 }
1181 void AlignAndDistribute::addExchangePositionsByZOrderButton(const Glib::ustring &id, const Glib::ustring tiptext,
1182 guint row, guint col)
1183 {
1184 _actionList.push_back(
1185 new ActionExchangePositions(
1186 id, tiptext, row, col, *this, ActionExchangePositions::ZOrder)
1187 );
1188 }
1190 void AlignAndDistribute::addExchangePositionsClockwiseButton(const Glib::ustring &id, const Glib::ustring tiptext,
1191 guint row, guint col)
1192 {
1193 _actionList.push_back(
1194 new ActionExchangePositions(
1195 id, tiptext, row, col, *this, ActionExchangePositions::Clockwise)
1196 );
1197 }
1199 void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
1200 guint row, guint col)
1201 {
1202 _actionList.push_back(
1203 new ActionUnclump(
1204 id, tiptext, row, col, *this)
1205 );
1206 }
1208 void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1209 guint row, guint col)
1210 {
1211 _actionList.push_back(
1212 new ActionRandomize(
1213 id, tiptext, row, col, *this)
1214 );
1215 }
1217 void AlignAndDistribute::addBaselineButton(const Glib::ustring &id, const Glib::ustring tiptext,
1218 guint row, guint col, Gtk::Table &table, Geom::Dim2 orientation, bool distribute)
1219 {
1220 _actionList.push_back(
1221 new ActionBaseline(
1222 id, tiptext, row, col,
1223 *this, table, orientation, distribute));
1224 }
1229 std::list<SPItem *>::iterator AlignAndDistribute::find_master( std::list<SPItem *> &list, bool horizontal){
1230 std::list<SPItem *>::iterator master = list.end();
1231 switch (getAlignTarget()) {
1232 case LAST:
1233 return list.begin();
1234 break;
1236 case FIRST:
1237 return --(list.end());
1238 break;
1240 case BIGGEST:
1241 {
1242 gdouble max = -1e18;
1243 for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1244 Geom::OptRect b = (*it)->getBboxDesktop ();
1245 if (b) {
1246 gdouble dim = (*b)[horizontal ? Geom::X : Geom::Y].extent();
1247 if (dim > max) {
1248 max = dim;
1249 master = it;
1250 }
1251 }
1252 }
1253 return master;
1254 break;
1255 }
1257 case SMALLEST:
1258 {
1259 gdouble max = 1e18;
1260 for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1261 Geom::OptRect b = (*it)->getBboxDesktop ();
1262 if (b) {
1263 gdouble dim = (*b)[horizontal ? Geom::X : Geom::Y].extent();
1264 if (dim < max) {
1265 max = dim;
1266 master = it;
1267 }
1268 }
1269 }
1270 return master;
1271 break;
1272 }
1274 default:
1275 g_assert_not_reached ();
1276 break;
1278 } // end of switch statement
1279 return master;
1280 }
1282 AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
1283 return AlignTarget(_combo.get_active_row_number());
1284 }
1288 } // namespace Dialog
1289 } // namespace UI
1290 } // namespace Inkscape
1292 /*
1293 Local Variables:
1294 mode:c++
1295 c-file-style:"stroustrup"
1296 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1297 indent-tabs-mode:nil
1298 fill-column:99
1299 End:
1300 */
1301 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :