Code

convert almost all libnrtype to Geom::
[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 = _dialog.getDesktop();
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         Geom::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             boost::optional<NR::Rect> b = sp_item_bbox_desktop (thing);
157             if (b) {
158                 mp = Geom::Point(a.mx0 * b->min()[Geom::X] + a.mx1 * b->max()[Geom::X],
159                                a.my0 * b->min()[Geom::Y] + a.my1 * b->max()[Geom::Y]);
160             } else {
161                 return;
162             }
163             break;
164         }
166         case AlignAndDistribute::PAGE:
167             mp = Geom::Point(a.mx1 * sp_document_width(sp_desktop_document(desktop)),
168                            a.my1 * sp_document_height(sp_desktop_document(desktop)));
169             break;
171         case AlignAndDistribute::DRAWING:
172         {
173             boost::optional<NR::Rect> b = sp_item_bbox_desktop
174                 ( (SPItem *) sp_document_root (sp_desktop_document (desktop)) );
175             if (b) {
176                 mp = Geom::Point(a.mx0 * b->min()[Geom::X] + a.mx1 * b->max()[Geom::X],
177                                a.my0 * b->min()[Geom::Y] + a.my1 * b->max()[Geom::Y]);
178             } else {
179                 return;
180             }
181             break;
182         }
184         case AlignAndDistribute::SELECTION:
185         {
186             boost::optional<NR::Rect> b =  selection->bounds();
187             if (b) {
188                 mp = Geom::Point(a.mx0 * b->min()[Geom::X] + a.mx1 * b->max()[Geom::X],
189                                a.my0 * b->min()[Geom::Y] + a.my1 * b->max()[Geom::Y]);
190             } else {
191                 return;
192             }
193             break;
194         }
196         default:
197             g_assert_not_reached ();
198             break;
199         };  // end of switch
201         // Top hack: temporarily set clone compensation to unmoved, so that we can align/distribute
202         // clones with their original (and the move of the original does not disturb the
203         // clones). The only problem with this is that if there are outside-of-selection clones of
204         // a selected original, they will be unmoved too, possibly contrary to user's
205         // expecation. However this is a minor point compared to making align/distribute always
206         // work as expected, and "unmoved" is the default option anyway.
207         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
208         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
210         bool changed = false;
211         //Move each item in the selected list
212         for (std::list<SPItem *>::iterator it(selected.begin());
213              it != selected.end();
214              it++)
215         {
216             sp_document_ensure_up_to_date(sp_desktop_document (desktop));
217             boost::optional<NR::Rect> b = sp_item_bbox_desktop (*it);
218             if (b) {
219                 Geom::Point const sp(a.sx0 * b->min()[Geom::X] + a.sx1 * b->max()[Geom::X],
220                                    a.sy0 * b->min()[Geom::Y] + a.sy1 * b->max()[Geom::Y]);
221                 Geom::Point const mp_rel( mp - sp );
222                 if (LInfty(mp_rel) > 1e-9) {
223                     sp_item_move_rel(*it, NR::translate(mp_rel));
224                     changed = true;
225                 }
226             }
227         }
229         // restore compensation setting
230         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
232         if (changed) {
233             sp_document_done ( sp_desktop_document (desktop) , SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
234                                _("Align"));
235         }
238     }
239     guint _index;
240     AlignAndDistribute &_dialog;
242     static const Coeffs _allCoeffs[10];
244 };
245 ActionAlign::Coeffs const ActionAlign::_allCoeffs[10] = {
246     {1., 0., 0., 0., 0., 1., 0., 0.},
247     {1., 0., 0., 0., 1., 0., 0., 0.},
248     {.5, .5, 0., 0., .5, .5, 0., 0.},
249     {0., 1., 0., 0., 0., 1., 0., 0.},
250     {0., 1., 0., 0., 1., 0., 0., 0.},
251     {0., 0., 0., 1., 0., 0., 1., 0.},
252     {0., 0., 0., 1., 0., 0., 0., 1.},
253     {0., 0., .5, .5, 0., 0., .5, .5},
254     {0., 0., 1., 0., 0., 0., 1., 0.},
255     {0., 0., 1., 0., 0., 0., 0., 1.}
256 };
258 BBoxSort::BBoxSort(SPItem *pItem, Geom::Rect bounds, Geom::Dim2 orientation, double kBegin, double kEnd) :
259         item(pItem),
260         bbox (bounds)
262         anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
264 BBoxSort::BBoxSort(const BBoxSort &rhs) :
265         //NOTE :  this copy ctor is called O(sort) when sorting the vector
266         //this is bad. The vector should be a vector of pointers.
267         //But I'll wait the bohem GC before doing that
268         item(rhs.item), anchor(rhs.anchor), bbox(rhs.bbox) 
272 bool operator< (const BBoxSort &a, const BBoxSort &b)
274     return (a.anchor < b.anchor);
277 class ActionDistribute : public Action {
278 public :
279     ActionDistribute(const Glib::ustring &id,
280                      const Glib::ustring &tiptext,
281                      guint row, guint column,
282                      AlignAndDistribute &dialog,
283                      bool onInterSpace,
284                      Geom::Dim2 orientation,
285                      double kBegin, double kEnd
286         ):
287         Action(id, tiptext, row, column,
288                dialog.distribute_table(), dialog.tooltips(), dialog),
289         _dialog(dialog),
290         _onInterSpace(onInterSpace),
291         _orientation(orientation),
292         _kBegin(kBegin),
293         _kEnd( kEnd)
294     {}
296 private :
297     virtual void on_button_click() {
298         //Retreive selected objects
299         SPDesktop *desktop = _dialog.getDesktop();
300         if (!desktop) return;
302         Inkscape::Selection *selection = sp_desktop_selection(desktop);
303         if (!selection) return;
305         using Inkscape::Util::GSListConstIterator;
306         std::list<SPItem *> selected;
307         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
308         if (selected.empty()) return;
310         //Check 2 or more selected objects
311         std::list<SPItem *>::iterator second(selected.begin());
312         ++second;
313         if (second == selected.end()) return;
316         std::vector< BBoxSort  > sorted;
317         for (std::list<SPItem *>::iterator it(selected.begin());
318             it != selected.end();
319             ++it)
320         {
321             boost::optional<NR::Rect> bbox = sp_item_bbox_desktop(*it);
322             if (bbox) {
323                 sorted.push_back(BBoxSort(*it, to_2geom(*bbox), _orientation, _kBegin, _kEnd));
324             }
325         }
326         //sort bbox by anchors
327         std::sort(sorted.begin(), sorted.end());
329         // see comment in ActionAlign above
330         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
331         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
333         unsigned int len = sorted.size();
334         bool changed = false;
335         if (_onInterSpace)
336         {
337             //overall bboxes span
338             float dist = (sorted.back().bbox.max()[_orientation] -
339                           sorted.front().bbox.min()[_orientation]);
340             //space eaten by bboxes
341             float span = 0;
342             for (unsigned int i = 0; i < len; i++)
343             {
344                 span += sorted[i].bbox[_orientation].extent();
345             }
346             //new distance between each bbox
347             float step = (dist - span) / (len - 1);
348             float pos = sorted.front().bbox.min()[_orientation];
349             for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
350                   it < sorted.end();
351                   it ++ )
352             {
353                 if (!NR_DF_TEST_CLOSE (pos, it->bbox.min()[_orientation], 1e-6)) {
354                     Geom::Point t(0.0, 0.0);
355                     t[_orientation] = pos - it->bbox.min()[_orientation];
356                     sp_item_move_rel(it->item, NR::translate(t));
357                     changed = true;
358                 }
359                 pos += it->bbox[_orientation].extent();
360                 pos += step;
361             }
362         }
363         else
364         {
365             //overall anchor span
366             float dist = sorted.back().anchor - sorted.front().anchor;
367             //distance between anchors
368             float step = dist / (len - 1);
370             for ( unsigned int i = 0; i < len ; i ++ )
371             {
372                 BBoxSort & it(sorted[i]);
373                 //new anchor position
374                 float pos = sorted.front().anchor + i * step;
375                 //Don't move if we are really close
376                 if (!NR_DF_TEST_CLOSE (pos, it.anchor, 1e-6)) {
377                     //Compute translation
378                     Geom::Point t(0.0, 0.0);
379                     t[_orientation] = pos - it.anchor;
380                     //translate
381                     sp_item_move_rel(it.item, NR::translate(t));
382                     changed = true;
383                 }
384             }
385         }
387         // restore compensation setting
388         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
390         if (changed) {
391             sp_document_done ( sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
392                                _("Distribute"));
393         }
394     }
395     guint _index;
396     AlignAndDistribute &_dialog;
397     bool _onInterSpace;
398     Geom::Dim2 _orientation;
400     double _kBegin;
401     double _kEnd;
403 };
406 class ActionNode : public Action {
407 public :
408     ActionNode(const Glib::ustring &id,
409                const Glib::ustring &tiptext,
410                guint column,
411                AlignAndDistribute &dialog,
412                Geom::Dim2 orientation, bool distribute):
413         Action(id, tiptext, 0, column,
414                dialog.nodes_table(), dialog.tooltips(), dialog),
415         _orientation(orientation),
416         _distribute(distribute)
417     {}
419 private :
420     Geom::Dim2 _orientation;
421     bool _distribute;
422     virtual void on_button_click()
423     {
425         if (!_dialog.getDesktop()) return;
426         SPEventContext *event_context = sp_desktop_event_context(_dialog.getDesktop());
427         if (!SP_IS_NODE_CONTEXT (event_context)) return ;
429         if (_distribute)
430             SP_NODE_CONTEXT (event_context)->shape_editor->distribute((NR::Dim2)_orientation);
431         else
432             SP_NODE_CONTEXT (event_context)->shape_editor->align((NR::Dim2)_orientation);
434     }
435 };
437 class ActionRemoveOverlaps : public Action {
438 private:
439     Gtk::Label removeOverlapXGapLabel;
440     Gtk::Label removeOverlapYGapLabel;
441     Gtk::SpinButton removeOverlapXGap;
442     Gtk::SpinButton removeOverlapYGap;
444 public:
445     ActionRemoveOverlaps(Glib::ustring const &id,
446                          Glib::ustring const &tiptext,
447                          guint row,
448                          guint column,
449                          AlignAndDistribute &dialog) :
450         Action(id, tiptext, row, column + 4,
451                dialog.removeOverlap_table(), dialog.tooltips(), dialog)
452     {
453         dialog.removeOverlap_table().set_col_spacings(3);
455         removeOverlapXGap.set_digits(1);
456         removeOverlapXGap.set_size_request(60, -1);
457         removeOverlapXGap.set_increments(1.0, 5.0);
458         removeOverlapXGap.set_range(-1000.0, 1000.0);
459         removeOverlapXGap.set_value(0);
460         dialog.tooltips().set_tip(removeOverlapXGap,
461                                   _("Minimum horizontal gap (in px units) between bounding boxes"));
462         /* TRANSLATORS: Horizontal gap. Only put "H:" equivalent in the translation */
463         removeOverlapXGapLabel.set_label(Q_("gap|H:"));
465         removeOverlapYGap.set_digits(1);
466         removeOverlapYGap.set_size_request(60, -1);
467         removeOverlapYGap.set_increments(1.0, 5.0);
468         removeOverlapYGap.set_range(-1000.0, 1000.0);
469         removeOverlapYGap.set_value(0);
470         dialog.tooltips().set_tip(removeOverlapYGap,
471                                   _("Minimum vertical gap (in px units) between bounding boxes"));
472         /* TRANSLATORS: Vertical gap */
473         removeOverlapYGapLabel.set_label(_("V:"));
475         dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
476         dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
477         dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, column+3, row, row+1, Gtk::FILL, Gtk::FILL);
478         dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
480     }
482 private :
483     virtual void on_button_click()
484     {
485         if (!_dialog.getDesktop()) return;
487         // see comment in ActionAlign above
488         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
489         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
491         // xGap and yGap are the minimum space required between bounding rectangles.
492         double const xGap = removeOverlapXGap.get_value();
493         double const yGap = removeOverlapYGap.get_value();
494         removeoverlap(sp_desktop_selection(_dialog.getDesktop())->itemList(),
495                       xGap, yGap);
497         // restore compensation setting
498         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
500         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
501                          _("Remove overlaps"));
502     }
503 };
505 class ActionGraphLayout : public Action {
506 public:
507     ActionGraphLayout(Glib::ustring const &id,
508                          Glib::ustring const &tiptext,
509                          guint row,
510                          guint column,
511                          AlignAndDistribute &dialog) :
512         Action(id, tiptext, row, column + 4,
513                dialog.graphLayout_table(), dialog.tooltips(), dialog)
514     {}
516 private :
517     virtual void on_button_click()
518     {
519         if (!_dialog.getDesktop()) return;
521         // see comment in ActionAlign above
522         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
523         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
525         graphlayout(sp_desktop_selection(_dialog.getDesktop())->itemList());
527         // restore compensation setting
528         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
530         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
531                          _("Arrange connector network"));
532     }
533 };
535 class ActionUnclump : public Action {
536 public :
537     ActionUnclump(const Glib::ustring &id,
538                const Glib::ustring &tiptext,
539                guint row,
540                guint column,
541                AlignAndDistribute &dialog):
542         Action(id, tiptext, row, column,
543                dialog.distribute_table(), dialog.tooltips(), dialog)
544     {}
546 private :
547     virtual void on_button_click()
548     {
549         if (!_dialog.getDesktop()) return;
551         // see comment in ActionAlign above
552         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
553         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
555         unclump ((GSList *) sp_desktop_selection(_dialog.getDesktop())->itemList());
557         // restore compensation setting
558         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
560         sp_document_done (sp_desktop_document (_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
561                           _("Unclump"));
562     }
563 };
565 class ActionRandomize : public Action {
566 public :
567     ActionRandomize(const Glib::ustring &id,
568                const Glib::ustring &tiptext,
569                guint row,
570                guint column,
571                AlignAndDistribute &dialog):
572         Action(id, tiptext, row, column,
573                dialog.distribute_table(), dialog.tooltips(), dialog)
574     {}
576 private :
577     virtual void on_button_click()
578     {
579         SPDesktop *desktop = _dialog.getDesktop();
580         if (!desktop) return;
582         Inkscape::Selection *selection = sp_desktop_selection(desktop);
583         if (!selection) return;
585         using Inkscape::Util::GSListConstIterator;
586         std::list<SPItem *> selected;
587         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
588         if (selected.empty()) return;
590         //Check 2 or more selected objects
591         if (selected.size() < 2) return;
593         boost::optional<NR::Rect> sel_bbox = selection->bounds();
594         if (!sel_bbox) {
595             return;
596         }
598         // This bbox is cached between calls to randomize, so that there's no growth nor shrink
599         // nor drift on sequential randomizations. Discard cache on global (or better active
600         // desktop's) selection_change signal.
601         if (!_dialog.randomize_bbox) {
602             _dialog.randomize_bbox = to_2geom(*sel_bbox);
603         }
605         // see comment in ActionAlign above
606         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
607         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
609         for (std::list<SPItem *>::iterator it(selected.begin());
610             it != selected.end();
611             ++it)
612         {
613             sp_document_ensure_up_to_date(sp_desktop_document (desktop));
614             boost::optional<NR::Rect> item_box = sp_item_bbox_desktop (*it);
615             if (item_box) {
616                 // find new center, staying within bbox
617                 double x = _dialog.randomize_bbox->min()[Geom::X] + (*item_box).extent(Geom::X)/2 +
618                     g_random_double_range (0, (*_dialog.randomize_bbox)[Geom::X].extent() - (*item_box).extent(Geom::X));
619                 double y = _dialog.randomize_bbox->min()[Geom::Y] + (*item_box).extent(Geom::Y)/2 +
620                     g_random_double_range (0, (*_dialog.randomize_bbox)[Geom::Y].extent() - (*item_box).extent(Geom::Y));
621                 // displacement is the new center minus old:
622                 NR::Point t = NR::Point (x, y) - 0.5*(item_box->max() + item_box->min());
623                 sp_item_move_rel(*it, NR::translate(t));
624             }
625         }
627         // restore compensation setting
628         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
630         sp_document_done (sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
631                           _("Randomize positions"));
632     }
633 };
635 struct Baselines
637     SPItem *_item;
638     Geom::Point _base;
639     Geom::Dim2 _orientation;
640     Baselines(SPItem *item, Geom::Point base, Geom::Dim2 orientation) :
641         _item (item),
642         _base (base),
643         _orientation (orientation)
644     {}
645 };
647 bool operator< (const Baselines &a, const Baselines &b)
649     return (a._base[a._orientation] < b._base[b._orientation]);
652 class ActionBaseline : public Action {
653 public :
654     ActionBaseline(const Glib::ustring &id,
655                const Glib::ustring &tiptext,
656                guint row,
657                guint column,
658                AlignAndDistribute &dialog,
659                Gtk::Table &table,
660                Geom::Dim2 orientation, bool distribute):
661         Action(id, tiptext, row, column,
662                table, dialog.tooltips(), dialog),
663         _orientation(orientation),
664         _distribute(distribute)
665     {}
667 private :
668     Geom::Dim2 _orientation;
669     bool _distribute;
670     virtual void on_button_click()
671     {
672         SPDesktop *desktop = _dialog.getDesktop();
673         if (!desktop) return;
675         Inkscape::Selection *selection = sp_desktop_selection(desktop);
676         if (!selection) return;
678         using Inkscape::Util::GSListConstIterator;
679         std::list<SPItem *> selected;
680         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
681         if (selected.empty()) return;
683         //Check 2 or more selected objects
684         if (selected.size() < 2) return;
686         Geom::Point b_min = Geom::Point (HUGE_VAL, HUGE_VAL);
687         Geom::Point b_max = Geom::Point (-HUGE_VAL, -HUGE_VAL);
689         std::vector<Baselines> sorted;
691         for (std::list<SPItem *>::iterator it(selected.begin());
692             it != selected.end();
693             ++it)
694         {
695             if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
696                 Inkscape::Text::Layout const *layout = te_get_layout(*it);
697                 Geom::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
698                 if (base[Geom::X] < b_min[Geom::X]) b_min[Geom::X] = base[Geom::X];
699                 if (base[Geom::Y] < b_min[Geom::Y]) b_min[Geom::Y] = base[Geom::Y];
700                 if (base[Geom::X] > b_max[Geom::X]) b_max[Geom::X] = base[Geom::X];
701                 if (base[Geom::Y] > b_max[Geom::Y]) b_max[Geom::Y] = base[Geom::Y];
703                 Baselines b (*it, base, _orientation);
704                 sorted.push_back(b);
705             }
706         }
708         if (sorted.size() <= 1) return;
710         //sort baselines
711         std::sort(sorted.begin(), sorted.end());
713         bool changed = false;
715         if (_distribute) {
716             double step = (b_max[_orientation] - b_min[_orientation])/(sorted.size() - 1);
717             for (unsigned int i = 0; i < sorted.size(); i++) {
718                 SPItem *item = sorted[i]._item;
719                 Geom::Point base = sorted[i]._base;
720                 Geom::Point t(0.0, 0.0);
721                 t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
722                 sp_item_move_rel(item, NR::translate(t));
723                 changed = true;
724             }
726             if (changed) {
727                 sp_document_done (sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
728                                   _("Distribute text baselines"));
729             }
731         } else {
732             for (std::list<SPItem *>::iterator it(selected.begin());
733                  it != selected.end();
734                  ++it)
735             {
736                 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
737                     Inkscape::Text::Layout const *layout = te_get_layout(*it);
738                     Geom::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
739                     Geom::Point t(0.0, 0.0);
740                     t[_orientation] = b_min[_orientation] - base[_orientation];
741                     sp_item_move_rel(*it, NR::translate(t));
742                     changed = true;
743                 }
744             }
746             if (changed) {
747                 sp_document_done (sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
748                                   _("Align text baselines"));
749             }
750         }
751     }
752 };
756 void on_tool_changed(Inkscape::Application */*inkscape*/, SPEventContext */*context*/, AlignAndDistribute *daad)
758     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
759     if (desktop && sp_desktop_event_context(desktop))
760         daad->setMode(tools_active(desktop) == TOOLS_NODES);
763 void on_selection_changed(Inkscape::Application */*inkscape*/, Inkscape::Selection */*selection*/, AlignAndDistribute *daad)
765     daad->randomize_bbox = boost::optional<Geom::Rect>();
768 /////////////////////////////////////////////////////////
773 AlignAndDistribute::AlignAndDistribute()
774     : UI::Widget::Panel ("", "dialogs.align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
775       randomize_bbox(),
776       _alignFrame(_("Align")),
777       _distributeFrame(_("Distribute")),
778       _removeOverlapFrame(_("Remove overlaps")),
779       _graphLayoutFrame(_("Connector network layout")),
780       _nodesFrame(_("Nodes")),
781       _alignTable(2, 6, true),
782       _distributeTable(3, 6, true),
783       _removeOverlapTable(1, 5, false),
784       _graphLayoutTable(1, 5, false),
785       _nodesTable(1, 4, true),
786       _anchorLabel(_("Relative to: "))
789     //Instanciate the align buttons
790     addAlignButton("al_left_out",
791                    _("Align right sides of objects to left side of anchor"),
792                    0, 0);
793     addAlignButton("al_left_in",
794                    _("Align left sides"),
795                    0, 1);
796     addAlignButton("al_center_hor",
797                    _("Center on vertical axis"),
798                    0, 2);
799     addAlignButton("al_right_in",
800                    _("Align right sides"),
801                    0, 3);
802     addAlignButton("al_right_out",
803                    _("Align left sides of objects to right side of anchor"),
804                    0, 4);
805     addAlignButton("al_top_out",
806                    _("Align bottoms of objects to top of anchor"),
807                    1, 0);
808     addAlignButton("al_top_in",
809                    _("Align tops"),
810                    1, 1);
811     addAlignButton("al_center_ver",
812                    _("Center on horizontal axis"),
813                    1, 2);
814     addAlignButton("al_bottom_in",
815                    _("Align bottoms"),
816                    1, 3);
817     addAlignButton("al_bottom_out",
818                    _("Align tops of objects to bottom of anchor"),
819                    1, 4);
821     //Baseline aligns
822     addBaselineButton("al_baselines_vert",
823                    _("Align baseline anchors of texts vertically"),
824                       0, 5, this->align_table(), Geom::X, false);
825     addBaselineButton("al_baselines_hor",
826                    _("Align baseline anchors of texts horizontally"),
827                      1, 5, this->align_table(), Geom::Y, false);
829     //The distribute buttons
830     addDistributeButton("distribute_hdist",
831                         _("Make horizontal gaps between objects equal"),
832                         0, 4, true, Geom::X, .5, .5);
834     addDistributeButton("distribute_left",
835                         _("Distribute left sides equidistantly"),
836                         0, 1, false, Geom::X, 1., 0.);
837     addDistributeButton("distribute_hcentre",
838                         _("Distribute centers equidistantly horizontally"),
839                         0, 2, false, Geom::X, .5, .5);
840     addDistributeButton("distribute_right",
841                         _("Distribute right sides equidistantly"),
842                         0, 3, false, Geom::X, 0., 1.);
844     addDistributeButton("distribute_vdist",
845                         _("Make vertical gaps between objects equal"),
846                         1, 4, true, Geom::Y, .5, .5);
848     addDistributeButton("distribute_top",
849                         _("Distribute tops equidistantly"),
850                         1, 1, false, Geom::Y, 0, 1);
851     addDistributeButton("distribute_vcentre",
852                         _("Distribute centers equidistantly vertically"),
853                         1, 2, false, Geom::Y, .5, .5);
854     addDistributeButton("distribute_bottom",
855                         _("Distribute bottoms equidistantly"),
856                         1, 3, false, Geom::Y, 1., 0.);
858     //Baseline distribs
859     addBaselineButton("distribute_baselines_hor",
860                    _("Distribute baseline anchors of texts horizontally"),
861                       0, 5, this->distribute_table(), Geom::X, true);
862     addBaselineButton("distribute_baselines_vert",
863                    _("Distribute baseline anchors of texts vertically"),
864                      1, 5, this->distribute_table(), Geom::Y, true);
866     //Randomize & Unclump
867     addRandomizeButton("distribute_randomize",
868                         _("Randomize centers in both dimensions"),
869                         2, 2);
870     addUnclumpButton("unclump",
871                         _("Unclump objects: try to equalize edge-to-edge distances"),
872                         2, 4);
874     //Remove overlaps
875     addRemoveOverlapsButton("remove_overlaps",
876                             _("Move objects as little as possible so that their bounding boxes do not overlap"),
877                             0, 0);
878     //Graph Layout
879     addGraphLayoutButton("graph_layout",
880                             _("Nicely arrange selected connector network"),
881                             0, 0);
883     //Node Mode buttons
884     addNodeButton("node_halign",
885                   _("Align selected nodes horizontally"),
886                   0, Geom::X, false);
887     addNodeButton("node_valign",
888                   _("Align selected nodes vertically"),
889                   1, Geom::Y, false);
890     addNodeButton("node_hdistribute",
891                   _("Distribute selected nodes horizontally"),
892                   2, Geom::X, true);
893     addNodeButton("node_vdistribute",
894                   _("Distribute selected nodes vertically"),
895                   3, Geom::Y, true);
897     //Rest of the widgetry
899     _combo.append_text(_("Last selected"));
900     _combo.append_text(_("First selected"));
901     _combo.append_text(_("Biggest item"));
902     _combo.append_text(_("Smallest item"));
903     _combo.append_text(_("Page"));
904     _combo.append_text(_("Drawing"));
905     _combo.append_text(_("Selection"));
907     _combo.set_active(prefs_get_int_attribute("dialogs.align", "align-to", 6));
908     _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
910     _anchorBox.pack_start(_anchorLabel);
911     _anchorBox.pack_start(_combo);
913     _alignBox.pack_start(_anchorBox);
914     _alignBox.pack_start(_alignTable);
916     _alignFrame.add(_alignBox);
917     _distributeFrame.add(_distributeTable);
918     _removeOverlapFrame.add(_removeOverlapTable);
919     _graphLayoutFrame.add(_graphLayoutTable);
920     _nodesFrame.add(_nodesTable);
922     Gtk::Box *contents = _getContents();
923     contents->set_spacing(4);
925     // Notebook for individual transformations
927     contents->pack_start(_alignFrame, true, true);
928     contents->pack_start(_distributeFrame, true, true);
929     contents->pack_start(_removeOverlapFrame, true, true);
930     contents->pack_start(_graphLayoutFrame, true, true);
931     contents->pack_start(_nodesFrame, true, true);
933     //Connect to the global tool change signal
934     g_signal_connect (G_OBJECT (INKSCAPE), "set_eventcontext", G_CALLBACK (on_tool_changed), this);
936     // Connect to the global selection change, to invalidate cached randomize_bbox
937     g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
938     randomize_bbox = boost::optional<Geom::Rect>();
940     show_all_children();
942     on_tool_changed (NULL, NULL, this); // set current mode
945 AlignAndDistribute::~AlignAndDistribute()
947     sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
949     for (std::list<Action *>::iterator it = _actionList.begin();
950          it != _actionList.end();
951          it ++)
952         delete *it;
955 void AlignAndDistribute::on_ref_change(){
957     prefs_set_int_attribute("dialogs.align", "align-to", _combo.get_active_row_number());
959     //Make blink the master
965 void AlignAndDistribute::setMode(bool nodeEdit)
967     //Act on widgets used in node mode
968     void ( Gtk::Widget::*mNode) ()  = nodeEdit ?
969         &Gtk::Widget::show_all : &Gtk::Widget::hide_all;
971     //Act on widgets used in selection mode
972   void ( Gtk::Widget::*mSel) ()  = nodeEdit ?
973       &Gtk::Widget::hide_all : &Gtk::Widget::show_all;
976     ((_alignFrame).*(mSel))();
977     ((_distributeFrame).*(mSel))();
978     ((_removeOverlapFrame).*(mSel))();
979     ((_graphLayoutFrame).*(mSel))();
980     ((_nodesFrame).*(mNode))();
983 void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
984                                  guint row, guint col)
986     _actionList.push_back(
987         new ActionAlign(
988             id, tiptext, row, col,
989             *this , col + row * 5));
991 void AlignAndDistribute::addDistributeButton(const Glib::ustring &id, const Glib::ustring tiptext,
992                                       guint row, guint col, bool onInterSpace,
993                                       Geom::Dim2 orientation, float kBegin, float kEnd)
995     _actionList.push_back(
996         new ActionDistribute(
997             id, tiptext, row, col, *this ,
998             onInterSpace, orientation,
999             kBegin, kEnd
1000             )
1001         );
1004 void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1005                    guint col, Geom::Dim2 orientation, bool distribute)
1007     _actionList.push_back(
1008         new ActionNode(
1009             id, tiptext, col,
1010             *this, orientation, distribute));
1013 void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
1014                                       guint row, guint col)
1016     _actionList.push_back(
1017         new ActionRemoveOverlaps(
1018             id, tiptext, row, col, *this)
1019         );
1022 void AlignAndDistribute::addGraphLayoutButton(const Glib::ustring &id, const Glib::ustring tiptext,
1023                                       guint row, guint col)
1025     _actionList.push_back(
1026         new ActionGraphLayout(
1027             id, tiptext, row, col, *this)
1028         );
1031 void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
1032                                       guint row, guint col)
1034     _actionList.push_back(
1035         new ActionUnclump(
1036             id, tiptext, row, col, *this)
1037         );
1040 void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1041                                       guint row, guint col)
1043     _actionList.push_back(
1044         new ActionRandomize(
1045             id, tiptext, row, col, *this)
1046         );
1049 void AlignAndDistribute::addBaselineButton(const Glib::ustring &id, const Glib::ustring tiptext,
1050                                     guint row, guint col, Gtk::Table &table, Geom::Dim2 orientation, bool distribute)
1052     _actionList.push_back(
1053         new ActionBaseline(
1054             id, tiptext, row, col,
1055             *this, table, orientation, distribute));
1061 std::list<SPItem *>::iterator AlignAndDistribute::find_master( std::list<SPItem *> &list, bool horizontal){
1062     std::list<SPItem *>::iterator master = list.end();
1063     switch (getAlignTarget()) {
1064     case LAST:
1065         return list.begin();
1066         break;
1068     case FIRST:
1069         return --(list.end());
1070         break;
1072     case BIGGEST:
1073     {
1074         gdouble max = -1e18;
1075         for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1076             boost::optional<NR::Rect> b = sp_item_bbox_desktop (*it);
1077             if (b) {
1078                 gdouble dim = (*b).extent(horizontal ? Geom::X : Geom::Y);
1079                 if (dim > max) {
1080                     max = dim;
1081                     master = it;
1082                 }
1083             }
1084         }
1085         return master;
1086         break;
1087     }
1089     case SMALLEST:
1090     {
1091         gdouble max = 1e18;
1092         for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1093             boost::optional<NR::Rect> b = sp_item_bbox_desktop (*it);
1094             if (b) {
1095                 gdouble dim = (*b).extent(horizontal ? Geom::X : Geom::Y);
1096                 if (dim < max) {
1097                     max = dim;
1098                     master = it;
1099                 }
1100             }
1101         }
1102         return master;
1103         break;
1104     }
1106     default:
1107         g_assert_not_reached ();
1108         break;
1110     } // end of switch statement
1111     return master;
1114 AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
1115     return AlignTarget(_combo.get_active_row_number());
1120 } // namespace Dialog
1121 } // namespace UI
1122 } // namespace Inkscape
1124 /*
1125   Local Variables:
1126   mode:c++
1127   c-file-style:"stroustrup"
1128   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1129   indent-tabs-mode:nil
1130   fill-column:99
1131   End:
1132 */
1133 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :