b8f840520ee880420d960f9ee65ba14bd4107bff
1 /**
2 * \brief Align and Distribute dialog
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 "verbs.h"
23 #include "dialogs/unclump.h"
24 #include "removeoverlap/removeoverlap.h"
25 #include "graphlayout/graphlayout.h"
27 #include <gtkmm/spinbutton.h>
32 #include "util/glib-list-iterators.h"
34 #include "widgets/icon.h"
36 #include "inkscape.h"
37 #include "document.h"
38 #include "selection.h"
39 #include "desktop-handles.h"
40 #include "macros.h"
41 #include "sp-item-transform.h"
42 #include "prefs-utils.h"
43 #include "enums.h"
45 #include "sp-text.h"
46 #include "sp-flowtext.h"
47 #include "text-editing.h"
49 #include "node-context.h" //For access to ShapeEditor
50 #include "shape-editor.h" //For node align/distribute methods
52 #include "tools-switch.h"
54 #include "align-and-distribute.h"
56 namespace Inkscape {
57 namespace UI {
58 namespace Dialog {
60 /////////helper classes//////////////////////////////////
62 class Action {
63 public :
64 Action(const Glib::ustring &id,
65 const Glib::ustring &tiptext,
66 guint row, guint column,
67 Gtk::Table &parent,
68 Gtk::Tooltips &tooltips,
69 AlignAndDistribute &dialog):
70 _dialog(dialog),
71 _id(id),
72 _parent(parent)
73 {
74 Gtk::Widget* pIcon = Gtk::manage( sp_icon_get_icon( _id, Inkscape::ICON_SIZE_LARGE_TOOLBAR) );
75 Gtk::Button * pButton = Gtk::manage(new Gtk::Button());
76 pButton->set_relief(Gtk::RELIEF_NONE);
77 pIcon->show();
78 pButton->add(*pIcon);
79 pButton->show();
81 pButton->signal_clicked()
82 .connect(sigc::mem_fun(*this, &Action::on_button_click));
83 tooltips.set_tip(*pButton, tiptext);
84 parent.attach(*pButton, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
85 }
86 virtual ~Action(){}
88 AlignAndDistribute &_dialog;
90 private :
91 virtual void on_button_click(){}
93 Glib::ustring _id;
94 Gtk::Table &_parent;
95 };
98 class ActionAlign : public Action {
99 public :
100 struct Coeffs {
101 double mx0, mx1, my0, my1;
102 double sx0, sx1, sy0, sy1;
103 };
104 ActionAlign(const Glib::ustring &id,
105 const Glib::ustring &tiptext,
106 guint row, guint column,
107 AlignAndDistribute &dialog,
108 guint coeffIndex):
109 Action(id, tiptext, row, column,
110 dialog.align_table(), dialog.tooltips(), dialog),
111 _index(coeffIndex),
112 _dialog(dialog)
113 {}
115 private :
117 virtual void on_button_click() {
118 //Retreive selected objects
119 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
120 if (!desktop) return;
122 Inkscape::Selection *selection = sp_desktop_selection(desktop);
123 if (!selection) return;
125 using Inkscape::Util::GSListConstIterator;
126 std::list<SPItem *> selected;
127 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
128 if (selected.empty()) return;
130 NR::Point mp; //Anchor point
131 AlignAndDistribute::AlignTarget target = _dialog.getAlignTarget();
132 const Coeffs &a= _allCoeffs[_index];
133 switch (target)
134 {
135 case AlignAndDistribute::LAST:
136 case AlignAndDistribute::FIRST:
137 case AlignAndDistribute::BIGGEST:
138 case AlignAndDistribute::SMALLEST:
139 {
140 //Check 2 or more selected objects
141 std::list<SPItem *>::iterator second(selected.begin());
142 ++second;
143 if (second == selected.end())
144 return;
145 //Find the master (anchor on which the other objects are aligned)
146 std::list<SPItem *>::iterator master(
147 _dialog.find_master (
148 selected,
149 (a.mx0 != 0.0) ||
150 (a.mx1 != 0.0) )
151 );
152 //remove the master from the selection
153 SPItem * thing = *master;
154 selected.erase(master);
155 //Compute the anchor point
156 NR::Rect b = sp_item_bbox_desktop (thing);
157 mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
158 a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
159 break;
160 }
162 case AlignAndDistribute::PAGE:
163 mp = NR::Point(a.mx1 * sp_document_width(sp_desktop_document(desktop)),
164 a.my1 * sp_document_height(sp_desktop_document(desktop)));
165 break;
167 case AlignAndDistribute::DRAWING:
168 {
169 NR::Rect b = sp_item_bbox_desktop
170 ( (SPItem *) sp_document_root (sp_desktop_document (desktop)) );
171 mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
172 a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
173 break;
174 }
176 case AlignAndDistribute::SELECTION:
177 {
178 NR::Rect b = selection->bounds();
179 mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
180 a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
181 break;
182 }
184 default:
185 g_assert_not_reached ();
186 break;
187 }; // end of switch
189 // Top hack: temporarily set clone compensation to unmoved, so that we can align/distribute
190 // clones with their original (and the move of the original does not disturb the
191 // clones). The only problem with this is that if there are outside-of-selection clones of
192 // a selected original, they will be unmoved too, possibly contrary to user's
193 // expecation. However this is a minor point compared to making align/distribute always
194 // work as expected, and "unmoved" is the default option anyway.
195 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
196 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
198 bool changed = false;
199 //Move each item in the selected list
200 for (std::list<SPItem *>::iterator it(selected.begin());
201 it != selected.end();
202 it++)
203 {
204 sp_document_ensure_up_to_date(sp_desktop_document (desktop));
205 NR::Rect b = sp_item_bbox_desktop (*it);
206 NR::Point const sp(a.sx0 * b.min()[NR::X] + a.sx1 * b.max()[NR::X],
207 a.sy0 * b.min()[NR::Y] + a.sy1 * b.max()[NR::Y]);
208 NR::Point const mp_rel( mp - sp );
209 if (LInfty(mp_rel) > 1e-9) {
210 sp_item_move_rel(*it, NR::translate(mp_rel));
211 changed = true;
212 }
213 }
215 // restore compensation setting
216 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
218 if (changed) {
219 sp_document_done ( sp_desktop_document (desktop) , SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
220 _("Align"));
221 }
224 }
225 guint _index;
226 AlignAndDistribute &_dialog;
228 static const Coeffs _allCoeffs[10];
230 };
231 ActionAlign::Coeffs const ActionAlign::_allCoeffs[10] = {
232 {1., 0., 0., 0., 0., 1., 0., 0.},
233 {1., 0., 0., 0., 1., 0., 0., 0.},
234 {.5, .5, 0., 0., .5, .5, 0., 0.},
235 {0., 1., 0., 0., 0., 1., 0., 0.},
236 {0., 1., 0., 0., 1., 0., 0., 0.},
237 {0., 0., 0., 1., 0., 0., 1., 0.},
238 {0., 0., 0., 1., 0., 0., 0., 1.},
239 {0., 0., .5, .5, 0., 0., .5, .5},
240 {0., 0., 1., 0., 0., 0., 1., 0.},
241 {0., 0., 1., 0., 0., 0., 0., 1.}
242 };
244 struct BBoxSort
245 {
246 SPItem *item;
247 float anchor;
248 NR::Rect bbox;
249 BBoxSort(SPItem *pItem, NR::Dim2 orientation, double kBegin, double kEnd) :
250 item(pItem),
251 bbox (sp_item_bbox_desktop (pItem))
252 {
253 anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
254 }
255 BBoxSort(const BBoxSort &rhs):
256 //NOTE : this copy ctor is called O(sort) when sorting the vector
257 //this is bad. The vector should be a vector of pointers.
258 //But I'll wait the bohem GC before doing that
259 item(rhs.item), anchor(rhs.anchor), bbox(rhs.bbox) {
260 }
261 };
262 bool operator< (const BBoxSort &a, const BBoxSort &b)
263 {
264 return (a.anchor < b.anchor);
265 }
267 class ActionDistribute : public Action {
268 public :
269 ActionDistribute(const Glib::ustring &id,
270 const Glib::ustring &tiptext,
271 guint row, guint column,
272 AlignAndDistribute &dialog,
273 bool onInterSpace,
274 NR::Dim2 orientation,
275 double kBegin, double kEnd
276 ):
277 Action(id, tiptext, row, column,
278 dialog.distribute_table(), dialog.tooltips(), dialog),
279 _dialog(dialog),
280 _onInterSpace(onInterSpace),
281 _orientation(orientation),
282 _kBegin(kBegin),
283 _kEnd( kEnd)
284 {}
286 private :
287 virtual void on_button_click() {
288 //Retreive selected objects
289 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
290 if (!desktop) return;
292 Inkscape::Selection *selection = sp_desktop_selection(desktop);
293 if (!selection) return;
295 using Inkscape::Util::GSListConstIterator;
296 std::list<SPItem *> selected;
297 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
298 if (selected.empty()) return;
300 //Check 2 or more selected objects
301 std::list<SPItem *>::iterator second(selected.begin());
302 ++second;
303 if (second == selected.end()) return;
306 std::vector< BBoxSort > sorted;
307 for (std::list<SPItem *>::iterator it(selected.begin());
308 it != selected.end();
309 ++it)
310 {
311 BBoxSort b (*it, _orientation, _kBegin, _kEnd);
312 sorted.push_back(b);
313 }
314 //sort bbox by anchors
315 std::sort(sorted.begin(), sorted.end());
317 // see comment in ActionAlign above
318 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
319 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
321 unsigned int len = sorted.size();
322 bool changed = false;
323 if (_onInterSpace)
324 {
325 //overall bboxes span
326 float dist = (sorted.back().bbox.max()[_orientation] -
327 sorted.front().bbox.min()[_orientation]);
328 //space eaten by bboxes
329 float span = 0;
330 for (unsigned int i = 0; i < len; i++)
331 {
332 span += sorted[i].bbox.extent(_orientation);
333 }
334 //new distance between each bbox
335 float step = (dist - span) / (len - 1);
336 float pos = sorted.front().bbox.min()[_orientation];
337 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
338 it < sorted.end();
339 it ++ )
340 {
341 if (!NR_DF_TEST_CLOSE (pos, it->bbox.min()[_orientation], 1e-6)) {
342 NR::Point t(0.0, 0.0);
343 t[_orientation] = pos - it->bbox.min()[_orientation];
344 sp_item_move_rel(it->item, NR::translate(t));
345 changed = true;
346 }
347 pos += it->bbox.extent(_orientation);
348 pos += step;
349 }
350 }
351 else
352 {
353 //overall anchor span
354 float dist = sorted.back().anchor - sorted.front().anchor;
355 //distance between anchors
356 float step = dist / (len - 1);
358 for ( unsigned int i = 0; i < len ; i ++ )
359 {
360 BBoxSort & it(sorted[i]);
361 //new anchor position
362 float pos = sorted.front().anchor + i * step;
363 //Don't move if we are really close
364 if (!NR_DF_TEST_CLOSE (pos, it.anchor, 1e-6)) {
365 //Compute translation
366 NR::Point t(0.0, 0.0);
367 t[_orientation] = pos - it.anchor;
368 //translate
369 sp_item_move_rel(it.item, NR::translate(t));
370 changed = true;
371 }
372 }
373 }
375 // restore compensation setting
376 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
378 if (changed) {
379 sp_document_done ( sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
380 _("Distribute"));
381 }
382 }
383 guint _index;
384 AlignAndDistribute &_dialog;
385 bool _onInterSpace;
386 NR::Dim2 _orientation;
388 double _kBegin;
389 double _kEnd;
391 };
394 class ActionNode : public Action {
395 public :
396 ActionNode(const Glib::ustring &id,
397 const Glib::ustring &tiptext,
398 guint column,
399 AlignAndDistribute &dialog,
400 NR::Dim2 orientation, bool distribute):
401 Action(id, tiptext, 0, column,
402 dialog.nodes_table(), dialog.tooltips(), dialog),
403 _orientation(orientation),
404 _distribute(distribute)
405 {}
407 private :
408 NR::Dim2 _orientation;
409 bool _distribute;
410 virtual void on_button_click()
411 {
413 if (!SP_ACTIVE_DESKTOP) return;
414 SPEventContext *event_context = sp_desktop_event_context(SP_ACTIVE_DESKTOP);
415 if (!SP_IS_NODE_CONTEXT (event_context)) return ;
417 if (_distribute)
418 SP_NODE_CONTEXT (event_context)->shape_editor->distribute(_orientation);
419 else
420 SP_NODE_CONTEXT (event_context)->shape_editor->align(_orientation);
422 }
423 };
425 class ActionRemoveOverlaps : public Action {
426 private:
427 Gtk::Label removeOverlapXGapLabel;
428 Gtk::Label removeOverlapYGapLabel;
429 Gtk::SpinButton removeOverlapXGap;
430 Gtk::SpinButton removeOverlapYGap;
432 public:
433 ActionRemoveOverlaps(Glib::ustring const &id,
434 Glib::ustring const &tiptext,
435 guint row,
436 guint column,
437 AlignAndDistribute &dialog) :
438 Action(id, tiptext, row, column + 4,
439 dialog.removeOverlap_table(), dialog.tooltips(), dialog)
440 {
441 dialog.removeOverlap_table().set_col_spacings(3);
443 removeOverlapXGap.set_digits(1);
444 removeOverlapXGap.set_size_request(60, -1);
445 removeOverlapXGap.set_increments(1.0, 5.0);
446 removeOverlapXGap.set_range(-1000.0, 1000.0);
447 removeOverlapXGap.set_value(0);
448 dialog.tooltips().set_tip(removeOverlapXGap,
449 _("Minimum horizontal gap (in px units) between bounding boxes"));
450 /* TRANSLATORS: Horizontal gap */
451 removeOverlapXGapLabel.set_label(_("H:"));
453 removeOverlapYGap.set_digits(1);
454 removeOverlapYGap.set_size_request(60, -1);
455 removeOverlapYGap.set_increments(1.0, 5.0);
456 removeOverlapYGap.set_range(-1000.0, 1000.0);
457 removeOverlapYGap.set_value(0);
458 dialog.tooltips().set_tip(removeOverlapYGap,
459 _("Minimum vertical gap (in px units) between bounding boxes"));
460 /* TRANSLATORS: Vertical gap */
461 removeOverlapYGapLabel.set_label(_("V:"));
463 dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
464 dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
465 dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, column+3, row, row+1, Gtk::FILL, Gtk::FILL);
466 dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
468 }
470 private :
471 virtual void on_button_click()
472 {
473 if (!SP_ACTIVE_DESKTOP) return;
475 // see comment in ActionAlign above
476 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
477 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
479 // xGap and yGap are the minimum space required between bounding rectangles.
480 double const xGap = removeOverlapXGap.get_value();
481 double const yGap = removeOverlapYGap.get_value();
482 removeoverlap(sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList(),
483 xGap, yGap);
485 // restore compensation setting
486 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
488 sp_document_done(sp_desktop_document(SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
489 _("Remove overlaps"));
490 }
491 };
493 class ActionGraphLayout : public Action {
494 public:
495 ActionGraphLayout(Glib::ustring const &id,
496 Glib::ustring const &tiptext,
497 guint row,
498 guint column,
499 AlignAndDistribute &dialog) :
500 Action(id, tiptext, row, column + 4,
501 dialog.graphLayout_table(), dialog.tooltips(), dialog)
502 {}
504 private :
505 virtual void on_button_click()
506 {
507 if (!SP_ACTIVE_DESKTOP) return;
509 // see comment in ActionAlign above
510 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
511 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
513 graphlayout(sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList());
515 // restore compensation setting
516 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
518 sp_document_done(sp_desktop_document(SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
519 _("Arrange connector network"));
520 }
521 };
523 class ActionUnclump : public Action {
524 public :
525 ActionUnclump(const Glib::ustring &id,
526 const Glib::ustring &tiptext,
527 guint row,
528 guint column,
529 AlignAndDistribute &dialog):
530 Action(id, tiptext, row, column,
531 dialog.distribute_table(), dialog.tooltips(), dialog)
532 {}
534 private :
535 virtual void on_button_click()
536 {
537 if (!SP_ACTIVE_DESKTOP) return;
539 // see comment in ActionAlign above
540 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
541 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
543 unclump ((GSList *) sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList());
545 // restore compensation setting
546 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
548 sp_document_done (sp_desktop_document (SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
549 _("Unclump"));
550 }
551 };
553 class ActionRandomize : public Action {
554 public :
555 ActionRandomize(const Glib::ustring &id,
556 const Glib::ustring &tiptext,
557 guint row,
558 guint column,
559 AlignAndDistribute &dialog):
560 Action(id, tiptext, row, column,
561 dialog.distribute_table(), dialog.tooltips(), dialog)
562 {}
564 private :
565 virtual void on_button_click()
566 {
567 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
568 if (!desktop) return;
570 Inkscape::Selection *selection = sp_desktop_selection(desktop);
571 if (!selection) return;
573 using Inkscape::Util::GSListConstIterator;
574 std::list<SPItem *> selected;
575 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
576 if (selected.empty()) return;
578 //Check 2 or more selected objects
579 if (selected.size() < 2) return;
581 // This bbox is cached between calls to randomize, so that there's no growth nor shrink
582 // nor drift on sequential randomizations. Discard cache on global (or better active
583 // desktop's) selection_change signal.
584 if (!_dialog.randomize_bbox_set) {
585 _dialog.randomize_bbox = selection->bounds();
586 _dialog.randomize_bbox_set = true;
587 }
589 // see comment in ActionAlign above
590 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
591 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
593 for (std::list<SPItem *>::iterator it(selected.begin());
594 it != selected.end();
595 ++it)
596 {
597 sp_document_ensure_up_to_date(sp_desktop_document (desktop));
598 NR::Rect item_box = sp_item_bbox_desktop (*it);
599 // find new center, staying within bbox
600 double x = _dialog.randomize_bbox.min()[NR::X] + item_box.extent(NR::X)/2 +
601 g_random_double_range (0, _dialog.randomize_bbox.extent(NR::X) - item_box.extent(NR::X));
602 double y = _dialog.randomize_bbox.min()[NR::Y] + item_box.extent(NR::Y)/2 +
603 g_random_double_range (0, _dialog.randomize_bbox.extent(NR::Y) - item_box.extent(NR::Y));
604 // displacement is the new center minus old:
605 NR::Point t = NR::Point (x, y) - 0.5*(item_box.max() + item_box.min());
606 sp_item_move_rel(*it, NR::translate(t));
607 }
609 // restore compensation setting
610 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
612 sp_document_done (sp_desktop_document (SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
613 _("Randomize positions"));
614 }
615 };
617 struct Baselines
618 {
619 SPItem *_item;
620 NR::Point _base;
621 NR::Dim2 _orientation;
622 Baselines(SPItem *item, NR::Point base, NR::Dim2 orientation) :
623 _item (item),
624 _base (base),
625 _orientation (orientation)
626 {}
627 };
629 bool operator< (const Baselines &a, const Baselines &b)
630 {
631 return (a._base[a._orientation] < b._base[b._orientation]);
632 }
634 class ActionBaseline : public Action {
635 public :
636 ActionBaseline(const Glib::ustring &id,
637 const Glib::ustring &tiptext,
638 guint row,
639 guint column,
640 AlignAndDistribute &dialog,
641 Gtk::Table &table,
642 NR::Dim2 orientation, bool distribute):
643 Action(id, tiptext, row, column,
644 table, dialog.tooltips(), dialog),
645 _orientation(orientation),
646 _distribute(distribute)
647 {}
649 private :
650 NR::Dim2 _orientation;
651 bool _distribute;
652 virtual void on_button_click()
653 {
654 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
655 if (!desktop) return;
657 Inkscape::Selection *selection = sp_desktop_selection(desktop);
658 if (!selection) return;
660 using Inkscape::Util::GSListConstIterator;
661 std::list<SPItem *> selected;
662 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
663 if (selected.empty()) return;
665 //Check 2 or more selected objects
666 if (selected.size() < 2) return;
668 NR::Point b_min = NR::Point (HUGE_VAL, HUGE_VAL);
669 NR::Point b_max = NR::Point (-HUGE_VAL, -HUGE_VAL);
671 std::vector<Baselines> sorted;
673 for (std::list<SPItem *>::iterator it(selected.begin());
674 it != selected.end();
675 ++it)
676 {
677 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
678 Inkscape::Text::Layout const *layout = te_get_layout(*it);
679 NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
680 if (base[NR::X] < b_min[NR::X]) b_min[NR::X] = base[NR::X];
681 if (base[NR::Y] < b_min[NR::Y]) b_min[NR::Y] = base[NR::Y];
682 if (base[NR::X] > b_max[NR::X]) b_max[NR::X] = base[NR::X];
683 if (base[NR::Y] > b_max[NR::Y]) b_max[NR::Y] = base[NR::Y];
685 Baselines b (*it, base, _orientation);
686 sorted.push_back(b);
687 }
688 }
690 if (sorted.size() <= 1) return;
692 //sort baselines
693 std::sort(sorted.begin(), sorted.end());
695 bool changed = false;
697 if (_distribute) {
698 double step = (b_max[_orientation] - b_min[_orientation])/(sorted.size() - 1);
699 for (unsigned int i = 0; i < sorted.size(); i++) {
700 SPItem *item = sorted[i]._item;
701 NR::Point base = sorted[i]._base;
702 NR::Point t(0.0, 0.0);
703 t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
704 sp_item_move_rel(item, NR::translate(t));
705 changed = true;
706 }
708 if (changed) {
709 sp_document_done (sp_desktop_document (SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
710 _("Distribute text baselines"));
711 }
713 } else {
714 for (std::list<SPItem *>::iterator it(selected.begin());
715 it != selected.end();
716 ++it)
717 {
718 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
719 Inkscape::Text::Layout const *layout = te_get_layout(*it);
720 NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
721 NR::Point t(0.0, 0.0);
722 t[_orientation] = b_min[_orientation] - base[_orientation];
723 sp_item_move_rel(*it, NR::translate(t));
724 changed = true;
725 }
726 }
728 if (changed) {
729 sp_document_done (sp_desktop_document (SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
730 _("Align text baselines"));
731 }
732 }
733 }
734 };
738 void on_tool_changed(Inkscape::Application *inkscape, SPEventContext *context, AlignAndDistribute *daad)
739 {
740 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
741 if (desktop && sp_desktop_event_context(desktop))
742 daad->setMode(tools_active(desktop) == TOOLS_NODES);
743 }
745 void on_selection_changed(Inkscape::Application *inkscape, Inkscape::Selection *selection, AlignAndDistribute *daad)
746 {
747 daad->randomize_bbox_set = false;
748 }
750 /////////////////////////////////////////////////////////
755 AlignAndDistribute::AlignAndDistribute()
756 : Dialog ("dialogs.align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
757 randomize_bbox (NR::Point (0, 0), NR::Point (0, 0)),
758 _alignFrame(_("Align")),
759 _distributeFrame(_("Distribute")),
760 _removeOverlapFrame(_("Remove overlaps")),
761 _graphLayoutFrame(_("Connector network layout")),
762 _nodesFrame(_("Nodes")),
763 _alignTable(2, 6, true),
764 _distributeTable(3, 6, true),
765 _removeOverlapTable(1, 5, false),
766 _graphLayoutTable(1, 5, false),
767 _nodesTable(1, 4, true),
768 _anchorLabel(_("Relative to: "))
769 {
771 //Instanciate the align buttons
772 addAlignButton("al_left_out",
773 _("Align right sides of objects to left side of anchor"),
774 0, 0);
775 addAlignButton("al_left_in",
776 _("Align left sides"),
777 0, 1);
778 addAlignButton("al_center_hor",
779 _("Center on vertical axis"),
780 0, 2);
781 addAlignButton("al_right_in",
782 _("Align right sides"),
783 0, 3);
784 addAlignButton("al_right_out",
785 _("Align left sides of objects to right side of anchor"),
786 0, 4);
787 addAlignButton("al_top_out",
788 _("Align bottoms of objects to top of anchor"),
789 1, 0);
790 addAlignButton("al_top_in",
791 _("Align tops"),
792 1, 1);
793 addAlignButton("al_center_ver",
794 _("Center on horizontal axis"),
795 1, 2);
796 addAlignButton("al_bottom_in",
797 _("Align bottoms"),
798 1, 3);
799 addAlignButton("al_bottom_out",
800 _("Align tops of objects to bottom of anchor"),
801 1, 4);
803 //Baseline aligns
804 addBaselineButton("al_baselines_vert",
805 _("Align baseline anchors of texts vertically"),
806 0, 5, this->align_table(), NR::X, false);
807 addBaselineButton("al_baselines_hor",
808 _("Align baseline anchors of texts horizontally"),
809 1, 5, this->align_table(), NR::Y, false);
811 //The distribute buttons
812 addDistributeButton("distribute_hdist",
813 _("Make horizontal gaps between objects equal"),
814 0, 4, true, NR::X, .5, .5);
816 addDistributeButton("distribute_left",
817 _("Distribute left sides equidistantly"),
818 0, 1, false, NR::X, 1., 0.);
819 addDistributeButton("distribute_hcentre",
820 _("Distribute centers equidistantly horizontally"),
821 0, 2, false, NR::X, .5, .5);
822 addDistributeButton("distribute_right",
823 _("Distribute right sides equidistantly"),
824 0, 3, false, NR::X, 0., 1.);
826 addDistributeButton("distribute_vdist",
827 _("Make vertical gaps between objects equal"),
828 1, 4, true, NR::Y, .5, .5);
830 addDistributeButton("distribute_top",
831 _("Distribute tops equidistantly"),
832 1, 1, false, NR::Y, 0, 1);
833 addDistributeButton("distribute_vcentre",
834 _("Distribute centers equidistantly vertically"),
835 1, 2, false, NR::Y, .5, .5);
836 addDistributeButton("distribute_bottom",
837 _("Distribute bottoms equidistantly"),
838 1, 3, false, NR::Y, 1., 0.);
840 //Baseline distribs
841 addBaselineButton("distribute_baselines_hor",
842 _("Distribute baseline anchors of texts horizontally"),
843 0, 5, this->distribute_table(), NR::X, true);
844 addBaselineButton("distribute_baselines_vert",
845 _("Distribute baseline anchors of texts vertically"),
846 1, 5, this->distribute_table(), NR::Y, true);
848 //Randomize & Unclump
849 addRandomizeButton("distribute_randomize",
850 _("Randomize centers in both dimensions"),
851 2, 2);
852 addUnclumpButton("unclump",
853 _("Unclump objects: try to equalize edge-to-edge distances"),
854 2, 4);
856 //Remove overlaps
857 addRemoveOverlapsButton("remove_overlaps",
858 _("Move objects as little as possible so that their bounding boxes do not overlap"),
859 0, 0);
860 //Graph Layout
861 addGraphLayoutButton("graph_layout",
862 _("Nicely arrange selected connector network"),
863 0, 0);
865 //Node Mode buttons
866 addNodeButton("node_halign",
867 _("Align selected nodes horizontally"),
868 0, NR::X, false);
869 addNodeButton("node_valign",
870 _("Align selected nodes vertically"),
871 1, NR::Y, false);
872 addNodeButton("node_hdistribute",
873 _("Distribute selected nodes horizontally"),
874 2, NR::X, true);
875 addNodeButton("node_vdistribute",
876 _("Distribute selected nodes vertically"),
877 3, NR::Y, true);
879 //Rest of the widgetry
881 _combo.append_text(_("Last selected"));
882 _combo.append_text(_("First selected"));
883 _combo.append_text(_("Biggest item"));
884 _combo.append_text(_("Smallest item"));
885 _combo.append_text(_("Page"));
886 _combo.append_text(_("Drawing"));
887 _combo.append_text(_("Selection"));
889 _combo.set_active(6);
890 _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
892 _anchorBox.pack_start(_anchorLabel);
893 _anchorBox.pack_start(_combo);
895 _alignBox.pack_start(_anchorBox);
896 _alignBox.pack_start(_alignTable);
898 _alignFrame.add(_alignBox);
899 _distributeFrame.add(_distributeTable);
900 _removeOverlapFrame.add(_removeOverlapTable);
901 _graphLayoutFrame.add(_graphLayoutTable);
902 _nodesFrame.add(_nodesTable);
904 // Top level vbox
905 Gtk::VBox *vbox = get_vbox();
906 vbox->set_spacing(4);
908 // Notebook for individual transformations
910 vbox->pack_start(_alignFrame, true, true);
911 vbox->pack_start(_distributeFrame, true, true);
912 vbox->pack_start(_removeOverlapFrame, true, true);
913 vbox->pack_start(_graphLayoutFrame, true, true);
914 vbox->pack_start(_nodesFrame, true, true);
916 //Connect to the global tool change signal
917 g_signal_connect (G_OBJECT (INKSCAPE), "set_eventcontext", G_CALLBACK (on_tool_changed), this);
919 // Connect to the global selection change, to invalidate cached randomize_bbox
920 g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
921 randomize_bbox = NR::Rect (NR::Point (0, 0), NR::Point (0, 0));
922 randomize_bbox_set = false;
924 show_all_children();
926 on_tool_changed (NULL, NULL, this); // set current mode
927 }
929 AlignAndDistribute::~AlignAndDistribute()
930 {
931 sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
933 for (std::list<Action *>::iterator it = _actionList.begin();
934 it != _actionList.end();
935 it ++)
936 delete *it;
937 }
939 void AlignAndDistribute::on_ref_change(){
940 //Make blink the master
941 }
946 void AlignAndDistribute::setMode(bool nodeEdit)
947 {
948 //Act on widgets used in node mode
949 void ( Gtk::Widget::*mNode) () = nodeEdit ?
950 &Gtk::Widget::show_all : &Gtk::Widget::hide_all;
952 //Act on widgets used in selection mode
953 void ( Gtk::Widget::*mSel) () = nodeEdit ?
954 &Gtk::Widget::hide_all : &Gtk::Widget::show_all;
957 ((_alignFrame).*(mSel))();
958 ((_distributeFrame).*(mSel))();
959 ((_removeOverlapFrame).*(mSel))();
960 ((_graphLayoutFrame).*(mSel))();
961 ((_nodesFrame).*(mNode))();
963 }
964 void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
965 guint row, guint col)
966 {
967 _actionList.push_back(
968 new ActionAlign(
969 id, tiptext, row, col,
970 *this , col + row * 5));
971 }
972 void AlignAndDistribute::addDistributeButton(const Glib::ustring &id, const Glib::ustring tiptext,
973 guint row, guint col, bool onInterSpace,
974 NR::Dim2 orientation, float kBegin, float kEnd)
975 {
976 _actionList.push_back(
977 new ActionDistribute(
978 id, tiptext, row, col, *this ,
979 onInterSpace, orientation,
980 kBegin, kEnd
981 )
982 );
983 }
985 void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
986 guint col, NR::Dim2 orientation, bool distribute)
987 {
988 _actionList.push_back(
989 new ActionNode(
990 id, tiptext, col,
991 *this, orientation, distribute));
992 }
994 void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
995 guint row, guint col)
996 {
997 _actionList.push_back(
998 new ActionRemoveOverlaps(
999 id, tiptext, row, col, *this)
1000 );
1001 }
1003 void AlignAndDistribute::addGraphLayoutButton(const Glib::ustring &id, const Glib::ustring tiptext,
1004 guint row, guint col)
1005 {
1006 _actionList.push_back(
1007 new ActionGraphLayout(
1008 id, tiptext, row, col, *this)
1009 );
1010 }
1012 void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
1013 guint row, guint col)
1014 {
1015 _actionList.push_back(
1016 new ActionUnclump(
1017 id, tiptext, row, col, *this)
1018 );
1019 }
1021 void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1022 guint row, guint col)
1023 {
1024 _actionList.push_back(
1025 new ActionRandomize(
1026 id, tiptext, row, col, *this)
1027 );
1028 }
1030 void AlignAndDistribute::addBaselineButton(const Glib::ustring &id, const Glib::ustring tiptext,
1031 guint row, guint col, Gtk::Table &table, NR::Dim2 orientation, bool distribute)
1032 {
1033 _actionList.push_back(
1034 new ActionBaseline(
1035 id, tiptext, row, col,
1036 *this, table, orientation, distribute));
1037 }
1042 std::list<SPItem *>::iterator AlignAndDistribute::find_master( std::list<SPItem *> &list, bool horizontal){
1043 std::list<SPItem *>::iterator master = list.end();
1044 switch (getAlignTarget()) {
1045 case LAST:
1046 return list.begin();
1047 break;
1049 case FIRST:
1050 return --(list.end());
1051 break;
1053 case BIGGEST:
1054 {
1055 gdouble max = -1e18;
1056 for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1057 NR::Rect b = sp_item_bbox_desktop (*it);
1058 gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
1059 if (dim > max) {
1060 max = dim;
1061 master = it;
1062 }
1063 }
1064 return master;
1065 break;
1066 }
1068 case SMALLEST:
1069 {
1070 gdouble max = 1e18;
1071 for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1072 NR::Rect b = sp_item_bbox_desktop (*it);
1073 gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
1074 if (dim < max) {
1075 max = dim;
1076 master = it;
1077 }
1078 }
1079 return master;
1080 break;
1081 }
1083 default:
1084 g_assert_not_reached ();
1085 break;
1087 } // end of switch statement
1088 return master;
1089 }
1091 AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
1092 return AlignTarget(_combo.get_active_row_number());
1093 }
1097 } // namespace Dialog
1098 } // namespace UI
1099 } // namespace Inkscape
1101 /*
1102 Local Variables:
1103 mode:c++
1104 c-file-style:"stroustrup"
1105 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1106 indent-tabs-mode:nil
1107 fill-column:99
1108 End:
1109 */
1110 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :