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 node align/distribute function
51 #include "tools-switch.h"
53 #include "align-and-distribute.h"
55 namespace Inkscape {
56 namespace UI {
57 namespace Dialog {
59 /////////helper classes//////////////////////////////////
61 class Action {
62 public :
63 Action(const Glib::ustring &id,
64 const Glib::ustring &tiptext,
65 guint row, guint column,
66 Gtk::Table &parent,
67 Gtk::Tooltips &tooltips,
68 AlignAndDistribute &dialog):
69 _dialog(dialog),
70 _id(id),
71 _parent(parent)
72 {
73 Gtk::Widget* pIcon = Gtk::manage( sp_icon_get_icon( _id, GTK_ICON_SIZE_LARGE_TOOLBAR) );
74 Gtk::Button * pButton = Gtk::manage(new Gtk::Button());
75 pButton->set_relief(Gtk::RELIEF_NONE);
76 pIcon->show();
77 pButton->add(*pIcon);
78 pButton->show();
80 pButton->signal_clicked()
81 .connect(sigc::mem_fun(*this, &Action::on_button_click));
82 tooltips.set_tip(*pButton, tiptext);
83 parent.attach(*pButton, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
84 }
85 virtual ~Action(){}
87 AlignAndDistribute &_dialog;
89 private :
90 virtual void on_button_click(){}
92 Glib::ustring _id;
93 Gtk::Table &_parent;
94 };
97 class ActionAlign : public Action {
98 public :
99 struct Coeffs {
100 double mx0, mx1, my0, my1;
101 double sx0, sx1, sy0, sy1;
102 };
103 ActionAlign(const Glib::ustring &id,
104 const Glib::ustring &tiptext,
105 guint row, guint column,
106 AlignAndDistribute &dialog,
107 guint coeffIndex):
108 Action(id, tiptext, row, column,
109 dialog.align_table(), dialog.tooltips(), dialog),
110 _index(coeffIndex),
111 _dialog(dialog)
112 {}
114 private :
116 virtual void on_button_click() {
117 //Retreive selected objects
118 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
119 if (!desktop) return;
121 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
122 if (!selection) return;
124 using Inkscape::Util::GSListConstIterator;
125 std::list<SPItem *> selected;
126 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
127 if (selected.empty()) return;
129 NR::Point mp; //Anchor point
130 AlignAndDistribute::AlignTarget target = _dialog.getAlignTarget();
131 const Coeffs &a= _allCoeffs[_index];
132 switch (target)
133 {
134 case AlignAndDistribute::LAST:
135 case AlignAndDistribute::FIRST:
136 case AlignAndDistribute::BIGGEST:
137 case AlignAndDistribute::SMALLEST:
138 {
139 //Check 2 or more selected objects
140 std::list<SPItem *>::iterator second(selected.begin());
141 ++second;
142 if (second == selected.end())
143 return;
144 //Find the master (anchor on which the other objects are aligned)
145 std::list<SPItem *>::iterator master(
146 _dialog.find_master (
147 selected,
148 (a.mx0 != 0.0) ||
149 (a.mx1 != 0.0) )
150 );
151 //remove the master from the selection
152 SPItem * thing = *master;
153 selected.erase(master);
154 //Compute the anchor point
155 NR::Rect b = sp_item_bbox_desktop (thing);
156 mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
157 a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
158 break;
159 }
161 case AlignAndDistribute::PAGE:
162 mp = NR::Point(a.mx1 * sp_document_width(SP_DT_DOCUMENT(desktop)),
163 a.my1 * sp_document_height(SP_DT_DOCUMENT(desktop)));
164 break;
166 case AlignAndDistribute::DRAWING:
167 {
168 NR::Rect b = sp_item_bbox_desktop
169 ( (SPItem *) sp_document_root (SP_DT_DOCUMENT (desktop)) );
170 mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
171 a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
172 break;
173 }
175 case AlignAndDistribute::SELECTION:
176 {
177 NR::Rect b = selection->bounds();
178 mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
179 a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
180 break;
181 }
183 default:
184 g_assert_not_reached ();
185 break;
186 }; // end of switch
188 // Top hack: temporarily set clone compensation to unmoved, so that we can align/distribute
189 // clones with their original (and the move of the original does not disturb the
190 // clones). The only problem with this is that if there are outside-of-selection clones of
191 // a selected original, they will be unmoved too, possibly contrary to user's
192 // expecation. However this is a minor point compared to making align/distribute always
193 // work as expected, and "unmoved" is the default option anyway.
194 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
195 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
197 bool changed = false;
198 //Move each item in the selected list
199 for (std::list<SPItem *>::iterator it(selected.begin());
200 it != selected.end();
201 it++)
202 {
203 sp_document_ensure_up_to_date(SP_DT_DOCUMENT (desktop));
204 NR::Rect b = sp_item_bbox_desktop (*it);
205 NR::Point const sp(a.sx0 * b.min()[NR::X] + a.sx1 * b.max()[NR::X],
206 a.sy0 * b.min()[NR::Y] + a.sy1 * b.max()[NR::Y]);
207 NR::Point const mp_rel( mp - sp );
208 if (LInfty(mp_rel) > 1e-9) {
209 sp_item_move_rel(*it, NR::translate(mp_rel));
210 changed = true;
211 }
212 }
214 // restore compensation setting
215 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
217 if (changed) {
218 sp_document_done ( SP_DT_DOCUMENT (desktop) );
219 }
222 }
223 guint _index;
224 AlignAndDistribute &_dialog;
226 static const Coeffs _allCoeffs[10];
228 };
229 ActionAlign::Coeffs const ActionAlign::_allCoeffs[10] = {
230 {1., 0., 0., 0., 0., 1., 0., 0.},
231 {1., 0., 0., 0., 1., 0., 0., 0.},
232 {.5, .5, 0., 0., .5, .5, 0., 0.},
233 {0., 1., 0., 0., 0., 1., 0., 0.},
234 {0., 1., 0., 0., 1., 0., 0., 0.},
235 {0., 0., 0., 1., 0., 0., 1., 0.},
236 {0., 0., 0., 1., 0., 0., 0., 1.},
237 {0., 0., .5, .5, 0., 0., .5, .5},
238 {0., 0., 1., 0., 0., 0., 1., 0.},
239 {0., 0., 1., 0., 0., 0., 0., 1.}
240 };
242 struct BBoxSort
243 {
244 SPItem *item;
245 float anchor;
246 NR::Rect bbox;
247 BBoxSort(SPItem *pItem, NR::Dim2 orientation, double kBegin, double kEnd) :
248 item(pItem),
249 bbox (sp_item_bbox_desktop (pItem))
250 {
251 anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
252 }
253 BBoxSort(const BBoxSort &rhs):
254 //NOTE : this copy ctor is called O(sort) when sorting the vector
255 //this is bad. The vector should be a vector of pointers.
256 //But I'll wait the bohem GC before doing that
257 item(rhs.item), anchor(rhs.anchor), bbox(rhs.bbox) {
258 }
259 };
260 bool operator< (const BBoxSort &a, const BBoxSort &b)
261 {
262 return (a.anchor < b.anchor);
263 }
265 class ActionDistribute : public Action {
266 public :
267 ActionDistribute(const Glib::ustring &id,
268 const Glib::ustring &tiptext,
269 guint row, guint column,
270 AlignAndDistribute &dialog,
271 bool onInterSpace,
272 NR::Dim2 orientation,
273 double kBegin, double kEnd
274 ):
275 Action(id, tiptext, row, column,
276 dialog.distribute_table(), dialog.tooltips(), dialog),
277 _dialog(dialog),
278 _onInterSpace(onInterSpace),
279 _orientation(orientation),
280 _kBegin(kBegin),
281 _kEnd( kEnd)
282 {}
284 private :
285 virtual void on_button_click() {
286 //Retreive selected objects
287 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
288 if (!desktop) return;
290 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
291 if (!selection) return;
293 using Inkscape::Util::GSListConstIterator;
294 std::list<SPItem *> selected;
295 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
296 if (selected.empty()) return;
298 //Check 2 or more selected objects
299 std::list<SPItem *>::iterator second(selected.begin());
300 ++second;
301 if (second == selected.end()) return;
304 std::vector< BBoxSort > sorted;
305 for (std::list<SPItem *>::iterator it(selected.begin());
306 it != selected.end();
307 ++it)
308 {
309 BBoxSort b (*it, _orientation, _kBegin, _kEnd);
310 sorted.push_back(b);
311 }
312 //sort bbox by anchors
313 std::sort(sorted.begin(), sorted.end());
315 // see comment in ActionAlign above
316 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
317 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
319 unsigned int len = sorted.size();
320 bool changed = false;
321 if (_onInterSpace)
322 {
323 //overall bboxes span
324 float dist = (sorted.back().bbox.max()[_orientation] -
325 sorted.front().bbox.min()[_orientation]);
326 //space eaten by bboxes
327 float span = 0;
328 for (unsigned int i = 0; i < len; i++)
329 {
330 span += sorted[i].bbox.extent(_orientation);
331 }
332 //new distance between each bbox
333 float step = (dist - span) / (len - 1);
334 float pos = sorted.front().bbox.min()[_orientation];
335 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
336 it < sorted.end();
337 it ++ )
338 {
339 if (!NR_DF_TEST_CLOSE (pos, it->bbox.min()[_orientation], 1e-6)) {
340 NR::Point t(0.0, 0.0);
341 t[_orientation] = pos - it->bbox.min()[_orientation];
342 sp_item_move_rel(it->item, NR::translate(t));
343 changed = true;
344 }
345 pos += it->bbox.extent(_orientation);
346 pos += step;
347 }
348 }
349 else
350 {
351 //overall anchor span
352 float dist = sorted.back().anchor - sorted.front().anchor;
353 //distance between anchors
354 float step = dist / (len - 1);
356 for ( unsigned int i = 0; i < len ; i ++ )
357 {
358 BBoxSort & it(sorted[i]);
359 //new anchor position
360 float pos = sorted.front().anchor + i * step;
361 //Don't move if we are really close
362 if (!NR_DF_TEST_CLOSE (pos, it.anchor, 1e-6)) {
363 //Compute translation
364 NR::Point t(0.0, 0.0);
365 t[_orientation] = pos - it.anchor;
366 //translate
367 sp_item_move_rel(it.item, NR::translate(t));
368 changed = true;
369 }
370 }
371 }
373 // restore compensation setting
374 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
376 if (changed) {
377 sp_document_done ( SP_DT_DOCUMENT (desktop) );
378 }
379 }
380 guint _index;
381 AlignAndDistribute &_dialog;
382 bool _onInterSpace;
383 NR::Dim2 _orientation;
385 double _kBegin;
386 double _kEnd;
388 };
391 class ActionNode : public Action {
392 public :
393 ActionNode(const Glib::ustring &id,
394 const Glib::ustring &tiptext,
395 guint column,
396 AlignAndDistribute &dialog,
397 NR::Dim2 orientation, bool distribute):
398 Action(id, tiptext, 0, column,
399 dialog.nodes_table(), dialog.tooltips(), dialog),
400 _orientation(orientation),
401 _distribute(distribute)
402 {}
404 private :
405 NR::Dim2 _orientation;
406 bool _distribute;
407 virtual void on_button_click()
408 {
410 if (!SP_ACTIVE_DESKTOP) return;
411 SPEventContext *event_context = SP_DT_EVENTCONTEXT(SP_ACTIVE_DESKTOP);
412 if (!SP_IS_NODE_CONTEXT (event_context)) return ;
414 Inkscape::NodePath::Path *nodepath = SP_NODE_CONTEXT (event_context)->nodepath;
415 if (!nodepath) return;
416 if (_distribute)
417 sp_nodepath_selected_distribute(nodepath, _orientation);
418 else
419 sp_nodepath_selected_align(nodepath, _orientation);
421 }
422 };
424 class ActionRemoveOverlaps : public Action {
425 private:
426 Gtk::Label removeOverlapXGapLabel;
427 Gtk::Label removeOverlapYGapLabel;
428 Gtk::SpinButton removeOverlapXGap;
429 Gtk::SpinButton removeOverlapYGap;
431 public:
432 ActionRemoveOverlaps(Glib::ustring const &id,
433 Glib::ustring const &tiptext,
434 guint row,
435 guint column,
436 AlignAndDistribute &dialog) :
437 Action(id, tiptext, row, column + 4,
438 dialog.removeOverlap_table(), dialog.tooltips(), dialog)
439 {
440 dialog.removeOverlap_table().set_col_spacings(3);
442 removeOverlapXGap.set_digits(1);
443 removeOverlapXGap.set_size_request(60, -1);
444 removeOverlapXGap.set_increments(1.0, 5.0);
445 removeOverlapXGap.set_range(-1000.0, 1000.0);
446 removeOverlapXGap.set_value(0);
447 dialog.tooltips().set_tip(removeOverlapXGap,
448 _("Minimum horizontal gap (in px units) between bounding boxes"));
449 /* TRANSLATORS: Horizontal gap */
450 removeOverlapXGapLabel.set_label(_("H:"));
452 removeOverlapYGap.set_digits(1);
453 removeOverlapYGap.set_size_request(60, -1);
454 removeOverlapYGap.set_increments(1.0, 5.0);
455 removeOverlapYGap.set_range(-1000.0, 1000.0);
456 removeOverlapYGap.set_value(0);
457 dialog.tooltips().set_tip(removeOverlapYGap,
458 _("Minimum vertical gap (in px units) between bounding boxes"));
459 /* TRANSLATORS: Vertical gap */
460 removeOverlapYGapLabel.set_label(_("V:"));
462 dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
463 dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
464 dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, column+3, row, row+1, Gtk::FILL, Gtk::FILL);
465 dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
467 }
469 private :
470 virtual void on_button_click()
471 {
472 if (!SP_ACTIVE_DESKTOP) return;
474 // see comment in ActionAlign above
475 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
476 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
478 // xGap and yGap are the minimum space required between bounding rectangles.
479 double const xGap = removeOverlapXGap.get_value();
480 double const yGap = removeOverlapYGap.get_value();
481 removeoverlap(SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->itemList(),
482 xGap, yGap);
484 // restore compensation setting
485 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
487 sp_document_done(SP_DT_DOCUMENT(SP_ACTIVE_DESKTOP));
488 }
489 };
491 class ActionGraphLayout : public Action {
492 public:
493 ActionGraphLayout(Glib::ustring const &id,
494 Glib::ustring const &tiptext,
495 guint row,
496 guint column,
497 AlignAndDistribute &dialog) :
498 Action(id, tiptext, row, column + 4,
499 dialog.graphLayout_table(), dialog.tooltips(), dialog)
500 {}
502 private :
503 virtual void on_button_click()
504 {
505 if (!SP_ACTIVE_DESKTOP) return;
507 // see comment in ActionAlign above
508 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
509 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
511 graphlayout(SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->itemList());
513 // restore compensation setting
514 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
516 sp_document_done(SP_DT_DOCUMENT(SP_ACTIVE_DESKTOP));
517 }
518 };
520 class ActionUnclump : public Action {
521 public :
522 ActionUnclump(const Glib::ustring &id,
523 const Glib::ustring &tiptext,
524 guint row,
525 guint column,
526 AlignAndDistribute &dialog):
527 Action(id, tiptext, row, column,
528 dialog.distribute_table(), dialog.tooltips(), dialog)
529 {}
531 private :
532 virtual void on_button_click()
533 {
534 if (!SP_ACTIVE_DESKTOP) return;
536 // see comment in ActionAlign above
537 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
538 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
540 unclump ((GSList *) SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->itemList());
542 // restore compensation setting
543 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
545 sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP));
546 }
547 };
549 class ActionRandomize : public Action {
550 public :
551 ActionRandomize(const Glib::ustring &id,
552 const Glib::ustring &tiptext,
553 guint row,
554 guint column,
555 AlignAndDistribute &dialog):
556 Action(id, tiptext, row, column,
557 dialog.distribute_table(), dialog.tooltips(), dialog)
558 {}
560 private :
561 virtual void on_button_click()
562 {
563 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
564 if (!desktop) return;
566 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
567 if (!selection) return;
569 using Inkscape::Util::GSListConstIterator;
570 std::list<SPItem *> selected;
571 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
572 if (selected.empty()) return;
574 //Check 2 or more selected objects
575 if (selected.size() < 2) return;
577 // This bbox is cached between calls to randomize, so that there's no growth nor shrink
578 // nor drift on sequential randomizations. Discard cache on global (or better active
579 // desktop's) selection_change signal.
580 if (!_dialog.randomize_bbox_set) {
581 _dialog.randomize_bbox = selection->bounds();
582 _dialog.randomize_bbox_set = true;
583 }
585 // see comment in ActionAlign above
586 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
587 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
589 for (std::list<SPItem *>::iterator it(selected.begin());
590 it != selected.end();
591 ++it)
592 {
593 sp_document_ensure_up_to_date(SP_DT_DOCUMENT (desktop));
594 NR::Rect item_box = sp_item_bbox_desktop (*it);
595 // find new center, staying within bbox
596 double x = _dialog.randomize_bbox.min()[NR::X] + item_box.extent(NR::X)/2 +
597 g_random_double_range (0, _dialog.randomize_bbox.extent(NR::X) - item_box.extent(NR::X));
598 double y = _dialog.randomize_bbox.min()[NR::Y] + item_box.extent(NR::Y)/2 +
599 g_random_double_range (0, _dialog.randomize_bbox.extent(NR::Y) - item_box.extent(NR::Y));
600 // displacement is the new center minus old:
601 NR::Point t = NR::Point (x, y) - 0.5*(item_box.max() + item_box.min());
602 sp_item_move_rel(*it, NR::translate(t));
603 }
605 // restore compensation setting
606 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
608 sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP));
609 }
610 };
612 struct Baselines
613 {
614 SPItem *_item;
615 NR::Point _base;
616 NR::Dim2 _orientation;
617 Baselines(SPItem *item, NR::Point base, NR::Dim2 orientation) :
618 _item (item),
619 _base (base),
620 _orientation (orientation)
621 {}
622 };
624 bool operator< (const Baselines &a, const Baselines &b)
625 {
626 return (a._base[a._orientation] < b._base[b._orientation]);
627 }
629 class ActionBaseline : public Action {
630 public :
631 ActionBaseline(const Glib::ustring &id,
632 const Glib::ustring &tiptext,
633 guint row,
634 guint column,
635 AlignAndDistribute &dialog,
636 Gtk::Table &table,
637 NR::Dim2 orientation, bool distribute):
638 Action(id, tiptext, row, column,
639 table, dialog.tooltips(), dialog),
640 _orientation(orientation),
641 _distribute(distribute)
642 {}
644 private :
645 NR::Dim2 _orientation;
646 bool _distribute;
647 virtual void on_button_click()
648 {
649 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
650 if (!desktop) return;
652 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
653 if (!selection) return;
655 using Inkscape::Util::GSListConstIterator;
656 std::list<SPItem *> selected;
657 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
658 if (selected.empty()) return;
660 //Check 2 or more selected objects
661 if (selected.size() < 2) return;
663 NR::Point b_min = NR::Point (HUGE_VAL, HUGE_VAL);
664 NR::Point b_max = NR::Point (-HUGE_VAL, -HUGE_VAL);
666 std::vector<Baselines> sorted;
668 for (std::list<SPItem *>::iterator it(selected.begin());
669 it != selected.end();
670 ++it)
671 {
672 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
673 Inkscape::Text::Layout const *layout = te_get_layout(*it);
674 NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
675 if (base[NR::X] < b_min[NR::X]) b_min[NR::X] = base[NR::X];
676 if (base[NR::Y] < b_min[NR::Y]) b_min[NR::Y] = base[NR::Y];
677 if (base[NR::X] > b_max[NR::X]) b_max[NR::X] = base[NR::X];
678 if (base[NR::Y] > b_max[NR::Y]) b_max[NR::Y] = base[NR::Y];
680 Baselines b (*it, base, _orientation);
681 sorted.push_back(b);
682 }
683 }
685 if (sorted.size() <= 1) return;
687 //sort baselines
688 std::sort(sorted.begin(), sorted.end());
690 bool changed = false;
692 if (_distribute) {
693 double step = (b_max[_orientation] - b_min[_orientation])/(sorted.size() - 1);
694 for (unsigned int i = 0; i < sorted.size(); i++) {
695 SPItem *item = sorted[i]._item;
696 NR::Point base = sorted[i]._base;
697 NR::Point t(0.0, 0.0);
698 t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
699 sp_item_move_rel(item, NR::translate(t));
700 changed = true;
701 }
703 } else {
704 for (std::list<SPItem *>::iterator it(selected.begin());
705 it != selected.end();
706 ++it)
707 {
708 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
709 Inkscape::Text::Layout const *layout = te_get_layout(*it);
710 NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
711 NR::Point t(0.0, 0.0);
712 t[_orientation] = b_min[_orientation] - base[_orientation];
713 sp_item_move_rel(*it, NR::translate(t));
714 changed = true;
715 }
716 }
717 }
719 if (changed) {
720 sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP));
721 }
722 }
723 };
727 void on_tool_changed(Inkscape::Application *inkscape, SPEventContext *context, AlignAndDistribute *daad)
728 {
729 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
730 if (desktop && SP_DT_EVENTCONTEXT(desktop))
731 daad->setMode(tools_active(desktop) == TOOLS_NODES);
732 }
734 void on_selection_changed(Inkscape::Application *inkscape, Inkscape::Selection *selection, AlignAndDistribute *daad)
735 {
736 daad->randomize_bbox_set = false;
737 }
739 /////////////////////////////////////////////////////////
744 AlignAndDistribute::AlignAndDistribute()
745 : Dialog ("dialogs.align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
746 randomize_bbox (NR::Point (0, 0), NR::Point (0, 0)),
747 _alignFrame(_("Align")),
748 _distributeFrame(_("Distribute")),
749 _removeOverlapFrame(_("Remove overlaps")),
750 _graphLayoutFrame(_("Connector network layout")),
751 _nodesFrame(_("Nodes")),
752 _alignTable(2, 6, true),
753 _distributeTable(3, 6, true),
754 _removeOverlapTable(1, 5, false),
755 _graphLayoutTable(1, 5, false),
756 _nodesTable(1, 4, true),
757 _anchorLabel(_("Relative to: "))
758 {
760 //Instanciate the align buttons
761 addAlignButton("al_left_out",
762 _("Align right sides of objects to left side of anchor"),
763 0, 0);
764 addAlignButton("al_left_in",
765 _("Align left sides"),
766 0, 1);
767 addAlignButton("al_center_hor",
768 _("Center on vertical axis"),
769 0, 2);
770 addAlignButton("al_right_in",
771 _("Align right sides"),
772 0, 3);
773 addAlignButton("al_right_out",
774 _("Align left sides of objects to right side of anchor"),
775 0, 4);
776 addAlignButton("al_top_out",
777 _("Align bottoms of objects to top of anchor"),
778 1, 0);
779 addAlignButton("al_top_in",
780 _("Align tops"),
781 1, 1);
782 addAlignButton("al_center_ver",
783 _("Center on horizontal axis"),
784 1, 2);
785 addAlignButton("al_bottom_in",
786 _("Align bottoms"),
787 1, 3);
788 addAlignButton("al_bottom_out",
789 _("Align tops of objects to bottom of anchor"),
790 1, 4);
792 //Baseline aligns
793 addBaselineButton("al_baselines_vert",
794 _("Align baseline anchors of texts vertically"),
795 0, 5, this->align_table(), NR::X, false);
796 addBaselineButton("al_baselines_hor",
797 _("Align baseline anchors of texts horizontally"),
798 1, 5, this->align_table(), NR::Y, false);
800 //The distribute buttons
801 addDistributeButton("distribute_hdist",
802 _("Make horizontal gaps between objects equal"),
803 0, 4, true, NR::X, .5, .5);
805 addDistributeButton("distribute_left",
806 _("Distribute left sides equidistantly"),
807 0, 1, false, NR::X, 1., 0.);
808 addDistributeButton("distribute_hcentre",
809 _("Distribute centers equidistantly horizontally"),
810 0, 2, false, NR::X, .5, .5);
811 addDistributeButton("distribute_right",
812 _("Distribute right sides equidistantly"),
813 0, 3, false, NR::X, 0., 1.);
815 addDistributeButton("distribute_vdist",
816 _("Make vertical gaps between objects equal"),
817 1, 4, true, NR::Y, .5, .5);
819 addDistributeButton("distribute_top",
820 _("Distribute tops equidistantly"),
821 1, 1, false, NR::Y, 0, 1);
822 addDistributeButton("distribute_vcentre",
823 _("Distribute centers equidistantly vertically"),
824 1, 2, false, NR::Y, .5, .5);
825 addDistributeButton("distribute_bottom",
826 _("Distribute bottoms equidistantly"),
827 1, 3, false, NR::Y, 1., 0.);
829 //Baseline distribs
830 addBaselineButton("distribute_baselines_hor",
831 _("Distribute baseline anchors of texts horizontally"),
832 0, 5, this->distribute_table(), NR::X, true);
833 addBaselineButton("distribute_baselines_vert",
834 _("Distribute baseline anchors of texts vertically"),
835 1, 5, this->distribute_table(), NR::Y, true);
837 //Randomize & Unclump
838 addRandomizeButton("distribute_randomize",
839 _("Randomize centers in both dimensions"),
840 2, 2);
841 addUnclumpButton("unclump",
842 _("Unclump objects: try to equalize edge-to-edge distances"),
843 2, 4);
845 //Remove overlaps
846 addRemoveOverlapsButton("remove_overlaps",
847 _("Move objects as little as possible so that their bounding boxes do not overlap"),
848 0, 0);
849 //Graph Layout
850 addGraphLayoutButton("graph_layout",
851 _("Nicely arrange selected connector network"),
852 0, 0);
854 //Node Mode buttons
855 addNodeButton("node_halign",
856 _("Align selected nodes horizontally"),
857 0, NR::X, false);
858 addNodeButton("node_valign",
859 _("Align selected nodes vertically"),
860 1, NR::Y, false);
861 addNodeButton("node_hdistribute",
862 _("Distribute selected nodes horizontally"),
863 2, NR::X, true);
864 addNodeButton("node_vdistribute",
865 _("Distribute selected nodes vertically"),
866 3, NR::Y, true);
868 //Rest of the widgetry
870 _combo.append_text(_("Last selected"));
871 _combo.append_text(_("First selected"));
872 _combo.append_text(_("Biggest item"));
873 _combo.append_text(_("Smallest item"));
874 _combo.append_text(_("Page"));
875 _combo.append_text(_("Drawing"));
876 _combo.append_text(_("Selection"));
878 _combo.set_active(6);
879 _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
881 _anchorBox.pack_start(_anchorLabel);
882 _anchorBox.pack_start(_combo);
884 _alignBox.pack_start(_anchorBox);
885 _alignBox.pack_start(_alignTable);
887 _alignFrame.add(_alignBox);
888 _distributeFrame.add(_distributeTable);
889 _removeOverlapFrame.add(_removeOverlapTable);
890 _graphLayoutFrame.add(_graphLayoutTable);
891 _nodesFrame.add(_nodesTable);
893 // Top level vbox
894 Gtk::VBox *vbox = get_vbox();
895 vbox->set_spacing(4);
897 // Notebook for individual transformations
899 vbox->pack_start(_alignFrame, true, true);
900 vbox->pack_start(_distributeFrame, true, true);
901 vbox->pack_start(_removeOverlapFrame, true, true);
902 vbox->pack_start(_graphLayoutFrame, true, true);
903 vbox->pack_start(_nodesFrame, true, true);
905 //Connect to the global tool change signal
906 g_signal_connect (G_OBJECT (INKSCAPE), "set_eventcontext", G_CALLBACK (on_tool_changed), this);
908 // Connect to the global selection change, to invalidate cached randomize_bbox
909 g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
910 randomize_bbox = NR::Rect (NR::Point (0, 0), NR::Point (0, 0));
911 randomize_bbox_set = false;
913 show_all_children();
915 on_tool_changed (NULL, NULL, this); // set current mode
916 }
918 AlignAndDistribute::~AlignAndDistribute()
919 {
920 sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
922 for (std::list<Action *>::iterator it = _actionList.begin();
923 it != _actionList.end();
924 it ++)
925 delete *it;
926 }
928 void AlignAndDistribute::on_ref_change(){
929 //Make blink the master
930 }
935 void AlignAndDistribute::setMode(bool nodeEdit)
936 {
937 //Act on widgets used in node mode
938 void ( Gtk::Widget::*mNode) () = nodeEdit ?
939 &Gtk::Widget::show_all : &Gtk::Widget::hide_all;
941 //Act on widgets used in selection mode
942 void ( Gtk::Widget::*mSel) () = nodeEdit ?
943 &Gtk::Widget::hide_all : &Gtk::Widget::show_all;
946 ((_alignFrame).*(mSel))();
947 ((_distributeFrame).*(mSel))();
948 ((_removeOverlapFrame).*(mSel))();
949 ((_graphLayoutFrame).*(mSel))();
950 ((_nodesFrame).*(mNode))();
952 }
953 void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
954 guint row, guint col)
955 {
956 _actionList.push_back(
957 new ActionAlign(
958 id, tiptext, row, col,
959 *this , col + row * 5));
960 }
961 void AlignAndDistribute::addDistributeButton(const Glib::ustring &id, const Glib::ustring tiptext,
962 guint row, guint col, bool onInterSpace,
963 NR::Dim2 orientation, float kBegin, float kEnd)
964 {
965 _actionList.push_back(
966 new ActionDistribute(
967 id, tiptext, row, col, *this ,
968 onInterSpace, orientation,
969 kBegin, kEnd
970 )
971 );
972 }
974 void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
975 guint col, NR::Dim2 orientation, bool distribute)
976 {
977 _actionList.push_back(
978 new ActionNode(
979 id, tiptext, col,
980 *this, orientation, distribute));
981 }
983 void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
984 guint row, guint col)
985 {
986 _actionList.push_back(
987 new ActionRemoveOverlaps(
988 id, tiptext, row, col, *this)
989 );
990 }
992 void AlignAndDistribute::addGraphLayoutButton(const Glib::ustring &id, const Glib::ustring tiptext,
993 guint row, guint col)
994 {
995 _actionList.push_back(
996 new ActionGraphLayout(
997 id, tiptext, row, col, *this)
998 );
999 }
1001 void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
1002 guint row, guint col)
1003 {
1004 _actionList.push_back(
1005 new ActionUnclump(
1006 id, tiptext, row, col, *this)
1007 );
1008 }
1010 void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1011 guint row, guint col)
1012 {
1013 _actionList.push_back(
1014 new ActionRandomize(
1015 id, tiptext, row, col, *this)
1016 );
1017 }
1019 void AlignAndDistribute::addBaselineButton(const Glib::ustring &id, const Glib::ustring tiptext,
1020 guint row, guint col, Gtk::Table &table, NR::Dim2 orientation, bool distribute)
1021 {
1022 _actionList.push_back(
1023 new ActionBaseline(
1024 id, tiptext, row, col,
1025 *this, table, orientation, distribute));
1026 }
1031 std::list<SPItem *>::iterator AlignAndDistribute::find_master( std::list<SPItem *> &list, bool horizontal){
1032 std::list<SPItem *>::iterator master = list.end();
1033 switch (getAlignTarget()) {
1034 case LAST:
1035 return list.begin();
1036 break;
1038 case FIRST:
1039 return --(list.end());
1040 break;
1042 case BIGGEST:
1043 {
1044 gdouble max = -1e18;
1045 for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1046 NR::Rect b = sp_item_bbox_desktop (*it);
1047 gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
1048 if (dim > max) {
1049 max = dim;
1050 master = it;
1051 }
1052 }
1053 return master;
1054 break;
1055 }
1057 case SMALLEST:
1058 {
1059 gdouble max = 1e18;
1060 for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1061 NR::Rect b = sp_item_bbox_desktop (*it);
1062 gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
1063 if (dim < max) {
1064 max = dim;
1065 master = it;
1066 }
1067 }
1068 return master;
1069 break;
1070 }
1072 default:
1073 g_assert_not_reached ();
1074 break;
1076 } // end of switch statement
1077 return master;
1078 }
1080 AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
1081 return AlignTarget(_combo.get_active_row_number());
1082 }
1086 } // namespace Dialog
1087 } // namespace UI
1088 } // namespace Inkscape
1090 /*
1091 Local Variables:
1092 mode:c++
1093 c-file-style:"stroustrup"
1094 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1095 indent-tabs-mode:nil
1096 fill-column:99
1097 End:
1098 */
1099 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :