Code

remove screen pixel toggle for now, add always-snap widget for all
[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"
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
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)
261     return (a.anchor < b.anchor);
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);
440     
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
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)
596     return (a._base[a._orientation] < b._base[b._orientation]);
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)
699     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
700     if (desktop && SP_DT_EVENTCONTEXT(desktop))
701         daad->setMode(tools_active(desktop) == TOOLS_NODES);
704 void on_selection_changed(Inkscape::Application *inkscape, Inkscape::Selection *selection, AlignAndDistribute *daad)
706     daad->randomize_bbox_set = false;
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: "))
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
880 AlignAndDistribute::~AlignAndDistribute() 
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;
890 void AlignAndDistribute::on_ref_change(){
891 //Make blink the master
897 void AlignAndDistribute::setMode(bool nodeEdit)
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))();
914 void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
915                                  guint row, guint col)
917     _actionList.push_back(
918         new ActionAlign(
919             id, tiptext, row, col,
920             *this , col + row * 5));
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)
926     _actionList.push_back(
927         new ActionDistribute(
928             id, tiptext, row, col, *this ,
929             onInterSpace, orientation,
930             kBegin, kEnd
931             )
932         );
935 void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
936                    guint col, NR::Dim2 orientation, bool distribute)
938     _actionList.push_back(
939         new ActionNode(
940             id, tiptext, col,
941             *this, orientation, distribute));
944 void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
945                                       guint row, guint col)
947     _actionList.push_back(
948         new ActionRemoveOverlaps(
949             id, tiptext, row, col, *this)
950         );
953 void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
954                                       guint row, guint col)
956     _actionList.push_back(
957         new ActionUnclump(
958             id, tiptext, row, col, *this)
959         );
962 void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
963                                       guint row, guint col)
965     _actionList.push_back(
966         new ActionRandomize(
967             id, tiptext, row, col, *this)
968         );
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)
974     _actionList.push_back(
975         new ActionBaseline(
976             id, tiptext, row, col, 
977             *this, table, orientation, distribute));
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;
1032 AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
1033     return AlignTarget(_combo.get_active_row_number());
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 :