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"
26 #include <gtkmm/spinbutton.h>
31 #include "util/glib-list-iterators.h"
33 #include "widgets/icon.h"
35 #include "inkscape.h"
36 #include "document.h"
37 #include "selection.h"
38 #include "desktop-handles.h"
39 #include "macros.h"
40 #include "sp-item-transform.h"
41 #include "prefs-utils.h"
42 #include "enums.h"
44 #include "sp-text.h"
45 #include "sp-flowtext.h"
46 #include "text-editing.h"
48 #include "node-context.h" //For node align/distribute function
50 #include "tools-switch.h"
52 #include "align-and-distribute.h"
54 namespace Inkscape {
55 namespace UI {
56 namespace Dialog {
58 /////////helper classes//////////////////////////////////
60 class Action {
61 public :
62 Action(const Glib::ustring &id,
63 const Glib::ustring &tiptext,
64 guint row, guint column,
65 Gtk::Table &parent,
66 Gtk::Tooltips &tooltips,
67 AlignAndDistribute &dialog):
68 _dialog(dialog),
69 _id(id),
70 _parent(parent)
71 {
72 Gtk::Widget* pIcon = Gtk::manage( sp_icon_get_icon( _id, GTK_ICON_SIZE_LARGE_TOOLBAR) );
73 Gtk::Button * pButton = Gtk::manage(new Gtk::Button());
74 pButton->set_relief(Gtk::RELIEF_NONE);
75 pIcon->show();
76 pButton->add(*pIcon);
77 pButton->show();
79 pButton->signal_clicked()
80 .connect(sigc::mem_fun(*this, &Action::on_button_click));
81 tooltips.set_tip(*pButton, tiptext);
82 parent.attach(*pButton, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
83 }
84 virtual ~Action(){}
86 AlignAndDistribute &_dialog;
88 private :
89 virtual void on_button_click(){}
91 Glib::ustring _id;
92 Gtk::Table &_parent;
93 };
96 class ActionAlign : public Action {
97 public :
98 struct Coeffs {
99 double mx0, mx1, my0, my1;
100 double sx0, sx1, sy0, sy1;
101 };
102 ActionAlign(const Glib::ustring &id,
103 const Glib::ustring &tiptext,
104 guint row, guint column,
105 AlignAndDistribute &dialog,
106 guint coeffIndex):
107 Action(id, tiptext, row, column,
108 dialog.align_table(), dialog.tooltips(), dialog),
109 _index(coeffIndex),
110 _dialog(dialog)
111 {}
113 private :
115 virtual void on_button_click() {
116 //Retreive selected objects
117 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
118 if (!desktop) return;
120 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
121 if (!selection) return;
123 using Inkscape::Util::GSListConstIterator;
124 std::list<SPItem *> selected;
125 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
126 if (selected.empty()) return;
128 NR::Point mp; //Anchor point
129 AlignAndDistribute::AlignTarget target = _dialog.getAlignTarget();
130 const Coeffs &a= _allCoeffs[_index];
131 switch (target)
132 {
133 case AlignAndDistribute::LAST:
134 case AlignAndDistribute::FIRST:
135 case AlignAndDistribute::BIGGEST:
136 case AlignAndDistribute::SMALLEST:
137 {
138 //Check 2 or more selected objects
139 std::list<SPItem *>::iterator second(selected.begin());
140 ++second;
141 if (second == selected.end())
142 return;
143 //Find the master (anchor on which the other objects are aligned)
144 std::list<SPItem *>::iterator master(
145 _dialog.find_master (
146 selected,
147 (a.mx0 != 0.0) ||
148 (a.mx1 != 0.0) )
149 );
150 //remove the master from the selection
151 SPItem * thing = *master;
152 selected.erase(master);
153 //Compute the anchor point
154 NR::Rect b = sp_item_bbox_desktop (thing);
155 mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
156 a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
157 break;
158 }
160 case AlignAndDistribute::PAGE:
161 mp = NR::Point(a.mx1 * sp_document_width(SP_DT_DOCUMENT(desktop)),
162 a.my1 * sp_document_height(SP_DT_DOCUMENT(desktop)));
163 break;
165 case AlignAndDistribute::DRAWING:
166 {
167 NR::Rect b = sp_item_bbox_desktop
168 ( (SPItem *) sp_document_root (SP_DT_DOCUMENT (desktop)) );
169 mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
170 a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
171 break;
172 }
174 case AlignAndDistribute::SELECTION:
175 {
176 NR::Rect b = selection->bounds();
177 mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
178 a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
179 break;
180 }
182 default:
183 g_assert_not_reached ();
184 break;
185 }; // end of switch
187 // Top hack: temporarily set clone compensation to unmoved, so that we can align/distribute
188 // clones with their original (and the move of the original does not disturb the
189 // clones). The only problem with this is that if there are outside-of-selection clones of
190 // a selected original, they will be unmoved too, possibly contrary to user's
191 // expecation. However this is a minor point compared to making align/distribute always
192 // work as expected, and "unmoved" is the default option anyway.
193 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
194 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
196 bool changed = false;
197 //Move each item in the selected list
198 for (std::list<SPItem *>::iterator it(selected.begin());
199 it != selected.end();
200 it++)
201 {
202 sp_document_ensure_up_to_date(SP_DT_DOCUMENT (desktop));
203 NR::Rect b = sp_item_bbox_desktop (*it);
204 NR::Point const sp(a.sx0 * b.min()[NR::X] + a.sx1 * b.max()[NR::X],
205 a.sy0 * b.min()[NR::Y] + a.sy1 * b.max()[NR::Y]);
206 NR::Point const mp_rel( mp - sp );
207 if (LInfty(mp_rel) > 1e-9) {
208 sp_item_move_rel(*it, NR::translate(mp_rel));
209 changed = true;
210 }
211 }
213 // restore compensation setting
214 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
216 if (changed) {
217 sp_document_done ( SP_DT_DOCUMENT (desktop) );
218 }
221 }
222 guint _index;
223 AlignAndDistribute &_dialog;
225 static const Coeffs _allCoeffs[10];
227 };
228 ActionAlign::Coeffs const ActionAlign::_allCoeffs[10] = {
229 {1., 0., 0., 0., 0., 1., 0., 0.},
230 {1., 0., 0., 0., 1., 0., 0., 0.},
231 {.5, .5, 0., 0., .5, .5, 0., 0.},
232 {0., 1., 0., 0., 0., 1., 0., 0.},
233 {0., 1., 0., 0., 1., 0., 0., 0.},
234 {0., 0., 0., 1., 0., 0., 1., 0.},
235 {0., 0., 0., 1., 0., 0., 0., 1.},
236 {0., 0., .5, .5, 0., 0., .5, .5},
237 {0., 0., 1., 0., 0., 0., 1., 0.},
238 {0., 0., 1., 0., 0., 0., 0., 1.}
239 };
241 struct BBoxSort
242 {
243 SPItem *item;
244 float anchor;
245 NR::Rect bbox;
246 BBoxSort(SPItem *pItem, NR::Dim2 orientation, double kBegin, double kEnd) :
247 item(pItem),
248 bbox (sp_item_bbox_desktop (pItem))
249 {
250 anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
251 }
252 BBoxSort(const BBoxSort &rhs):
253 //NOTE : this copy ctor is called O(sort) when sorting the vector
254 //this is bad. The vector should be a vector of pointers.
255 //But I'll wait the bohem GC before doing that
256 item(rhs.item), anchor(rhs.anchor), bbox(rhs.bbox) {
257 }
258 };
259 bool operator< (const BBoxSort &a, const BBoxSort &b)
260 {
261 return (a.anchor < b.anchor);
262 }
264 class ActionDistribute : public Action {
265 public :
266 ActionDistribute(const Glib::ustring &id,
267 const Glib::ustring &tiptext,
268 guint row, guint column,
269 AlignAndDistribute &dialog,
270 bool onInterSpace,
271 NR::Dim2 orientation,
272 double kBegin, double kEnd
273 ):
274 Action(id, tiptext, row, column,
275 dialog.distribute_table(), dialog.tooltips(), dialog),
276 _dialog(dialog),
277 _onInterSpace(onInterSpace),
278 _orientation(orientation),
279 _kBegin(kBegin),
280 _kEnd( kEnd)
281 {}
283 private :
284 virtual void on_button_click() {
285 //Retreive selected objects
286 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
287 if (!desktop) return;
289 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
290 if (!selection) return;
292 using Inkscape::Util::GSListConstIterator;
293 std::list<SPItem *> selected;
294 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
295 if (selected.empty()) return;
297 //Check 2 or more selected objects
298 std::list<SPItem *>::iterator second(selected.begin());
299 ++second;
300 if (second == selected.end()) return;
303 std::vector< BBoxSort > sorted;
304 for (std::list<SPItem *>::iterator it(selected.begin());
305 it != selected.end();
306 ++it)
307 {
308 BBoxSort b (*it, _orientation, _kBegin, _kEnd);
309 sorted.push_back(b);
310 }
311 //sort bbox by anchors
312 std::sort(sorted.begin(), sorted.end());
314 // see comment in ActionAlign above
315 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
316 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
318 unsigned int len = sorted.size();
319 bool changed = false;
320 if (_onInterSpace)
321 {
322 //overall bboxes span
323 float dist = (sorted.back().bbox.max()[_orientation] -
324 sorted.front().bbox.min()[_orientation]);
325 //space eaten by bboxes
326 float span = 0;
327 for (unsigned int i = 0; i < len; i++)
328 {
329 span += sorted[i].bbox.extent(_orientation);
330 }
331 //new distance between each bbox
332 float step = (dist - span) / (len - 1);
333 float pos = sorted.front().bbox.min()[_orientation];
334 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
335 it < sorted.end();
336 it ++ )
337 {
338 if (!NR_DF_TEST_CLOSE (pos, it->bbox.min()[_orientation], 1e-6)) {
339 NR::Point t(0.0, 0.0);
340 t[_orientation] = pos - it->bbox.min()[_orientation];
341 sp_item_move_rel(it->item, NR::translate(t));
342 changed = true;
343 }
344 pos += it->bbox.extent(_orientation);
345 pos += step;
346 }
347 }
348 else
349 {
350 //overall anchor span
351 float dist = sorted.back().anchor - sorted.front().anchor;
352 //distance between anchors
353 float step = dist / (len - 1);
355 for ( unsigned int i = 0; i < len ; i ++ )
356 {
357 BBoxSort & it(sorted[i]);
358 //new anchor position
359 float pos = sorted.front().anchor + i * step;
360 //Don't move if we are really close
361 if (!NR_DF_TEST_CLOSE (pos, it.anchor, 1e-6)) {
362 //Compute translation
363 NR::Point t(0.0, 0.0);
364 t[_orientation] = pos - it.anchor;
365 //translate
366 sp_item_move_rel(it.item, NR::translate(t));
367 changed = true;
368 }
369 }
370 }
372 // restore compensation setting
373 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
375 if (changed) {
376 sp_document_done ( SP_DT_DOCUMENT (desktop) );
377 }
378 }
379 guint _index;
380 AlignAndDistribute &_dialog;
381 bool _onInterSpace;
382 NR::Dim2 _orientation;
384 double _kBegin;
385 double _kEnd;
387 };
390 class ActionNode : public Action {
391 public :
392 ActionNode(const Glib::ustring &id,
393 const Glib::ustring &tiptext,
394 guint column,
395 AlignAndDistribute &dialog,
396 NR::Dim2 orientation, bool distribute):
397 Action(id, tiptext, 0, column,
398 dialog.nodes_table(), dialog.tooltips(), dialog),
399 _orientation(orientation),
400 _distribute(distribute)
401 {}
403 private :
404 NR::Dim2 _orientation;
405 bool _distribute;
406 virtual void on_button_click()
407 {
409 if (!SP_ACTIVE_DESKTOP) return;
410 SPEventContext *event_context = SP_DT_EVENTCONTEXT(SP_ACTIVE_DESKTOP);
411 if (!SP_IS_NODE_CONTEXT (event_context)) return ;
413 Inkscape::NodePath::Path *nodepath = SP_NODE_CONTEXT (event_context)->nodepath;
414 if (!nodepath) return;
415 if (_distribute)
416 sp_nodepath_selected_distribute(nodepath, _orientation);
417 else
418 sp_nodepath_selected_align(nodepath, _orientation);
420 }
421 };
423 class ActionRemoveOverlaps : public Action {
424 private:
425 Gtk::Label removeOverlapXGapLabel;
426 Gtk::Label removeOverlapYGapLabel;
427 Gtk::SpinButton removeOverlapXGap;
428 Gtk::SpinButton removeOverlapYGap;
430 public:
431 ActionRemoveOverlaps(Glib::ustring const &id,
432 Glib::ustring const &tiptext,
433 guint row,
434 guint column,
435 AlignAndDistribute &dialog) :
436 Action(id, tiptext, row, column + 4,
437 dialog.removeOverlap_table(), dialog.tooltips(), dialog)
438 {
439 dialog.removeOverlap_table().set_col_spacings(3);
441 removeOverlapXGap.set_digits(1);
442 removeOverlapXGap.set_size_request(60, -1);
443 removeOverlapXGap.set_increments(1.0, 5.0);
444 removeOverlapXGap.set_range(-1000.0, 1000.0);
445 removeOverlapXGap.set_value(0);
446 dialog.tooltips().set_tip(removeOverlapXGap,
447 _("Minimum horizontal gap (in px units) between bounding boxes"));
448 /* TRANSLATORS: Horizontal gap */
449 removeOverlapXGapLabel.set_label(_("H:"));
451 removeOverlapYGap.set_digits(1);
452 removeOverlapYGap.set_size_request(60, -1);
453 removeOverlapYGap.set_increments(1.0, 5.0);
454 removeOverlapYGap.set_range(-1000.0, 1000.0);
455 removeOverlapYGap.set_value(0);
456 dialog.tooltips().set_tip(removeOverlapYGap,
457 _("Minimum vertical gap (in px units) between bounding boxes"));
458 /* TRANSLATORS: Vertical gap */
459 removeOverlapYGapLabel.set_label(_("V:"));
461 dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
462 dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
463 dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, column+3, row, row+1, Gtk::FILL, Gtk::FILL);
464 dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
466 }
468 private :
469 virtual void on_button_click()
470 {
471 if (!SP_ACTIVE_DESKTOP) return;
473 // see comment in ActionAlign above
474 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
475 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
477 // xGap and yGap are the minimum space required between bounding rectangles.
478 double const xGap = removeOverlapXGap.get_value();
479 double const yGap = removeOverlapYGap.get_value();
480 removeoverlap(SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->itemList(),
481 xGap, yGap);
483 // restore compensation setting
484 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
486 sp_document_done(SP_DT_DOCUMENT(SP_ACTIVE_DESKTOP));
487 }
488 };
490 class ActionUnclump : public Action {
491 public :
492 ActionUnclump(const Glib::ustring &id,
493 const Glib::ustring &tiptext,
494 guint row,
495 guint column,
496 AlignAndDistribute &dialog):
497 Action(id, tiptext, row, column,
498 dialog.distribute_table(), dialog.tooltips(), dialog)
499 {}
501 private :
502 virtual void on_button_click()
503 {
504 if (!SP_ACTIVE_DESKTOP) return;
506 // see comment in ActionAlign above
507 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
508 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
510 unclump ((GSList *) SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->itemList());
512 // restore compensation setting
513 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
515 sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP));
516 }
517 };
519 class ActionRandomize : public Action {
520 public :
521 ActionRandomize(const Glib::ustring &id,
522 const Glib::ustring &tiptext,
523 guint row,
524 guint column,
525 AlignAndDistribute &dialog):
526 Action(id, tiptext, row, column,
527 dialog.distribute_table(), dialog.tooltips(), dialog)
528 {}
530 private :
531 virtual void on_button_click()
532 {
533 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
534 if (!desktop) return;
536 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
537 if (!selection) return;
539 using Inkscape::Util::GSListConstIterator;
540 std::list<SPItem *> selected;
541 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
542 if (selected.empty()) return;
544 //Check 2 or more selected objects
545 if (selected.size() < 2) return;
547 // This bbox is cached between calls to randomize, so that there's no growth nor shrink
548 // nor drift on sequential randomizations. Discard cache on global (or better active
549 // desktop's) selection_change signal.
550 if (!_dialog.randomize_bbox_set) {
551 _dialog.randomize_bbox = selection->bounds();
552 _dialog.randomize_bbox_set = true;
553 }
555 // see comment in ActionAlign above
556 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
557 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
559 for (std::list<SPItem *>::iterator it(selected.begin());
560 it != selected.end();
561 ++it)
562 {
563 sp_document_ensure_up_to_date(SP_DT_DOCUMENT (desktop));
564 NR::Rect item_box = sp_item_bbox_desktop (*it);
565 // find new center, staying within bbox
566 double x = _dialog.randomize_bbox.min()[NR::X] + item_box.extent(NR::X)/2 +
567 g_random_double_range (0, _dialog.randomize_bbox.extent(NR::X) - item_box.extent(NR::X));
568 double y = _dialog.randomize_bbox.min()[NR::Y] + item_box.extent(NR::Y)/2 +
569 g_random_double_range (0, _dialog.randomize_bbox.extent(NR::Y) - item_box.extent(NR::Y));
570 // displacement is the new center minus old:
571 NR::Point t = NR::Point (x, y) - 0.5*(item_box.max() + item_box.min());
572 sp_item_move_rel(*it, NR::translate(t));
573 }
575 // restore compensation setting
576 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
578 sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP));
579 }
580 };
582 struct Baselines
583 {
584 SPItem *_item;
585 NR::Point _base;
586 NR::Dim2 _orientation;
587 Baselines(SPItem *item, NR::Point base, NR::Dim2 orientation) :
588 _item (item),
589 _base (base),
590 _orientation (orientation)
591 {}
592 };
594 bool operator< (const Baselines &a, const Baselines &b)
595 {
596 return (a._base[a._orientation] < b._base[b._orientation]);
597 }
599 class ActionBaseline : public Action {
600 public :
601 ActionBaseline(const Glib::ustring &id,
602 const Glib::ustring &tiptext,
603 guint row,
604 guint column,
605 AlignAndDistribute &dialog,
606 Gtk::Table &table,
607 NR::Dim2 orientation, bool distribute):
608 Action(id, tiptext, row, column,
609 table, dialog.tooltips(), dialog),
610 _orientation(orientation),
611 _distribute(distribute)
612 {}
614 private :
615 NR::Dim2 _orientation;
616 bool _distribute;
617 virtual void on_button_click()
618 {
619 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
620 if (!desktop) return;
622 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
623 if (!selection) return;
625 using Inkscape::Util::GSListConstIterator;
626 std::list<SPItem *> selected;
627 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
628 if (selected.empty()) return;
630 //Check 2 or more selected objects
631 if (selected.size() < 2) return;
633 NR::Point b_min = NR::Point (HUGE_VAL, HUGE_VAL);
634 NR::Point b_max = NR::Point (-HUGE_VAL, -HUGE_VAL);
636 std::vector<Baselines> sorted;
638 for (std::list<SPItem *>::iterator it(selected.begin());
639 it != selected.end();
640 ++it)
641 {
642 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
643 Inkscape::Text::Layout const *layout = te_get_layout(*it);
644 NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
645 if (base[NR::X] < b_min[NR::X]) b_min[NR::X] = base[NR::X];
646 if (base[NR::Y] < b_min[NR::Y]) b_min[NR::Y] = base[NR::Y];
647 if (base[NR::X] > b_max[NR::X]) b_max[NR::X] = base[NR::X];
648 if (base[NR::Y] > b_max[NR::Y]) b_max[NR::Y] = base[NR::Y];
650 Baselines b (*it, base, _orientation);
651 sorted.push_back(b);
652 }
653 }
655 if (sorted.size() <= 1) return;
657 //sort baselines
658 std::sort(sorted.begin(), sorted.end());
660 bool changed = false;
662 if (_distribute) {
663 double step = (b_max[_orientation] - b_min[_orientation])/(sorted.size() - 1);
664 for (unsigned int i = 0; i < sorted.size(); i++) {
665 SPItem *item = sorted[i]._item;
666 NR::Point base = sorted[i]._base;
667 NR::Point t(0.0, 0.0);
668 t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
669 sp_item_move_rel(item, NR::translate(t));
670 changed = true;
671 }
673 } else {
674 for (std::list<SPItem *>::iterator it(selected.begin());
675 it != selected.end();
676 ++it)
677 {
678 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
679 Inkscape::Text::Layout const *layout = te_get_layout(*it);
680 NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
681 NR::Point t(0.0, 0.0);
682 t[_orientation] = b_min[_orientation] - base[_orientation];
683 sp_item_move_rel(*it, NR::translate(t));
684 changed = true;
685 }
686 }
687 }
689 if (changed) {
690 sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP));
691 }
692 }
693 };
697 void on_tool_changed(Inkscape::Application *inkscape, SPEventContext *context, AlignAndDistribute *daad)
698 {
699 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
700 if (desktop && SP_DT_EVENTCONTEXT(desktop))
701 daad->setMode(tools_active(desktop) == TOOLS_NODES);
702 }
704 void on_selection_changed(Inkscape::Application *inkscape, Inkscape::Selection *selection, AlignAndDistribute *daad)
705 {
706 daad->randomize_bbox_set = false;
707 }
709 /////////////////////////////////////////////////////////
714 AlignAndDistribute::AlignAndDistribute()
715 : Dialog ("dialogs.align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
716 randomize_bbox (NR::Point (0, 0), NR::Point (0, 0)),
717 _alignFrame(_("Align")),
718 _distributeFrame(_("Distribute")),
719 _removeOverlapFrame(_("Remove overlaps")),
720 _nodesFrame(_("Nodes")),
721 _alignTable(2, 6, true),
722 _distributeTable(3, 6, true),
723 _removeOverlapTable(1, 5, false),
724 _nodesTable(1, 4, true),
725 _anchorLabel(_("Relative to: "))
726 {
728 //Instanciate the align buttons
729 addAlignButton("al_left_out",
730 _("Align right sides of objects to left side of anchor"),
731 0, 0);
732 addAlignButton("al_left_in",
733 _("Align left sides"),
734 0, 1);
735 addAlignButton("al_center_hor",
736 _("Center on vertical axis"),
737 0, 2);
738 addAlignButton("al_right_in",
739 _("Align right sides"),
740 0, 3);
741 addAlignButton("al_right_out",
742 _("Align left sides of objects to right side of anchor"),
743 0, 4);
744 addAlignButton("al_top_out",
745 _("Align bottoms of objects to top of anchor"),
746 1, 0);
747 addAlignButton("al_top_in",
748 _("Align tops"),
749 1, 1);
750 addAlignButton("al_center_ver",
751 _("Center on horizontal axis"),
752 1, 2);
753 addAlignButton("al_bottom_in",
754 _("Align bottoms"),
755 1, 3);
756 addAlignButton("al_bottom_out",
757 _("Align tops of objects to bottom of anchor"),
758 1, 4);
760 //Baseline aligns
761 addBaselineButton("al_baselines_vert",
762 _("Align baseline anchors of texts vertically"),
763 0, 5, this->align_table(), NR::X, false);
764 addBaselineButton("al_baselines_hor",
765 _("Align baseline anchors of texts horizontally"),
766 1, 5, this->align_table(), NR::Y, false);
768 //The distribute buttons
769 addDistributeButton("distribute_hdist",
770 _("Make horizontal gaps between objects equal"),
771 0, 4, true, NR::X, .5, .5);
773 addDistributeButton("distribute_left",
774 _("Distribute left sides equidistantly"),
775 0, 1, false, NR::X, 1., 0.);
776 addDistributeButton("distribute_hcentre",
777 _("Distribute centers equidistantly horizontally"),
778 0, 2, false, NR::X, .5, .5);
779 addDistributeButton("distribute_right",
780 _("Distribute right sides equidistantly"),
781 0, 3, false, NR::X, 0., 1.);
783 addDistributeButton("distribute_vdist",
784 _("Make vertical gaps between objects equal"),
785 1, 4, true, NR::Y, .5, .5);
787 addDistributeButton("distribute_top",
788 _("Distribute tops equidistantly"),
789 1, 1, false, NR::Y, 0, 1);
790 addDistributeButton("distribute_vcentre",
791 _("Distribute centers equidistantly vertically"),
792 1, 2, false, NR::Y, .5, .5);
793 addDistributeButton("distribute_bottom",
794 _("Distribute bottoms equidistantly"),
795 1, 3, false, NR::Y, 1., 0.);
797 //Baseline distribs
798 addBaselineButton("distribute_baselines_hor",
799 _("Distribute baseline anchors of texts horizontally"),
800 0, 5, this->distribute_table(), NR::X, true);
801 addBaselineButton("distribute_baselines_vert",
802 _("Distribute baseline anchors of texts vertically"),
803 1, 5, this->distribute_table(), NR::Y, true);
805 //Randomize & Unclump
806 addRandomizeButton("distribute_randomize",
807 _("Randomize centers in both dimensions"),
808 2, 2);
809 addUnclumpButton("unclump",
810 _("Unclump objects: try to equalize edge-to-edge distances"),
811 2, 4);
813 //Remove overlaps
814 addRemoveOverlapsButton("remove_overlaps",
815 _("Move objects as little as possible so that their bounding boxes do not overlap"),
816 0, 0);
818 //Node Mode buttons
819 addNodeButton("node_halign",
820 _("Align selected nodes horizontally"),
821 0, NR::X, false);
822 addNodeButton("node_valign",
823 _("Align selected nodes vertically"),
824 1, NR::Y, false);
825 addNodeButton("node_hdistribute",
826 _("Distribute selected nodes horizontally"),
827 2, NR::X, true);
828 addNodeButton("node_vdistribute",
829 _("Distribute selected nodes vertically"),
830 3, NR::Y, true);
832 //Rest of the widgetry
834 _combo.append_text(_("Last selected"));
835 _combo.append_text(_("First selected"));
836 _combo.append_text(_("Biggest item"));
837 _combo.append_text(_("Smallest item"));
838 _combo.append_text(_("Page"));
839 _combo.append_text(_("Drawing"));
840 _combo.append_text(_("Selection"));
842 _combo.set_active(6);
843 _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
845 _anchorBox.pack_start(_anchorLabel);
846 _anchorBox.pack_start(_combo);
848 _alignBox.pack_start(_anchorBox);
849 _alignBox.pack_start(_alignTable);
851 _alignFrame.add(_alignBox);
852 _distributeFrame.add(_distributeTable);
853 _removeOverlapFrame.add(_removeOverlapTable);
854 _nodesFrame.add(_nodesTable);
856 // Top level vbox
857 Gtk::VBox *vbox = get_vbox();
858 vbox->set_spacing(4);
860 // Notebook for individual transformations
862 vbox->pack_start(_alignFrame, true, true);
863 vbox->pack_start(_distributeFrame, true, true);
864 vbox->pack_start(_removeOverlapFrame, true, true);
865 vbox->pack_start(_nodesFrame, true, true);
867 //Connect to the global tool change signal
868 g_signal_connect (G_OBJECT (INKSCAPE), "set_eventcontext", G_CALLBACK (on_tool_changed), this);
870 // Connect to the global selection change, to invalidate cached randomize_bbox
871 g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
872 randomize_bbox = NR::Rect (NR::Point (0, 0), NR::Point (0, 0));
873 randomize_bbox_set = false;
875 show_all_children();
877 on_tool_changed (NULL, NULL, this); // set current mode
878 }
880 AlignAndDistribute::~AlignAndDistribute()
881 {
882 sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
884 for (std::list<Action *>::iterator it = _actionList.begin();
885 it != _actionList.end();
886 it ++)
887 delete *it;
888 }
890 void AlignAndDistribute::on_ref_change(){
891 //Make blink the master
892 }
897 void AlignAndDistribute::setMode(bool nodeEdit)
898 {
899 //Act on widgets used in node mode
900 void ( Gtk::Widget::*mNode) () = nodeEdit ?
901 &Gtk::Widget::show_all : &Gtk::Widget::hide_all;
903 //Act on widgets used in selection mode
904 void ( Gtk::Widget::*mSel) () = nodeEdit ?
905 &Gtk::Widget::hide_all : &Gtk::Widget::show_all;
908 ((_alignFrame).*(mSel))();
909 ((_distributeFrame).*(mSel))();
910 ((_removeOverlapFrame).*(mSel))();
911 ((_nodesFrame).*(mNode))();
913 }
914 void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
915 guint row, guint col)
916 {
917 _actionList.push_back(
918 new ActionAlign(
919 id, tiptext, row, col,
920 *this , col + row * 5));
921 }
922 void AlignAndDistribute::addDistributeButton(const Glib::ustring &id, const Glib::ustring tiptext,
923 guint row, guint col, bool onInterSpace,
924 NR::Dim2 orientation, float kBegin, float kEnd)
925 {
926 _actionList.push_back(
927 new ActionDistribute(
928 id, tiptext, row, col, *this ,
929 onInterSpace, orientation,
930 kBegin, kEnd
931 )
932 );
933 }
935 void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
936 guint col, NR::Dim2 orientation, bool distribute)
937 {
938 _actionList.push_back(
939 new ActionNode(
940 id, tiptext, col,
941 *this, orientation, distribute));
942 }
944 void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
945 guint row, guint col)
946 {
947 _actionList.push_back(
948 new ActionRemoveOverlaps(
949 id, tiptext, row, col, *this)
950 );
951 }
953 void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
954 guint row, guint col)
955 {
956 _actionList.push_back(
957 new ActionUnclump(
958 id, tiptext, row, col, *this)
959 );
960 }
962 void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
963 guint row, guint col)
964 {
965 _actionList.push_back(
966 new ActionRandomize(
967 id, tiptext, row, col, *this)
968 );
969 }
971 void AlignAndDistribute::addBaselineButton(const Glib::ustring &id, const Glib::ustring tiptext,
972 guint row, guint col, Gtk::Table &table, NR::Dim2 orientation, bool distribute)
973 {
974 _actionList.push_back(
975 new ActionBaseline(
976 id, tiptext, row, col,
977 *this, table, orientation, distribute));
978 }
983 std::list<SPItem *>::iterator AlignAndDistribute::find_master( std::list<SPItem *> &list, bool horizontal){
984 std::list<SPItem *>::iterator master = list.end();
985 switch (getAlignTarget()) {
986 case LAST:
987 return list.begin();
988 break;
990 case FIRST:
991 return --(list.end());
992 break;
994 case BIGGEST:
995 {
996 gdouble max = -1e18;
997 for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
998 NR::Rect b = sp_item_bbox_desktop (*it);
999 gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
1000 if (dim > max) {
1001 max = dim;
1002 master = it;
1003 }
1004 }
1005 return master;
1006 break;
1007 }
1009 case SMALLEST:
1010 {
1011 gdouble max = 1e18;
1012 for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1013 NR::Rect b = sp_item_bbox_desktop (*it);
1014 gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
1015 if (dim < max) {
1016 max = dim;
1017 master = it;
1018 }
1019 }
1020 return master;
1021 break;
1022 }
1024 default:
1025 g_assert_not_reached ();
1026 break;
1028 } // end of switch statement
1029 return NULL;
1030 }
1032 AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
1033 return AlignTarget(_combo.get_active_row_number());
1034 }
1038 } // namespace Dialog
1039 } // namespace UI
1040 } // namespace Inkscape
1042 /*
1043 Local Variables:
1044 mode:c++
1045 c-file-style:"stroustrup"
1046 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1047 indent-tabs-mode:nil
1048 fill-column:99
1049 End:
1050 */
1051 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :