Code

Added connector graph layout functionality
[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 node align/distribute function
51 #include "tools-switch.h"
53 #include "align-and-distribute.h"
55 namespace Inkscape {
56 namespace UI {
57 namespace Dialog {
59 /////////helper classes//////////////////////////////////
61 class Action {
62 public :
63     Action(const Glib::ustring &id,
64            const Glib::ustring &tiptext,
65            guint row, guint column,
66            Gtk::Table &parent,
67            Gtk::Tooltips &tooltips,
68            AlignAndDistribute &dialog):
69         _dialog(dialog),
70         _id(id),
71         _parent(parent)
72     {
73         Gtk::Widget*  pIcon = Gtk::manage( sp_icon_get_icon( _id, GTK_ICON_SIZE_LARGE_TOOLBAR) );
74         Gtk::Button * pButton = Gtk::manage(new Gtk::Button());
75         pButton->set_relief(Gtk::RELIEF_NONE);
76         pIcon->show();
77         pButton->add(*pIcon);
78         pButton->show();
80         pButton->signal_clicked()
81             .connect(sigc::mem_fun(*this, &Action::on_button_click));
82         tooltips.set_tip(*pButton, tiptext);
83         parent.attach(*pButton, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
84     }
85     virtual ~Action(){}
87     AlignAndDistribute &_dialog;
89 private :
90     virtual void on_button_click(){}
92     Glib::ustring _id;
93     Gtk::Table &_parent;
94 };
97 class ActionAlign : public Action {
98 public :
99     struct Coeffs {
100        double mx0, mx1, my0, my1;
101         double sx0, sx1, sy0, sy1;
102     };
103     ActionAlign(const Glib::ustring &id,
104                 const Glib::ustring &tiptext,
105                 guint row, guint column,
106                 AlignAndDistribute &dialog,
107                 guint coeffIndex):
108         Action(id, tiptext, row, column,
109                dialog.align_table(), dialog.tooltips(), dialog),
110         _index(coeffIndex),
111         _dialog(dialog)
112     {}
114 private :
116     virtual void on_button_click() {
117         //Retreive selected objects
118         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
119         if (!desktop) return;
121         Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
122         if (!selection) return;
124         using Inkscape::Util::GSListConstIterator;
125         std::list<SPItem *> selected;
126         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
127         if (selected.empty()) return;
129         NR::Point mp; //Anchor point
130         AlignAndDistribute::AlignTarget target = _dialog.getAlignTarget();
131         const Coeffs &a= _allCoeffs[_index];
132         switch (target)
133         {
134         case AlignAndDistribute::LAST:
135         case AlignAndDistribute::FIRST:
136         case AlignAndDistribute::BIGGEST:
137         case AlignAndDistribute::SMALLEST:
138         {
139             //Check 2 or more selected objects
140             std::list<SPItem *>::iterator second(selected.begin());
141             ++second;
142             if (second == selected.end())
143                 return;
144             //Find the master (anchor on which the other objects are aligned)
145             std::list<SPItem *>::iterator master(
146                 _dialog.find_master (
147                     selected,
148                     (a.mx0 != 0.0) ||
149                     (a.mx1 != 0.0) )
150                 );
151             //remove the master from the selection
152             SPItem * thing = *master;
153             selected.erase(master);
154             //Compute the anchor point
155             NR::Rect b = sp_item_bbox_desktop (thing);
156             mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
157                            a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
158             break;
159         }
161         case AlignAndDistribute::PAGE:
162             mp = NR::Point(a.mx1 * sp_document_width(SP_DT_DOCUMENT(desktop)),
163                            a.my1 * sp_document_height(SP_DT_DOCUMENT(desktop)));
164             break;
166         case AlignAndDistribute::DRAWING:
167         {
168             NR::Rect b = sp_item_bbox_desktop
169                 ( (SPItem *) sp_document_root (SP_DT_DOCUMENT (desktop)) );
170             mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
171                            a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
172             break;
173         }
175         case AlignAndDistribute::SELECTION:
176         {
177             NR::Rect b =  selection->bounds();
178             mp = NR::Point(a.mx0 * b.min()[NR::X] + a.mx1 * b.max()[NR::X],
179                            a.my0 * b.min()[NR::Y] + a.my1 * b.max()[NR::Y]);
180             break;
181         }
183         default:
184             g_assert_not_reached ();
185             break;
186         };  // end of switch
188         // Top hack: temporarily set clone compensation to unmoved, so that we can align/distribute
189         // clones with their original (and the move of the original does not disturb the
190         // clones). The only problem with this is that if there are outside-of-selection clones of
191         // a selected original, they will be unmoved too, possibly contrary to user's
192         // expecation. However this is a minor point compared to making align/distribute always
193         // work as expected, and "unmoved" is the default option anyway.
194         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
195         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
197         bool changed = false;
198         //Move each item in the selected list
199         for (std::list<SPItem *>::iterator it(selected.begin());
200              it != selected.end();
201              it++)
202         {
203             sp_document_ensure_up_to_date(SP_DT_DOCUMENT (desktop));
204             NR::Rect b = sp_item_bbox_desktop (*it);
205             NR::Point const sp(a.sx0 * b.min()[NR::X] + a.sx1 * b.max()[NR::X],
206                                a.sy0 * b.min()[NR::Y] + a.sy1 * b.max()[NR::Y]);
207             NR::Point const mp_rel( mp - sp );
208             if (LInfty(mp_rel) > 1e-9) {
209                 sp_item_move_rel(*it, NR::translate(mp_rel));
210                 changed = true;
211             }
212         }
214         // restore compensation setting
215         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
217         if (changed) {
218             sp_document_done ( SP_DT_DOCUMENT (desktop) );
219         }
222     }
223     guint _index;
224     AlignAndDistribute &_dialog;
226     static const Coeffs _allCoeffs[10];
228 };
229 ActionAlign::Coeffs const ActionAlign::_allCoeffs[10] = {
230     {1., 0., 0., 0., 0., 1., 0., 0.},
231     {1., 0., 0., 0., 1., 0., 0., 0.},
232     {.5, .5, 0., 0., .5, .5, 0., 0.},
233     {0., 1., 0., 0., 0., 1., 0., 0.},
234     {0., 1., 0., 0., 1., 0., 0., 0.},
235     {0., 0., 0., 1., 0., 0., 1., 0.},
236     {0., 0., 0., 1., 0., 0., 0., 1.},
237     {0., 0., .5, .5, 0., 0., .5, .5},
238     {0., 0., 1., 0., 0., 0., 1., 0.},
239     {0., 0., 1., 0., 0., 0., 0., 1.}
240 };
242 struct BBoxSort
244     SPItem *item;
245     float anchor;
246     NR::Rect bbox;
247     BBoxSort(SPItem *pItem, NR::Dim2 orientation, double kBegin, double kEnd) :
248         item(pItem),
249         bbox (sp_item_bbox_desktop (pItem))
250     {
251         anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
252     }
253     BBoxSort(const BBoxSort &rhs):
254         //NOTE :  this copy ctor is called O(sort) when sorting the vector
255         //this is bad. The vector should be a vector of pointers.
256         //But I'll wait the bohem GC before doing that
257         item(rhs.item), anchor(rhs.anchor), bbox(rhs.bbox) {
258     }
259 };
260 bool operator< (const BBoxSort &a, const BBoxSort &b)
262     return (a.anchor < b.anchor);
265 class ActionDistribute : public Action {
266 public :
267     ActionDistribute(const Glib::ustring &id,
268                      const Glib::ustring &tiptext,
269                      guint row, guint column,
270                      AlignAndDistribute &dialog,
271                      bool onInterSpace,
272                      NR::Dim2 orientation,
273                      double kBegin, double kEnd
274         ):
275         Action(id, tiptext, row, column,
276                dialog.distribute_table(), dialog.tooltips(), dialog),
277         _dialog(dialog),
278         _onInterSpace(onInterSpace),
279         _orientation(orientation),
280         _kBegin(kBegin),
281         _kEnd( kEnd)
282     {}
284 private :
285     virtual void on_button_click() {
286         //Retreive selected objects
287         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
288         if (!desktop) return;
290         Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
291         if (!selection) return;
293         using Inkscape::Util::GSListConstIterator;
294         std::list<SPItem *> selected;
295         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
296         if (selected.empty()) return;
298         //Check 2 or more selected objects
299         std::list<SPItem *>::iterator second(selected.begin());
300         ++second;
301         if (second == selected.end()) return;
304         std::vector< BBoxSort  > sorted;
305         for (std::list<SPItem *>::iterator it(selected.begin());
306             it != selected.end();
307             ++it)
308         {
309             BBoxSort b (*it, _orientation, _kBegin, _kEnd);
310             sorted.push_back(b);
311         }
312         //sort bbox by anchors
313         std::sort(sorted.begin(), sorted.end());
315         // see comment in ActionAlign above
316         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
317         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
319         unsigned int len = sorted.size();
320         bool changed = false;
321         if (_onInterSpace)
322         {
323             //overall bboxes span
324             float dist = (sorted.back().bbox.max()[_orientation] -
325                           sorted.front().bbox.min()[_orientation]);
326             //space eaten by bboxes
327             float span = 0;
328             for (unsigned int i = 0; i < len; i++)
329             {
330                 span += sorted[i].bbox.extent(_orientation);
331             }
332             //new distance between each bbox
333             float step = (dist - span) / (len - 1);
334             float pos = sorted.front().bbox.min()[_orientation];
335             for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
336                   it < sorted.end();
337                   it ++ )
338             {
339                 if (!NR_DF_TEST_CLOSE (pos, it->bbox.min()[_orientation], 1e-6)) {
340                     NR::Point t(0.0, 0.0);
341                     t[_orientation] = pos - it->bbox.min()[_orientation];
342                     sp_item_move_rel(it->item, NR::translate(t));
343                     changed = true;
344                 }
345                 pos += it->bbox.extent(_orientation);
346                 pos += step;
347             }
348         }
349         else
350         {
351             //overall anchor span
352             float dist = sorted.back().anchor - sorted.front().anchor;
353             //distance between anchors
354             float step = dist / (len - 1);
356             for ( unsigned int i = 0; i < len ; i ++ )
357             {
358                 BBoxSort & it(sorted[i]);
359                 //new anchor position
360                 float pos = sorted.front().anchor + i * step;
361                 //Don't move if we are really close
362                 if (!NR_DF_TEST_CLOSE (pos, it.anchor, 1e-6)) {
363                     //Compute translation
364                     NR::Point t(0.0, 0.0);
365                     t[_orientation] = pos - it.anchor;
366                     //translate
367                     sp_item_move_rel(it.item, NR::translate(t));
368                     changed = true;
369                 }
370             }
371         }
373         // restore compensation setting
374         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
376         if (changed) {
377             sp_document_done ( SP_DT_DOCUMENT (desktop) );
378         }
379     }
380     guint _index;
381     AlignAndDistribute &_dialog;
382     bool _onInterSpace;
383     NR::Dim2 _orientation;
385     double _kBegin;
386     double _kEnd;
388 };
391 class ActionNode : public Action {
392 public :
393     ActionNode(const Glib::ustring &id,
394                const Glib::ustring &tiptext,
395                guint column,
396                AlignAndDistribute &dialog,
397                NR::Dim2 orientation, bool distribute):
398         Action(id, tiptext, 0, column,
399                dialog.nodes_table(), dialog.tooltips(), dialog),
400         _orientation(orientation),
401         _distribute(distribute)
402     {}
404 private :
405     NR::Dim2 _orientation;
406     bool _distribute;
407     virtual void on_button_click()
408     {
410         if (!SP_ACTIVE_DESKTOP) return;
411         SPEventContext *event_context = SP_DT_EVENTCONTEXT(SP_ACTIVE_DESKTOP);
412         if (!SP_IS_NODE_CONTEXT (event_context)) return ;
414         Inkscape::NodePath::Path *nodepath = SP_NODE_CONTEXT (event_context)->nodepath;
415         if (!nodepath) return;
416         if (_distribute)
417             sp_nodepath_selected_distribute(nodepath, _orientation);
418         else
419             sp_nodepath_selected_align(nodepath, _orientation);
421     }
422 };
424 class ActionRemoveOverlaps : public Action {
425 private:
426     Gtk::Label removeOverlapXGapLabel;
427     Gtk::Label removeOverlapYGapLabel;
428     Gtk::SpinButton removeOverlapXGap;
429     Gtk::SpinButton removeOverlapYGap;
431 public:
432     ActionRemoveOverlaps(Glib::ustring const &id,
433                          Glib::ustring const &tiptext,
434                          guint row,
435                          guint column,
436                          AlignAndDistribute &dialog) :
437         Action(id, tiptext, row, column + 4,
438                dialog.removeOverlap_table(), dialog.tooltips(), dialog)
439     {
440         dialog.removeOverlap_table().set_col_spacings(3);
441     
442         removeOverlapXGap.set_digits(1);
443         removeOverlapXGap.set_size_request(60, -1);
444         removeOverlapXGap.set_increments(1.0, 5.0);
445         removeOverlapXGap.set_range(-1000.0, 1000.0);
446         removeOverlapXGap.set_value(0);
447         dialog.tooltips().set_tip(removeOverlapXGap,
448                                   _("Minimum horizontal gap (in px units) between bounding boxes"));
449         /* TRANSLATORS: Horizontal gap */
450         removeOverlapXGapLabel.set_label(_("H:"));
452         removeOverlapYGap.set_digits(1);
453         removeOverlapYGap.set_size_request(60, -1);
454         removeOverlapYGap.set_increments(1.0, 5.0);
455         removeOverlapYGap.set_range(-1000.0, 1000.0);
456         removeOverlapYGap.set_value(0);
457         dialog.tooltips().set_tip(removeOverlapYGap,
458                                   _("Minimum vertical gap (in px units) between bounding boxes"));
459         /* TRANSLATORS: Vertical gap */
460         removeOverlapYGapLabel.set_label(_("V:"));
462         dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
463         dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
464         dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, column+3, row, row+1, Gtk::FILL, Gtk::FILL);
465         dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
467     }
469 private :
470     virtual void on_button_click()
471     {
472         if (!SP_ACTIVE_DESKTOP) return;
474         // see comment in ActionAlign above
475         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
476         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
478         // xGap and yGap are the minimum space required between bounding rectangles.
479         double const xGap = removeOverlapXGap.get_value();
480         double const yGap = removeOverlapYGap.get_value();
481         removeoverlap(SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->itemList(),
482                       xGap, yGap);
484         // restore compensation setting
485         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
487         sp_document_done(SP_DT_DOCUMENT(SP_ACTIVE_DESKTOP));
488     }
489 };
491 class ActionGraphLayout : public Action {
492 public:
493     ActionGraphLayout(Glib::ustring const &id,
494                          Glib::ustring const &tiptext,
495                          guint row,
496                          guint column,
497                          AlignAndDistribute &dialog) :
498         Action(id, tiptext, row, column + 4,
499                dialog.graphLayout_table(), dialog.tooltips(), dialog)
500     {}
502 private :
503     virtual void on_button_click()
504     {
505         if (!SP_ACTIVE_DESKTOP) return;
507         // see comment in ActionAlign above
508         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
509         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
511         graphlayout(SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->itemList());
513         // restore compensation setting
514         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
516         sp_document_done(SP_DT_DOCUMENT(SP_ACTIVE_DESKTOP));
517     }
518 };
520 class ActionUnclump : public Action {
521 public :
522     ActionUnclump(const Glib::ustring &id,
523                const Glib::ustring &tiptext,
524                guint row,
525                guint column,
526                AlignAndDistribute &dialog):
527         Action(id, tiptext, row, column,
528                dialog.distribute_table(), dialog.tooltips(), dialog)
529     {}
531 private :
532     virtual void on_button_click()
533     {
534         if (!SP_ACTIVE_DESKTOP) return;
536         // see comment in ActionAlign above
537         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
538         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
540         unclump ((GSList *) SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->itemList());
542         // restore compensation setting
543         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
545         sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP));
546     }
547 };
549 class ActionRandomize : public Action {
550 public :
551     ActionRandomize(const Glib::ustring &id,
552                const Glib::ustring &tiptext,
553                guint row,
554                guint column,
555                AlignAndDistribute &dialog):
556         Action(id, tiptext, row, column,
557                dialog.distribute_table(), dialog.tooltips(), dialog)
558     {}
560 private :
561     virtual void on_button_click()
562     {
563         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
564         if (!desktop) return;
566         Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
567         if (!selection) return;
569         using Inkscape::Util::GSListConstIterator;
570         std::list<SPItem *> selected;
571         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
572         if (selected.empty()) return;
574         //Check 2 or more selected objects
575         if (selected.size() < 2) return;
577         // This bbox is cached between calls to randomize, so that there's no growth nor shrink
578         // nor drift on sequential randomizations. Discard cache on global (or better active
579         // desktop's) selection_change signal.
580         if (!_dialog.randomize_bbox_set) {
581             _dialog.randomize_bbox = selection->bounds();
582             _dialog.randomize_bbox_set = true;
583         }
585         // see comment in ActionAlign above
586         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
587         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
589         for (std::list<SPItem *>::iterator it(selected.begin());
590             it != selected.end();
591             ++it)
592         {
593             sp_document_ensure_up_to_date(SP_DT_DOCUMENT (desktop));
594             NR::Rect item_box = sp_item_bbox_desktop (*it);
595             // find new center, staying within bbox 
596             double x = _dialog.randomize_bbox.min()[NR::X] + item_box.extent(NR::X)/2 +
597                 g_random_double_range (0, _dialog.randomize_bbox.extent(NR::X) - item_box.extent(NR::X));
598             double y = _dialog.randomize_bbox.min()[NR::Y] + item_box.extent(NR::Y)/2 +
599                 g_random_double_range (0, _dialog.randomize_bbox.extent(NR::Y) - item_box.extent(NR::Y));
600             // displacement is the new center minus old:
601             NR::Point t = NR::Point (x, y) - 0.5*(item_box.max() + item_box.min());
602             sp_item_move_rel(*it, NR::translate(t));
603         }
605         // restore compensation setting
606         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
608         sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP));
609     }
610 };
612 struct Baselines
614     SPItem *_item;
615     NR::Point _base;
616     NR::Dim2 _orientation;
617     Baselines(SPItem *item, NR::Point base, NR::Dim2 orientation) :
618         _item (item),
619         _base (base),
620         _orientation (orientation)
621     {}
622 };
624 bool operator< (const Baselines &a, const Baselines &b)
626     return (a._base[a._orientation] < b._base[b._orientation]);
629 class ActionBaseline : public Action {
630 public :
631     ActionBaseline(const Glib::ustring &id,
632                const Glib::ustring &tiptext,
633                guint row,
634                guint column,
635                AlignAndDistribute &dialog,
636                Gtk::Table &table,
637                NR::Dim2 orientation, bool distribute):
638         Action(id, tiptext, row, column,
639                table, dialog.tooltips(), dialog),
640         _orientation(orientation),
641         _distribute(distribute)
642     {}
644 private :
645     NR::Dim2 _orientation;
646     bool _distribute;
647     virtual void on_button_click()
648     {
649         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
650         if (!desktop) return;
652         Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
653         if (!selection) return;
655         using Inkscape::Util::GSListConstIterator;
656         std::list<SPItem *> selected;
657         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
658         if (selected.empty()) return;
660         //Check 2 or more selected objects
661         if (selected.size() < 2) return;
663         NR::Point b_min = NR::Point (HUGE_VAL, HUGE_VAL);
664         NR::Point b_max = NR::Point (-HUGE_VAL, -HUGE_VAL);
666         std::vector<Baselines> sorted;
668         for (std::list<SPItem *>::iterator it(selected.begin());
669             it != selected.end();
670             ++it)
671         {
672             if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
673                 Inkscape::Text::Layout const *layout = te_get_layout(*it);
674                 NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
675                 if (base[NR::X] < b_min[NR::X]) b_min[NR::X] = base[NR::X];
676                 if (base[NR::Y] < b_min[NR::Y]) b_min[NR::Y] = base[NR::Y];
677                 if (base[NR::X] > b_max[NR::X]) b_max[NR::X] = base[NR::X];
678                 if (base[NR::Y] > b_max[NR::Y]) b_max[NR::Y] = base[NR::Y];
680                 Baselines b (*it, base, _orientation);
681                 sorted.push_back(b);
682             }
683         }
685         if (sorted.size() <= 1) return;
687         //sort baselines
688         std::sort(sorted.begin(), sorted.end());
690         bool changed = false;
692         if (_distribute) {
693             double step = (b_max[_orientation] - b_min[_orientation])/(sorted.size() - 1);
694             for (unsigned int i = 0; i < sorted.size(); i++) {
695                 SPItem *item = sorted[i]._item;
696                 NR::Point base = sorted[i]._base;
697                 NR::Point t(0.0, 0.0);
698                 t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
699                 sp_item_move_rel(item, NR::translate(t));
700                 changed = true;
701             }
703         } else {
704             for (std::list<SPItem *>::iterator it(selected.begin());
705                  it != selected.end();
706                  ++it)
707             {
708                 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
709                     Inkscape::Text::Layout const *layout = te_get_layout(*it);
710                     NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
711                     NR::Point t(0.0, 0.0);
712                     t[_orientation] = b_min[_orientation] - base[_orientation];
713                     sp_item_move_rel(*it, NR::translate(t));
714                     changed = true;
715                 }
716             }
717         }
719         if (changed) {
720             sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP));
721         }
722     }
723 };
727 void on_tool_changed(Inkscape::Application *inkscape, SPEventContext *context, AlignAndDistribute *daad)
729     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
730     if (desktop && SP_DT_EVENTCONTEXT(desktop))
731         daad->setMode(tools_active(desktop) == TOOLS_NODES);
734 void on_selection_changed(Inkscape::Application *inkscape, Inkscape::Selection *selection, AlignAndDistribute *daad)
736     daad->randomize_bbox_set = false;
739 /////////////////////////////////////////////////////////
744 AlignAndDistribute::AlignAndDistribute() 
745     : Dialog ("dialogs.align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
746       randomize_bbox (NR::Point (0, 0), NR::Point (0, 0)),
747       _alignFrame(_("Align")),
748       _distributeFrame(_("Distribute")),
749       _removeOverlapFrame(_("Remove overlaps")),
750       _graphLayoutFrame(_("Connector network layout")),
751       _nodesFrame(_("Nodes")),
752       _alignTable(2, 6, true),
753       _distributeTable(3, 6, true),
754       _removeOverlapTable(1, 5, false),
755       _graphLayoutTable(1, 5, false),
756       _nodesTable(1, 4, true),
757       _anchorLabel(_("Relative to: "))
760     //Instanciate the align buttons
761     addAlignButton("al_left_out",
762                    _("Align right sides of objects to left side of anchor"),
763                    0, 0);
764     addAlignButton("al_left_in",
765                    _("Align left sides"),
766                    0, 1);
767     addAlignButton("al_center_hor",
768                    _("Center on vertical axis"),
769                    0, 2);
770     addAlignButton("al_right_in",
771                    _("Align right sides"),
772                    0, 3);
773     addAlignButton("al_right_out",
774                    _("Align left sides of objects to right side of anchor"),
775                    0, 4);
776     addAlignButton("al_top_out",
777                    _("Align bottoms of objects to top of anchor"),
778                    1, 0);
779     addAlignButton("al_top_in",
780                    _("Align tops"),
781                    1, 1);
782     addAlignButton("al_center_ver",
783                    _("Center on horizontal axis"),
784                    1, 2);
785     addAlignButton("al_bottom_in",
786                    _("Align bottoms"),
787                    1, 3);
788     addAlignButton("al_bottom_out",
789                    _("Align tops of objects to bottom of anchor"),
790                    1, 4);
792     //Baseline aligns
793     addBaselineButton("al_baselines_vert",
794                    _("Align baseline anchors of texts vertically"),
795                       0, 5, this->align_table(), NR::X, false);
796     addBaselineButton("al_baselines_hor",
797                    _("Align baseline anchors of texts horizontally"),
798                      1, 5, this->align_table(), NR::Y, false);
800     //The distribute buttons
801     addDistributeButton("distribute_hdist",
802                         _("Make horizontal gaps between objects equal"),
803                         0, 4, true, NR::X, .5, .5);
805     addDistributeButton("distribute_left",
806                         _("Distribute left sides equidistantly"),
807                         0, 1, false, NR::X, 1., 0.);
808     addDistributeButton("distribute_hcentre",
809                         _("Distribute centers equidistantly horizontally"),
810                         0, 2, false, NR::X, .5, .5);
811     addDistributeButton("distribute_right",
812                         _("Distribute right sides equidistantly"),
813                         0, 3, false, NR::X, 0., 1.);
815     addDistributeButton("distribute_vdist",
816                         _("Make vertical gaps between objects equal"),
817                         1, 4, true, NR::Y, .5, .5);
819     addDistributeButton("distribute_top",
820                         _("Distribute tops equidistantly"),
821                         1, 1, false, NR::Y, 0, 1);
822     addDistributeButton("distribute_vcentre",
823                         _("Distribute centers equidistantly vertically"),
824                         1, 2, false, NR::Y, .5, .5);
825     addDistributeButton("distribute_bottom",
826                         _("Distribute bottoms equidistantly"),
827                         1, 3, false, NR::Y, 1., 0.);
829     //Baseline distribs
830     addBaselineButton("distribute_baselines_hor",
831                    _("Distribute baseline anchors of texts horizontally"),
832                       0, 5, this->distribute_table(), NR::X, true);
833     addBaselineButton("distribute_baselines_vert",
834                    _("Distribute baseline anchors of texts vertically"),
835                      1, 5, this->distribute_table(), NR::Y, true);
837     //Randomize & Unclump
838     addRandomizeButton("distribute_randomize",
839                         _("Randomize centers in both dimensions"),
840                         2, 2);
841     addUnclumpButton("unclump",
842                         _("Unclump objects: try to equalize edge-to-edge distances"),
843                         2, 4);
845     //Remove overlaps
846     addRemoveOverlapsButton("remove_overlaps",
847                             _("Move objects as little as possible so that their bounding boxes do not overlap"),
848                             0, 0);
849     //Graph Layout
850     addGraphLayoutButton("graph_layout",
851                             _("Nicely arrange selected connector network"),
852                             0, 0);
854     //Node Mode buttons
855     addNodeButton("node_halign",
856                   _("Align selected nodes horizontally"),
857                   0, NR::X, false);
858     addNodeButton("node_valign",
859                   _("Align selected nodes vertically"),
860                   1, NR::Y, false);
861     addNodeButton("node_hdistribute",
862                   _("Distribute selected nodes horizontally"),
863                   2, NR::X, true);
864     addNodeButton("node_vdistribute",
865                   _("Distribute selected nodes vertically"),
866                   3, NR::Y, true);
868     //Rest of the widgetry
870     _combo.append_text(_("Last selected"));
871     _combo.append_text(_("First selected"));
872     _combo.append_text(_("Biggest item"));
873     _combo.append_text(_("Smallest item"));
874     _combo.append_text(_("Page"));
875     _combo.append_text(_("Drawing"));
876     _combo.append_text(_("Selection"));
878     _combo.set_active(6);
879     _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
881     _anchorBox.pack_start(_anchorLabel);
882     _anchorBox.pack_start(_combo);
884     _alignBox.pack_start(_anchorBox);
885     _alignBox.pack_start(_alignTable);
887     _alignFrame.add(_alignBox);
888     _distributeFrame.add(_distributeTable);
889     _removeOverlapFrame.add(_removeOverlapTable);
890     _graphLayoutFrame.add(_graphLayoutTable);
891     _nodesFrame.add(_nodesTable);
893     // Top level vbox
894     Gtk::VBox *vbox = get_vbox();
895     vbox->set_spacing(4);
897     // Notebook for individual transformations
899     vbox->pack_start(_alignFrame, true, true);
900     vbox->pack_start(_distributeFrame, true, true);
901     vbox->pack_start(_removeOverlapFrame, true, true);
902     vbox->pack_start(_graphLayoutFrame, true, true);
903     vbox->pack_start(_nodesFrame, true, true);
905     //Connect to the global tool change signal
906     g_signal_connect (G_OBJECT (INKSCAPE), "set_eventcontext", G_CALLBACK (on_tool_changed), this);
908     // Connect to the global selection change, to invalidate cached randomize_bbox
909     g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
910     randomize_bbox = NR::Rect (NR::Point (0, 0), NR::Point (0, 0));
911     randomize_bbox_set = false;
913     show_all_children();
915     on_tool_changed (NULL, NULL, this); // set current mode
918 AlignAndDistribute::~AlignAndDistribute() 
920     sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
922     for (std::list<Action *>::iterator it = _actionList.begin();
923          it != _actionList.end();
924          it ++)
925         delete *it;
928 void AlignAndDistribute::on_ref_change(){
929 //Make blink the master
935 void AlignAndDistribute::setMode(bool nodeEdit)
937     //Act on widgets used in node mode
938     void ( Gtk::Widget::*mNode) ()  = nodeEdit ?
939         &Gtk::Widget::show_all : &Gtk::Widget::hide_all;
941     //Act on widgets used in selection mode
942   void ( Gtk::Widget::*mSel) ()  = nodeEdit ?
943       &Gtk::Widget::hide_all : &Gtk::Widget::show_all;
946     ((_alignFrame).*(mSel))();
947     ((_distributeFrame).*(mSel))();
948     ((_removeOverlapFrame).*(mSel))();
949     ((_graphLayoutFrame).*(mSel))();
950     ((_nodesFrame).*(mNode))();
953 void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
954                                  guint row, guint col)
956     _actionList.push_back(
957         new ActionAlign(
958             id, tiptext, row, col,
959             *this , col + row * 5));
961 void AlignAndDistribute::addDistributeButton(const Glib::ustring &id, const Glib::ustring tiptext,
962                                       guint row, guint col, bool onInterSpace,
963                                       NR::Dim2 orientation, float kBegin, float kEnd)
965     _actionList.push_back(
966         new ActionDistribute(
967             id, tiptext, row, col, *this ,
968             onInterSpace, orientation,
969             kBegin, kEnd
970             )
971         );
974 void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
975                    guint col, NR::Dim2 orientation, bool distribute)
977     _actionList.push_back(
978         new ActionNode(
979             id, tiptext, col,
980             *this, orientation, distribute));
983 void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
984                                       guint row, guint col)
986     _actionList.push_back(
987         new ActionRemoveOverlaps(
988             id, tiptext, row, col, *this)
989         );
992 void AlignAndDistribute::addGraphLayoutButton(const Glib::ustring &id, const Glib::ustring tiptext,
993                                       guint row, guint col)
995     _actionList.push_back(
996         new ActionGraphLayout(
997             id, tiptext, row, col, *this)
998         );
1001 void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
1002                                       guint row, guint col)
1004     _actionList.push_back(
1005         new ActionUnclump(
1006             id, tiptext, row, col, *this)
1007         );
1010 void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1011                                       guint row, guint col)
1013     _actionList.push_back(
1014         new ActionRandomize(
1015             id, tiptext, row, col, *this)
1016         );
1019 void AlignAndDistribute::addBaselineButton(const Glib::ustring &id, const Glib::ustring tiptext,
1020                                     guint row, guint col, Gtk::Table &table, NR::Dim2 orientation, bool distribute)
1022     _actionList.push_back(
1023         new ActionBaseline(
1024             id, tiptext, row, col, 
1025             *this, table, orientation, distribute));
1031 std::list<SPItem *>::iterator AlignAndDistribute::find_master( std::list<SPItem *> &list, bool horizontal){
1032     std::list<SPItem *>::iterator master = list.end();
1033     switch (getAlignTarget()) {
1034     case LAST:
1035         return list.begin();
1036         break;
1038     case FIRST:
1039         return --(list.end());
1040         break;
1042     case BIGGEST:
1043     {
1044         gdouble max = -1e18;
1045         for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1046             NR::Rect b = sp_item_bbox_desktop (*it);
1047             gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
1048             if (dim > max) {
1049                 max = dim;
1050                 master = it;
1051             }
1052         }
1053         return master;
1054         break;
1055     }
1057     case SMALLEST:
1058     {
1059         gdouble max = 1e18;
1060         for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1061             NR::Rect b = sp_item_bbox_desktop (*it);
1062             gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
1063             if (dim < max) {
1064                 max = dim;
1065                 master = it;
1066             }
1067         }
1068         return master;
1069         break;
1070     }
1072     default:
1073         g_assert_not_reached ();
1074         break;
1076     } // end of switch statement
1077     return NULL;
1080 AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
1081     return AlignTarget(_combo.get_active_row_number());
1086 } // namespace Dialog
1087 } // namespace UI
1088 } // namespace Inkscape
1090 /*
1091   Local Variables:
1092   mode:c++
1093   c-file-style:"stroustrup"
1094   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1095   indent-tabs-mode:nil
1096   fill-column:99
1097   End:
1098 */
1099 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :