Code

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