dda9cad5f0a29978b17e3c21354c213d47093d73
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, Inkscape::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_desktop_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_desktop_document(desktop)),
163 a.my1 * sp_document_height(sp_desktop_document(desktop)));
164 break;
166 case AlignAndDistribute::DRAWING:
167 {
168 NR::Rect b = sp_item_bbox_desktop
169 ( (SPItem *) sp_document_root (sp_desktop_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_desktop_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_desktop_document (desktop) , SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
219 /* TODO: annotate */ "align-and-distribute.cpp:219" );
220 }
223 }
224 guint _index;
225 AlignAndDistribute &_dialog;
227 static const Coeffs _allCoeffs[10];
229 };
230 ActionAlign::Coeffs const ActionAlign::_allCoeffs[10] = {
231 {1., 0., 0., 0., 0., 1., 0., 0.},
232 {1., 0., 0., 0., 1., 0., 0., 0.},
233 {.5, .5, 0., 0., .5, .5, 0., 0.},
234 {0., 1., 0., 0., 0., 1., 0., 0.},
235 {0., 1., 0., 0., 1., 0., 0., 0.},
236 {0., 0., 0., 1., 0., 0., 1., 0.},
237 {0., 0., 0., 1., 0., 0., 0., 1.},
238 {0., 0., .5, .5, 0., 0., .5, .5},
239 {0., 0., 1., 0., 0., 0., 1., 0.},
240 {0., 0., 1., 0., 0., 0., 0., 1.}
241 };
243 struct BBoxSort
244 {
245 SPItem *item;
246 float anchor;
247 NR::Rect bbox;
248 BBoxSort(SPItem *pItem, NR::Dim2 orientation, double kBegin, double kEnd) :
249 item(pItem),
250 bbox (sp_item_bbox_desktop (pItem))
251 {
252 anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
253 }
254 BBoxSort(const BBoxSort &rhs):
255 //NOTE : this copy ctor is called O(sort) when sorting the vector
256 //this is bad. The vector should be a vector of pointers.
257 //But I'll wait the bohem GC before doing that
258 item(rhs.item), anchor(rhs.anchor), bbox(rhs.bbox) {
259 }
260 };
261 bool operator< (const BBoxSort &a, const BBoxSort &b)
262 {
263 return (a.anchor < b.anchor);
264 }
266 class ActionDistribute : public Action {
267 public :
268 ActionDistribute(const Glib::ustring &id,
269 const Glib::ustring &tiptext,
270 guint row, guint column,
271 AlignAndDistribute &dialog,
272 bool onInterSpace,
273 NR::Dim2 orientation,
274 double kBegin, double kEnd
275 ):
276 Action(id, tiptext, row, column,
277 dialog.distribute_table(), dialog.tooltips(), dialog),
278 _dialog(dialog),
279 _onInterSpace(onInterSpace),
280 _orientation(orientation),
281 _kBegin(kBegin),
282 _kEnd( kEnd)
283 {}
285 private :
286 virtual void on_button_click() {
287 //Retreive selected objects
288 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
289 if (!desktop) return;
291 Inkscape::Selection *selection = sp_desktop_selection(desktop);
292 if (!selection) return;
294 using Inkscape::Util::GSListConstIterator;
295 std::list<SPItem *> selected;
296 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
297 if (selected.empty()) return;
299 //Check 2 or more selected objects
300 std::list<SPItem *>::iterator second(selected.begin());
301 ++second;
302 if (second == selected.end()) return;
305 std::vector< BBoxSort > sorted;
306 for (std::list<SPItem *>::iterator it(selected.begin());
307 it != selected.end();
308 ++it)
309 {
310 BBoxSort b (*it, _orientation, _kBegin, _kEnd);
311 sorted.push_back(b);
312 }
313 //sort bbox by anchors
314 std::sort(sorted.begin(), sorted.end());
316 // see comment in ActionAlign above
317 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
318 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
320 unsigned int len = sorted.size();
321 bool changed = false;
322 if (_onInterSpace)
323 {
324 //overall bboxes span
325 float dist = (sorted.back().bbox.max()[_orientation] -
326 sorted.front().bbox.min()[_orientation]);
327 //space eaten by bboxes
328 float span = 0;
329 for (unsigned int i = 0; i < len; i++)
330 {
331 span += sorted[i].bbox.extent(_orientation);
332 }
333 //new distance between each bbox
334 float step = (dist - span) / (len - 1);
335 float pos = sorted.front().bbox.min()[_orientation];
336 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
337 it < sorted.end();
338 it ++ )
339 {
340 if (!NR_DF_TEST_CLOSE (pos, it->bbox.min()[_orientation], 1e-6)) {
341 NR::Point t(0.0, 0.0);
342 t[_orientation] = pos - it->bbox.min()[_orientation];
343 sp_item_move_rel(it->item, NR::translate(t));
344 changed = true;
345 }
346 pos += it->bbox.extent(_orientation);
347 pos += step;
348 }
349 }
350 else
351 {
352 //overall anchor span
353 float dist = sorted.back().anchor - sorted.front().anchor;
354 //distance between anchors
355 float step = dist / (len - 1);
357 for ( unsigned int i = 0; i < len ; i ++ )
358 {
359 BBoxSort & it(sorted[i]);
360 //new anchor position
361 float pos = sorted.front().anchor + i * step;
362 //Don't move if we are really close
363 if (!NR_DF_TEST_CLOSE (pos, it.anchor, 1e-6)) {
364 //Compute translation
365 NR::Point t(0.0, 0.0);
366 t[_orientation] = pos - it.anchor;
367 //translate
368 sp_item_move_rel(it.item, NR::translate(t));
369 changed = true;
370 }
371 }
372 }
374 // restore compensation setting
375 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
377 if (changed) {
378 sp_document_done ( sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
379 /* TODO: annotate */ "align-and-distribute.cpp:379");
380 }
381 }
382 guint _index;
383 AlignAndDistribute &_dialog;
384 bool _onInterSpace;
385 NR::Dim2 _orientation;
387 double _kBegin;
388 double _kEnd;
390 };
393 class ActionNode : public Action {
394 public :
395 ActionNode(const Glib::ustring &id,
396 const Glib::ustring &tiptext,
397 guint column,
398 AlignAndDistribute &dialog,
399 NR::Dim2 orientation, bool distribute):
400 Action(id, tiptext, 0, column,
401 dialog.nodes_table(), dialog.tooltips(), dialog),
402 _orientation(orientation),
403 _distribute(distribute)
404 {}
406 private :
407 NR::Dim2 _orientation;
408 bool _distribute;
409 virtual void on_button_click()
410 {
412 if (!SP_ACTIVE_DESKTOP) return;
413 SPEventContext *event_context = sp_desktop_event_context(SP_ACTIVE_DESKTOP);
414 if (!SP_IS_NODE_CONTEXT (event_context)) return ;
416 Inkscape::NodePath::Path *nodepath = SP_NODE_CONTEXT (event_context)->nodepath;
417 if (!nodepath) return;
418 if (_distribute)
419 sp_nodepath_selected_distribute(nodepath, _orientation);
420 else
421 sp_nodepath_selected_align(nodepath, _orientation);
423 }
424 };
426 class ActionRemoveOverlaps : public Action {
427 private:
428 Gtk::Label removeOverlapXGapLabel;
429 Gtk::Label removeOverlapYGapLabel;
430 Gtk::SpinButton removeOverlapXGap;
431 Gtk::SpinButton removeOverlapYGap;
433 public:
434 ActionRemoveOverlaps(Glib::ustring const &id,
435 Glib::ustring const &tiptext,
436 guint row,
437 guint column,
438 AlignAndDistribute &dialog) :
439 Action(id, tiptext, row, column + 4,
440 dialog.removeOverlap_table(), dialog.tooltips(), dialog)
441 {
442 dialog.removeOverlap_table().set_col_spacings(3);
444 removeOverlapXGap.set_digits(1);
445 removeOverlapXGap.set_size_request(60, -1);
446 removeOverlapXGap.set_increments(1.0, 5.0);
447 removeOverlapXGap.set_range(-1000.0, 1000.0);
448 removeOverlapXGap.set_value(0);
449 dialog.tooltips().set_tip(removeOverlapXGap,
450 _("Minimum horizontal gap (in px units) between bounding boxes"));
451 /* TRANSLATORS: Horizontal gap */
452 removeOverlapXGapLabel.set_label(_("H:"));
454 removeOverlapYGap.set_digits(1);
455 removeOverlapYGap.set_size_request(60, -1);
456 removeOverlapYGap.set_increments(1.0, 5.0);
457 removeOverlapYGap.set_range(-1000.0, 1000.0);
458 removeOverlapYGap.set_value(0);
459 dialog.tooltips().set_tip(removeOverlapYGap,
460 _("Minimum vertical gap (in px units) between bounding boxes"));
461 /* TRANSLATORS: Vertical gap */
462 removeOverlapYGapLabel.set_label(_("V:"));
464 dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
465 dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
466 dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, column+3, row, row+1, Gtk::FILL, Gtk::FILL);
467 dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
469 }
471 private :
472 virtual void on_button_click()
473 {
474 if (!SP_ACTIVE_DESKTOP) return;
476 // see comment in ActionAlign above
477 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
478 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
480 // xGap and yGap are the minimum space required between bounding rectangles.
481 double const xGap = removeOverlapXGap.get_value();
482 double const yGap = removeOverlapYGap.get_value();
483 removeoverlap(sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList(),
484 xGap, yGap);
486 // restore compensation setting
487 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
489 sp_document_done(sp_desktop_document(SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
490 /* TODO: annotate */ "align-and-distribute.cpp:490");
491 }
492 };
494 class ActionGraphLayout : public Action {
495 public:
496 ActionGraphLayout(Glib::ustring const &id,
497 Glib::ustring const &tiptext,
498 guint row,
499 guint column,
500 AlignAndDistribute &dialog) :
501 Action(id, tiptext, row, column + 4,
502 dialog.graphLayout_table(), dialog.tooltips(), dialog)
503 {}
505 private :
506 virtual void on_button_click()
507 {
508 if (!SP_ACTIVE_DESKTOP) return;
510 // see comment in ActionAlign above
511 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
512 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
514 graphlayout(sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList(),100);
516 // restore compensation setting
517 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
519 sp_document_done(sp_desktop_document(SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
520 /* TODO: annotate */ "align-and-distribute.cpp:520");
521 }
522 };
524 class ActionUnclump : public Action {
525 public :
526 ActionUnclump(const Glib::ustring &id,
527 const Glib::ustring &tiptext,
528 guint row,
529 guint column,
530 AlignAndDistribute &dialog):
531 Action(id, tiptext, row, column,
532 dialog.distribute_table(), dialog.tooltips(), dialog)
533 {}
535 private :
536 virtual void on_button_click()
537 {
538 if (!SP_ACTIVE_DESKTOP) return;
540 // see comment in ActionAlign above
541 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
542 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
544 unclump ((GSList *) sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList());
546 // restore compensation setting
547 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
549 sp_document_done (sp_desktop_document (SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
550 /* TODO: annotate */ "align-and-distribute.cpp:550");
551 }
552 };
554 class ActionRandomize : public Action {
555 public :
556 ActionRandomize(const Glib::ustring &id,
557 const Glib::ustring &tiptext,
558 guint row,
559 guint column,
560 AlignAndDistribute &dialog):
561 Action(id, tiptext, row, column,
562 dialog.distribute_table(), dialog.tooltips(), dialog)
563 {}
565 private :
566 virtual void on_button_click()
567 {
568 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
569 if (!desktop) return;
571 Inkscape::Selection *selection = sp_desktop_selection(desktop);
572 if (!selection) return;
574 using Inkscape::Util::GSListConstIterator;
575 std::list<SPItem *> selected;
576 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
577 if (selected.empty()) return;
579 //Check 2 or more selected objects
580 if (selected.size() < 2) return;
582 // This bbox is cached between calls to randomize, so that there's no growth nor shrink
583 // nor drift on sequential randomizations. Discard cache on global (or better active
584 // desktop's) selection_change signal.
585 if (!_dialog.randomize_bbox_set) {
586 _dialog.randomize_bbox = selection->bounds();
587 _dialog.randomize_bbox_set = true;
588 }
590 // see comment in ActionAlign above
591 int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
592 prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
594 for (std::list<SPItem *>::iterator it(selected.begin());
595 it != selected.end();
596 ++it)
597 {
598 sp_document_ensure_up_to_date(sp_desktop_document (desktop));
599 NR::Rect item_box = sp_item_bbox_desktop (*it);
600 // find new center, staying within bbox
601 double x = _dialog.randomize_bbox.min()[NR::X] + item_box.extent(NR::X)/2 +
602 g_random_double_range (0, _dialog.randomize_bbox.extent(NR::X) - item_box.extent(NR::X));
603 double y = _dialog.randomize_bbox.min()[NR::Y] + item_box.extent(NR::Y)/2 +
604 g_random_double_range (0, _dialog.randomize_bbox.extent(NR::Y) - item_box.extent(NR::Y));
605 // displacement is the new center minus old:
606 NR::Point t = NR::Point (x, y) - 0.5*(item_box.max() + item_box.min());
607 sp_item_move_rel(*it, NR::translate(t));
608 }
610 // restore compensation setting
611 prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
613 sp_document_done (sp_desktop_document (SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
614 /* TODO: annotate */ "align-and-distribute.cpp:614");
615 }
616 };
618 struct Baselines
619 {
620 SPItem *_item;
621 NR::Point _base;
622 NR::Dim2 _orientation;
623 Baselines(SPItem *item, NR::Point base, NR::Dim2 orientation) :
624 _item (item),
625 _base (base),
626 _orientation (orientation)
627 {}
628 };
630 bool operator< (const Baselines &a, const Baselines &b)
631 {
632 return (a._base[a._orientation] < b._base[b._orientation]);
633 }
635 class ActionBaseline : public Action {
636 public :
637 ActionBaseline(const Glib::ustring &id,
638 const Glib::ustring &tiptext,
639 guint row,
640 guint column,
641 AlignAndDistribute &dialog,
642 Gtk::Table &table,
643 NR::Dim2 orientation, bool distribute):
644 Action(id, tiptext, row, column,
645 table, dialog.tooltips(), dialog),
646 _orientation(orientation),
647 _distribute(distribute)
648 {}
650 private :
651 NR::Dim2 _orientation;
652 bool _distribute;
653 virtual void on_button_click()
654 {
655 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
656 if (!desktop) return;
658 Inkscape::Selection *selection = sp_desktop_selection(desktop);
659 if (!selection) return;
661 using Inkscape::Util::GSListConstIterator;
662 std::list<SPItem *> selected;
663 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
664 if (selected.empty()) return;
666 //Check 2 or more selected objects
667 if (selected.size() < 2) return;
669 NR::Point b_min = NR::Point (HUGE_VAL, HUGE_VAL);
670 NR::Point b_max = NR::Point (-HUGE_VAL, -HUGE_VAL);
672 std::vector<Baselines> sorted;
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 if (base[NR::X] < b_min[NR::X]) b_min[NR::X] = base[NR::X];
682 if (base[NR::Y] < b_min[NR::Y]) b_min[NR::Y] = base[NR::Y];
683 if (base[NR::X] > b_max[NR::X]) b_max[NR::X] = base[NR::X];
684 if (base[NR::Y] > b_max[NR::Y]) b_max[NR::Y] = base[NR::Y];
686 Baselines b (*it, base, _orientation);
687 sorted.push_back(b);
688 }
689 }
691 if (sorted.size() <= 1) return;
693 //sort baselines
694 std::sort(sorted.begin(), sorted.end());
696 bool changed = false;
698 if (_distribute) {
699 double step = (b_max[_orientation] - b_min[_orientation])/(sorted.size() - 1);
700 for (unsigned int i = 0; i < sorted.size(); i++) {
701 SPItem *item = sorted[i]._item;
702 NR::Point base = sorted[i]._base;
703 NR::Point t(0.0, 0.0);
704 t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
705 sp_item_move_rel(item, NR::translate(t));
706 changed = true;
707 }
709 } else {
710 for (std::list<SPItem *>::iterator it(selected.begin());
711 it != selected.end();
712 ++it)
713 {
714 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
715 Inkscape::Text::Layout const *layout = te_get_layout(*it);
716 NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
717 NR::Point t(0.0, 0.0);
718 t[_orientation] = b_min[_orientation] - base[_orientation];
719 sp_item_move_rel(*it, NR::translate(t));
720 changed = true;
721 }
722 }
723 }
725 if (changed) {
726 sp_document_done (sp_desktop_document (SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
727 /* TODO: annotate */ "align-and-distribute.cpp:727");
728 }
729 }
730 };
734 void on_tool_changed(Inkscape::Application *inkscape, SPEventContext *context, AlignAndDistribute *daad)
735 {
736 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
737 if (desktop && sp_desktop_event_context(desktop))
738 daad->setMode(tools_active(desktop) == TOOLS_NODES);
739 }
741 void on_selection_changed(Inkscape::Application *inkscape, Inkscape::Selection *selection, AlignAndDistribute *daad)
742 {
743 daad->randomize_bbox_set = false;
744 }
746 /////////////////////////////////////////////////////////
751 AlignAndDistribute::AlignAndDistribute()
752 : Dialog ("dialogs.align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
753 randomize_bbox (NR::Point (0, 0), NR::Point (0, 0)),
754 _alignFrame(_("Align")),
755 _distributeFrame(_("Distribute")),
756 _removeOverlapFrame(_("Remove overlaps")),
757 _graphLayoutFrame(_("Connector network layout")),
758 _nodesFrame(_("Nodes")),
759 _alignTable(2, 6, true),
760 _distributeTable(3, 6, true),
761 _removeOverlapTable(1, 5, false),
762 _graphLayoutTable(1, 5, false),
763 _nodesTable(1, 4, true),
764 _anchorLabel(_("Relative to: "))
765 {
767 //Instanciate the align buttons
768 addAlignButton("al_left_out",
769 _("Align right sides of objects to left side of anchor"),
770 0, 0);
771 addAlignButton("al_left_in",
772 _("Align left sides"),
773 0, 1);
774 addAlignButton("al_center_hor",
775 _("Center on vertical axis"),
776 0, 2);
777 addAlignButton("al_right_in",
778 _("Align right sides"),
779 0, 3);
780 addAlignButton("al_right_out",
781 _("Align left sides of objects to right side of anchor"),
782 0, 4);
783 addAlignButton("al_top_out",
784 _("Align bottoms of objects to top of anchor"),
785 1, 0);
786 addAlignButton("al_top_in",
787 _("Align tops"),
788 1, 1);
789 addAlignButton("al_center_ver",
790 _("Center on horizontal axis"),
791 1, 2);
792 addAlignButton("al_bottom_in",
793 _("Align bottoms"),
794 1, 3);
795 addAlignButton("al_bottom_out",
796 _("Align tops of objects to bottom of anchor"),
797 1, 4);
799 //Baseline aligns
800 addBaselineButton("al_baselines_vert",
801 _("Align baseline anchors of texts vertically"),
802 0, 5, this->align_table(), NR::X, false);
803 addBaselineButton("al_baselines_hor",
804 _("Align baseline anchors of texts horizontally"),
805 1, 5, this->align_table(), NR::Y, false);
807 //The distribute buttons
808 addDistributeButton("distribute_hdist",
809 _("Make horizontal gaps between objects equal"),
810 0, 4, true, NR::X, .5, .5);
812 addDistributeButton("distribute_left",
813 _("Distribute left sides equidistantly"),
814 0, 1, false, NR::X, 1., 0.);
815 addDistributeButton("distribute_hcentre",
816 _("Distribute centers equidistantly horizontally"),
817 0, 2, false, NR::X, .5, .5);
818 addDistributeButton("distribute_right",
819 _("Distribute right sides equidistantly"),
820 0, 3, false, NR::X, 0., 1.);
822 addDistributeButton("distribute_vdist",
823 _("Make vertical gaps between objects equal"),
824 1, 4, true, NR::Y, .5, .5);
826 addDistributeButton("distribute_top",
827 _("Distribute tops equidistantly"),
828 1, 1, false, NR::Y, 0, 1);
829 addDistributeButton("distribute_vcentre",
830 _("Distribute centers equidistantly vertically"),
831 1, 2, false, NR::Y, .5, .5);
832 addDistributeButton("distribute_bottom",
833 _("Distribute bottoms equidistantly"),
834 1, 3, false, NR::Y, 1., 0.);
836 //Baseline distribs
837 addBaselineButton("distribute_baselines_hor",
838 _("Distribute baseline anchors of texts horizontally"),
839 0, 5, this->distribute_table(), NR::X, true);
840 addBaselineButton("distribute_baselines_vert",
841 _("Distribute baseline anchors of texts vertically"),
842 1, 5, this->distribute_table(), NR::Y, true);
844 //Randomize & Unclump
845 addRandomizeButton("distribute_randomize",
846 _("Randomize centers in both dimensions"),
847 2, 2);
848 addUnclumpButton("unclump",
849 _("Unclump objects: try to equalize edge-to-edge distances"),
850 2, 4);
852 //Remove overlaps
853 addRemoveOverlapsButton("remove_overlaps",
854 _("Move objects as little as possible so that their bounding boxes do not overlap"),
855 0, 0);
856 //Graph Layout
857 addGraphLayoutButton("graph_layout",
858 _("Nicely arrange selected connector network"),
859 0, 0);
861 //Node Mode buttons
862 addNodeButton("node_halign",
863 _("Align selected nodes horizontally"),
864 0, NR::X, false);
865 addNodeButton("node_valign",
866 _("Align selected nodes vertically"),
867 1, NR::Y, false);
868 addNodeButton("node_hdistribute",
869 _("Distribute selected nodes horizontally"),
870 2, NR::X, true);
871 addNodeButton("node_vdistribute",
872 _("Distribute selected nodes vertically"),
873 3, NR::Y, true);
875 //Rest of the widgetry
877 _combo.append_text(_("Last selected"));
878 _combo.append_text(_("First selected"));
879 _combo.append_text(_("Biggest item"));
880 _combo.append_text(_("Smallest item"));
881 _combo.append_text(_("Page"));
882 _combo.append_text(_("Drawing"));
883 _combo.append_text(_("Selection"));
885 _combo.set_active(6);
886 _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
888 _anchorBox.pack_start(_anchorLabel);
889 _anchorBox.pack_start(_combo);
891 _alignBox.pack_start(_anchorBox);
892 _alignBox.pack_start(_alignTable);
894 _alignFrame.add(_alignBox);
895 _distributeFrame.add(_distributeTable);
896 _removeOverlapFrame.add(_removeOverlapTable);
897 _graphLayoutFrame.add(_graphLayoutTable);
898 _nodesFrame.add(_nodesTable);
900 // Top level vbox
901 Gtk::VBox *vbox = get_vbox();
902 vbox->set_spacing(4);
904 // Notebook for individual transformations
906 vbox->pack_start(_alignFrame, true, true);
907 vbox->pack_start(_distributeFrame, true, true);
908 vbox->pack_start(_removeOverlapFrame, true, true);
909 vbox->pack_start(_graphLayoutFrame, true, true);
910 vbox->pack_start(_nodesFrame, true, true);
912 //Connect to the global tool change signal
913 g_signal_connect (G_OBJECT (INKSCAPE), "set_eventcontext", G_CALLBACK (on_tool_changed), this);
915 // Connect to the global selection change, to invalidate cached randomize_bbox
916 g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
917 randomize_bbox = NR::Rect (NR::Point (0, 0), NR::Point (0, 0));
918 randomize_bbox_set = false;
920 show_all_children();
922 on_tool_changed (NULL, NULL, this); // set current mode
923 }
925 AlignAndDistribute::~AlignAndDistribute()
926 {
927 sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
929 for (std::list<Action *>::iterator it = _actionList.begin();
930 it != _actionList.end();
931 it ++)
932 delete *it;
933 }
935 void AlignAndDistribute::on_ref_change(){
936 //Make blink the master
937 }
942 void AlignAndDistribute::setMode(bool nodeEdit)
943 {
944 //Act on widgets used in node mode
945 void ( Gtk::Widget::*mNode) () = nodeEdit ?
946 &Gtk::Widget::show_all : &Gtk::Widget::hide_all;
948 //Act on widgets used in selection mode
949 void ( Gtk::Widget::*mSel) () = nodeEdit ?
950 &Gtk::Widget::hide_all : &Gtk::Widget::show_all;
953 ((_alignFrame).*(mSel))();
954 ((_distributeFrame).*(mSel))();
955 ((_removeOverlapFrame).*(mSel))();
956 ((_graphLayoutFrame).*(mSel))();
957 ((_nodesFrame).*(mNode))();
959 }
960 void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
961 guint row, guint col)
962 {
963 _actionList.push_back(
964 new ActionAlign(
965 id, tiptext, row, col,
966 *this , col + row * 5));
967 }
968 void AlignAndDistribute::addDistributeButton(const Glib::ustring &id, const Glib::ustring tiptext,
969 guint row, guint col, bool onInterSpace,
970 NR::Dim2 orientation, float kBegin, float kEnd)
971 {
972 _actionList.push_back(
973 new ActionDistribute(
974 id, tiptext, row, col, *this ,
975 onInterSpace, orientation,
976 kBegin, kEnd
977 )
978 );
979 }
981 void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
982 guint col, NR::Dim2 orientation, bool distribute)
983 {
984 _actionList.push_back(
985 new ActionNode(
986 id, tiptext, col,
987 *this, orientation, distribute));
988 }
990 void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
991 guint row, guint col)
992 {
993 _actionList.push_back(
994 new ActionRemoveOverlaps(
995 id, tiptext, row, col, *this)
996 );
997 }
999 void AlignAndDistribute::addGraphLayoutButton(const Glib::ustring &id, const Glib::ustring tiptext,
1000 guint row, guint col)
1001 {
1002 _actionList.push_back(
1003 new ActionGraphLayout(
1004 id, tiptext, row, col, *this)
1005 );
1006 }
1008 void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
1009 guint row, guint col)
1010 {
1011 _actionList.push_back(
1012 new ActionUnclump(
1013 id, tiptext, row, col, *this)
1014 );
1015 }
1017 void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1018 guint row, guint col)
1019 {
1020 _actionList.push_back(
1021 new ActionRandomize(
1022 id, tiptext, row, col, *this)
1023 );
1024 }
1026 void AlignAndDistribute::addBaselineButton(const Glib::ustring &id, const Glib::ustring tiptext,
1027 guint row, guint col, Gtk::Table &table, NR::Dim2 orientation, bool distribute)
1028 {
1029 _actionList.push_back(
1030 new ActionBaseline(
1031 id, tiptext, row, col,
1032 *this, table, orientation, distribute));
1033 }
1038 std::list<SPItem *>::iterator AlignAndDistribute::find_master( std::list<SPItem *> &list, bool horizontal){
1039 std::list<SPItem *>::iterator master = list.end();
1040 switch (getAlignTarget()) {
1041 case LAST:
1042 return list.begin();
1043 break;
1045 case FIRST:
1046 return --(list.end());
1047 break;
1049 case BIGGEST:
1050 {
1051 gdouble max = -1e18;
1052 for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1053 NR::Rect b = sp_item_bbox_desktop (*it);
1054 gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
1055 if (dim > max) {
1056 max = dim;
1057 master = it;
1058 }
1059 }
1060 return master;
1061 break;
1062 }
1064 case SMALLEST:
1065 {
1066 gdouble max = 1e18;
1067 for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1068 NR::Rect b = sp_item_bbox_desktop (*it);
1069 gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
1070 if (dim < max) {
1071 max = dim;
1072 master = it;
1073 }
1074 }
1075 return master;
1076 break;
1077 }
1079 default:
1080 g_assert_not_reached ();
1081 break;
1083 } // end of switch statement
1084 return master;
1085 }
1087 AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
1088 return AlignTarget(_combo.get_active_row_number());
1089 }
1093 } // namespace Dialog
1094 } // namespace UI
1095 } // namespace Inkscape
1097 /*
1098 Local Variables:
1099 mode:c++
1100 c-file-style:"stroustrup"
1101 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1102 indent-tabs-mode:nil
1103 fill-column:99
1104 End:
1105 */
1106 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :