Code

Several different i18n issues fixed following report from a_b (adresses bug #215387...
[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         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::Maybe<NR::Rect> b = sp_item_bbox_desktop (thing);
157             if (b) {
158                 mp = NR::Point(a.mx0 * b->min()[NR::X] + a.mx1 * b->max()[NR::X],
159                                a.my0 * b->min()[NR::Y] + a.my1 * b->max()[NR::Y]);
160             } else {
161                 return;
162             }
163             break;
164         }
166         case AlignAndDistribute::PAGE:
167             mp = NR::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             NR::Maybe<NR::Rect> b = sp_item_bbox_desktop
174                 ( (SPItem *) sp_document_root (sp_desktop_document (desktop)) );
175             if (b) {
176                 mp = NR::Point(a.mx0 * b->min()[NR::X] + a.mx1 * b->max()[NR::X],
177                                a.my0 * b->min()[NR::Y] + a.my1 * b->max()[NR::Y]);
178             } else {
179                 return;
180             }
181             break;
182         }
184         case AlignAndDistribute::SELECTION:
185         {
186             NR::Maybe<NR::Rect> b =  selection->bounds();
187             if (b) {
188                 mp = NR::Point(a.mx0 * b->min()[NR::X] + a.mx1 * b->max()[NR::X],
189                                a.my0 * b->min()[NR::Y] + a.my1 * b->max()[NR::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             NR::Maybe<NR::Rect> b = sp_item_bbox_desktop (*it);
218             if (b) {
219                 NR::Point const sp(a.sx0 * b->min()[NR::X] + a.sx1 * b->max()[NR::X],
220                                    a.sy0 * b->min()[NR::Y] + a.sy1 * b->max()[NR::Y]);
221                 NR::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 struct BBoxSort
260     SPItem *item;
261     float anchor;
262     NR::Rect bbox;
263     BBoxSort(SPItem *pItem, NR::Rect bounds, NR::Dim2 orientation, double kBegin, double kEnd) :
264         item(pItem),
265         bbox (bounds)
266     {
267         anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
268     }
269     BBoxSort(const BBoxSort &rhs):
270         //NOTE :  this copy ctor is called O(sort) when sorting the vector
271         //this is bad. The vector should be a vector of pointers.
272         //But I'll wait the bohem GC before doing that
273         item(rhs.item), anchor(rhs.anchor), bbox(rhs.bbox) {
274     }
275 };
276 bool operator< (const BBoxSort &a, const BBoxSort &b)
278     return (a.anchor < b.anchor);
281 class ActionDistribute : public Action {
282 public :
283     ActionDistribute(const Glib::ustring &id,
284                      const Glib::ustring &tiptext,
285                      guint row, guint column,
286                      AlignAndDistribute &dialog,
287                      bool onInterSpace,
288                      NR::Dim2 orientation,
289                      double kBegin, double kEnd
290         ):
291         Action(id, tiptext, row, column,
292                dialog.distribute_table(), dialog.tooltips(), dialog),
293         _dialog(dialog),
294         _onInterSpace(onInterSpace),
295         _orientation(orientation),
296         _kBegin(kBegin),
297         _kEnd( kEnd)
298     {}
300 private :
301     virtual void on_button_click() {
302         //Retreive selected objects
303         SPDesktop *desktop = _dialog.getDesktop();
304         if (!desktop) return;
306         Inkscape::Selection *selection = sp_desktop_selection(desktop);
307         if (!selection) return;
309         using Inkscape::Util::GSListConstIterator;
310         std::list<SPItem *> selected;
311         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
312         if (selected.empty()) return;
314         //Check 2 or more selected objects
315         std::list<SPItem *>::iterator second(selected.begin());
316         ++second;
317         if (second == selected.end()) return;
320         std::vector< BBoxSort  > sorted;
321         for (std::list<SPItem *>::iterator it(selected.begin());
322             it != selected.end();
323             ++it)
324         {
325             NR::Maybe<NR::Rect> bbox = sp_item_bbox_desktop(*it);
326             if (bbox) {
327                 sorted.push_back(BBoxSort(*it, *bbox, _orientation, _kBegin, _kEnd));
328             }
329         }
330         //sort bbox by anchors
331         std::sort(sorted.begin(), sorted.end());
333         // see comment in ActionAlign above
334         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
335         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
337         unsigned int len = sorted.size();
338         bool changed = false;
339         if (_onInterSpace)
340         {
341             //overall bboxes span
342             float dist = (sorted.back().bbox.max()[_orientation] -
343                           sorted.front().bbox.min()[_orientation]);
344             //space eaten by bboxes
345             float span = 0;
346             for (unsigned int i = 0; i < len; i++)
347             {
348                 span += sorted[i].bbox.extent(_orientation);
349             }
350             //new distance between each bbox
351             float step = (dist - span) / (len - 1);
352             float pos = sorted.front().bbox.min()[_orientation];
353             for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
354                   it < sorted.end();
355                   it ++ )
356             {
357                 if (!NR_DF_TEST_CLOSE (pos, it->bbox.min()[_orientation], 1e-6)) {
358                     NR::Point t(0.0, 0.0);
359                     t[_orientation] = pos - it->bbox.min()[_orientation];
360                     sp_item_move_rel(it->item, NR::translate(t));
361                     changed = true;
362                 }
363                 pos += it->bbox.extent(_orientation);
364                 pos += step;
365             }
366         }
367         else
368         {
369             //overall anchor span
370             float dist = sorted.back().anchor - sorted.front().anchor;
371             //distance between anchors
372             float step = dist / (len - 1);
374             for ( unsigned int i = 0; i < len ; i ++ )
375             {
376                 BBoxSort & it(sorted[i]);
377                 //new anchor position
378                 float pos = sorted.front().anchor + i * step;
379                 //Don't move if we are really close
380                 if (!NR_DF_TEST_CLOSE (pos, it.anchor, 1e-6)) {
381                     //Compute translation
382                     NR::Point t(0.0, 0.0);
383                     t[_orientation] = pos - it.anchor;
384                     //translate
385                     sp_item_move_rel(it.item, NR::translate(t));
386                     changed = true;
387                 }
388             }
389         }
391         // restore compensation setting
392         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
394         if (changed) {
395             sp_document_done ( sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
396                                _("Distribute"));
397         }
398     }
399     guint _index;
400     AlignAndDistribute &_dialog;
401     bool _onInterSpace;
402     NR::Dim2 _orientation;
404     double _kBegin;
405     double _kEnd;
407 };
410 class ActionNode : public Action {
411 public :
412     ActionNode(const Glib::ustring &id,
413                const Glib::ustring &tiptext,
414                guint column,
415                AlignAndDistribute &dialog,
416                NR::Dim2 orientation, bool distribute):
417         Action(id, tiptext, 0, column,
418                dialog.nodes_table(), dialog.tooltips(), dialog),
419         _orientation(orientation),
420         _distribute(distribute)
421     {}
423 private :
424     NR::Dim2 _orientation;
425     bool _distribute;
426     virtual void on_button_click()
427     {
429         if (!_dialog.getDesktop()) return;
430         SPEventContext *event_context = sp_desktop_event_context(_dialog.getDesktop());
431         if (!SP_IS_NODE_CONTEXT (event_context)) return ;
433         if (_distribute)
434             SP_NODE_CONTEXT (event_context)->shape_editor->distribute(_orientation);
435         else
436             SP_NODE_CONTEXT (event_context)->shape_editor->align(_orientation);
438     }
439 };
441 class ActionRemoveOverlaps : public Action {
442 private:
443     Gtk::Label removeOverlapXGapLabel;
444     Gtk::Label removeOverlapYGapLabel;
445     Gtk::SpinButton removeOverlapXGap;
446     Gtk::SpinButton removeOverlapYGap;
448 public:
449     ActionRemoveOverlaps(Glib::ustring const &id,
450                          Glib::ustring const &tiptext,
451                          guint row,
452                          guint column,
453                          AlignAndDistribute &dialog) :
454         Action(id, tiptext, row, column + 4,
455                dialog.removeOverlap_table(), dialog.tooltips(), dialog)
456     {
457         dialog.removeOverlap_table().set_col_spacings(3);
459         removeOverlapXGap.set_digits(1);
460         removeOverlapXGap.set_size_request(60, -1);
461         removeOverlapXGap.set_increments(1.0, 5.0);
462         removeOverlapXGap.set_range(-1000.0, 1000.0);
463         removeOverlapXGap.set_value(0);
464         dialog.tooltips().set_tip(removeOverlapXGap,
465                                   _("Minimum horizontal gap (in px units) between bounding boxes"));
466         /* TRANSLATORS: Horizontal gap. Only put "H:" equivalent in the translation */
467         removeOverlapXGapLabel.set_label(Q_("gap|H:"));
469         removeOverlapYGap.set_digits(1);
470         removeOverlapYGap.set_size_request(60, -1);
471         removeOverlapYGap.set_increments(1.0, 5.0);
472         removeOverlapYGap.set_range(-1000.0, 1000.0);
473         removeOverlapYGap.set_value(0);
474         dialog.tooltips().set_tip(removeOverlapYGap,
475                                   _("Minimum vertical gap (in px units) between bounding boxes"));
476         /* TRANSLATORS: Vertical gap */
477         removeOverlapYGapLabel.set_label(_("V:"));
479         dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
480         dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
481         dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, column+3, row, row+1, Gtk::FILL, Gtk::FILL);
482         dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
484     }
486 private :
487     virtual void on_button_click()
488     {
489         if (!_dialog.getDesktop()) return;
491         // see comment in ActionAlign above
492         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
493         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
495         // xGap and yGap are the minimum space required between bounding rectangles.
496         double const xGap = removeOverlapXGap.get_value();
497         double const yGap = removeOverlapYGap.get_value();
498         removeoverlap(sp_desktop_selection(_dialog.getDesktop())->itemList(),
499                       xGap, yGap);
501         // restore compensation setting
502         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
504         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
505                          _("Remove overlaps"));
506     }
507 };
509 class ActionGraphLayout : public Action {
510 public:
511     ActionGraphLayout(Glib::ustring const &id,
512                          Glib::ustring const &tiptext,
513                          guint row,
514                          guint column,
515                          AlignAndDistribute &dialog) :
516         Action(id, tiptext, row, column + 4,
517                dialog.graphLayout_table(), dialog.tooltips(), dialog)
518     {}
520 private :
521     virtual void on_button_click()
522     {
523         if (!_dialog.getDesktop()) return;
525         // see comment in ActionAlign above
526         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
527         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
529         graphlayout(sp_desktop_selection(_dialog.getDesktop())->itemList());
531         // restore compensation setting
532         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
534         sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
535                          _("Arrange connector network"));
536     }
537 };
539 class ActionUnclump : public Action {
540 public :
541     ActionUnclump(const Glib::ustring &id,
542                const Glib::ustring &tiptext,
543                guint row,
544                guint column,
545                AlignAndDistribute &dialog):
546         Action(id, tiptext, row, column,
547                dialog.distribute_table(), dialog.tooltips(), dialog)
548     {}
550 private :
551     virtual void on_button_click()
552     {
553         if (!_dialog.getDesktop()) return;
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         unclump ((GSList *) sp_desktop_selection(_dialog.getDesktop())->itemList());
561         // restore compensation setting
562         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
564         sp_document_done (sp_desktop_document (_dialog.getDesktop()), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
565                           _("Unclump"));
566     }
567 };
569 class ActionRandomize : public Action {
570 public :
571     ActionRandomize(const Glib::ustring &id,
572                const Glib::ustring &tiptext,
573                guint row,
574                guint column,
575                AlignAndDistribute &dialog):
576         Action(id, tiptext, row, column,
577                dialog.distribute_table(), dialog.tooltips(), dialog)
578     {}
580 private :
581     virtual void on_button_click()
582     {
583         SPDesktop *desktop = _dialog.getDesktop();
584         if (!desktop) return;
586         Inkscape::Selection *selection = sp_desktop_selection(desktop);
587         if (!selection) return;
589         using Inkscape::Util::GSListConstIterator;
590         std::list<SPItem *> selected;
591         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
592         if (selected.empty()) return;
594         //Check 2 or more selected objects
595         if (selected.size() < 2) return;
597         NR::Maybe<NR::Rect> sel_bbox = selection->bounds();
598         if (!sel_bbox) {
599             return;
600         }
602         // This bbox is cached between calls to randomize, so that there's no growth nor shrink
603         // nor drift on sequential randomizations. Discard cache on global (or better active
604         // desktop's) selection_change signal.
605         if (!_dialog.randomize_bbox) {
606             _dialog.randomize_bbox = *sel_bbox;
607         }
609         // see comment in ActionAlign above
610         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
611         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
613         for (std::list<SPItem *>::iterator it(selected.begin());
614             it != selected.end();
615             ++it)
616         {
617             sp_document_ensure_up_to_date(sp_desktop_document (desktop));
618             NR::Maybe<NR::Rect> item_box = sp_item_bbox_desktop (*it);
619             if (item_box) {
620                 // find new center, staying within bbox
621                 double x = _dialog.randomize_bbox->min()[NR::X] + item_box->extent(NR::X)/2 +
622                     g_random_double_range (0, _dialog.randomize_bbox->extent(NR::X) - item_box->extent(NR::X));
623                 double y = _dialog.randomize_bbox->min()[NR::Y] + item_box->extent(NR::Y)/2 +
624                     g_random_double_range (0, _dialog.randomize_bbox->extent(NR::Y) - item_box->extent(NR::Y));
625                 // displacement is the new center minus old:
626                 NR::Point t = NR::Point (x, y) - 0.5*(item_box->max() + item_box->min());
627                 sp_item_move_rel(*it, NR::translate(t));
628             }
629         }
631         // restore compensation setting
632         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
634         sp_document_done (sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
635                           _("Randomize positions"));
636     }
637 };
639 struct Baselines
641     SPItem *_item;
642     NR::Point _base;
643     NR::Dim2 _orientation;
644     Baselines(SPItem *item, NR::Point base, NR::Dim2 orientation) :
645         _item (item),
646         _base (base),
647         _orientation (orientation)
648     {}
649 };
651 bool operator< (const Baselines &a, const Baselines &b)
653     return (a._base[a._orientation] < b._base[b._orientation]);
656 class ActionBaseline : public Action {
657 public :
658     ActionBaseline(const Glib::ustring &id,
659                const Glib::ustring &tiptext,
660                guint row,
661                guint column,
662                AlignAndDistribute &dialog,
663                Gtk::Table &table,
664                NR::Dim2 orientation, bool distribute):
665         Action(id, tiptext, row, column,
666                table, dialog.tooltips(), dialog),
667         _orientation(orientation),
668         _distribute(distribute)
669     {}
671 private :
672     NR::Dim2 _orientation;
673     bool _distribute;
674     virtual void on_button_click()
675     {
676         SPDesktop *desktop = _dialog.getDesktop();
677         if (!desktop) return;
679         Inkscape::Selection *selection = sp_desktop_selection(desktop);
680         if (!selection) return;
682         using Inkscape::Util::GSListConstIterator;
683         std::list<SPItem *> selected;
684         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
685         if (selected.empty()) return;
687         //Check 2 or more selected objects
688         if (selected.size() < 2) return;
690         NR::Point b_min = NR::Point (HUGE_VAL, HUGE_VAL);
691         NR::Point b_max = NR::Point (-HUGE_VAL, -HUGE_VAL);
693         std::vector<Baselines> sorted;
695         for (std::list<SPItem *>::iterator it(selected.begin());
696             it != selected.end();
697             ++it)
698         {
699             if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
700                 Inkscape::Text::Layout const *layout = te_get_layout(*it);
701                 NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
702                 if (base[NR::X] < b_min[NR::X]) b_min[NR::X] = base[NR::X];
703                 if (base[NR::Y] < b_min[NR::Y]) b_min[NR::Y] = base[NR::Y];
704                 if (base[NR::X] > b_max[NR::X]) b_max[NR::X] = base[NR::X];
705                 if (base[NR::Y] > b_max[NR::Y]) b_max[NR::Y] = base[NR::Y];
707                 Baselines b (*it, base, _orientation);
708                 sorted.push_back(b);
709             }
710         }
712         if (sorted.size() <= 1) return;
714         //sort baselines
715         std::sort(sorted.begin(), sorted.end());
717         bool changed = false;
719         if (_distribute) {
720             double step = (b_max[_orientation] - b_min[_orientation])/(sorted.size() - 1);
721             for (unsigned int i = 0; i < sorted.size(); i++) {
722                 SPItem *item = sorted[i]._item;
723                 NR::Point base = sorted[i]._base;
724                 NR::Point t(0.0, 0.0);
725                 t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
726                 sp_item_move_rel(item, NR::translate(t));
727                 changed = true;
728             }
730             if (changed) {
731                 sp_document_done (sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
732                                   _("Distribute text baselines"));
733             }
735         } else {
736             for (std::list<SPItem *>::iterator it(selected.begin());
737                  it != selected.end();
738                  ++it)
739             {
740                 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
741                     Inkscape::Text::Layout const *layout = te_get_layout(*it);
742                     NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
743                     NR::Point t(0.0, 0.0);
744                     t[_orientation] = b_min[_orientation] - base[_orientation];
745                     sp_item_move_rel(*it, NR::translate(t));
746                     changed = true;
747                 }
748             }
750             if (changed) {
751                 sp_document_done (sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
752                                   _("Align text baselines"));
753             }
754         }
755     }
756 };
760 void on_tool_changed(Inkscape::Application */*inkscape*/, SPEventContext */*context*/, AlignAndDistribute *daad)
762     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
763     if (desktop && sp_desktop_event_context(desktop))
764         daad->setMode(tools_active(desktop) == TOOLS_NODES);
767 void on_selection_changed(Inkscape::Application */*inkscape*/, Inkscape::Selection */*selection*/, AlignAndDistribute *daad)
769     daad->randomize_bbox = NR::Nothing();
772 /////////////////////////////////////////////////////////
777 AlignAndDistribute::AlignAndDistribute()
778     : UI::Widget::Panel ("", "dialogs.align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
779       randomize_bbox(NR::Nothing()),
780       _alignFrame(_("Align")),
781       _distributeFrame(_("Distribute")),
782       _removeOverlapFrame(_("Remove overlaps")),
783       _graphLayoutFrame(_("Connector network layout")),
784       _nodesFrame(_("Nodes")),
785       _alignTable(2, 6, true),
786       _distributeTable(3, 6, true),
787       _removeOverlapTable(1, 5, false),
788       _graphLayoutTable(1, 5, false),
789       _nodesTable(1, 4, true),
790       _anchorLabel(_("Relative to: "))
793     //Instanciate the align buttons
794     addAlignButton("al_left_out",
795                    _("Align right sides of objects to left side of anchor"),
796                    0, 0);
797     addAlignButton("al_left_in",
798                    _("Align left sides"),
799                    0, 1);
800     addAlignButton("al_center_hor",
801                    _("Center on vertical axis"),
802                    0, 2);
803     addAlignButton("al_right_in",
804                    _("Align right sides"),
805                    0, 3);
806     addAlignButton("al_right_out",
807                    _("Align left sides of objects to right side of anchor"),
808                    0, 4);
809     addAlignButton("al_top_out",
810                    _("Align bottoms of objects to top of anchor"),
811                    1, 0);
812     addAlignButton("al_top_in",
813                    _("Align tops"),
814                    1, 1);
815     addAlignButton("al_center_ver",
816                    _("Center on horizontal axis"),
817                    1, 2);
818     addAlignButton("al_bottom_in",
819                    _("Align bottoms"),
820                    1, 3);
821     addAlignButton("al_bottom_out",
822                    _("Align tops of objects to bottom of anchor"),
823                    1, 4);
825     //Baseline aligns
826     addBaselineButton("al_baselines_vert",
827                    _("Align baseline anchors of texts vertically"),
828                       0, 5, this->align_table(), NR::X, false);
829     addBaselineButton("al_baselines_hor",
830                    _("Align baseline anchors of texts horizontally"),
831                      1, 5, this->align_table(), NR::Y, false);
833     //The distribute buttons
834     addDistributeButton("distribute_hdist",
835                         _("Make horizontal gaps between objects equal"),
836                         0, 4, true, NR::X, .5, .5);
838     addDistributeButton("distribute_left",
839                         _("Distribute left sides equidistantly"),
840                         0, 1, false, NR::X, 1., 0.);
841     addDistributeButton("distribute_hcentre",
842                         _("Distribute centers equidistantly horizontally"),
843                         0, 2, false, NR::X, .5, .5);
844     addDistributeButton("distribute_right",
845                         _("Distribute right sides equidistantly"),
846                         0, 3, false, NR::X, 0., 1.);
848     addDistributeButton("distribute_vdist",
849                         _("Make vertical gaps between objects equal"),
850                         1, 4, true, NR::Y, .5, .5);
852     addDistributeButton("distribute_top",
853                         _("Distribute tops equidistantly"),
854                         1, 1, false, NR::Y, 0, 1);
855     addDistributeButton("distribute_vcentre",
856                         _("Distribute centers equidistantly vertically"),
857                         1, 2, false, NR::Y, .5, .5);
858     addDistributeButton("distribute_bottom",
859                         _("Distribute bottoms equidistantly"),
860                         1, 3, false, NR::Y, 1., 0.);
862     //Baseline distribs
863     addBaselineButton("distribute_baselines_hor",
864                    _("Distribute baseline anchors of texts horizontally"),
865                       0, 5, this->distribute_table(), NR::X, true);
866     addBaselineButton("distribute_baselines_vert",
867                    _("Distribute baseline anchors of texts vertically"),
868                      1, 5, this->distribute_table(), NR::Y, true);
870     //Randomize & Unclump
871     addRandomizeButton("distribute_randomize",
872                         _("Randomize centers in both dimensions"),
873                         2, 2);
874     addUnclumpButton("unclump",
875                         _("Unclump objects: try to equalize edge-to-edge distances"),
876                         2, 4);
878     //Remove overlaps
879     addRemoveOverlapsButton("remove_overlaps",
880                             _("Move objects as little as possible so that their bounding boxes do not overlap"),
881                             0, 0);
882     //Graph Layout
883     addGraphLayoutButton("graph_layout",
884                             _("Nicely arrange selected connector network"),
885                             0, 0);
887     //Node Mode buttons
888     addNodeButton("node_halign",
889                   _("Align selected nodes horizontally"),
890                   0, NR::X, false);
891     addNodeButton("node_valign",
892                   _("Align selected nodes vertically"),
893                   1, NR::Y, false);
894     addNodeButton("node_hdistribute",
895                   _("Distribute selected nodes horizontally"),
896                   2, NR::X, true);
897     addNodeButton("node_vdistribute",
898                   _("Distribute selected nodes vertically"),
899                   3, NR::Y, true);
901     //Rest of the widgetry
903     _combo.append_text(_("Last selected"));
904     _combo.append_text(_("First selected"));
905     _combo.append_text(_("Biggest item"));
906     _combo.append_text(_("Smallest item"));
907     _combo.append_text(_("Page"));
908     _combo.append_text(_("Drawing"));
909     _combo.append_text(_("Selection"));
911     _combo.set_active(prefs_get_int_attribute("dialogs.align", "align-to", 6));
912     _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
914     _anchorBox.pack_start(_anchorLabel);
915     _anchorBox.pack_start(_combo);
917     _alignBox.pack_start(_anchorBox);
918     _alignBox.pack_start(_alignTable);
920     _alignFrame.add(_alignBox);
921     _distributeFrame.add(_distributeTable);
922     _removeOverlapFrame.add(_removeOverlapTable);
923     _graphLayoutFrame.add(_graphLayoutTable);
924     _nodesFrame.add(_nodesTable);
926     Gtk::Box *contents = _getContents();
927     contents->set_spacing(4);
929     // Notebook for individual transformations
931     contents->pack_start(_alignFrame, true, true);
932     contents->pack_start(_distributeFrame, true, true);
933     contents->pack_start(_removeOverlapFrame, true, true);
934     contents->pack_start(_graphLayoutFrame, true, true);
935     contents->pack_start(_nodesFrame, true, true);
937     //Connect to the global tool change signal
938     g_signal_connect (G_OBJECT (INKSCAPE), "set_eventcontext", G_CALLBACK (on_tool_changed), this);
940     // Connect to the global selection change, to invalidate cached randomize_bbox
941     g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
942     randomize_bbox = NR::Nothing();
944     show_all_children();
946     on_tool_changed (NULL, NULL, this); // set current mode
949 AlignAndDistribute::~AlignAndDistribute()
951     sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
953     for (std::list<Action *>::iterator it = _actionList.begin();
954          it != _actionList.end();
955          it ++)
956         delete *it;
959 void AlignAndDistribute::on_ref_change(){
961     prefs_set_int_attribute("dialogs.align", "align-to", _combo.get_active_row_number());
963     //Make blink the master
969 void AlignAndDistribute::setMode(bool nodeEdit)
971     //Act on widgets used in node mode
972     void ( Gtk::Widget::*mNode) ()  = nodeEdit ?
973         &Gtk::Widget::show_all : &Gtk::Widget::hide_all;
975     //Act on widgets used in selection mode
976   void ( Gtk::Widget::*mSel) ()  = nodeEdit ?
977       &Gtk::Widget::hide_all : &Gtk::Widget::show_all;
980     ((_alignFrame).*(mSel))();
981     ((_distributeFrame).*(mSel))();
982     ((_removeOverlapFrame).*(mSel))();
983     ((_graphLayoutFrame).*(mSel))();
984     ((_nodesFrame).*(mNode))();
987 void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
988                                  guint row, guint col)
990     _actionList.push_back(
991         new ActionAlign(
992             id, tiptext, row, col,
993             *this , col + row * 5));
995 void AlignAndDistribute::addDistributeButton(const Glib::ustring &id, const Glib::ustring tiptext,
996                                       guint row, guint col, bool onInterSpace,
997                                       NR::Dim2 orientation, float kBegin, float kEnd)
999     _actionList.push_back(
1000         new ActionDistribute(
1001             id, tiptext, row, col, *this ,
1002             onInterSpace, orientation,
1003             kBegin, kEnd
1004             )
1005         );
1008 void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1009                    guint col, NR::Dim2 orientation, bool distribute)
1011     _actionList.push_back(
1012         new ActionNode(
1013             id, tiptext, col,
1014             *this, orientation, distribute));
1017 void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
1018                                       guint row, guint col)
1020     _actionList.push_back(
1021         new ActionRemoveOverlaps(
1022             id, tiptext, row, col, *this)
1023         );
1026 void AlignAndDistribute::addGraphLayoutButton(const Glib::ustring &id, const Glib::ustring tiptext,
1027                                       guint row, guint col)
1029     _actionList.push_back(
1030         new ActionGraphLayout(
1031             id, tiptext, row, col, *this)
1032         );
1035 void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
1036                                       guint row, guint col)
1038     _actionList.push_back(
1039         new ActionUnclump(
1040             id, tiptext, row, col, *this)
1041         );
1044 void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1045                                       guint row, guint col)
1047     _actionList.push_back(
1048         new ActionRandomize(
1049             id, tiptext, row, col, *this)
1050         );
1053 void AlignAndDistribute::addBaselineButton(const Glib::ustring &id, const Glib::ustring tiptext,
1054                                     guint row, guint col, Gtk::Table &table, NR::Dim2 orientation, bool distribute)
1056     _actionList.push_back(
1057         new ActionBaseline(
1058             id, tiptext, row, col,
1059             *this, table, orientation, distribute));
1065 std::list<SPItem *>::iterator AlignAndDistribute::find_master( std::list<SPItem *> &list, bool horizontal){
1066     std::list<SPItem *>::iterator master = list.end();
1067     switch (getAlignTarget()) {
1068     case LAST:
1069         return list.begin();
1070         break;
1072     case FIRST:
1073         return --(list.end());
1074         break;
1076     case BIGGEST:
1077     {
1078         gdouble max = -1e18;
1079         for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1080             NR::Maybe<NR::Rect> b = sp_item_bbox_desktop (*it);
1081             if (b) {
1082                 gdouble dim = b->extent(horizontal ? NR::X : NR::Y);
1083                 if (dim > max) {
1084                     max = dim;
1085                     master = it;
1086                 }
1087             }
1088         }
1089         return master;
1090         break;
1091     }
1093     case SMALLEST:
1094     {
1095         gdouble max = 1e18;
1096         for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1097             NR::Maybe<NR::Rect> b = sp_item_bbox_desktop (*it);
1098             if (b) {
1099                 gdouble dim = b->extent(horizontal ? NR::X : NR::Y);
1100                 if (dim < max) {
1101                     max = dim;
1102                     master = it;
1103                 }
1104             }
1105         }
1106         return master;
1107         break;
1108     }
1110     default:
1111         g_assert_not_reached ();
1112         break;
1114     } // end of switch statement
1115     return master;
1118 AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
1119     return AlignTarget(_combo.get_active_row_number());
1124 } // namespace Dialog
1125 } // namespace UI
1126 } // namespace Inkscape
1128 /*
1129   Local Variables:
1130   mode:c++
1131   c-file-style:"stroustrup"
1132   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1133   indent-tabs-mode:nil
1134   fill-column:99
1135   End:
1136 */
1137 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :